系统优化(安全,限流,数据存储)

2023-12-14 19:37:07

系统优化

1.安全性优化(方法:校验)

  1. 问题:用户上传一个超大的文件怎么办?比如1000G?此时网站很容易崩

  2. 优化方法==>校验:

    • 文件大小
    • 文件后缀
    • 文件内容
    • 文件的合规性(排除敏感内容)(可以用第三发的审核功能)
      • 接入腾讯云的图片万象数据审核(cos对象存储审核功能)
  3. 实现:在后端用户输入内容处,添加校验用户上传文件的逻辑

     //校验文件
            String originalFilename = multipartFile.getName();
            long size = multipartFile.getSize();
            //1.文件大小
            final long ONE_MB = 1024*1024L;
            ThrowUtils.throwIf(size > ONE_MB,ErrorCode.PARAMS_ERROR,"文件超过1M");
            //2.校验文件后缀 aaa.png
            //2.1获取文件名的后缀
            String suffix = FileUtil.getSuffix(originalFilename);
            //2.2定义一个允许传入的文件后缀名列表
            final List<String> vaildFileSuffix = Arrays.asList("png","jpg","webp","jpeg");
            ThrowUtils.throwIf(!vaildFileSuffix.contains(suffix),ErrorCode.PARAMS_ERROR,"文件类型不合法");
    
    

2. 数据存储优化

  1. 问题:数据表中某个字段原始数据量很大,图表数越来越多,查表就会很慢。如果要查询此字段中的某一部分数据,就需要查询整个字段,浪费资源

  2. 优化方法:分库分表(把该字段的数据单独存储到新的数据表中)

    • 存储时:分开存储,互不影响(增加安全性)

    • 查询时:用SQL语句进行高效的数据检索,仅查询需要的列或行;更快、更精确地对数据进行定位和查询,从而提高查询效率和系统的响应速度

  3. 实现方式:

    • 分开存储:存储图表信息时,不把数据存储为字段,而是新建一个chart_{ 图表id } 的数据表通过图表id、数据列名、数据类型等字段,生成以下SQL语句,并且执行即可。

    • mybatis的动态sql实现(更具代码灵活的生成动态sql):

      • 哪部分是需要动态查询的

        select * from chart_{图表id}
        select 指定查询字段 from chart_{图表id}
        
      • 在ChartMapper.xml中定义sql语句

        <select id="queryChartData" resultType="java.util.Map">${queryChartSql}</select>
        
      • 测试类

        @Test
        void queryChartData() {
            String chartId = "1659210482555121666";
            String querySql = String.format("select * from chart_%s", chartId);
            List<Map<String, Object>> resultData = chartMapper.queryChartData(querySql);
            System.out.println(resultData);
        }
        

