1. 程式人生 > 程式設計 >使用redis的increment()方法實現計數器功能案例

使用redis的increment()方法實現計數器功能案例

一直知道redis可以用來實現計數器功能,但是之前沒有實際使用過,昨天碰到一個需求:使用者掃碼當天達到20次即提示:當日掃碼次數達到上限!

當時就想到使用redis的遞增方法increment()來實現計數器功能,一定要注意redisTemplate和stringRedisTemplate的使用

首先設定key:

使用redis的increment()方法實現計數器功能案例

該key我使用了使用者id和當天日期作為key的一部分,date:xxxx-xx-xx格式,這樣一來該使用者在第二天掃碼的時候又是一個新key,因為日期不同了

設定key的過期時間:

使用redis的increment()方法實現計數器功能案例

實現計數器功能:

使用redis的increment()方法實現計數器功能案例

使用redis的increment()方法實現計數器功能案例

使用redis的increment()方法實現計數器功能案例

通過使用上面的方法,redis的計數器功能就可以實現了。

在使用過程過遇到的問題:

在使用的過程中,老是會拋錯:ERR value is not an integer or out of range

後來發現當時我使用的方法底層用的redisTemplate和stringRedisTemplate串了,當時setKey的時候用的方法底層是

stringRedisTemplate,後面我想get(key)的時候方法底層的模板使用的是redisTemplate,後面統一了一下模板的使用,然後計數

器功能正常執行不再拋錯。

看過很多文章說是序列化器的鍋,increment方法必須是stringRedisTemplate模板才能使用,但是我在實際使用的時候也確實是

用了redisTemplate,這個具體原因我還在看,此次使用中最主要的問題是setKey的時候使用的模板和取key的時候使用的模板不

一致導致的。寫個筆記記錄一下,一個坑不踩第二遍。大家如果遇到一樣的問題可以一起討論學習一下。

補充知識:認識redis:redis計數器與數量控制

這篇文章是我個人對redis的一些理解,可以幫助大家系統的認識redis。本文的目標讀者是使用過redis,但對redis瞭解不深的朋友。文章內容以redis為主,也會少量提到memcached。文章從redis的設計目的、工作模式、應用場景等方面闡述,最後會講解一些具體的應用場景,還會夾帶一些程式碼作為“乾貨”。

鑑於本人水平有限,文中如有不準確的內容,敬請斧正。

redis是什麼

redis是一種記憶體型的資料儲存器,使用場景

資料庫

快取

訊息代理(message broker)

同memcached對比

memcached是分散式的記憶體物件快取系統,設計意圖為通過緩解資料庫壓力來加快web應用的響應速度。

上面的描述分別被放在了redis和memcached的官網首頁最顯眼的位置,這也回答了redis和memcached的本質區別。

redis的工作模式

redis的工作模式為單程序,這意味著redis只能利用到一個cpu核心。 redis對請求的處理是序列的,對於同時湧進來的多個請求,redis首先把請求存入佇列,按請求到達的先後順序序列處理。

瞭解單程序和序列這兩個特點,有助於我們使用redis時“揚長避短”。

之前有同事提到過,為何redis不適合儲存大塊的資料?從redis的工作模式我們可以窺知一二:大塊的資料意味著需要較長的io時間,包括記憶體io和網路的io,cpu資源在io過程中是一直被佔用的,這會阻塞其它請求,從而影響redis的整體效能。

資料型別

大家對redis的資料型別已經比較熟悉了,主要有以下5種。

string

list

hash

set

sorted set

各種資料型別的常用操作

string

set,get,setnx,setex,psetex

拼接

增加、減少(整數型字串)

位操作

list

入列,出列(這兩個命令都有阻塞模式)

按排位獲取部分元素

hash

設定某個索引

獲取某個索引

增加/減少某個索引的值(整數型字串)

獲取所有索引的值

set

集合是一個數學概念,囉嗦提一下:集合中的元素都是唯一的

加入元素

刪除元素

檢查元素是否在集合

獲取集合中的元素數量

取差集/並集/交集

元素轉移(從集合a移至集合b)

