接口限流
我正在参加「启航计划」
中间件层面的限流处理
Tomcat:能够设置最大衔接数,针对于单体的项目有用
Nginx:漏桶算法
Gateway:令牌桶算法
Nginx 限流
- 运用漏桶算法对恳求进行限流
http {
limit_req_zone $binary_remote_addr zone=servicelRateLimit:10m rate=10r/s
server {
listen 80;
server_name localhost;
location / {
limit_req_zone servicelRateLimit burst=20 nodelay;
proxy_pass http://targetserver;
}
}
}
语法:limit_req_zone key zone rate
-
key
:界说限流对像,binary_remote_addr
便是一种key
,根据客户端 ip 限流 -
Zone
:界说共享存储区来存储拜访信息,10m 能够存储 16wip 地址拜访信息
-
Rate
:最大拜访速率,rate=10r/s 表明每秒最多恳求 10 个恳求 -
burst=20
:相当于桶的巨细 -
Nodelay
:快速处理
- 操控并发的衔接数
http {
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
server {
listen 80;
server name localhost;
location / {
limit conn perip 20;
limit_conn perserver 100;
proxy_pass http://targetserver;
}
}
}
-
limit conn perip 20
:对应的 key 是$binary_remote_addr
,表明约束单个lP同时最多能持有20个衔接 -
limit_conn perserver 100
:对应的 key 是$server_name
表明虚拟主机 (server) 同时能处理并发衔接的总数
Gateway 限流
yml 配置文件中,微服务路由设置增加局部过滤器 RequestRateLimiter
,根据的是令牌桶算法,默许运用 redis 存储令牌,需要配置 redis
的衔接
- id:gateway-consumer
uri:1b://GATEWAY-CONSUMER
predicates:
- Path=/order/**
filters:
- name:RequestRateLimiter
args:
#运用SpEL从容器中获取目标
key-resolver:'#@pathKeyResolver}'
#令牌桶每秒填充均匀速率
redis-rate-limiter.replenishRate:1
#令牌桶的上限
redis-rate-limiter.burstCapacity:3
-
key-resolver
:界说限流对像(ip、路径、参数),需代码实现,运用 spel 表达式获取 -
redis-rate-limiter.replenishRate
:令牌桶每秒填充均匀速率 -
redis-rate-limiter.burstCapacity
:令牌桶总容量。
Sentinel
Sentinel供给了丰厚的功用特性,如流量操控、异常熔断、集群限流和速率操控等
虽然Sentinel供给了丰厚的功用特性,但我们当下需要重点关注的是流量操控部分。所谓流量操控,其原理是监控运用流量的 QPS 或并发线程数等目标,当达到指定的阈值时对流量进行操控,以防止被瞬时的流量高峰冲垮,然后保障运用的高可用性。
@GetMapping("/{activityId}/list/{itemId}")
@SentinelResource((value = "GetSeckillGood")
public BaseResponse<SeckillGoodResponse> getSeckillGood(@RequestHeader(value = "TokenInfo") Long userId,
@PathVariable Long activityId,
@PathVariable Long itemId,
@RequestParam(required = false) Long version) {
return seckillGoodService.getSeckillGood(userId, activityId, itemId, version);
}
恳求接口增加 @SentinelResourse
接口
限流形式
单机限流
运用 Guava 中的单机限流东西即可
import com.google.common.util.concurrent.RateLimiter;
public class RateLimiterExample {
public static void main(String[] args) {
// 创立一个每秒答应2个恳求的限流器
RateLimiter rateLimiter = RateLimiter.create(2);
// 模仿10个恳求
for (int i = 1; i <= 10; i++) {
// 尝试获取令牌
if (rateLimiter.tryAcquire()) {
System.out.println("Request " + i + " is processed.");
} else {
System.out.println("Request " + i + " is rejected.");
}
}
}
}
分布式限流
运用 Redis 记录用户的拜访频率或许运用 Gateway 来进行一致的限流处理,这儿展示运用 Redisson 自带的限流东西进行限流处理
/**
* @author Ezreal
* @Date 2023/6/22
*/
@Component
public class CurrentLimitManager {
@Resource
private RedissonClient redissonClient;
public void doRateLimit(String key) {
RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
// 每秒钟最多拜访两次
rateLimiter.trySetRate(RateType.OVERALL, 2, 1, RateIntervalUnit.SECONDS);
boolean acquire = rateLimiter.tryAcquire(1);
if (!acquire) {
throw new ToManyRequestException("to many request");
}
}
}
常见的限流算法
漏桶算法
规划一个漏桶,假如漏桶满了就能够拒绝服务,假如没有满,则能够经过固定的速率来处理漏桶中的恳求
假如漏桶中没有水:
- 假如进水速率小于等于最大出水速率,漏桶内不会有积水
- 假如进水速率大于最大出水速率,漏桶内会产生积水
假如漏桶中存在水
- 假如进水速率小于等于最大出水速率,那么漏桶内的水会被排干
- 假如进水速率大于最大出水速率,那么漏桶中的水就会满,多于的水会溢出
/**
* @author Ezreal
* @Date 2023/6/22
*/
public class LeakyBucketWater {
long lastModifyTime = 0L;
long currentWater = 0L;
long capacity;
long rate = 2L;
public LeakyBucketWater(long capacity) {
this.capacity = capacity;
}
public Boolean doProcess() {
long currentTimeMillis = System.currentTimeMillis();
// 每分钟出水的个数,如何体现固定限流(currentTimeMillis - lastModifyTime) / 1000 取余的操作
long outWater = (currentTimeMillis - lastModifyTime) / 1000 * rate;
// 当前水的容量巨细
currentWater = Math.max(0, currentWater - outWater);
if (currentWater < capacity) {
lastModifyTime = currentTimeMillis;
currentWater++;
return true;
} else {
return false;
}
}
}
令牌桶算法
规划一个桶,以固定的速率向里面放入令牌,每次恳求到来时,都会先领取令牌,再去执行相关的业务
与漏桶算法相比,令牌桶算法能够支持很多突发的恳求,而漏桶算法处理的恳求相对平滑
/**
* @author Ezreal
* @Date 2023/6/22
*/
public class TokenBucket {
long lastModifyTime = 0L;
long bucketCounts = 10L;
long capacity = 50L;
long currentBucket = 0;
public Boolean doProcess() {
long currentTimeMillis = System.currentTimeMillis();
long generateBucket = (currentTimeMillis - lastModifyTime) / 1000 * bucketCounts;
currentBucket = Math.min(capacity, generateBucket + currentBucket);
lastModifyTime = currentTimeMillis;
if (currentBucket > 0) {
currentBucket--;
return true;
} else {
return false;
}
}
}
根据 Redis 的滑动窗口限流算法
思路:
- 界说一个时间段的长度(即窗口长度 len)
- 统计
[now - len, now]
之间恳求的个数 - 若超过最大值,则直接返回错误信息即可;
运用 redis 中的 zset 来实现
- 运用用户的唯一标识(id、ip 等等)作为 key,当前时间 的作为 value,当前时间作为分数 score
- 当用户恳求到来时,将当前的
key - value - score
参加到 zset 中(key 要设置过期时间) - 核算 start 和 end 的值
-
end
:now time -
start
:end – len
- 移除[0, start] 之间的符号
- 统计 [start, end] 之间 key 的数量,判别是否超过最大值即可
@Component
public class SlidingWindowLimitServiceImpl implements SlidingWindowLimitService {
private final Long maxCount = 100L;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Override
public boolean pass(String userKey, int period, int size) {
int len = period * size;
long now = System.currentTimeMillis();
long start = now - len;
// 将当前时间参加
redisTemplate.opsForZSet().add(userKey, String.valueOf(now), now);
redisTemplate.expire(userKey, len + period, TimeUnit.MILLISECONDS);
// 移除 [0, start] 之间的记录
redisTemplate.opsForZSet().reverseRangeByScore(userKey, 0, start);
// 统计 (start, now] 的数量
Long count = redisTemplate.opsForZSet().zCard(userKey);
if (count == null) {
return false;
}
return count <= maxCount;
}
}