自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Redis 到底能不能保證原子性?

數(shù)據(jù)庫(kù) Redis
Redis是 Java程序員工作中經(jīng)常使用的一個(gè) NoSQL,很多人把它對(duì)標(biāo)成數(shù)據(jù)庫(kù),因此,原子性成了特別關(guān)注的問(wèn)題。那么,Redis到底能不能保證原子性?這篇文章來(lái)聊一聊。

Redis是 Java程序員工作中經(jīng)常使用的一個(gè) NoSQL,很多人把它對(duì)標(biāo)成數(shù)據(jù)庫(kù),因此,原子性成了特別關(guān)注的問(wèn)題。那么,Redis到底能不能保證原子性?這篇文章來(lái)聊一聊。

一、原子性

要想弄清楚這個(gè)問(wèn)題,我們需要對(duì)“原子性”這個(gè)概念有一個(gè)清晰的認(rèn)識(shí),因此,首先要分析的是原子性的概念。

1. 通常意義的原子性

通常意義上,我們說(shuō)的原子性是指關(guān)系型數(shù)據(jù)庫(kù) RDBMS(比如 MySQL)的原子性,也就是 ACID(Atomicity、Consistency、Isolation、Durability)中 Atomicity這一項(xiàng)特性。

ACID 中的原子性指:事務(wù)中的所有操作要么全部執(zhí)行,要么全部不執(zhí)行。

這里以銀行轉(zhuǎn)賬,賬戶(hù)A 給賬戶(hù)B 轉(zhuǎn)賬100元為例來(lái)解釋原子性:

  • 賬戶(hù)A 減去100元;
  • 賬戶(hù)B 增加100元;

原子性是指上面兩個(gè)過(guò)程,要么全部執(zhí)行,要么全部不執(zhí)行。也就是說(shuō),賬戶(hù)A 減去 100元的同時(shí),賬戶(hù)B 必須增加100元,否則,該操作就不具備原子性。Java代碼簡(jiǎn)要實(shí)現(xiàn)如下圖:

2. Lua 原子性

在分析 Lua的原子性之前,我們先看看 Lua是什么,下圖摘自 Lua官方描述:

從官方描述可以得知:Lua 是一種功能強(qiáng)大、高效、輕量級(jí)、可嵌入的腳本語(yǔ)言。它支持過(guò)程編程、面向?qū)ο缶幊?、函?shù)式編程、數(shù)據(jù)驅(qū)動(dòng)編程和數(shù)據(jù)描述。 Lua 將簡(jiǎn)單的過(guò)程語(yǔ)法與基于關(guān)聯(lián)數(shù)組和可擴(kuò)展語(yǔ)義的強(qiáng)大數(shù)據(jù)描述結(jié)構(gòu)相結(jié)合。Lua 是動(dòng)態(tài)類(lèi)型的,通過(guò)使用基于寄存器的虛擬機(jī)解釋字節(jié)碼來(lái)運(yùn)行,并具有自動(dòng)內(nèi)存管理和增量垃圾回收功能,使其成為配置、腳本編寫(xiě)和快速原型設(shè)計(jì)的理想選擇。

Lua 本身并沒(méi)有提供對(duì)于原子性的直接支持,它只是一種腳本語(yǔ)言,通常是嵌入到其他宿主程序中運(yùn)行,比如 Redis。

在 Redis中執(zhí)行 Lua的原子性是指:整個(gè) Lua腳本在執(zhí)行期間,會(huì)被當(dāng)作一個(gè)整體,不會(huì)被其他客戶(hù)端的命令打斷。

為了對(duì) Redis執(zhí)行 Lua的原子性有一個(gè)感官上的認(rèn)識(shí),這里以 Lua腳本中需要完成 SET key1 value1 和 INCRBY key2 value2 和 SET key3 value3 三個(gè)命令為例:

上述例子,整個(gè) luaScript 字符串腳本作為一個(gè)整體被執(zhí)行且不被其他事務(wù)打斷,這就是一個(gè)原子性的操作。

好了,總結(jié)下 ACID的原子性和 Redis執(zhí)行 Lua腳本原子性在概念上的差異:

  • ACID的原子性是指:事務(wù)中的命令要么全執(zhí)行,要么全部不執(zhí)行;
  • Redis中執(zhí)行 Lua腳本原子性是指:Lua腳本會(huì)作為一個(gè)整體執(zhí)行且不被其他客戶(hù)端打斷,至于 Lua腳本里面的命令是否必須全部成功,或者全部失敗,并不要求。關(guān)于這一點(diǎn),在接下來(lái)的內(nèi)容也會(huì)詳細(xì)解釋?zhuān)?/li>

在分析原子性概念時(shí),我們可以發(fā)現(xiàn)“原子性”其實(shí)是事務(wù)中的一項(xiàng)特性,因此,接下來(lái)分析 Redis的事務(wù)。

二、Redis 事務(wù)

下圖是 Redis官方對(duì)事務(wù)描述的摘要:

文檔看起來(lái)很長(zhǎng),總結(jié)成一句話(huà):Redis 事務(wù)允許執(zhí)行一批命令,通過(guò)執(zhí)行 MULTI命令開(kāi)啟事務(wù),執(zhí)行 EXEC命令結(jié)束事務(wù),WATCH 和 DISCARD 配合事務(wù)一起使用,提供了一種 CAS(check-and-set) 樂(lè)觀(guān)鎖的機(jī)制。WATCH 用于監(jiān)聽(tīng) Key,如果被監(jiān)聽(tīng)的 Key有任何一個(gè)發(fā)生變化,則中止事務(wù)(被動(dòng)關(guān)閉事務(wù)),而 DISCARD 用于主動(dòng)中止事務(wù)。

1. MULTI/EXEC

用一個(gè)示例來(lái)理解 MULTI/EXEC:

通過(guò)執(zhí)行的結(jié)果可以看出:Redis的事務(wù)是以 MULTI命令開(kāi)啟,以 EXEC命令結(jié)束,期間所有的命令都是先進(jìn)入隊(duì)列,只有執(zhí)行 EXEC命令時(shí),才會(huì)把隊(duì)列中的所有命令順序串行執(zhí)行,并且返回一個(gè)所有命令執(zhí)行結(jié)果的數(shù)組,包括命令執(zhí)行的錯(cuò)誤信息。

需要注意的是:在 EXEC 執(zhí)行后,即使事務(wù)隊(duì)列中有命令執(zhí)行失敗,隊(duì)列中的所有其他命令也會(huì)被處理,Redis 不會(huì)停止執(zhí)行這些命令。

DISCARD 和 WATCH 也是 Redis 中用于事務(wù)的兩個(gè)命令,它們與 MULTI 和 EXEC 一起使用,提供更復(fù)雜的事務(wù)處理機(jī)制。

2. WATCH

WATCH 命令用于監(jiān)聽(tīng)一個(gè)或多個(gè) Key,如果在執(zhí)行事務(wù)期間這些 Key中任何一個(gè)Key的 value被其他事務(wù)修改,當(dāng)前整個(gè)事務(wù)將會(huì)被中止。(需要注意:低于 6.0.9 的 Redis 版本,Key過(guò)期不會(huì)中止事務(wù))

如下示例:事務(wù)1 watch key1 key2,事務(wù)2在事務(wù)1執(zhí)行期間修改 key2 = 10,當(dāng)事務(wù)1執(zhí)行 exec命令時(shí),因?yàn)?watch監(jiān)聽(tīng)到 key2被其他事務(wù)(事務(wù)2)修改了(value=10) , 因此事務(wù)1被取消,事務(wù)隊(duì)列中的所有命令被清除,即 set key1 value1 和 incrby key 2兩條命令都不執(zhí)行,key2的 value還是10;