zset

有序集合,每個元素都有一個分值,用於對元素進行排序

取交集/並集

獲取一個元素的rank

獲取分值在某個範圍內的元素數量

獲取分值在某個範圍內的元素

按rank範圍獲取元素

redis事務

redis事務和我們熟悉的mysql事務有所區別,它們的相同在於都是對一個或一組命令的打包執行,不同的地方在於redis事務不可回滾。

redis的事務具有原子性,一個事務的執行有兩種結果

完全執行

完全不執行

一些不可抗力因素除外,如服務掛掉,伺服器斷電。redis事務是一個獨立的請求,執行過程中會阻塞其它請求。

實現redis事務有以下兩種方式

multi-exec

lua指令碼

multi-exec

127.0.0.1:6380[1]> set counter1 1
OK
127.0.0.1:6380[1]> set counter2 2
OK
127.0.0.1:6380[1]> set counter3 3
OK
127.0.0.1:6380[1]>
127.0.0.1:6380[1]>
127.0.0.1:6380[1]> multi
OK
127.0.0.1:6380[1]> incr counter1
QUEUED
127.0.0.1:6380[1]> sadd counter2 1
QUEUED
127.0.0.1:6380[1]> incr counter3
QUEUED
127.0.0.1:6380[1]> exec
1) (integer) 2
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) (integer) 4

從上面的事務執行輸出可以看到,我們的sadd執行出錯了,原因是操作的資料型別不正確,但redis沒有中止整個事務,而是繼續往下執行。redis這麼做是有道理的,見參考文獻1。

multi-exec和watch的組合可以實現類似於mysql事務的功能,如果被watch的key在事務執行前被修改了,redis會放棄執行事務。

lua指令碼

和multi-exec相比,lua指令碼的優勢在於靈活性,也可以減少一定的網路時間消耗(為什麼?)。

鑑於redis的工作模式,不建議用lua指令碼實現耗時較長的事務。

下面模擬了lua指令碼的執行阻塞其它請求的場景,大家可以親自試一下。

client 1

eval "local i=0; while(i<1000000) do redis.call('keys','*'); i=i+1; end" 0

client 2

incr counter

redis的實際應用

型別:string

命令:setnx name alice |set name alice NX

返回true則鎖定成功,否則鎖定失敗

瞭解了redis的工作模式,就知道為什麼用redis實現鎖是如此容易了。用memcache也可以實現鎖,但程式碼層面要複雜一些,參見memcached.cas。

事件佇列

型別:zset

命令:zadd,zrangebyscore,zrem

適合儲存一些需要順序處理的事件,將事件的score值設為時間戳或自增id即可。為什麼用list不可以?

計數器

型別:string

命令:incr

抽獎限額

型別:string

命令:incrby

某活動的現金紅包每天最多隻能傳送10000元。下面是這段邏輯的虛擬碼,如果能夠舉一反三,這段程式碼將大有用武之地。大家可以用併發測試工具測試這段程式碼,如果發現了bug,或者能有更好的實現方式,請不要告訴我 -_-

$key = 'max_amount';
$amountLimit = 10000;

if (!$currAmount = Redis::get($key)) {
  $currAmount = 9990;     // 從持久化資料庫獲取當前已發放金額
  // 初始化
  Redis::setnx($key,$currAmount);
}

if ($currAmount >= $amountLimit) {
  // 超出限額退出
}

// 抽獎金額
$rewardAmount = 10;
if ($rewardAmount > $amountLimit - $currAmount) {
  $rewardAmount = $amountLimit - $currAmount;
}
if (Redis::incrby($key,$rewardAmount) > $amountLimit) {
  // 超出限額退出
} else {
  // 成功抽獎
}

初始化為何用setnx,用set可以嗎?

最後為何使用incrby,不用可以嗎?

總結

文章內容不多,所謂的乾貨更少,這和作者的水平有關,也和文章的定位有關。細心的朋友可能發現了,文中有一些內容是帶有問號的,有興趣的朋友可以加以思考。希望能給大家一個參考,也希望大家多多支援我們