1. 程式人生 > 程式設計 >Redis原子計數器incr,防止併發請求操作

Redis原子計數器incr,防止併發請求操作

一、前言

在一些對高併發請求有限制的系統或者功能裡,比如說秒殺活動,或者一些網站返回的當前使用者過多,請稍後嘗試。這些都是通過對同一時刻請求數量進行了限制,一般用作對後臺系統的保護,防止系統因為過大的流量衝擊而崩潰。對於系統崩潰帶來的後果,顯然還是拒絕一部分請求更能被維護者所接受。

而在各種限流中,除了系統自身設計的帶鎖機制的計數器外,利用Redis實現顯然是一種既高效安全又便捷方便的方式。

二、incr命令

Redis Incr 命令將 key 中儲存的數字值增一。

如果 key 不存在,那麼 key 的值會先被初始化為 0 ,然後再執行 INCR 操作。

如果值包含錯誤的型別,或字串型別的值不能表示為數字,那麼返回一個錯誤。

本操作的值限制在 64 位(bit)有符號數字表示之內。

示例:

127.0.0.1:6379> set num 10
OK
127.0.0.1:6379> incr num
(integer) 11
127.0.0.1:6379> get num  # 數字值在 Redis 中以字串的形式儲存
"11"

注意: 由於redis並沒有一個明確的型別來表示整型資料,所以這個操作是一個字串操作。

執行這個操作的時候,key對應儲存的字串被解析為10進位制的64位有符號整型資料。

事實上,Redis 內部採用整數形式(Integer representation)來儲存對應的整數值,所以對該類字串值實際上是用整數儲存,也就不存在儲存整數的字串表示(String representation)所帶來的額外消耗。

三、使用場景

1.計數器

使用思路是:每次有相關操作的時候,就向Redis伺服器傳送一個incr命令。

例如這樣一個場景:我們有一個web應用,我們想記錄每個使用者每天訪問這個網站的次數。

web應用只需要通過拼接使用者id和代表當前時間的字串作為key,每次使用者訪問這個頁面的時候對這個key執行一下incr命令。

這個場景可以有很多種擴充套件方法:

通過結合使用INCR和EXPIRE命令,可以實現一個只記錄用戶在指定間隔時間內的訪問次數的計數器

客戶端可以通過GETSET命令獲取當前計數器的值並且重置為0

通過類似於DECR或者INCRBY等原子遞增/遞減的命令,可以根據使用者的操作來增加或者減少某些值 比如線上遊戲,需要對使用者的遊戲分數進行實時控制,分數可能增加也可能減少。

2.限速器

限速器是一種可以限制某些操作執行速率的特殊場景。

傳統的例子就是限制某個公共api的請求數目。

假設我們要解決如下問題:限制某個api每秒每個ip的請求次數不超過10次。

我們可以通過incr命令來實現兩種方法解決這個問題。

四、流量控制之java實現

這裡我們將在java中使用redis-incr的特性來構建一個1分鐘內只允許 請求100次的控制程式碼,key代表在redis記憶體放的被控制的鍵值。

public static boolean flowControl(String key){
    //最大允許100
    int max = 100;
    long total = 1L;
    try {
      if (jedisInstance.get(key) == null) {
        //jedisInstance是Jedis連線例項,可以使單鏈接也可以使用連結池獲取,實現方式請參考之前的blog內容
        //如果redis目前沒有這個key,建立並賦予0,有效時間為60s
        jedisInstance.setex(key,60,"0");
      } else {
        //獲取加1後的值
        total = jedisInstance.incr(redisKey).longValue();
        //Redis TTL命令以秒為單位返回key的剩餘過期時間。當key不存在時,返回-2。當key存在但沒有設定剩餘生存時間時,返回-1。否則,以秒為單位,返回key的剩餘生存時間。
        if (jedisInstance.ttl(redisKey).longValue() == -1L)
        {
          //為給定key設定生存時間,當key過期時(生存時間為0),它會被自動刪除。
          jedisInstance.expire(redisKey,60);
        }
      }
    } catch (Exception e) {
      logger.error("流量控制組件:執行計數操作失敗,無法執行計數");
    }
    long keytotaltransations = max;
    //判斷是否已超過最大值,超過則返回false
    if (total > keytotaltransations) {
      return false;
    }
    return true;
  }

補充知識:【限流演算法】java實現redis分散式時間視窗計數器演算法

本文實現一種基於java的分散式固定時間視窗計數器演算法

//每次請求進來,查詢一下當前的計數值,如果超出請求數閾值,則拒絕請求,返回系統繁忙提示
private long limitFlow(String key) {
    //Setnx(SET if Not eXists) 命令在指定的 key 不存在時,為 key 設定指定的值。設定成功返回1,設定失敗返回0
    Long lng = redisCacheClient.setnx(jedisGroup,key,"1");
    if (lng == 1) {
      //設定時間視窗,redis-key時效為10秒
      redisCacheClient.expire(jedisGroup,10);
      return 1L;
    } else {
      //Redis Incrby 命令將 key 中儲存的數字加上指定的增量值。相當於放在redis中的計數器,每次請求到來計數器自增1
      long val = redisCacheClient.incrBy(jedisGroup,1);
 
      return val;
    }
  }

以上這篇Redis原子計數器incr,防止併發請求操作就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。