简介:
采用令牌桶方式对短信发送进行分布式限流
业务背景:
触达平台通过短信触达用户的场景日均推送量500万条,高峰时触达会达1000万+,超过了短信系统性能,短信系统需要对模块设置上限,超过上限会被丢弃。短信系统初步评估改造成本大,需触达系统管控推送速率,限制接口请求为QPS200(1s内同时请求的手机号码数)。
首先对触达项目短信场景做初步了解:
由此图可看到,短信推送是触达项目的一个中间环节,如果只是对短信接口进行限流,会产生“瓶颈”效应,拖慢整个项目执行速率。当项目高峰时,会有更多的线程阻塞等待短
信执行,产生系统风险。
优势:
1 短信发送异步机制:为避免产生短信场景“瓶颈效应”,采用kafka将短信发送作为异步事件,逐步消费。
2 令牌桶限流: 令牌桶算法允许在限流值内的突发流量,因为它会积累令牌直到达到桶的容量。这意味着在短时间内可以处理超过平均速率的请求,而不是简单地按照固定的速率限制流量;
3 分布式环境下的一致性:RRateLimiter是基于Redis实现的,因此它可以在分布式系统中保持限流策略的一致性。无论是多个应用实例还是多个服务节点,都可以使用相同的限流规则。
建设方案:
一:限流算法的选择
- 固定窗口计数器算法(Fixed Window Counter)
优点:实现简单,容易理解。
缺点:可能会出现临界问题,即在窗口切换的瞬间,流量会翻倍,导致短时间内的流量超过阈值。 - 滑动窗口计数器算法(Sliding Window Counter)
优点:相比滑动窗口日志算法,减少了存储和计算开销。
缺点:实现相对复杂,需要维护多个时间窗口的计数器。 - 令牌桶算法(Token Bucket)
优点:提供平滑的限流控制,允许一定程度的突发流量,灵活性高。
缺点:如果配置不当,可能会导致短时间内的流量超过系统极限。 - 漏桶算法(Leaky Bucket)
优点:输出流量非常平滑,无论输入流量如何变化。
缺点:不允许突发流量,可能会导致系统处理能力未被充分利用。
二 实现步骤
1、短信发送异步化
原短信场景直接调用UNP批量短信发送接口发送短信。此处将批量短信发送内容封装为JSON消息串,通过kafka生产者将消息发送至topic。
2、创建RedissonRateLimiter限流器
使用RedissonClient客户端创建限流器:
RRateLimiter rateLimiter = this.redissonClient.getRateLimiter(rateLimiterName);
初始化限流器的同时就给限流器起了名字,及对象的行为,如myRateLimit,下面用到限流器名的地名军用myRateLimit代替
3、配置限流器
创建限流器后要对其进行配置,并将配置存到redis总,主要配置包括
RateType:限流器类型:OVERALL:分布式限流;PER_CLIENT:单例限流
rate:速率(时间窗口内产生令牌数量)
rateInterval: 窗口时长
RateIntervalUnit :时间单位,如毫秒
//将限流器配置设置到redis的hash中 参数依次为上述4个入参
redis.call('hsetnx', KEYS[1], 'rate', ARGV[1])
redis.call('hsetnx', KEYS[1], 'interval', ARGV[2])
return redis.call('hsetnx', KEYS[1], 'type', ARGV[3])
4、申请令牌(LUA流程)
// 核心LUA
local currentValue = redis.call('get', valueName);
if currentValue ~= false then
if tonumber(currentValue) < tonumber(ARGV[1]) then
return redis.call('pttl', valueName);
else
redis.call('decrby', valueName, ARGV[1]);
return nil;
end;
else
redis.call('set', valueName, rate, 'px', interval); redis.call('decrby', valueName, ARGV[1]);
return nil;
首先尝试获取 valueName 对应的当前值。如果当前值存在(不为 false),脚本会检查这个值是否小于 ARGV[1](传入的参数,表示请求的数量)。如果小于,说明已经达到了限制,返回该键的剩余过期时间(使用 pttl 命令)。如果不小于,脚本将使用 decrby命令减少当前值,并返回 nil,表示请求可以继续。
如果当前值不存在,脚本将使用 set命令初始化这个键,设置其值为 rate,并设置过期时间为 interval(使用毫秒为单位)。然后,脚本将使用 decrby减少当前值,并返回 nil。
线上效果:
短信限流上线后,请求速率被均匀控制在200QPS(1s内同时请求的手机号码数)内。实现UNP系统方的要求。
下图为最近1小时内的消费情况,由于请求UNP是调用批量请求接口,每批次约10个手机号。APM采集为分钟级维度。可明显识别限流效果已生效。