事務(wù)1

事務(wù)2

watch key1 key2


multi


set key1 value1


incrby key2 2

set key2 10

exec


keys * // 只有key2=10

keys * // 只有key2=10DISCARD

DISCARD 命令用于中止事務(wù)。

如下示例,執(zhí)行 DISCARD命令后,當(dāng)前事務(wù)被中止,因此,執(zhí)行 EXEC 時(shí)會(huì)報(bào)“ERR EXEC without MULTI”錯(cuò)誤。

3. 事務(wù)中的錯(cuò)誤

事務(wù)中主要會(huì)出現(xiàn)兩種類(lèi)型的錯(cuò)誤:

(1) 事務(wù)命令進(jìn)入事務(wù)隊(duì)列之前出錯(cuò)。例如,命令語(yǔ)法錯(cuò)誤(參數(shù)錯(cuò)誤、命令名稱(chēng)錯(cuò)誤等),或者可能存在一些關(guān)鍵情況,比如內(nèi)存不足。如下示例,命令incr key2 1/0 在進(jìn)入事務(wù)隊(duì)列之前報(bào)錯(cuò),所以,當(dāng)前事務(wù)被中止,執(zhí)行 EXEC命令會(huì)報(bào)錯(cuò):

(2) 調(diào)用 EXEC 命令后,事務(wù)隊(duì)列中的命令執(zhí)行失敗。例如,對(duì)字符串值進(jìn)行加1操作。如下示例,key的 value是字符串,當(dāng)對(duì) key 執(zhí)行incr key 操作時(shí)報(bào)錯(cuò),因此,該條命令執(zhí)行失?。?/p>

4. 事務(wù)回滾

Redis的事務(wù)不支持回滾。 官方說(shuō)明如下:

Redis 不支持事務(wù)回滾,因?yàn)橹С只貪L會(huì)對(duì) Redis 的簡(jiǎn)單性和性能產(chǎn)生重大影響。

官方說(shuō)明簡(jiǎn)明扼要,其實(shí),多加思考也能理解:"Redis" 是 "REmote DIctionary Server" 的縮寫(xiě),翻譯為“遠(yuǎn)程字典服務(wù)”,設(shè)計(jì)的初衷是用于緩存,追求快速高效。而了解過(guò) ACID事務(wù)的小伙伴應(yīng)該能明白事務(wù)回滾的復(fù)雜度,因此,Redis不支持事務(wù)回滾似乎也合情合理。

到此,我們也對(duì) Redis事務(wù)做個(gè)小結(jié):Redis的事務(wù)由 MULTI/EXEC 兩個(gè)命令完成,WATCH/DISCARD 兩個(gè)命令的加持,給 Redis事務(wù)提供了 CAS 樂(lè)觀(guān)鎖機(jī)制。Redis 事務(wù)不支持回滾,它和關(guān)系型數(shù)據(jù)庫(kù)(比如 MySQL)的事務(wù)(ACID)是不一樣的。

三、Redis 如何執(zhí)行 Lua?

分析完原子性和 Redis事務(wù)這些理論知識(shí)后,我們就得動(dòng)手實(shí)操,看看 Redis是如何執(zhí)行 Lua的。

一般情況下,Redis執(zhí)行 Lua常用的方法有 2種:

  • 原生命令,比如 EVAL/EVALSHA命令等;
  • 編程工具,比如編程語(yǔ)言中提供的三方工具包或類(lèi)庫(kù);

在編寫(xiě) Lua腳本時(shí),需要注意區(qū)分 redis.call() 和 redis.pcall() 兩個(gè)命令的使用。

1. EVAL

語(yǔ)法:

EVAL script numkeys [key [key ...]] [arg [arg ...]]

EVAL語(yǔ)法很簡(jiǎn)單,EVAL script numkeys 是必填項(xiàng),[key [key ...]] [arg [arg ...]]是選填項(xiàng)。

