這篇文章是我個(gè)人對(duì)redis的一些理解,可以幫助大家系統(tǒng)的認(rèn)識(shí)redis纸泄。本文的目標(biāo)讀者是使用過redis条篷,但對(duì)redis了解不深的朋友。文章內(nèi)容以redis為主芦倒,也會(huì)少量提到memcached艺挪。文章從redis的設(shè)計(jì)目的、工作模式兵扬、應(yīng)用場(chǎng)景等方面闡述麻裳,最后會(huì)講解一些具體的應(yīng)用場(chǎng)景,還會(huì)夾帶一些代碼作為“干貨”器钟。
鑒于本人水平有限津坑,文中如有不準(zhǔn)確的內(nèi)容,敬請(qǐng)斧正傲霸。
redis是什么
redis是一種內(nèi)存型的數(shù)據(jù)存儲(chǔ)器疆瑰,使用場(chǎng)景
- 數(shù)據(jù)庫(kù)
- 緩存
- 消息代理(message broker)
同memcached對(duì)比
memcached是分布式的內(nèi)存對(duì)象緩存系統(tǒng),設(shè)計(jì)意圖為通過緩解數(shù)據(jù)庫(kù)壓力來加快web應(yīng)用的響應(yīng)速度昙啄。
上面的描述分別被放在了redis和memcached的官網(wǎng)首頁(yè)最顯眼的位置穆役,這也回答了redis和memcached的本質(zhì)區(qū)別。
redis的工作模式
redis的工作模式為單進(jìn)程梳凛,這意味著redis只能利用到一個(gè)cpu內(nèi)核耿币。
redis對(duì)請(qǐng)求的處理是串行的,對(duì)于同時(shí)涌進(jìn)來的多個(gè)請(qǐng)求伶跷,redis首先把請(qǐng)求存入隊(duì)列掰读,按請(qǐng)求到達(dá)的先后順序串行處理秘狞。
了解單進(jìn)程和串行這兩個(gè)特點(diǎn)叭莫,有助于我們使用redis時(shí)“揚(yáng)長(zhǎng)避短”。
之前有同事提到過烁试,為何redis不適合存儲(chǔ)大塊的數(shù)據(jù)雇初?從redis的工作模式我們可以窺知一二:大塊的數(shù)據(jù)意味著需要較長(zhǎng)的io時(shí)間,包括內(nèi)存io和網(wǎng)絡(luò)的io减响,cpu資源在io過程中是一直被占用的靖诗,這會(huì)阻塞其它請(qǐng)求郭怪,從而影響redis的整體性能。
數(shù)據(jù)類型
大家對(duì)redis的數(shù)據(jù)類型已經(jīng)比較熟悉了刊橘,主要有以下5種鄙才。
- string
- list
- hash
- set
- sorted set
各種數(shù)據(jù)類型的常用操作
string
- set,get促绵,setnx攒庵,setex,psetex
- 拼接
- 增加败晴、減少(整數(shù)型字符串)
- 位操作
list
- 入列浓冒,出列(這兩個(gè)命令都有阻塞模式)
- 按排位獲取部分元素
hash
- 設(shè)置某個(gè)索引
- 獲取某個(gè)索引
- 增加/減少某個(gè)索引的值(整數(shù)型字符串)
- 獲取所有索引的值
set
集合是一個(gè)數(shù)學(xué)概念,啰嗦提一下:集合中的元素都是唯一的
- 加入元素
- 刪除元素
- 檢查元素是否在集合
- 獲取集合中的元素?cái)?shù)量
- 取差集/并集/交集
- 元素轉(zhuǎn)移(從集合a移至集合b)
zset
有序集合尖坤,每個(gè)元素都有一個(gè)分值稳懒,用于對(duì)元素進(jìn)行排序
- 取交集/并集
- 獲取一個(gè)元素的rank
- 獲取分值在某個(gè)范圍內(nèi)的元素?cái)?shù)量
- 獲取分值在某個(gè)范圍內(nèi)的元素
- 按rank范圍獲取元素
redis事務(wù)
redis事務(wù)和我們熟悉的mysql事務(wù)有所區(qū)別,它們的相同在于都是對(duì)一個(gè)或一組命令的打包執(zhí)行慢味,不同的地方在于redis事務(wù)不可回滾场梆。
redis的事務(wù)具有原子性,一個(gè)事務(wù)的執(zhí)行有兩種結(jié)果
- 完全執(zhí)行
- 完全不執(zhí)行
一些不可抗力因素除外纯路,如服務(wù)掛掉辙谜,服務(wù)器斷電。redis事務(wù)是一個(gè)獨(dú)立的請(qǐng)求感昼,執(zhí)行過程中會(huì)阻塞其它請(qǐng)求装哆。
實(shí)現(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í)行出錯(cuò)了定嗓,原因是操作的數(shù)據(jù)類型不正確蜕琴,但redis沒有中止整個(gè)事務(wù),而是繼續(xù)往下執(zhí)行宵溅。redis這么做是有道理的凌简,見參考文獻(xiàn)1。
multi-exec和watch的組合可以實(shí)現(xiàn)類似于mysql事務(wù)的功能恃逻,如果被watch的key在事務(wù)執(zhí)行前被修改了雏搂,redis會(huì)放棄執(zhí)行事務(wù)。
lua腳本
和multi-exec相比寇损,lua腳本的優(yōu)勢(shì)在于靈活性凸郑,也可以減少一定的網(wǎng)絡(luò)時(shí)間消耗(為什么?)矛市。
鑒于redis的工作模式芙沥,不建議用lua腳本實(shí)現(xiàn)耗時(shí)較長(zhǎng)的事務(wù)。
下面模擬了lua腳本的執(zhí)行阻塞其它請(qǐng)求的場(chǎng)景,大家可以親自試一下而昨。
client 1
eval "local i=0; while(i<1000000) do redis.call('keys', '*'); i=i+1; end" 0
client 2
incr counter
redis的實(shí)際應(yīng)用
鎖
類型:string
命令:setnx name alice |set name alice NX
返回true則鎖定成功救氯,否則鎖定失敗
了解了redis的工作模式,就知道為什么用redis實(shí)現(xiàn)鎖是如此容易了歌憨。用memcache也可以實(shí)現(xiàn)鎖着憨,但代碼層面要復(fù)雜一些,參見memcached.cas务嫡。
事件隊(duì)列
類型:zset
命令:zadd享扔,zrangebyscore,zrem
適合存儲(chǔ)一些需要順序處理的事件植袍,將事件的score值設(shè)為時(shí)間戳或自增id即可惧眠。為什么用list不可以?
計(jì)數(shù)器
類型:string
命令:incr
抽獎(jiǎng)限額
類型:string
命令:incrby
某活動(dòng)的現(xiàn)金紅包每天最多只能發(fā)送10000元于个。下面是這段邏輯的偽代碼氛魁,如果能夠舉一反三,這段代碼將大有用武之地厅篓。大家可以用并發(fā)測(cè)試工具測(cè)試這段代碼秀存,如果發(fā)現(xiàn)了bug,或者能有更好的實(shí)現(xiàn)方式羽氮,請(qǐng)不要告訴我 -_-
$key = 'max_amount';
$amountLimit = 10000;
if (!$currAmount = Redis::get($key)) {
$currAmount = 9990; // 從持久化數(shù)據(jù)庫(kù)獲取當(dāng)前已發(fā)放金額
// 初始化
Redis::setnx($key, $currAmount);
}
if ($currAmount >= $amountLimit) {
// 超出限額退出
}
// 抽獎(jiǎng)金額
$rewardAmount = 10;
if ($rewardAmount > $amountLimit - $currAmount) {
$rewardAmount = $amountLimit - $currAmount;
}
if (Redis::incrby($key, $rewardAmount) > $amountLimit) {
// 超出限額退出
} else {
// 成功抽獎(jiǎng)
}
初始化為何用setnx或链,用set可以嗎?
最后為何使用incrby档押,不用可以嗎澳盐?
總結(jié)
文章內(nèi)容不多,所謂的干貨更少令宿,這和作者的水平有關(guān)叼耙,也和文章的定位有關(guān)。細(xì)心的朋友可能發(fā)現(xiàn)了粒没,文中有一些內(nèi)容是帶有問號(hào)的筛婉,有興趣的朋友可以加以思考。
如果這篇文章能幫到你癞松,不妨點(diǎn)個(gè)贊爽撒;如果能給我一些具體的反饋,那就完美了响蓉。