前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用Redis中的zset实现一个限流器

用Redis中的zset实现一个限流器

作者头像
naget
发布2021-01-21 09:57:48
1.4K2
发布2021-01-21 09:57:48
举报
文章被收录于专栏:VegoutVegout

你被限流过吗

我还记得14年抢红米的时候,下面这个图是我最烦的一个图

抢了两个星期,才终于买到了我的第一台小米手机:红米1s。小米商城加入了一个排队的机制,于是我们可以感知到自己被限流了,但大部分服务,比如最近各大电商的抢茅台活动,并没有让我们感知到限流,不管你是手速不够还是被限流,都会给你返回“很遗憾,已经被抢光了”类似的提示。不过确实也没必要让用户感知到这个机制(你看,程序员又想做产品经理的主了),毕竟结果都是一样的。

对于这种火爆的活动,为了保证服务的稳定性,都需要对特定的接口进行限流,用Redis中的zset实现一个限流器该怎么做呢?

如何实现一个限流器

限流器需要实现的功能:在指定时间内,允许一定量的请求通过

如图所示,横坐标代表了时间,坐标轴上有一个窗口顺着时间的方向,向前移动。窗口最前面的那条线表示的就是“现在”,每进入一个请求,就会在时间轴对应的当下时间处打上一个点。比如我们要实现一个1分钟最多100000次访问的限流器。那么窗口的大小就是1分钟,窗口一直向前移动,我们要保证被窗口框住的请求永远不超过100000个。

使用Redis的zset可以很方便的实现这个功能。主要用到以下几个命令:zremrangeByScore,zcard,zadd。每当一个请求进入,我们就向zset中添加一个member,score值是当前时间的毫秒数。member叫什么不重要,只要保证他不重复就行了。当判断一个请求能否通过的时候,就检测score的值处于“当前时间”和“1分钟之前”之间的member数量,如果超过了限定值,则被限流,否则加入到zset中,给该请求“放行”。为了保证原子性,我们可以选择使用lua脚本来编写逻辑代码。

--KEYS[1]:该次限流对应的key
--ARGV[1]:一分钟之前的时间戳
--ARGV[2]:此时此刻的时间戳
--ARGV[3]:允许通过的最大数量
--ARGV[4]:member名称(随机生成)
redis.call('zremrangeByScore', KEYS[1], 0, ARGV[1])
local res = redis.call('zcard', KEYS[1])
if (res == nil) or (res < tonumber(ARGV[3])) then
    redis.call('zadd', KEYS[1], ARGV[2], ARGV[4])
    return 0
else return 1 end

写一个demo,并发校验,可以看到输出(为了方便测试,我设定的是一分钟最多进入10个请求):

[pool-1-thread-72] INFO blog20210109.Limiter - 进入
[pool-1-thread-16] INFO blog20210109.Limiter - 进入
[pool-1-thread-42] INFO blog20210109.Limiter - 进入
[pool-1-thread-22] INFO blog20210109.Limiter - 进入
[pool-1-thread-91] INFO blog20210109.Limiter - 进入
[pool-1-thread-10] INFO blog20210109.Limiter - 进入
[pool-1-thread-33] INFO blog20210109.Limiter - 进入
[pool-1-thread-83] INFO blog20210109.Limiter - 进入
[pool-1-thread-62] INFO blog20210109.Limiter - 进入
[pool-1-thread-35] INFO blog20210109.Limiter - 进入
[main] INFO blog20210109.Limiter - 一分钟内进入的请求数有:10

彩蛋

最开始我的脚本是这样写的:

redis.call('zremrangeByScore', KEYS[1], 0, ARGV[1])
local res = redis.call('zrangeByScore', KEYS[1], ARGV[1], ARGV[2])
if (res == nil) or (table.getn(res) < tonumber(ARGV[3])) then
    redis.call('zadd', KEYS[1], ARGV[2], ARGV[4])
    return 0
else return 1 end

测验的时候,总是限流失败。本来只允许进入10个,但每次总是会进入15个左右,你看出哪里的问题了吗?

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-01-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Vegout 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 你被限流过吗
  • 如何实现一个限流器
  • 彩蛋
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档