如下示例截圖,分別展示了不傳Key,傳 1個(gè)key 和 2個(gè) key 3種場(chǎng)景:

下圖示例展示了 [key [key ...]] [arg [arg ...]] 和 numkeys 匹配錯(cuò)誤時(shí)報(bào)錯(cuò)的場(chǎng)景:

2. redis.call()

redis.call() 用于執(zhí)行 Redis的命令。當(dāng)命令執(zhí)行出錯(cuò)時(shí),會(huì)阻斷整個(gè)腳本執(zhí)行,并將錯(cuò)誤信息返回給客戶(hù)端。

如下示例:當(dāng)執(zhí)行INCRBY key2 1/0 失敗時(shí),會(huì)拋異常,后續(xù)流程被阻斷,即SET key3 value3沒(méi)有被執(zhí)行。

Redis原生命令執(zhí)行示例如下:

EVAL "redis.call('SET', 'key1', 'value1'); redis.call('INCRBY', 'key2', 1/0); redis.call('SET', 'key3', 'value3')" 0

使用 Jedis框架執(zhí)行 Lua示例如下:

查看 Lua執(zhí)行后各個(gè)key的值。

3. redis.pcall()

redis.pcall() 也用于執(zhí)行 Redis的命令。當(dāng)命令執(zhí)行出錯(cuò)時(shí),不會(huì)阻斷腳本的執(zhí)行,而是內(nèi)部捕獲錯(cuò)誤,并繼續(xù)執(zhí)行后續(xù)的命令。

如下示例:當(dāng)執(zhí)行INCRBY key2 1/0 失敗時(shí),不會(huì)拋異常,后續(xù)流程繼續(xù)執(zhí)行,即SET key3 value3 也被執(zhí)行。

Redis原生命令執(zhí)行示例:

EVAL "redis.pcall('SET', 'key1', 'value1'); redis.pcall('INCRBY', 'key2', 1/0); redis.pcall('SET', 'key3', 'value3')" 0

使用 Jedis框架執(zhí)行 Lua示例:

對(duì)于 Lua中 redis.call() 和 redis.pcall() 如何選擇,需要根據(jù)實(shí)際業(yè)務(wù)來(lái)判斷,標(biāo)準(zhǔn)是:當(dāng) Lua腳本中某條命令執(zhí)行出錯(cuò)時(shí),是否需要阻斷后續(xù)的命令執(zhí)行。

四、如何保證原子性?

首先,可以肯定的是:Redis執(zhí)行 Lua腳本可以保證原子性,不過(guò)這和 Redis Server的部署方式密不可分。

Redis是典型的 C/S(Client/Server) 模型,如下圖:

因此,Redis 通常有 3種不同的部署方式,部署方式不同,原子性的保證也不一樣。

1. 單機(jī)部署

不管 Lua腳本中操作的 key是不是同一個(gè),都能保證原子性;

2. 主從部署

Redis 主從復(fù)制是用于將主節(jié)點(diǎn)的數(shù)據(jù)同步到從節(jié)點(diǎn),以保持?jǐn)?shù)據(jù)的一致性。而Redis的所有寫(xiě)操作都在主節(jié)點(diǎn)上,所以,不管 Lua腳本中操作的 key是不是同一個(gè),都能保證原子性;

需要注意:當(dāng)主節(jié)點(diǎn)執(zhí)行寫(xiě)命令時(shí),從節(jié)點(diǎn)會(huì)異步地復(fù)制這些寫(xiě)操作。在這個(gè)復(fù)制的過(guò)程中,從節(jié)點(diǎn)的數(shù)據(jù)可能與主節(jié)點(diǎn)存在一定的延遲。因此,如果在 Lua 腳本中包含讀操作,并且該腳本在主節(jié)點(diǎn)上執(zhí)行,可能會(huì)讀到最新的數(shù)據(jù),但如果在從節(jié)點(diǎn)上執(zhí)行,可能會(huì)讀到稍有延遲的數(shù)據(jù)。

