这篇文章记录基于 Guava、Nginx的服务限流的实现。
背景
服务器能处理的请求数有限,如果瞬时流量过大可能会造成服务宕机,限流是通过对并发访问/请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务。
限流算法
限流算法主要有:令牌桶算法和漏桶算法。网上有很多文章都对两个算法进行了详细的解说,这里就不在介绍了。下面记录下基于 Guava 和 Nginx 限流的实现。
基于 Guava
的 RateLimiter
实现
限流注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Limiter {
int NOT_LIMIT = 0;
/**
* qps:Queries-per-second, 每秒查询率,QPS = req/sec = 请求数/秒
*/
@AliasFor("qps") double value() default NOT_LIMIT;
@AliasFor("value") double qps() default NOT_LIMIT;
/**
* 超时时长
*/
int timeout() default 0;
/**
* 超时时间单位
*/
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}
使用切面进行统一处理:
@Aspect
@Component
@Slf4j
public class RateLimiterAspect {
private static final ConcurrentHashMap<String, RateLimiter> LIMIT_CACHE = new ConcurrentHashMap<>();
@Pointcut("@annotation(com.example.demo.aspect.Limiter)")
public void pointcut() {
}
@Around("pointcut()")
public Object pointcut(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
Limiter limiter = AnnotationUtils.findAnnotation(method, Limiter.class);
if (limiter != null && limiter.qps() > Limiter.NOT_LIMIT) {
double qps = limiter.qps();
if (LIMIT_CACHE.get(method.getName()) == null) {
LIMIT_CACHE.put(method.getName(), RateLimiter.create(qps));
}
log.debug("【{}】的QPS设置为: {}", method.getName(), LIMIT_CACHE.get(method.getName()).getRate());
// 尝试获取令牌
if (LIMIT_CACHE.get(method.getName()) != null &&
!LIMIT_CACHE.get(method.getName()).tryAcquire(limiter.timeout(), limiter.timeUnit())) {
throw new RuntimeException("访问太频繁了,请稍后再试...");
}
}
return point.proceed();
}
}
在设置QPS=1时,频繁的访问链接,则会抛出以下提示:
使用 Nginx 进行限流
用 Nginx 进行限流,主要使用 limit_req_zone 模块,用来限制单位时间内的请求数,主要采用的是漏桶算法。配置示例如下:
http {
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
location /search/ {
limit_req zone=one burst=5 nodelay;
}
}
- $binary_remote_addr 表示限制同一客户端 ip 地址。zone=one 表示生成一个大小为 10 M,名字为 one 的内存区域,用来存储访问频次的信息。rate = 1r/s 表示限制同一 ip 客户端每秒只能访问一次。
- burst=5 表示设置一个大小为5的缓冲区,当有大量请求(爆发)过来时,超过了访问频次限制的请求可以先放到这个缓冲区内。nodelay 表示访问频次而且缓冲区也满了的时候就会直接返回503,如果没有设置,则所有请求会等待排队。
访问过快,可以看到以下信息:
参考链接
个人博客:https://www.kangpeiqin.cn/#/index