检查文件上传的安全性
- 校验的维度:
- 文件的大小
- 文件的后缀
- 文件内容的合规性(比如敏感数据)
- 可以接入腾讯云的数据万象进行审核
//判断文件大小,不能超过1M
long size = multipartFile.getSize();
String filename = multipartFile.getOriginalFilename();
String suffix = FileUtil.getSuffix(filename);
//判断文件类型是否合规
List<String> validSuffixList = Arrays.asList("xlsx");
ThrowUtils.throwIf(!validSuffixList.contains(suffix), ErrorCode.PARAMS_ERROR, "文件类型不允许");
final long ONE_MB = 1024 * 1024L;
ThrowUtils.throwIf(size > ONE_MB, ErrorCode.PARAMS_ERROR, "文件不能超过1M");
数据存储优化
**规状:**找们巴每个图表的泉始数居全部存放仕了同一个数居表(chart表)的字段里。
问题:
- 如果用户上传的原始数据量很大、图表数日益增多,查询chart表就会很慢
- 对于BI平台,用户是有查看原始数据、对原始数据进行简单查询的需求的。现在如果把所有数据存放在一个字段(列)中,查询时,只取出这个列的所有内容。
**补充:**比如下图这张表,我只想要x,y这两列,就要先把所有的原始数据查出来,然后再去做过滤。
解决方案=>分库分表:
把每个图表对应的原始数据单独保存为一个新的数据表,而不是都存在一个字段里。
**比如:**我们的网站数据.xlsx
,如果要保存这个数据,就单独保存为一个新的数据表,表名为chart_{图表id}
。
新建表,然后填入如右下图所示的数据,分开查询测试时会用到。
优点:
- 存储时,能够分开存储,互不影响(也能增加安全性)
- 查询时,可以使用各种Sq语句灵活取出需要的字段,查询性能更快
水平分表,垂直分库
在大型互联网应用中,为了应对高并发、海量数据等挑战,往往需要对数据库进行拆分。常见的拆分方式有水平分表和垂直分库两种。
-
水平分表(Sharding):水平分表是将同一张表中的数据按一定的规则划分到不同的物理存储位置上,以达到分摊单张表的数据及访问压力的目的。
-
优点:
- 单个表的数据量减少,查询效率提高;
- 可以通过增加节点,提高系统的扩展性和容错性。
-
缺点:
- 事务并发处理复杂度增加,需要增加分布式事务的管理,性能和复杂度都有所牺牲;
- 跨节点查询困难,需要设计跨节点的查询模块。
-
-
垂直分库(Vertical Partitioning):垂直分库,指的是根据业务模块的不同,将不同的字段或表分到不同的数据库中。垂直分库基于数据库内核支持,对应用透明,无需额外的开发代码,易于维护升级。
- 优点:
- 减少单个数据库的数据量,提高系统的查询效率
- 增加了系统的可扩展性,比水平分表更容易实现。
- 缺点:
- 不同数据库之间的维护和同步成本较高:
- 现有系统的改造存在一定的难度:
- 系统的性能会受到数据库之间互相影响的影响。
- 优点:
需要根据实际的业务场景和技术架构情况,综合考虑各种因素来选择适合白己的分库分表策略。
限流
**问题:**使用系统是需要消耗成本的,用户有可能疯狂刷量,让你破产。
解决方案:
- 控制成本=>限制用户调用总次数
- 用户在短时间内疯狂使用,导致服务器资源被占满,其他用户无法使用=>限流
**思考:**限流阈值多大合适?
参考正常用户的使用,比如限制单个用户在每秒只能使用1次。
限流的算法
文章: https://juejin.cn/post/6967742960540581918
限流粒度
- 针对某个方法限流,即单位时间内最多允许同时X个操作使用这个方法
- 针对某个用户限流,比如单个用户单位时间内最多执行X次操作
- 针对某个用户X方限流,比如单个用户单位时间内最多护行X次这个方法
限流实现构思
- 本地限流(单机限流):每个服务器单独限流,一般适用于单体项目,就是你的项目只有一个服务器。
- 在Java中,有很多第三方库可以用来实现单机限流:
Guava RateLimiter
:这是谷歌Guaval
库提供的限流工具,可以对单位时间内的请求数量进行限制,是一种令牌桶算法的改造版
- 在Java中,有很多第三方库可以用来实现单机限流:
- 分布式限流(多机限流):如果你的项目有多个服务器,比如微服务,那么建议使用分布式限流。
- 把用户的使用频率等数据放到一个集中的存储进行统计,比如Redis,这样无论用户的请求落到了哪台服务器,都以集中存储中的数据为准(如Redission)
- 在网关集中进行限流和统计(比如Sentinel、Spring Cloud Gateway)
Redisson实现限流
- Redisson内置了一个限流工具类,可以帮助你利用Redis来存储、来统计,这里的限流也是令牌桶算法
- 导入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>
- 创建RedissonClient对象
@Configuration
@ConfigurationProperties(prefix = "spring.redis")
@Data
public class RedisConfig {
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);
return Redisson.create(config);
}
}
- 创建LimiterManager,这里的doLimit方法随时可以换成其他的限流实现方式,不只是Redission
@Service
public class LimiterManager {
@Resource
private RedissonClient redissonClient;
public void doLimit(String key) {
RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
rateLimiter.trySetRate(RateType.OVERALL, 2, 1, RateIntervalUnit.SECONDS); //表示1秒生成2个令牌
boolean isSuccess = rateLimiter.tryAcquire(1, 30, TimeUnit.SECONDS); //第一个参数为消耗的令牌数量,后面是设置数据30s之后过期,防止内存泄露
if (!isSuccess) {
throw new BusinessException(ErrorCode.TOO_MANY_REQUEST);
}
}
}
rateLimiter.trySetRate(RateType.OVERALL, 2, 1, RateIntervalUnit.SECONDS);
- 第一个参数有两个选择:
RateType.OVERALL
:全局限流,在这个模式下,限流是基于整个应用程序的总体请求速率进行控制。无论是哪个客户端发起的请求,限流规则都适用于所有的请求。这种方式适合于需要对整个应用程序的请求进行整体控制的场景。例如,限制整个系统每秒的请求次数。RateType.PER_CLIENT
:每个客户端限流,在这个模式下,限流是基于每个客户端(用户)的请求速率进行控制。每个客户端(用户)都有自己独立的限流规则,而不会受到其他客户端的请求影响。这种方式适合于需要对每个用户进行独立限流控制的场景。例如,限制每个用户每秒的请求次数。- 但是OVERALL模式下使用不同的key不可以实现每个客户端限流,所以这里用了OVERALL模式,比较通用
- 第二个参数:
permitsPerSecond
,该参数表示每秒钟生成的令牌数量,即令牌桶的速率。它决定了在每秒内可以处理的请求数量。例如,如果将permitsPerSecond
设置为10,表示每秒钟生成10个令牌,即每秒最多处理10个请求。 - 第三个参数:
maxBurstSeconds
:该参数表示令牌桶的最大容量或最大积累时间。它指定了令牌桶可以存储的最大令牌数量。当令牌桶未被使用时,令牌可以累积到一定数量。maxBurstSeconds
定义了令牌桶可以积累的最长时间,以秒为单位。例如,如果将maxBurstSeconds
设置为5,表示令牌桶最多可以积累5秒钟的令牌数量。
- 第一个参数有两个选择:
- 在方法中调用doLimit,key可以根据想要的限流粒度传入不同的key
//限流,每个用户调用这个方法1秒最多调用2次
String limiterKey = "genChartByAi:" + loginUser.getId();
limiterManager.doLimit(limiterKey);
. 在方法中调用doLimit,key可以根据想要的限流粒度传入不同的key
//限流,每个用户调用这个方法1秒最多调用2次
String limiterKey = "genChartByAi:" + loginUser.getId();
limiterManager.doLimit(limiterKey);