使用RateLimiter+自定义注解实现单机限流
实现步骤
- 自定义注解
- 编写aop
自定义注解
首先我们自定义注解然后使用aop去拦截,这样在需要限流的方法上面使用自定义注解就可以完成限流
@Target(ElementType.METHOD) // 使用范围: 方法
@Retention(RetentionPolicy.RUNTIME) // 有效范围: 运行时
public @interface MyLimitAnnotation {
}
编写aop
这里用guava的RateLimiter实现单机限流的效果,写的比较简单,如有需要可以自己定制化
@Aspect
@Component
public class RateLimitAop{
/**
* QPS 最好使用热加载的方式
*/
private static final double QPS=1;
private static RateLimiter LIMITER = RateLimiter.create(QPS);
@Pointcut("@annotation(com.chen.demo.limiter.MyLimitAnnotation)")
private void rateLimitPointcut(){
}
/**
* 通知
* @param joinPoint
* @return
*/
@Around("rateLimitPointcut()")
private void rateLimitAround(ProceedingJoinPoint joinPoint){
boolean tryAcquire = LIMITER.tryAcquire();
try {
if (tryAcquire) {
System.out.println(String.format("低于%sQPS继续执行",QPS));
joinPoint.proceed();
}else {
System.out.println(String.format("高于%sQPS获取到令牌后执行",QPS));
LIMITER.acquire();
joinPoint.proceed();
}
}catch (Throwable e){
e.printStackTrace();
}
}
}
测试
配置类
@Configuration
@ComponentScan("com.chen.demo.limiter")
@EnableAspectJAutoProxy
public class LimiterConfig {
}
我们编写一个方法来模拟操作DB
@Component
public class MockDbOperation {
@MyLimitAnnotation
public void operationDb() {
System.out.println("insert data to db");
}
}
再编写一个测试方法
public class LimitTest {
@Test
public void testLimiter(){
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(LimiterConfig.class);
MockDbOperation bean = context.getBean(MockDbOperation.class);
CompletableFuture.runAsync(
()->{
for (;;){
bean.operationDb();
Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MICROSECONDS);
}
}
, Executors.newFixedThreadPool(5));
Uninterruptibles.sleepUninterruptibly(10,TimeUnit.SECONDS);
}
}
最后来看一下执行效果
低于1.0QPS继续执行
insert data to db
高于1.0QPS获取到令牌后执行
insert data to db
最后附上单机限流和分布式限流的注意点
单机限流主要算法有。令牌桶算法,漏桶算法,计数器算法来实现,这里不再赘述。
分布式限流实现原理其实很简单。既然要达到分布式全局限流的效果,那自然需要一个第三方组件来记录请求的次数。其中 Redis 就非常适合这样的场景。
分布式限流实现x
- 每次请求时将方法名进行md5加密后作为Key 写入到 Redis 中,超时时间设置为 x 秒,Redis 将该 Key 的值进行自增。
- 当达到阈值时返回错误。
- 写入 Redis 的操作用 Lua 脚本来完成,利用 Redis 的单线程机制可以保证每个 Redis 请求的原子性
当使用分布式限流时需要注意,因为是集群限流,当集群中的某台服务器宕机后会给其他机器增加压力,如果集群中某个机器又因qps增加宕机,如果不及时处理最后会导致整个服务不可用,并且该服务是下游服务时,上游服务没有做熔断处理有可能会造成服务雪崩
当使用单机限流时,需要注意集群在增加实例时重新调整qps大小