上一篇
"叮咚~"王经理的手机又响了,这已经是今天第20条验证码短信。"我就注册个账号而已,怎么验证码发起来没完了?"他皱着眉头删掉了短信,运维小张正盯着监控大屏上火红的告警线发愁——短信接口又被刷爆了,这个月成本又超标了...
这种场景在互联网公司太常见了,短信作为重要的用户触达通道,既要保证正常业务需求,又要防止恶意刷量,今天我们就来聊聊,如何用Redis这把"瑞士军刀"实现短信发送频率控制。
Redis简直是频控场景的"天选之子":
最朴素的实现方式——给每个手机号配个计数器:
def can_send_sms(phone): key = f"sms_limit:{phone}" # 60秒内最多发1条 count = redis.incr(key) if count == 1: redis.expire(key, 60) return count <= 1
这个方法简单粗暴,但有个明显漏洞:如果在60秒的最后1秒和下一个60秒的第1秒各发1条,实际上2条短信间隔可能只有2秒,但系统却认为合规。
更科学的实现应该用滑动窗口,Redis的Sorted Set大显身手的时候到了:
def can_send_sms_v2(phone): now = time.time() key = f"sms_window:{phone}" # 移除1分钟前的记录 redis.zremrangebyscore(key, 0, now - 60) # 当前请求加入集合 redis.zadd(key, {str(now): now}) # 设置key过期时间避免内存泄漏 redis.expire(key, 60) # 检查1分钟内发送次数 return redis.zcard(key) <= 5 # 假设每分钟限5条
这个方案精准控制了任意60秒窗口内的发送次数,但相对耗资源,我们实测发现,在QPS 5000+的场景下,Redis CPU会涨到30%左右。
结合多家大厂实战经验,推荐这个混合方案:
def can_send_sms_pro(phone, biz_type): # 分级控制键 minute_key = f"sms:{biz_type}:{phone}:minute" hour_key = f"sms:{biz_type}:{phone}:hour" day_key = f"sms:{biz_type}:{phone}:day" # 多级检查(使用管道提升性能) with redis.pipeline() as pipe: pipe.incr(minute_key) pipe.incr(hour_key) pipe.incr(day_key) # 如果是首次设置,初始化过期时间 pipe.expire(minute_key, 60) pipe.expire(hour_key, 3600) pipe.expire(day_key, 86400) m_cnt, h_cnt, d_cnt = pipe.execute()[:3] # 分级阈值检查(不同业务类型可配置不同阈值) limits = { 'verify': {'minute':1, 'hour':5, 'day':10}, # 验证码类 'notify': {'minute':3, 'hour':20, 'day':50} # 通知类 } return (m_cnt <= limits[biz_type]['minute'] and h_cnt <= limits[biz_type]['hour'] and d_cnt <= limits[biz_type]['day'])
这个方案有三大亮点:
遇到秒杀等场景时,可以引入"令牌桶"算法:
def get_sms_token(phone): key = f"sms_token:{phone}" # 令牌桶参数 capacity = 10 # 桶容量 rate = 0.5 # 每秒补充0.5个令牌 with redis.pipeline() as pipe: while True: try: pipe.watch(key) current = float(pipe.get(key) or capacity) last_time = float(pipe.hget(key, 'last_time') or time.time()) # 计算新增令牌数 now = time.time() new_tokens = (now - last_time) * rate current = min(capacity, current + new_tokens) if current >= 1: # 有令牌可用 pipe.multi() pipe.set(key, current - 1) pipe.hset(key, 'last_time', now) pipe.execute() return True return False except WatchError: continue
监控指标:
参数调优:
异常处理:
try: if not can_send_sms(phone): log.warning(f"频控拦截:{phone}") return {"code": 429, "msg": "发送太频繁"} except RedisError: # Redis故障时降级处理 if config.allow_failover: log.error("Redis异常,降级放行") return True raise
去年双十一我们遇到过这些坑:
对应的解决方案:
用Redis实现短信频控就像给高速路口装ETC——既要保证合法车辆快速通行,又要拦截违规车辆,关键技术点:
最后提醒:任何技术方案都要结合业务实际,比如教育类APP的课程提醒和电商的秒杀通知,频控策略就应该有所区别,现在就去检查你们的短信平台,别让"短信轰炸"毁了用户体验!
本文由 栾芸芸 于2025-08-01发表在【云服务器提供商】,文中图片由(栾芸芸)上传,本平台仅提供信息存储服务;作者观点、意见不代表本站立场,如有侵权,请联系我们删除;若有图片侵权,请您准备原始证明材料和公证书后联系我方删除!
本文链接:https://vps.7tqx.com/wenda/503065.html
发表评论