前言
近期,我们的系统经历了重大的架构重构与下游子服务的升级改造。整个系统采用Spring Cloud进行服务间的调用与交互。作为负责业务服务端的我,专为Web端和PC端提供接口调用。在系统刚上线之际,我们遭遇了一场突如其来的雪崩事件。调用链路相当简洁:由于文本匹配服务需要分词、匹配,并从ES获取匹配后的术语语料等数据,导致了请求堆积,最终服务在短暂时间内崩溃。为了紧急应对这种情况,我们决定在业务方实施限流机制,并着手优化下游的匹配算法。这也为我提供了学习限流算法的良好机会。
限流算法概述
常见的限流算法包括:令牌桶、漏桶和计数器。
计数器限流算法详解
计数器是最简单且常用的限流算法之一,主要用于限制总并发数。无论是数据库连接池大小、线程池大小还是程序访问并发数,都常采用计数器算法。
在Redis中实施限流的方法
针对方法级别的限流措施,我们采用了Redis作为实现手段。以下是一个服务类的方法展示:
@Service
@Transactional
@Slf4j
public class MethodThrottleService {
@Autowired
private RedisTemplate redisTemplate;
/
限流方法,通过Redis进行方法级别的限流措施。
通过指定key值验证是否为合法请求,如果在规定缓存时间内该key值仍存在,则视为非法请求。
@param key 请求key值
@param expireTime 缓存过期时间
@param timeUnit 缓存时间单位(如秒、毫秒等)
@return 是否过期(true或false)
/
public Boolean validateKeyRequest(String key, int expireTime, TimeUnit timeUnit) {
ValueOperations ops = redisTemplate.opsForValue(); // 获取值操作对象
String result = ops.get(key); // 获取缓存中的值
if (StringUtils.isNotBlank(result)) { // 如果缓存中存在值(非空),说明是非法请求或已处理过的请求,返回false表示请求不合法。否则设置缓存并返回true表示是合法请求。}
ops.set(key, key, expireTime, timeUnit); // 设置缓存键值对及其过期时间。
通过Redis进行限流可以精确到用户及方法的级别。基于请求的key值进行限流,限制了特定时间段内相同参数请求的次数。Redis限流存在性能瓶颈,频繁的读写操作以及设置过期时间会对Redis性能产生较大影响。此种方法并不推荐。
对于限流策略,我们还有其他的选择如计数器(可以使用AtomicInteger和Semaphore)和两种常见的算法:令牌桶和漏桶。令牌桶算法描述的是一个拥有固定容量的桶以固定的速率添加令牌。当桶满时,新添加的令牌会被丢弃或拒绝。每个数据包到达时,会删除相应数量的令牌并发送数据包。如果令牌不足,则数据包会被限流。关于令牌桶的实现,我们可以使用Guava开源库的RateLimiter。每秒产生固定数量的令牌是这种算法的一个常见设置。漏桶算法则是固定容量的桶以常量的固定速率流出水滴,无论流入的速度如何,流出的速度始终保持不变。如果流入的请求超过了容量限制,则请求会被丢弃。令牌桶和漏桶算法各有特点,可以根据实际需求选择使用哪种算法。这两种算法在实际应用中都有很好的表现,可以根据具体情况选择最适合的限流策略。具体实现方式可以参考原文出处提供的链接内容了解更多细节。 |