解决问题:
- 控制成本--->限制用户调用总次数
- 用户在短时间内频繁使用,导致服务器资源被占满
限流的几种算法:
1.固定窗口限流
单位时间内允许部分操作
优点:最简单
缺点:可能出现流量突刺
比如:前 59 分钟没有 1 个操作,第 59 分钟来了 10 个操作;第 1 小时 01 分钟又来了 10 个操作。相当于 2 分钟内执行了 20 个操作,服务器仍然有高峰危险
2.滑动窗口限流:
单位时间内允许部分操作,但这个单位时间是滑动的,需要指定一个滑动单位
优点:能够解决上述流量突刺的问题,因为第 59 分钟时,限流窗口是 59 分 ~ 1小时 59 分,这个时间段内只能接受 10 次请求,只要还在这个窗口内,更多的操作就会被拒绝。
缺点:实现相对复杂,限流效果和你的滑动单位有关,滑动单位越小,限流效果越好,但往往很难选取到一个特别合适的滑动单位。
3.漏桶限流:
以 固定的速率 处理请求(漏水),当请求桶满了后,拒绝请求。
每秒处理 10 个请求,桶的容量是 10,每 0.1 秒固定处理一次请求,如果 1 秒内来了 10 个请求;都可以处理完,但如果 1 秒内来了 11 个请求,最后那个请求就会溢出桶,被拒绝。
优点:能够一定程度上应对流量突刺, 能够固定速率处理请求,保证服务器的安全
缺点:没有办法迅速处理一批请求,只能一个一个按顺序来处理(固定速率的缺点)
4.令牌桶限流:
管理员先生成一批令牌,每秒生成 10 个令牌;当用户要操作前,先去拿到一个令牌,有令牌的人就有资格执行操作、能同时执行操作;拿不到令牌的就等着
优点:能够并发处理同时的请求, 并发性能会更高
需要考虑的问题:还是存在时间单位选取的问题
限流的实现:
1.本地实现:
每个服务器单独限流,一般适用于单体项目,就是你的 项目只有一个服务器
import com.google.common.util.concurrent.RateLimiter;
public static void main(String[] args) {
// 每秒限流5个请求
RateLimiter limiter = RateLimiter.create(5.0);
while(true) {
if (limiter.tryAcquire()) {
// 处理请求
} else {
// 超过流量限制,需要做何处理
}
}
}
2.分布式限流:
如果你的项目有多个服务器,比如微服务,那么建议使用分布式限流。
1、把用户的使用频率等数据放到一个集中的存储进行统计,比如 Redis,这样无论用户的请求落到了哪台服务器,都以集中的数据存储内的数据为准(Redisson - 是一个操作 Redis 的工具库,伙伴匹配系统讲过)
2、在网关集中进行限流和统计(比如 Sentinel、Spring Cloud Gateway)
import org.redisson.Redisson;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
public static void main(String[] args) {
// 创建RedissonClient
RedissonClient redisson = Redisson.create();
// 获取限流器
RSemaphore semaphore = redisson.getSemaphore("mySemaphore");
// 尝试获取许可证
boolean result =semaphore.tryAcquire();
if (result) {
// 处理请求
} else {
// 超过流量限制,需要做何处理
}
}
Redisson限流实现:
1. 引入 Redisson 代码包:
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.21.3</version> </dependency>
2.创建 RedissonConfig 配置类,用于初始化 RedissonClient 对象单例:
@Configuration
@ConfigurationProperties(prefix = "spring.redis")
@Data
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);
RedissonClient redisson =Redisson.create(config);
return redisson;
}
}
3.编写 RedisLimiterManager:
/**
* 专门提供 RedisLimiter 限流基础服务的(提供了通用的能力)
*/
@Service
public class RedisLimiterManager {
@Resource
private RedissonClient redissonClient;
/**
* 限流操作
*
* @param key 区分不同的限流器,比如不同的用户 id 应该分别统计
*/
public void doRateLimit(String key) {
// 创建一个名称为user_limiter的限流器,每秒最多访问 2 次
RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
rateLimiter.trySetRate(RateType.OVERALL, 2, RateIntervalUnit.SECONDS);
// 每当一个操作来了后,请求一个令牌
boolean canOp = rateLimiter.tryAcquire(1);
if (!canOp) {
throw new BusinessException(ErrorCode.TOO_MANY_REQUEST);
}
}
}
4.应用到需要限流的方法在:
User loginUser =userService.getLoginUser(request);
// 限流判断,每个用户一个限流器
redisLimiterManager.doRateLimit("genChartByAi_" + loginUser.getId());
// 后续操作
xxxxx