3. Cluster集群部署

如果 Lua腳本操作的 key是同一個(gè),能保證原子性;

如果操作的 Key不相同,可能被 hash 到不同的 slot,也可能 hash 到相同的 slot,所以不一定能保證原子性;

因此,在 Cluster集群部署的環(huán)境下使用 Lua腳本時(shí)一定要注意:Lua腳本中操作的是同一個(gè) Key;

4. 原子性保證

這里以 Redis單機(jī)部署為例:當(dāng)客戶(hù)端向服務(wù)器發(fā)送一個(gè)帶有 Lua腳本的請(qǐng)求時(shí),Redis會(huì)把該腳本當(dāng)作一個(gè)整體,然后加載到一個(gè)腳本緩存中,因?yàn)?Redis讀寫(xiě)命令是單線(xiàn)程操作(關(guān)于 Redis的單線(xiàn)程模型和多路復(fù)用線(xiàn)程模型會(huì)在其他的文章中講解),最終,Lua腳本的讀寫(xiě)在 Redis服務(wù)器上可以簡(jiǎn)單地抽象成下圖,所有的 Lua腳本會(huì)按照進(jìn)入順序放入隊(duì)列中,然后串行進(jìn)行讀寫(xiě),這樣就保證每個(gè) Lua不會(huì)被其他的客戶(hù)端打斷,從而保證了原子性:

五、面試該如何回答?

在面試中,Redis 執(zhí)行 Lua腳本時(shí),能否保證原子性?這個(gè)問(wèn)題如何作答?

  • 第一步,需要解釋這里的原子性是什么?它和關(guān)系數(shù)據(jù)事務(wù) ACID中的一致性的差異是什么?消除原子性在具體載體(RDBMS/NoSQL)上概念的差異;
  • 第二步,需要解釋 Redis的事務(wù),說(shuō)明 RDBMS/NoSQL 在事務(wù)上的差異點(diǎn);
  • 第三步,需要解釋 Redis在不同部署方式下原子性能否保證。Redis部署方式有3種:?jiǎn)螜C(jī)部署,主從部署,Cluster集群部署,需要說(shuō)明在哪些部署方式下能保證原子性,哪些不能保證原子性;
  • 第四步,解釋 Redis 執(zhí)行 Lua腳本是如何保證原子性;
  • 第五步,分析下 Redis的單線(xiàn)程模型 和 IO多路復(fù)用模型(加分項(xiàng)),這步是可選項(xiàng);

六、Why Lua?

既然 Redis事務(wù)能保證原子性,為什么還需要 Lua腳本呢?

  • Lua 是一種嵌入式語(yǔ)言,是 Redis官方推薦的腳本語(yǔ)言;
  • Lua 腳本一般比 MULTI/EXEC 更快、更簡(jiǎn)單;
  • Redis 事務(wù)中,事務(wù)隊(duì)列中的所有命令都必須在 EXEC命令執(zhí)行才會(huì)被執(zhí)行,對(duì)于多個(gè)命令之間存在依賴(lài)關(guān)系,比如后面的命令需要依賴(lài)上一個(gè)命令結(jié)果的場(chǎng)景,Redis事務(wù)無(wú)法滿(mǎn)足,因此 Lua 腳本更適合復(fù)雜的場(chǎng)景;
  • Redis 事務(wù)能做的 Lua能做,Redis事務(wù)做不到的 Lua也能做;

七、Lua注意事項(xiàng)

