写点什么

注解式限流是如何实现的?

  • 2021 年 11 月 12 日
  • 本文字数:1941 字

    阅读完需:约 6 分钟

  1. 64 进制 or 62 进制。

  2. LRU 是什么,如何用简单的数据结构实现。


什么是限流




对服务器接收到的请求作出限制,只有一部分请求能真正到达服务器,其他的请求可以延迟,也可以拒绝。从而避免所有请求到数据库,打垮 DB。


举个生活中大家可能遇到的场景,特别是北上广深或者新一线城市,杭州一号线地铁,凤起路站,在客流量到达一定峰值时,警察叔叔???♀可能就不让你进地铁,让使用其他交通工具了?。。。都是泪啊!


限流算法用哪个比较合适




关于限流算法,网上的解释一大堆,漏桶算法,令牌桶算法等等,百度一下,你就知道,在这里车辙用最简单的计数器算法作为实现。

计数器算法

  1. 将一秒钟分为 10 个阶段,每个阶段 100ms。

  2. 每隔 100ms 记录下接口调用的次数。

  3. 当然随着时间的流逝,阶段会越来越多。这时候可以将最前面的 n 个阶段删除,只保留 10 个,也就是只剩 1s。

  4. 最后一个减去第一个的次数,就是 1s 中内该接口调用的次数。



如何用注解实现限流




在用 nginx 限流时,是将 nginx 作为代理层拦截请求处理,那么在 Spring 中代理层就是 AOP 啦。

AOP

在 web 服务器中,有很多场景都是可以靠 AOP 实现的,比如:


  1. 打印日志,记录时间类,方法,参数。

  2. 利用反射设置分页 PageRow、PageNum 的默认值。

  3. 游戏场景,判断游戏是否已经结束,不用每个方法都去判断。

  4. 解密,验签等等。

定时任务

在计数器算法中我们提到,每隔 100ms 需要记录接口调用的次数,并保存。这时候定时任务就派上用场了。


定时任务的实现有很多,像利用线程池的 ScheduledExecutorService,当然 Spring 的 Scheduled 也莫得问题。


其次,用什么数据结构保存调用次数 --> LinkedList。


另外,我们需要对多个方法限流,该如何解决呢?--> 每个方法都有唯一对应的值: package + class + methodName,于是我们将这个唯一值作为 key,linkedList 作为 map,下方代码:


1 /** 每个 key 对应的调用次数**/


2 private Map<String, Long> countMap = new ConcurrentHashMap<>();


3


4 /** 每个 key 对应的 linkedlist**/


5 private static Map<String, LinkedList<Long>> calListMap = new ConcurrentHashMap<>();


6


7 ## 每 s 一次查询


8 @Scheduled(cron = "*/1 * * * * ?")


9 private void timeGet(){


10 countMap.forEach((k,v)->{


11 LinkedList<Long> calList = calListMap.get(k);


12 if(calList == null){


13 calList = new LinkedList<>();


14 }


15 # 每个方法的调用次数放入 linkedList 中


16 calList.addLast(v);


17 calListMap.put(k, calList);


18


19 if (calList.size() > 10) {


20 calList.removeFirst();


21 }


22 });


23 }


AOP 检查

定义注解:

1import java.lang.annotation.*;


2


3


4@Target(ElementType.METHOD)


5@Retention(RetentionPolicy.RUNTIME)


6@Documented


7public @interface CalLimitAnno {


8


9 String value() default "" ;


10


11


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


String methodName() default "" ;
复制代码


12


13 long count() default 100;


14}


调用接口前检查:


1@Around(value = "@annotation(around)")


2 public Object initBean(ProceedingJoinPoint point, CalLimitAnno around) throws Throwable {


3 /** 获取类名和方法名 **/


4 MethodSignature signature = (MethodSignature) point.getSignature();


5 Method method = signature.getMethod();


6 String[] classNameArray = method.getDeclaringClass().getName().split("\.");


7 String methodName = classNameArray[classNameArray.length - 1] + "." + method.getName();


8 String classZ = signature.getDeclaringTypeName();


9 String countMapKey = classZ + "|" + methodName;


10


11


12 LinkedList<Long> calList = calListMap.get(countMapKey);


13 if(calList != null){


14 /** 调用次数判断是否已经超过注解设置的值 **/


15 if ((calList.peekLast() - calList.peekFirst()) > Long.valueOf(around.count())) {


16 throw new RuntimeException("被限流了");


17 }


18 /** 存放**/


19 countMap.putIfAbsent(countMapKey,0L);


20 countMap.put(countMapKey,countMap.get(countMapKey) + 1);


21 }


22 Object object = point.proceed();


23 return object;


24 }


方法考虑到定时任务的频率不能太小,因此我们的定时任务是每秒钟执行一次,这里我们需要设置 10s 钟的限流值,导致粒度变大了。

评论

发布
暂无评论
注解式限流是如何实现的?