zoukankan      html  css  js  c++  java
  • 使用Redis作为简单的限流计数器几种实现策略

    在实现简单的接口限流或者商品秒杀时,一般需要Redis来作为计数器。但是在并发场景下,使用不当的可能会踩坑。

    这里主要的坑就是:使用不当,会造成key永久有效,永不过期,导致value一直在increment,无法起到限流的作用。

    下面就以反面例子说明:

    本文使用的是spring-data-redis的RedisTemplate

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    反面实例
     1 public void limit() throws Exception {
     2         String redisKey = "com:xxx:activity:interfaceA:limit";
     3         Long incrResult = redisService.increment(redisKey, 1L);
     4         if (null != incrResult && incrResult == 1) {
     5             redisService.expire(redisKey, 1L, TimeUnit.SECONDS);
     6         }
     7         if (incrResult > 100) {
     8             throw new Exception("计数器超限");
     9         }
    10     }

    这个代码的错误在第4,5行。

    因为redisService.increment()在key为空的情况下,不是原子性操作。

    实际是两步操作,首先Redis 的Incr 命令将 key 中储存的数字值增1;如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作,且将key的有效时间设置为长期有效。

    当计数器设置成功之后,给key加expire时间时出现服务故障,将会导致整个key一直存在。无法起到限流作用

    正确写法1

     1 public void limit() throws Exception {
     2         String redisKey = "com:xxx:activity:interfaceA:limit";
     3         try {
     4             Long incrResult = redisService.increment(redisKey, 1L);
     5             if (null != incrResult && incrResult == 1) {
     6                 redisService.expire(redisKey, 1L, TimeUnit.SECONDS);
     7             }
     8             //防止出现并发操作未设置超时时间的场景,这样key就是永不过期,存在风险
     9             if (redisService.getExpire(redisKey, TimeUnit.SECONDS) == -1) {
    10                 //设置永不过期的时间
    11                 redisService.expire(redisKey, 1L, TimeUnit.SECONDS);
    12             }
    13             if (incrResult > 100) {
    14                 throw new Exception("计数器超限");
    15             }
    16         } catch (Exception e) {
    17             //出现故障时,删除key
    18             redisService.expire(redisKey, 1L, TimeUnit.MILLISECONDS);
    19         }
    20     }

    正确写法2:给key加一个时间后缀,这样即时出现永不过期的key也只影响其中某一时间段内的key

     1 public void limit() throws Exception {
     2         String redisKey = "com:xxx:activity:interfaceA:limit_" + TimeUnit.MILLISECONDS.toSeconds(DateTime.now().getMillis());
     3         try {
     4             Long incrResult = redisService.increment(redisKey, 1L);
     5             if (null != incrResult && incrResult == 1) {
     6                 redisService.expire(redisKey, 1L, TimeUnit.SECONDS);
     7             }
     8             if (incrResult > 100) {
     9                 throw new Exception("计数器超限");
    10             }
    11         } catch (Exception e) {
    12             //出现故障时,删除key
    13             redisService.expire(redisKey, 1L, TimeUnit.MILLISECONDS);
    14         }
    15     }
  • 相关阅读:
    JS控制SVG缩放+鼠标控制事件
    JS多线程之Web Worker
    通过Java调用Python脚本
    Cornerstone的使用
    SVN服务器的搭建
    Python 函数作用域
    RDD转换算子(transformantion)
    Spark RDD简介
    Django 外键
    Django 模型常用属性
  • 原文地址:https://www.cnblogs.com/yuerugou54/p/12775900.html
Copyright © 2011-2022 走看看