3. 限流

  1. 问题:用户在短时间内疯狂使用,导致服务器资源被占满,其他用户无法使用,避免用户频繁使用系统(比如此系统的某些功能需要收费,需要控制成本)

  2. 解决方法:

    1. 限流,比如说单个用户在每秒只能使用多少次(思考限流阈值多大合适,参考正常用户使用多少合适)
  3. 限流的算法(类别)

    1. 固定窗口限流(参考文章https://juejin.cn/post/6967742960540581918)如1小时只允许10个用户操作

      • 优点:最简单
      • 缺点:可出现流量突刺,如前59分钟没有1个操作,第59分钟来了10个操作;第1小时01分钟又来了10个操作。相当于2分钟内执行了20个操作,服务器仍然有高峰危险。
    2. 滑动窗口:流动的一个1小时只允许10个用户操作,解决了固定窗口限流存在的问题

      • 优点:能够解决上述流量突刺的问题,因为第59分钟时,限流窗口是59分~1小时59分,这个时间段内只能接受10次请求,只要还在这个窗口内,更多的操作就会被拒绝。
      • 缺点:实现相对固定窗口来说比较复杂,限流效果和你的滑动单位有关,滑动单位越小,限流效果越好,但往往很难选取到一个特别合适的滑动单位。
    3. 漏桶限流:以固定的速率处理请求(漏水),当请求桶满了后,拒绝请求。如每秒处理10个请求,桶的容量是10,每0.1秒固定处理一次请求,如果1秒内来了10个请求,这10此请求都可以处理完,但如果1秒内来了11个请求,最后那个请求就会溢出桶,被拒绝请求。

      • 优点:能够一定程度上应对流量突刺,能够固定速率处理请求,保证服务器的安全。
      • 缺点:没有固定速率处理一批请求,只能一个一个按顺序来处理(固定速率的缺点)
      • 实现过程:
        1. 初始化漏桶容量和速率;
        2. 不断将请求添加至漏桶;
        3. 如果漏桶已满,则拒绝该请求;
        4. 当漏桶中有请求时,在一定时间间隔内按照速率恒定地处理请求,并从漏桶中移除该请求。
    4. 令牌桶限流:管理员先生成一批令牌,每秒生成10个令牌;当用户要操作前,先去拿到一个令牌,有令牌的人就有资格执行操作、同时执行操作;拿不到令牌的就等着(可以解决漏桶存在的问题)

      • 优点:能够并发处理同时的请求,并发性能会更高

      • 时间单位选取的问题,比如在应对短时间内的高并发请求时,由于令牌数有限,引入过大的并发请求会导致严重的性能问题,也可能会造成请求失败或者拒绝。

      • 实现过程

        1. 初始化令牌桶容量和速率;
        2. 以恒定速率往令牌桶中添加令牌;
        3. 当请求到达时,如令牌桶中仍有令牌,则从桶中移除一个令牌,并处理该请求;
        4. 如果没有足够的令牌,拒绝该请求。
  4. 限流的实现:

    1. 本地限流(单机限流)

      每个服务器单独限流,使用于单体项目,项目只有一个服务器

      可以用Guava RateLimiter库实现

      //导入依赖
      <dependency>
          <groupId>com.google.guava</groupId>
          <artifactId>guava</artifactId>
          <version>30.1-jre</version> <!-- 请检查是否有更新的版本 -->
      </dependency>
      
      import com.google.common.util.concurrent.RateLimiter;
      
      // 创建一个每秒最多处理5个请求的RateLimiter实例
      RateLimiter rateLimiter = RateLimiter.create(5.0); // 5 //requests per second
      public void processRequest(Request request) {
          if (rateLimiter.tryAcquire()) { // 如果当前请求可以获取到许可,就执行处理逻辑
              // 处理请求的代码...
          } else {
              // 处理限流情况,例如返回错误信息或重定向到错误页面等...
          }
      }
      
    2. 分布式限流(多机限流)(Redisson限流实现【操作redis的工具类】实现限流)

      统计用户使用频率这些数据集中的存储统计,比如Redis。在网关集中限流和统计(如Sentinel,Spring Cloud GateWay)有多个服务器,微服务,可以使用分布式限流

      1. Redisson内置了一个限流工具类,可以帮助你利用Redis来存储、来统计。Redisson官网:https://github.com/redisson/redisson

      2. 实现过程

        1. 安装Radis

        2. 引入Redisson包

          <dependency>
             <groupId>org.redisson</groupId>
             <artifactId>redisson</artifactId>
             <version>3.21.3</version>
          </dependency>  
          
        3. 创建RedissonConfig配置类,用于初始化RedissonClient对象单例:

          @Data
          @ConfigurationProperties(prefix = "spring.redis")
          @Configuration
          public class RedissonConfig {
              private Integer database;
              private String host;
              private Integer port;
              private String password;
          
              @Bean
              public RedissonClient redissonClient() {
                  Config config = new Config();
                  config.useSingleServer()
                          .setDatabase(database)
                          .setAddress("redis://" + host + ":" + port)
                          .setPassword(password);
                  RedissonClient redisson = Redisson.create();
                  return redisson;
              }
          }
          
        4. 编写RedisLimiterManager

          什么是Manager??专门提供RedisLimiter限流基础服务的(提供了通用的能力,可以放到任何一个项目里)

          @Service
          public class RedisLimiterManager {
          
              @Resource
              private RedissonClient redissonClient;
          
              /**
               * 限流操作
               *
               * @param key 区分不同的限流器,比如不同的用户 id 应该分别统计
               */
              public void doRateLimit(String key) {
                  // 创建一个限流器
                  RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
                  // 每秒最多访问 2 次
                  // 参数1 type:限流类型,可以是自定义的任何类型,用于区分不同的限流策略。
                  // 参数2 rate:限流速率,即单位时间内允许通过的请求数量。
                  // 参数3 rateInterval:限流时间间隔,即限流速率的计算周期长度。
                  // 参数4 unit:限流时间间隔单位,可以是秒、毫秒等。
                  rateLimiter.trySetRate(RateType.OVERALL, 2, 1, RateIntervalUnit.SECONDS);
                  // 每当一个操作来了后,请求一个令牌
                  boolean canOp = rateLimiter.tryAcquire(1);
                  ThrowUtils.throwIf(!canOp,ErrorCode.TOO_MANY_REQUEST);
              }
          }
          
        5. 单元测试

          @SpringBootTest
          class RedisLimiterManagerTest {
              @Resource
              private RedisLimiterManager redisLimiterManager;
          
              @Test
              void doRateLimit() throws InterruptedException {
                  String userId = "1";
                  for (int i = 0; i < 2; i++) {
                      redisLimiterManager.doRateLimit(userId);
                      System.out.println("成功");
                  }
                  Thread.sleep(1000);
                  for (int i = 0; i < 5; i++) {
                      redisLimiterManager.doRateLimit(userId);
                      System.out.println("成功");
                  }
              }
          }
          
        6. 应用到要限流的方法中,比如智能分析接口

4. 零散知识点记录

  1. 文件分片上传!100M以上

  2. 水平分表,垂直分库

    • 水平分表:水平分表是将同一张表中的数据按一定的规侧划分到不同的物理存储位置上,以达到分难单张表的数据及访问压力的目的。对于SQL分为两类:id-based分表和range-based分表。

    • 垂直分库:指的是根据业务模块的不同,将不同的字段或表分到不同的数据库中。垂直分库基于数据库内核支持,对应用透明,无需额外的开发代码,易于维护升级。

  3. sql注入:

    • 是什么:SQL注入是一种常见的网络攻击手段,攻击者通过在Web表单中输入恶意SQL代码,并将其作为输入参数提交给应用程序,从而绕过预期的SQL查询,执行恶意SQL语句,并可能窃取敏感数据或破坏数据库结构。
    • 危害:SQL注入通常会导致数据泄露、数据篡改、数据库损坏等问题。
    • 解决方法:正确地验证和转义用户输入的数据,使其能够影响实际的SQL查询。应用程序需要正确验证和转义用户输入的数据,并使用参数化查询或绑定变量来执行SQL语句
  4. 创建测试的快捷键:在类名中上按alt+enter

文章来源:https://blog.csdn.net/weixin_52154534/article/details/134868602
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。