Redis執(zhí)行 Lua腳本時(shí),Lua的編寫(xiě)需要注意以下幾個(gè)點(diǎn):

  • 不要在 Lua腳本中使用阻塞命令(如BLPOP、BRPOP等)。因此這些命令可能會(huì)導(dǎo)致 Redis服務(wù)器在執(zhí)行腳本期間被阻塞,無(wú)法處理其他請(qǐng)求;
  • 不要編寫(xiě)過(guò)長(zhǎng)的 Lua腳本。因?yàn)?Redis讀寫(xiě)命令是單線(xiàn)程,過(guò)長(zhǎng)的腳本,加載,解析,運(yùn)行會(huì)比較耗時(shí),導(dǎo)致其他命令的延遲延遲增加;
  • 不要在 Lua腳本中進(jìn)行復(fù)雜耗時(shí)的邏輯;因?yàn)?Redis讀寫(xiě)命令是單線(xiàn)程的,長(zhǎng)時(shí)間運(yùn)行腳本可能導(dǎo)致其他命令的延遲增加;
  • Lua腳本中,需要注意區(qū)分 redis.call() 和 redis.pcall() 命令;
  • Lua 索引表從索引 1 開(kāi)始,而不是 0;

八、總結(jié)

  • 原子性需要區(qū)分具體使用的載體,在關(guān)系型數(shù)據(jù)庫(kù)(比如 MySQL))和 No SQL(比如Redis)中,原子性的概念是不相同的;
  • Redis的事務(wù)(MULTI/ESXEC)和關(guān)系型數(shù)據(jù)庫(kù)(比如 MySQL)的事務(wù)(ACID)也是不相同的;
  • ACID的原子性指:命令要么全部執(zhí)行,要么全部不執(zhí)行;
  • Redis執(zhí)行 Lua腳本的原子性指:Lua腳本會(huì)當(dāng)作一個(gè)整體被執(zhí)行且不被其他事務(wù)打斷,但是 Lua 腳本里面的命令無(wú)法保證“要么全部執(zhí)行,要么全部不執(zhí)行”;
  • Lua腳本使用 redis.pcall() 執(zhí)行命令出錯(cuò)時(shí)會(huì)被catch,后續(xù)命令會(huì)正常執(zhí)行;
  • Lua腳本使用 redis.call() 執(zhí)行命令出錯(cuò)時(shí)會(huì)拋給客戶(hù)端,后續(xù)命令會(huì)被阻斷;
  • Lua 腳本一般比 MULTI/EXEC 更快、更簡(jiǎn)單;
  • Redis的部署方式?jīng)Q定了 Redis執(zhí)行 Lua腳本是否能保證原子性,編寫(xiě) Lua腳本時(shí),特別需要注意在一個(gè)事務(wù)中是否要求操作同一個(gè) key。
責(zé)任編輯:趙寧寧 來(lái)源: 猿java
相關(guān)推薦

2013-04-19 10:42:02

打車(chē)軟件大數(shù)據(jù)

2024-04-26 09:37:43

國(guó)產(chǎn)數(shù)據(jù)庫(kù)開(kāi)發(fā)者

2021-06-15 11:33:48

監(jiān)控微信聊天前端

2018-03-05 07:38:11

2012-03-05 10:36:30

云計(jì)算節(jié)能減排數(shù)據(jù)中心

2009-07-16 22:39:11

2018-12-10 09:14:56

AI教育教育資源留守兒童

2019-01-24 09:53:49

2019-11-21 09:25:23

AI 數(shù)據(jù)人工智能

2019-02-27 09:28:15

Redis服務(wù)器事務(wù)

2021-05-11 15:50:52

比特幣加密貨幣貨幣

2016-05-19 17:10:27

銀行

2022-10-20 08:00:37

機(jī)器人ZadigChatOps

2021-02-26 21:25:08

比特幣投資貨幣

2020-10-16 18:33:18

Rust語(yǔ)言前端開(kāi)發(fā)

2010-04-13 10:02:16

索引

2020-12-21 15:09:23

人工智能安全人臉識(shí)別

2023-12-27 08:03:53

Go優(yōu)化代碼

2023-04-06 06:55:24

ChatGPTGPT算力

2012-06-13 11:01:59

英特爾
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)