Redis并發(fā)訪問(wèn)問(wèn)題詳細(xì)講解
前言
我們?cè)谑褂肦edis的過(guò)程中,難免會(huì)遇到并發(fā)訪問(wèn)及數(shù)據(jù)更新的問(wèn)題。但很多場(chǎng)景對(duì)數(shù)據(jù)的并發(fā)修改是很敏感的,比如庫(kù)存數(shù)據(jù)如果沒有做好并發(fā)讀取和更新的版本控制,就會(huì)導(dǎo)致嚴(yán)重的業(yè)務(wù)問(wèn)題。今天就來(lái)說(shuō)說(shuō)應(yīng)該如何做好并發(fā)訪問(wèn)及數(shù)據(jù)更新問(wèn)題。
什么場(chǎng)景需要控制并發(fā)訪問(wèn)
需要控制并發(fā)訪問(wèn),說(shuō)明這些并發(fā)的訪問(wèn)可能會(huì)對(duì)其他的訪問(wèn)造成影響。比如上面提到的庫(kù)存問(wèn)題,若同一時(shí)期有多個(gè)客戶端訪問(wèn)商品A的庫(kù)存數(shù)據(jù),并且可能要更更新庫(kù)存數(shù)據(jù),這時(shí)候就需要對(duì)并發(fā)訪問(wèn)進(jìn)行控制了。
說(shuō)到底,并發(fā)訪問(wèn)需要控制的就是對(duì)數(shù)據(jù)的更新動(dòng)作。 一般來(lái)說(shuō),客戶端要進(jìn)行數(shù)據(jù)更新時(shí)可分為2個(gè)步驟:
- 客戶端讀取Redis數(shù)據(jù)到本地。
- 確認(rèn)數(shù)據(jù)后,修改Redis的數(shù)據(jù)。
單個(gè)訪問(wèn)來(lái)看,這個(gè)過(guò)程并沒什么問(wèn)題。但是并發(fā)多了,一分為二的過(guò)程就會(huì)造成數(shù)據(jù)錯(cuò)誤的問(wèn)題。這里還是用庫(kù)存的例子來(lái)說(shuō):
- 時(shí)間a::客戶端1讀取到庫(kù)存=10,我們需要對(duì)庫(kù)存+1=11的操作。
- 時(shí)間b:客戶端2讀取到的庫(kù)存也是10,這次要對(duì)庫(kù)存-1=9的操作。
- 時(shí)間c:客戶端1將+1后的值11寫回到Redis中。
- 時(shí)間d:客戶端2將-1后的值9寫回到Redis中。
這樣下來(lái),很明顯能發(fā)現(xiàn)庫(kù)存數(shù)據(jù)錯(cuò)了。10+1-1 = 10,正確庫(kù)存是10,而上述場(chǎng)景最后為9。
由此可見,這個(gè)一分為二的操作不具有原子性,從而產(chǎn)生了錯(cuò)誤的結(jié)果。類型這種場(chǎng)景很多,因此我們需要對(duì)這些并發(fā)訪問(wèn)的場(chǎng)景加以控制。
并發(fā)訪問(wèn)的控制方法
Redis并發(fā)訪問(wèn)的控制,總的來(lái)說(shuō)有2種方式。分別是加入鎖機(jī)制和讓一系列操作原子化。
1、加入鎖機(jī)制
首先第一點(diǎn),加入鎖機(jī)制是很常見的解決方案。簡(jiǎn)單來(lái)說(shuō)就是一個(gè)客戶端訪問(wèn)數(shù)據(jù)之前,先要獲取鎖,等數(shù)據(jù)操作完之后再解鎖。而在這個(gè)客戶端擁有鎖的過(guò)程中,其他客戶端如果也想訪問(wèn)修改該數(shù)據(jù),必須得等鎖釋放了之后,獲取到了鎖才行。
加鎖這個(gè)方案是可以解決并發(fā)訪問(wèn)的數(shù)據(jù)準(zhǔn)確問(wèn)題,但放在redis這個(gè)場(chǎng)景中并不是很好。首先,Redis作為緩存本身并發(fā)訪問(wèn)就很多,頻繁的加鎖解鎖,會(huì)大大降低redis的訪問(wèn)性能;然后,Redis的客戶端在要加鎖時(shí),需要用到分布式鎖。我們又得用額外的精力去維護(hù)這個(gè)分布式鎖。
2、操作原子化
操作原子化,也就是讓要執(zhí)行的一系列動(dòng)作都保持原子性操作。它的優(yōu)點(diǎn)就是不需要加入額外的鎖機(jī)制。并發(fā)的數(shù)據(jù)準(zhǔn)確性達(dá)到了,對(duì)Redis的性能也不會(huì)有太大的影響。
Redis要實(shí)現(xiàn)原子操作,總結(jié)有2種方式:
- 單命令操作:也就是Redis中的INCR、HINCRBY等命令,直接將簡(jiǎn)單的加減操作合成一個(gè)命令執(zhí)行;
- Lua腳本:借助Lua腳本,讓多個(gè)操作在Lua腳本上實(shí)現(xiàn)原子性操作。
1.單命令操作
首先,單命令操作,將數(shù)值的加減直接用Redis命令來(lái)執(zhí)行。像string的加減可用INCR、DECR操作,hash列表field的加減可用HINCRBY操作。
比如下面截圖,兩個(gè)客戶端在不同時(shí)刻讀取的linux_pids a值為4,各自+1、-1后a值為4。結(jié)果是正確的。
由此可見,用Redis的INCR、DECR等命令可以解決數(shù)值簡(jiǎn)單增減的并發(fā)場(chǎng)景。但如果我們對(duì)數(shù)據(jù)的更新不僅僅是簡(jiǎn)單的加減操作時(shí),Redis的這些命令就無(wú)能為力了。此時(shí)我們可以考慮另一種方案:Lua腳本。
2.Lua腳本
Lua語(yǔ)言是由C寫的,因此支持多平臺(tái)和系統(tǒng)。從Redis2.6開始,Redis就內(nèi)置了Lua解釋器,我們能直接用Redis客戶端來(lái)執(zhí)行l(wèi)ua腳本。
我們可以將需要執(zhí)行的一系列操作用Lua腳本寫好,然后用Redis執(zhí)行它。Lua腳本的方法能保證原子性操作的原因是:Redis會(huì)將Lua腳本一次性執(zhí)行,也就是說(shuō)執(zhí)行Lua腳本是0-1的操作,要么成功,要么失敗??梢岳斫獬蒑ySQL的事務(wù)特性。
Redis使用lua腳本有2種方式:
- 客戶端中使用:用到script load腳本內(nèi)容、evalsha等命令
- 執(zhí)行直接執(zhí)行l(wèi)ua腳本
我們一般用第二種方式來(lái)執(zhí)行。
客戶端使用方法:
先用script load
加載腳本命令,再用evalsha
執(zhí)行加載得到的sha1值。
127.0.0.1:6379> script load "return 'hello'"
"1b936e3fe509bcbc9cd0664897bbe8fd0cac101b"
127.0.0.1:6379> evalsha "1b936e3fe509bcbc9cd0664897bbe8fd0cac101b" 0
"hello"
再來(lái)看看Redis使用Lua腳本的語(yǔ)法:
redis-cli --eval {lua_path} KEYS[1] KEYS[2]... , ARGV[1] ARGV[2]...
--eval: 執(zhí)行l(wèi)ua腳本的命令
{lua_path}: lua腳本的路徑
KEYS[1] KEYS[2]: lua腳本中要操作的redis鍵,我們可以在lua腳本中用KEYS[1],KEYS[2],KEYS[3]指定多個(gè)
ARGV[1] ARGV[2]: 傳入到lua腳本的參數(shù),在腳本中用ARGV[1],ARGV[2]...來(lái)獲取。
Redis使用lua腳本的場(chǎng)景很多,最經(jīng)典的案例當(dāng)屬利用lua來(lái)控制某個(gè)IP的訪問(wèn)頻率了。比如說(shuō)需要防止惡意訪問(wèn)網(wǎng)站的行為,我們規(guī)定1分鐘內(nèi)訪問(wèn)次數(shù)不能超過(guò)30次,實(shí)現(xiàn)的方法有很多,比如說(shuō)漏桶方案、令牌桶方案,但使用最多的還是Redis+lua的分布式限流方案。
我們用lua腳本(test_lua.script)來(lái)簡(jiǎn)單實(shí)現(xiàn)一下上述功能,就是1分鐘內(nèi)若訪問(wèn)次數(shù)超過(guò)30,直接攔截,否則訪問(wèn)次數(shù)+1:
-- 限流的key
local limit_key = KEYS[1]
-- 限流次數(shù)
local limit_nums = 30
-- 當(dāng)前訪問(wèn)次數(shù)
local current_num = tonumber(redis.call('get', limit_key) or 0)
-- 超出限流次數(shù)
if current_num + 1 > limit_num
then
return '超出訪問(wèn)次數(shù)'
-- 沒有超出限流數(shù),訪問(wèn)次數(shù)+1
else
redis.call("INCRBY", limit_key, "1")
-- 第一次訪問(wèn),設(shè)置過(guò)期時(shí)間
if current_num == 0 then
redis.call("expire", limit_key, "60")
return current + 1
end
用Redis執(zhí)行,命令如下:
redis-cli --eval test_lua.script limit_key
小結(jié)
本文介紹了Redis并發(fā)訪問(wèn)的控制問(wèn)題,以及如何保證并發(fā)操作的原子化。原子化操作可通過(guò)單命令操作和Lua腳本的方式實(shí)現(xiàn)。我們?cè)趹?yīng)對(duì)相關(guān)問(wèn)題時(shí),可根據(jù)需要選擇對(duì)應(yīng)方案解決之。
到此這篇關(guān)于Redis并發(fā)訪問(wèn)問(wèn)題詳細(xì)講解的文章就介紹到這了,更多相關(guān)Redis并發(fā)訪問(wèn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis?HyperLogLog數(shù)據(jù)統(tǒng)計(jì)輕量級(jí)解決方案詳解
這篇文章主要為大家介紹了Redis?HyperLogLog數(shù)據(jù)統(tǒng)計(jì)輕量級(jí)解決方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12使用Redis實(shí)現(xiàn)延時(shí)任務(wù)的解決方案
這篇文章主要介紹了使用Redis實(shí)現(xiàn)延時(shí)任務(wù)的解決方案,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08一文了解發(fā)現(xiàn)并解決Redis熱key與大key問(wèn)題
熱key是服務(wù)端的常見問(wèn)題,本文主要介紹Redis熱key與大key問(wèn)題的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-05-05