使用redis的increment()方法實現(xiàn)計數(shù)器功能案例
一直知道redis可以用來實現(xiàn)計數(shù)器功能,但是之前沒有實際使用過,昨天碰到一個需求:用戶掃碼當(dāng)天達(dá)到20次即提示:當(dāng)日掃碼次數(shù)達(dá)到上限!
當(dāng)時就想到使用redis的遞增方法increment()來實現(xiàn)計數(shù)器功能,一定要注意redisTemplate和stringRedisTemplate的使用
首先設(shè)置key:
該key我使用了用戶id和當(dāng)天日期作為key的一部分,date:xxxx-xx-xx格式,這樣一來該用戶在第二天掃碼的時候又是一個新key,因為日期不同了
設(shè)置key的過期時間:
實現(xiàn)計數(shù)器功能:
通過使用上面的方法,redis的計數(shù)器功能就可以實現(xiàn)了。
在使用過程過遇到的問題:
在使用的過程中,老是會拋錯:ERR value is not an integer or out of range
后來發(fā)現(xiàn)當(dāng)時我使用的方法底層用的redisTemplate和stringRedisTemplate串了,當(dāng)時setKey的時候用的方法底層是
stringRedisTemplate,后面我想get(key)的時候方法底層的模板使用的是redisTemplate,后面統(tǒng)一了一下模板的使用,然后計數(shù)
器功能正常運行不再拋錯。
看過很多文章說是序列化器的鍋,increment方法必須是stringRedisTemplate模板才能使用,但是我在實際使用的時候也確實是
用了redisTemplate,這個具體原因我還在看,此次使用中最主要的問題是setKey的時候使用的模板和取key的時候使用的模板不
一致導(dǎo)致的。寫個筆記記錄一下,一個坑不踩第二遍。大家如果遇到一樣的問題可以一起討論學(xué)習(xí)一下。
補(bǔ)充知識:認(rèn)識redis:redis計數(shù)器與數(shù)量控制
這篇文章是我個人對redis的一些理解,可以幫助大家系統(tǒng)的認(rèn)識redis。本文的目標(biāo)讀者是使用過redis,但對redis了解不深的朋友。文章內(nèi)容以redis為主,也會少量提到memcached。文章從redis的設(shè)計目的、工作模式、應(yīng)用場景等方面闡述,最后會講解一些具體的應(yīng)用場景,還會夾帶一些代碼作為“干貨”。
鑒于本人水平有限,文中如有不準(zhǔn)確的內(nèi)容,敬請斧正。
redis是什么
redis是一種內(nèi)存型的數(shù)據(jù)存儲器,使用場景
數(shù)據(jù)庫
緩存
消息代理(message broker)
同memcached對比
memcached是分布式的內(nèi)存對象緩存系統(tǒng),設(shè)計意圖為通過緩解數(shù)據(jù)庫壓力來加快web應(yīng)用的響應(yīng)速度。
上面的描述分別被放在了redis和memcached的官網(wǎng)首頁最顯眼的位置,這也回答了redis和memcached的本質(zhì)區(qū)別。
redis的工作模式
redis的工作模式為單進(jìn)程,這意味著redis只能利用到一個cpu內(nèi)核。 redis對請求的處理是串行的,對于同時涌進(jìn)來的多個請求,redis首先把請求存入隊列,按請求到達(dá)的先后順序串行處理。
了解單進(jìn)程和串行這兩個特點,有助于我們使用redis時“揚長避短”。
之前有同事提到過,為何redis不適合存儲大塊的數(shù)據(jù)?從redis的工作模式我們可以窺知一二:大塊的數(shù)據(jù)意味著需要較長的io時間,包括內(nèi)存io和網(wǎng)絡(luò)的io,cpu資源在io過程中是一直被占用的,這會阻塞其它請求,從而影響redis的整體性能。
數(shù)據(jù)類型
大家對redis的數(shù)據(jù)類型已經(jīng)比較熟悉了,主要有以下5種。
string
list
hash
set
sorted set
各種數(shù)據(jù)類型的常用操作
string
set,get,setnx,setex,psetex
拼接
增加、減少(整數(shù)型字符串)
位操作
list
入列,出列(這兩個命令都有阻塞模式)
按排位獲取部分元素
hash
設(shè)置某個索引
獲取某個索引
增加/減少某個索引的值(整數(shù)型字符串)
獲取所有索引的值
set
集合是一個數(shù)學(xué)概念,啰嗦提一下:集合中的元素都是唯一的
加入元素
刪除元素
檢查元素是否在集合
獲取集合中的元素數(shù)量
取差集/并集/交集
元素轉(zhuǎn)移(從集合a移至集合b)
zset
有序集合,每個元素都有一個分值,用于對元素進(jìn)行排序
取交集/并集
獲取一個元素的rank
獲取分值在某個范圍內(nèi)的元素數(shù)量
獲取分值在某個范圍內(nèi)的元素
按rank范圍獲取元素
redis事務(wù)
redis事務(wù)和我們熟悉的mysql事務(wù)有所區(qū)別,它們的相同在于都是對一個或一組命令的打包執(zhí)行,不同的地方在于redis事務(wù)不可回滾。
redis的事務(wù)具有原子性,一個事務(wù)的執(zhí)行有兩種結(jié)果
完全執(zhí)行
完全不執(zhí)行
一些不可抗力因素除外,如服務(wù)掛掉,服務(wù)器斷電。redis事務(wù)是一個獨立的請求,執(zhí)行過程中會阻塞其它請求。
實現(xiàn)redis事務(wù)有以下兩種方式
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
從上面的事務(wù)執(zhí)行輸出可以看到,我們的sadd執(zhí)行出錯了,原因是操作的數(shù)據(jù)類型不正確,但redis沒有中止整個事務(wù),而是繼續(xù)往下執(zhí)行。redis這么做是有道理的,見參考文獻(xiàn)1。
multi-exec和watch的組合可以實現(xiàn)類似于mysql事務(wù)的功能,如果被watch的key在事務(wù)執(zhí)行前被修改了,redis會放棄執(zhí)行事務(wù)。
lua腳本
和multi-exec相比,lua腳本的優(yōu)勢在于靈活性,也可以減少一定的網(wǎng)絡(luò)時間消耗(為什么?)。
鑒于redis的工作模式,不建議用lua腳本實現(xiàn)耗時較長的事務(wù)。
下面模擬了lua腳本的執(zhí)行阻塞其它請求的場景,大家可以親自試一下。
client 1
eval "local i=0; while(i<1000000) do redis.call('keys', '*'); i=i+1; end" 0
client 2
incr counter
redis的實際應(yīng)用
鎖
類型:string
命令:setnx name alice |set name alice NX
返回true則鎖定成功,否則鎖定失敗
了解了redis的工作模式,就知道為什么用redis實現(xiàn)鎖是如此容易了。用memcache也可以實現(xiàn)鎖,但代碼層面要復(fù)雜一些,參見memcached.cas。
事件隊列
類型:zset
命令:zadd,zrangebyscore,zrem
適合存儲一些需要順序處理的事件,將事件的score值設(shè)為時間戳或自增id即可。為什么用list不可以?
計數(shù)器
類型:string
命令:incr
抽獎限額
類型:string
命令:incrby
某活動的現(xiàn)金紅包每天最多只能發(fā)送10000元。下面是這段邏輯的偽代碼,如果能夠舉一反三,這段代碼將大有用武之地。大家可以用并發(fā)測試工具測試這段代碼,如果發(fā)現(xiàn)了bug,或者能有更好的實現(xiàn)方式,請不要告訴我 -_-
$key = 'max_amount'; $amountLimit = 10000; if (!$currAmount = Redis::get($key)) { $currAmount = 9990; // 從持久化數(shù)據(jù)庫獲取當(dāng)前已發(fā)放金額 // 初始化 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,不用可以嗎?
總結(jié)
文章內(nèi)容不多,所謂的干貨更少,這和作者的水平有關(guān),也和文章的定位有關(guān)。細(xì)心的朋友可能發(fā)現(xiàn)了,文中有一些內(nèi)容是帶有問號的,有興趣的朋友可以加以思考。希望能給大家一個參考,也希望大家多多支持腳本之家
相關(guān)文章
SpringBoot去除內(nèi)嵌tomcat的實現(xiàn)
這篇文章主要介紹了SpringBoot去除內(nèi)嵌tomcat的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09Java實現(xiàn)微信網(wǎng)頁授權(quán)的示例代碼
這篇文章主要介紹了Java實現(xiàn)微信網(wǎng)頁授權(quán)的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-07-07Java編程實現(xiàn)統(tǒng)計一個字符串中各個字符出現(xiàn)次數(shù)的方法
這篇文章主要介紹了Java編程實現(xiàn)統(tǒng)計一個字符串中各個字符出現(xiàn)次數(shù)的方法,涉及java針對字符串的遍歷、判斷、運算等相關(guān)操作技巧,需要的朋友可以參考下2017-12-12sharding-jdbc 兼容 MybatisPlus動態(tài)數(shù)據(jù)源的配置方法
這篇文章主要介紹了sharding-jdbc 兼容 MybatisPlus動態(tài)數(shù)據(jù)源的配置方法,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2024-07-07教你怎么用java實現(xiàn)客戶端與服務(wù)器一問一答
這篇文章主要介紹了教你怎么用java實現(xiàn)客戶端與服務(wù)器一問一答,文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04JDBC如何訪問MySQL數(shù)據(jù)庫,并增刪查改
這篇文章主要介紹了JDBC如何訪問MySQL數(shù)據(jù)庫,幫助大家更好的理解和學(xué)習(xí)java與MySQL,感興趣的朋友可以了解下2020-08-08Java Druid連接池與Apache的DBUtils使用教程
這篇文章主要介紹了Java Druid連接池與Apache的DBUtils使用方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-12-12SpringBoot之Helloword 快速搭建一個web項目(圖文)
這篇文章主要介紹了SpringBoot之Helloword 快速搭建一個web項目(圖文),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12