背景
后端接口经常会遇到并发验证问题,在一定的时间范围内,接口只能被请求一定的次数,例如:N秒内接口只能被调用M次;这时候可以用读写效率比较强劲的redis进行校验
- 先考虑普通get,set验证1s只能请求1次情况(对比demo,不推荐)
//1s 1次验证
let check = async (key, time, value) => {
let result = true
try {
let isCheck = await
redisClient
.getAsync(key)
if (isCheck) {
result = false
} else {
await redisClient
.multi()
.set(key, value)
.expire(key, time)
.execAsync()
}
} catch (error) {
redisClient
.delAsync(key)
.catch(err => { console.log(err) });
console.log(error)
}
return result
}
功能逻辑实现了,但是缺陷比较明显,redis的读和写是分两步,非原子性,存在时间差、
在极小的时间片内,会出现验证失效的情况(第一个请求的redis还没有写入完成,第二个请求已经在读取redis数据)
- INCR 操作
let check = async (key, time,count) => {
let result = true
try {
let value = await redisClient
.INCRAsync(key)
if (+value == 1) {
// 时间设置和incr操作不是事务,没有原子性,所以实际验证的时间比time稍长一点
await redisClient
.expireAsync(key, time)
} else if(+value > count){
result = false
}
} catch (error) {
// 防止Redis异常,导致验证key长期存在的情况
// 想要过期时间和incr操作实现原子性,可以考虑用lua脚本实现功能,业务方运行脚本即可
redisClient
.delAsync(key)
.catch(err => { console.log(err) });
console.log(error)
}
return result
}
取值范围:
INCR操作,如果key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行加1的操作,
其它请求执行 INCR 操作会再次增加1,INCR的value有效范围是64位有符号数字(作统计用的时候注意不要越界)
原子性:
INCR也是一种加锁的操作,自身具有原子性,对并发请求有很好的验证,对于 接口限流 和 请求统计 都是个不错的选择