一个问题往往会引出了一连串的问题,知识的盲区就这样被自己悄悄的发现了 。车辙在自己动手写限流注解时,遇到的问题那是真一个比一个多:
- 限流算法用哪个比较合适 。
- 如何用注解实现限流 。
- 如何对每个方法单独限流 。
- 长字符串如何转换成短字符串 。
- 64 进制 or 62进制 。
- LRU 是什么,如何用简单的数据结构实现 。
对服务器接收到的请求作出限制,只有一部分请求能真正到达服务器,其他的请求可以延迟,也可以拒绝 。从而避免所有请求到数据库,打垮 DB 。
举个生活中大家可能遇到的场景,特别是北上广深或者新一线城市,杭州一号线地铁,凤起路站,在客流量到达一定峰值时,警察叔叔♀可能就不让你进地铁,让使用其他交通工具了? 。。。都是泪啊!
限流算法用哪个比较合适
关于限流算法,网上的解释一大堆,漏桶算法,令牌桶算法等等,百度一下,你就知道,在这里车辙用最简单的计数器算法作为实现 。
计数器算法
- 将一秒钟分为 10 个阶段,每个阶段 100ms 。
- 每隔 100ms 记录下接口调用的次数 。
- 当然随着时间的流逝,阶段会越来越多 。这时候可以将最前面的 n 个阶段删除,只保留 10 个,也就是只剩 1s 。
- 最后一个减去第一个的次数,就是 1s 中内该接口调用的次数 。
文章插图
如何用注解实现限流
在用 Nginx 限流时,是将 nginx 作为代理层拦截请求处理,那么在 Spring 中代理层就是 AOP 啦 。
AOP
在 web 服务器中,有很多场景都是可以靠 AOP 实现的,比如:
- 打印日志,记录时间类,方法,参数 。
- 利用反射设置分页 PageRow、PageNum 的默认值 。
- 游戏场景,判断游戏是否已经结束,不用每个方法都去判断 。
- 解密,验签等等 。
在计数器算法中我们提到,每隔 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);1819 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 "" ;1011 String methodName() default "" ;1213 long count() default 100;14}调用接口前检查:
1@Around(value = https://www.isolves.com/it/wlyx/fwq/2019-12-03/"@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;101112 LinkedList
推荐阅读
- 什么是“分布式事务”?这回彻底懂了
- 安化黑茶集散中心黄沙坪古茶市项目正式启动
- 感应式小便器需要电源吗,感应式小便器一直冲水停不住怎么办
- Raw\TIFF\JPEG 格式有什么区别,哪种更好用?
- 重庆,斗茶盛宴 中式时尚在茗香中绽放
- 淘宝无货源开店模式靠谱吗 怎么开无货源淘宝店
- 改变人生的4种思维方式,学会一个受益终生
- 律师有哪些取证方式?
- 立式冷热饮水机价格清单
- 新Edge启动IE模式 如何取消ie浏览器edge默认