使用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     }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM