Open nikotung opened 3 years ago
在redis 中的存储是放在一个 sorted set 中,其中的member 和 score 都是请求进来时的时间戳.
['zremrangebyscore', key, 0, start]
这命令先把已经过期的请求记录删掉. 这里的 start 是这样确定的 start = now() - duration
['zcard', key]
计算一下在这个请求之前已经存在多少个在限流时间范围内的请求.['zadd', key, now, now]
把当前的请求加入到 zset 中, member 和 score 都是当前的时间戳.['zrange', key, 0, 0]
按请求的时间顺序获取到最早的那个请求.['zrange', key, -max, -max]
按请求时间的逆排序获取到最早的一个请求,这里获取到member 可能会存在跟上一步获取到的member 不一致.这里的max 是进行了取反的,看看redis 的文档描述
The indexes can also be negative numbers indicating offsets from the end of the sorted set, with -1 being the last element of the sorted set, -2 the penultimate element, and so on.
['zremrangebyrank', key, 0, -(max + 1)]
如果在当前的请求进行处理时,限流已经满了的话,把上面第三步中插入的数据删掉.这里用了-(max+1)
其实就是删除了最新的一个member,如果有max +1 个的话.['pexpire', key, duration]
最后一步就是更新整一个key 的过期时间.上述的几个步骤其实是放在pipeline 中操作的,保持原子性.
db.multi(operations)
.exec(function (err, res) {
if (err) return fn(err);
var isIoRedis = Array.isArray(res[0]);
var count = parseInt(isIoRedis ? res[1][1] : res[1]);
var oldest = parseInt(isIoRedis ? res[3][1] : res[3]);
var oldestInRange = parseInt(isIoRedis ? res[4][1] : res[4]);
var resetMicro = (Number.isNaN(oldestInRange) ? oldest : oldestInRange) + duration * 1000;
fn(null, {
remaining: count < max ? max - count : 0,
reset: Math.floor(resetMicro / 1000000),
resetMs: Math.floor(resetMicro / 1000),
total: max
});
});
Rate limiter implemented by redis, from here
The operation serials are :