限流
限流是在高并发或者某个瞬间高并发时,为了保证系统的稳定性,对超出服务处理能力之外的请求进行拦截,对访问服务的流量进行限制。
常见的限流算法有四种:固定窗口限流算法、滑动窗口限流算法、漏桶限流算法和令牌桶限流算法。
固定窗口限流算法
将每个固定时间设置为每个固定窗口,每个固定窗口只能处理固定数量的请求,如果这个窗口处理了限定的请求数量,之后的请求都不再处理,直到下一个时间窗口。固定窗口限流实现简单,但是会出现流量突刺的问题,假设窗口为1s,请求数量为100,999ms 之前都没有请求来,999ms 的时候来了100个请求都接收了,此时下一个窗口的第1ms 的时候也来了100个请求,这就导致系统在短短2ms 内要处理200个请求,无疑会加重系统负载。
打个比方,有一个面馆,每个小时只能20人吃面,划分9点到10点为一个窗口,10点到11点为一个窗口,假设9点59分59秒来了20人吃面,10点00分01秒来了20人吃面,这就导致两秒钟面馆要做40碗面,压力剧增。
滑动窗口限流算法
固定窗口是将时间划分为多个窗口,比如每一小时都是一个固定窗口,9点到10点是一个窗口,10点到11点是一个窗口,每个窗口随着时间变化逐渐变小,所以会出现两个流量突刺的问题。
滑动窗口是将一个小时作为窗口,窗口的大小始终不变,在这个窗口内只能存在20个请求。
打个比方,还是那个面馆,这个面馆是一个滑动窗口,面馆里有20个位置,最大只能容纳20个人同时吃面。假如出现上面的情况,9点59分59秒来了20人,10点00分01秒来了20人,虽然来了40人,但是店里只能容纳20人吃面。后来的20人只能等着或者被拒绝。
漏桶限流算法
系统处理请求就像是一个往一个漏桶中装水,如果装的水量很大,下面的孔来不及流出去,水就会溢出来。保证系统始终以稳定的速率处理请求,不会出现压力激增的问题。
但是因为始终以恒定的速率处理请求,如果系统有多余的资源,效率比较慢。
令牌桶算法
在漏桶的基础上,加上了令牌的概念,以恒定的速率往桶里加令牌,请求到来的时候先获取令牌,然后被处理,如果处理请求的速率慢于加令牌的速度,桶内始终会有令牌,那么请求来了就能被处理。如果处理速率快于令牌,那么桶里会出现没有令牌的情况,到来的请求就会被拒绝。
打个比方,流水线始终以固定的速率生产配件,配件即为令牌,生产的配件会被放在仓库,仓库就是桶,工厂接到了订单请求会先确认仓库中,如果有配件,就会处理订单,如果没配件了,订单就会被拒绝。
Redisson 实现限流
github地址:
redisson/redisson: Redisson - Easy Redis Java client and Real-Time Data Platform. Sync/Async/RxJava/Reactive API. Over 50 Redis based Java objects and services: Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Spring Cache, Tomcat, Scheduler, JCache API, Hibernate, RPC, local cache ... (github.com)
实现主要参考开源地址
1. 配置依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.30.0</version>
</dependency>
2. 创建Redisson配置类
/**
* Redisson 配置
*/
@Configuration
@EnableCaching
@ConfigurationProperties(prefix = "spring.redis")
@Data
public class RedissonConfig {
private String port;
private String host;
private Integer database;
private Integer timeout;
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient(){
// 1. 创建配置
Config config = new Config();
String redisAddress = String.format("redis://%s:%S",host,port);
//设置redis地址,redis 库,过期时间
config.useSingleServer().setAddress(redisAddress).setDatabase(database).setTimeout(timeout);
// 2. 创建Redisson实例
// Sync and Async API
return Redisson.create(config);
}
}
3. 创建限流管理器
@Service
public class RedisLimiterManager {
@Resource
private RedissonClient redissonClient;
/**
* 限流,令牌桶算法
* @param key 区分不同的限流器,这里是对应的redis的key
*/
public void doRateLimit(String key){
RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
//每秒执行两个请求,设置速率
rateLimiter.trySetRate(RateType.OVERALL,2,1, RateIntervalUnit.SECONDS);
//每个请求到来请求 1 个令牌
boolean b = rateLimiter.tryAcquire(1);
if(!b){
throw new BusinessException(ErrorCode.SYSTEM_ERROR,"请求频繁,请稍后再试");
}
}
}
4. 调用限流管理器即可
//注入管理器
@Resource
private RedisLimiterManager redisLimiterManager;
//调用
//限流每个用户调用方法的次数
redisLimiterManager.doRateLimit("genCharByAi:"+userId);