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

1、前言
在一些對高併發請求有限制的系統或者功能裏,好比說秒殺活動,或者一些網站返回的當前用戶過多,請稍後嘗試。這些都是經過對同一時刻請求數量進行了限制,通常用做對後臺系統的保護,防止系統由於過大的流量衝擊而崩潰。對於系統崩潰帶來的後果,顯然仍是拒絕一部分請求更能被維護者所接受。 
而在各類限流中,除了系統自身設計的帶鎖機制的計數器外,利用Redis實現顯然是一種既高效安全又便捷方便的方式。java

2、incr命令
Redis Incr 命令將 key 中儲存的數字值增一。 
若是 key 不存在,那麼 key 的值會先被初始化爲 0 ,而後再執行 INCR 操做。 
若是值包含錯誤的類型,或字符串類型的值不能表示爲數字,那麼返回一個錯誤。 
本操做的值限制在 64 位(bit)有符號數字表示以內。 
示例:web

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)所帶來的額外消耗。redis

 

3、使用場景
1.計數器
使用思路是:每次有相關操做的時候,就向Redis服務器發送一個incr命令。 
例如這樣一個場景:咱們有一個web應用,咱們想記錄每一個用戶天天訪問這個網站的次數。 
web應用只須要經過拼接用戶id和表明當前時間的字符串做爲key,每次用戶訪問這個頁面的時候對這個key執行一下incr命令。api

這個場景能夠有不少種擴展方法: 
經過結合使用INCR和EXPIRE命令,能夠實現一個只記錄用戶在指定間隔時間內的訪問次數的計數器 
客戶端能夠經過GETSET命令獲取當前計數器的值而且重置爲0 
經過相似於DECR或者INCRBY等原子遞增/遞減的命令,能夠根據用戶的操做來增長或者減小某些值 好比在線遊戲,須要對用戶的遊戲分數進行實時控制,分數可能增長也可能減小。安全

2.限速器
限速器是一種能夠限制某些操做執行速率的特殊場景。 
傳統的例子就是限制某個公共api的請求數目。 
假設咱們要解決以下問題:限制某個api每秒每一個ip的請求次數不超過10次。 
咱們能夠經過incr命令來實現兩種方法解決這個問題。服務器

 

4、流量控制之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;
    }
相關文章
相關標籤/搜索