使用Redis作为简单的限流计数器几种实现策略
在实现简单的接口限流或者商品秒杀时,一般需要Redis来作为计数器。但是在并发场景下,使用不当的可能会踩坑。
这里主要的坑就是:使用不当,会造成key永久有效,永不过期,导致value一直在increment,无法起到限流的作用。
下面就以反面例子说明:
本文使用的是spring-data-redis的RedisTemplate
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency>反面实例
public void limit() throws Exception { String redisKey = "com:xxx:activity:interfaceA:limit"; Long incrResult = redisService.increment(redisKey, 1L); if (null != incrResult && incrResult == 1) { redisService.expire(redisKey, 1L, TimeUnit.SECONDS); } if (incrResult > 100) { throw new Exception("计数器超限"); } }
这个代码的错误在第4,5行。
因为redisService.increment()在key为空的情况下,不是原子性操作。
实际是两步操作,首先Redis 的Incr 命令将 key 中储存的数字值增1;如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作,且将key的有效时间设置为长期有效。
当计数器设置成功之后,给key加expire时间时出现服务故障,将会导致整个key一直存在。无法起到限流作用
正确写法1
public void limit() throws Exception { String redisKey = "com:xxx:activity:interfaceA:limit"; try { Long incrResult = redisService.increment(redisKey, 1L); if (null != incrResult && incrResult == 1) { redisService.expire(redisKey, 1L, TimeUnit.SECONDS); } //防止出现并发操作未设置超时时间的场景,这样key就是永不过期,存在风险 if (redisService.getExpire(redisKey, TimeUnit.SECONDS) == -1) { //设置永不过期的时间 redisService.expire(redisKey, 1L, TimeUnit.SECONDS); } if (incrResult > 100) { throw new Exception("计数器超限"); } } catch (Exception e) { //出现故障时,删除key redisService.expire(redisKey, 1L, TimeUnit.MILLISECONDS); } }
正确写法2:给key加一个时间后缀,这样即时出现永不过期的key也只影响其中某一时间段内的key
public void limit() throws Exception { String redisKey = "com:xxx:activity:interfaceA:limit_" + TimeUnit.MILLISECONDS.toSeconds(DateTime.now().getMillis()); try { Long incrResult = redisService.increment(redisKey, 1L); if (null != incrResult && incrResult == 1) { redisService.expire(redisKey, 1L, TimeUnit.SECONDS); } if (incrResult > 100) { throw new Exception("计数器超限"); } } catch (Exception e) { //出现故障时,删除key redisService.expire(redisKey, 1L, TimeUnit.MILLISECONDS); } }
相关推荐
王道革 2020-11-25
wangdonghello 2020-11-03
Langeldep 2020-11-16
chenhualong0 2020-11-16
聚合室 2020-11-16
koushr 2020-11-12
MRFENGG 2020-11-11
guoyanga 2020-11-10
fackyou00 2020-11-10
Orangesss 2020-11-03
dongCSDN 2020-10-31
rainandtear 2020-10-30
Quietboy 2020-10-30
liuyulong 2020-10-29
fansili 2020-10-29
温攀峰 2020-10-23
jackbon 2020-10-19
kaixinfelix 2020-10-04