回顾限流器RateLimiter | 青训营笔记

70 阅读3分钟

这是我参与「第五届青训营」笔记创作活动的第2天 今天是1月17日

开营的第一天的阅读打卡活动给出的文章是关于各种限流方式的。在过去的实践中,我曾经跟着一个教程实践过一个基于Java的简单的秒杀项目。秒杀场景有着瞬时流量大的特点,这样大量的请求到达后端,会在瞬间将后端的资源全部占用,造成像并发中死锁一样的现象导致宕机(在我缺少内存资源的笔记本上这种问题尤为明显,在内存资源不足时,会触发内存与外存的交换非常频繁,磁盘使用率到达100%并且整个系统无响应)。为了防止这样的情况发生,导致无法响应用户请求或系统存储状态发生错误,需要进行针对性的改进。我当时主要使用的是增加后端处理能力,防止非认证请求和限流削峰。其中请求的认证通过统一认证的单点登录实现,我使用的是CAS(Central Authentication Service)服务,接入我之前简单登录模型。由于CAS不管理注册,我使用腾讯云的短信服务使用手机短信服务做用户注册。为了增加后端的处理能力,我使用Nginx做动静态分离,反向代理与负载均衡,在后端开启两个服务应用,在用户登录时使用Redis做分布式会话管理,Redis使用3主各有一从的Redis cluster,并使用tag和脚本保证一些Redis操作的原子性。使用Redis缓存热点数据,并通过消息队列异步地将数据更新操作在数据库上正确地执行。

而在削峰方面,我使用的方法是在处理数据处理请求时,使用类似漏桶的方式,以一定速率执行请求,这里的具体实现方法是使用一个固定线程数量的线程池,当线程池已满时新来的请求处理就会阻塞,等待之前的线程有执行完毕的来处理新的请求,当然也可以使用信号量达到类似的操作,但是这样还需要注意退出时释放信号量资源,实现起来比较复杂所以选用了前者实现。在限流方面,我使用的是GuavaRateLimiter,其底层是令牌桶的思想,在controller层面使用RateLimiter进行整体请求的限流。这也是Java使用令牌桶作为单机限流方案的原因。

在Guava中RateLimiter还可以对请求进行一些预借和等待的原理就是每次使用acquire(int)发起处理请求时,其内部会根据当前时间和记录下一次提供新令牌的时刻nextFreeTicketMicros(可以小于当前时间)和创建时给出的产生令牌的时间间隔stableIntervalMicros计算是否可以完成这次请求,如果可以就更新状态返回,如果不可以就会更新状态并sleep到可以完成请求的时刻或是抛弃请求。这里RateLimiter还可以对请求进行一些预借和后来者代替完成预借等待的实现,但是如果acquire(int)参数为1的话,应该就是上面的流程不涉及预借。