成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

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

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

一、原子性

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

1. 通常意義的原子性

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

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

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

  • 賬戶A 減去100元;
  • 賬戶B 增加100元;

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

2. Lua 原子性

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

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

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

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

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

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

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

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

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

二、Redis 事務(wù)

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

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

1. MULTI/EXEC

用一個示例來理解 MULTI/EXEC:

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

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

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

2. WATCH

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

如下示例:事務(wù)1 watch key1 key2,事務(wù)2在事務(wù)1執(zhí)行期間修改 key2 = 10,當(dāng)事務(wù)1執(zhí)行 exec命令時,因為 watch監(jiān)聽到 key2被其他事務(wù)(事務(wù)2)修改了(value=10) , 因此事務(wù)1被取消,事務(wù)隊列中的所有命令被清除,即 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 時會報“ERR EXEC without MULTI”錯誤。

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

事務(wù)中主要會出現(xiàn)兩種類型的錯誤:

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

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

4. 事務(wù)回滾

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

Redis 不支持事務(wù)回滾,因為支持回滾會對 Redis 的簡單性和性能產(chǎn)生重大影響。

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

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

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

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

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

  • 原生命令,比如 EVAL/EVALSHA命令等;
  • 編程工具,比如編程語言中提供的三方工具包或類庫;

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

1. EVAL

語法:

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

EVAL語法很簡單,EVAL script numkeys 是必填項,[key [key ...]] [arg [arg ...]]是選填項。

如下示例截圖,分別展示了不傳Key,傳 1個key 和 2個 key 3種場景:

下圖示例展示了 [key [key ...]] [arg [arg ...]] 和 numkeys 匹配錯誤時報錯的場景:

2. redis.call()

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

如下示例:當(dāng)執(zhí)行INCRBY key2 1/0 失敗時,會拋異常,后續(xù)流程被阻斷,即SET key3 value3沒有被執(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í)行后各個key的值。

3. redis.pcall()

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

如下示例:當(dāng)執(zhí)行INCRBY key2 1/0 失敗時,不會拋異常,后續(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示例:

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

四、如何保證原子性?

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

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

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

1. 單機(jī)部署

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

2. 主從部署

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

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

3. Cluster集群部署

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

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

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

4. 原子性保證

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

五、面試該如何回答?

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

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

六、Why Lua?

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

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

七、Lua注意事項

Redis執(zhí)行 Lua腳本時,Lua的編寫需要注意以下幾個點:

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

八、總結(jié)

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

2013-04-19 10:42:02

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

2024-04-26 09:37:43

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

2021-06-15 11:33:48

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

2018-03-05 07:38:11

2009-07-16 22:39:11

2012-03-05 10:36:30

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

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

銀行

2021-02-26 21:25:08

比特幣投資貨幣

2022-10-20 08:00:37

機(jī)器人ZadigChatOps

2020-10-16 18:33:18

Rust語言前端開發(fā)

2023-04-06 06:55:24

ChatGPTGPT算力

2022-04-24 11:52:04

元宇宙Web3去中心化

2012-06-13 11:01:59

英特爾

2023-12-27 08:03:53

Go優(yōu)化代碼

2020-12-21 15:09:23

人工智能安全人臉識別
點贊
收藏

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

主站蜘蛛池模板: 午夜影院普通用户体验区 | 成人欧美一区二区三区 | 可以在线观看av的网站 | 欧美在线精品一区 | 欧美色综合网 | 日日爱视频 | 国产精品一区二区久久 | 中文字幕一区二区在线观看 | 男女网站视频 | 欧美一区二区免费电影 | www日日日| 欧美aa在线 | 一区二区三区av | 人人干人人看 | 精品久久久久久国产 | 视频三区 | 成人精品福利 | 久久午夜精品 | 亚洲成av | 6080亚洲精品一区二区 | 精品国产欧美 | 青青草原综合久久大伊人精品 | 国产乱码精品一区二区三区五月婷 | 欧美日韩免费 | 一区二区三区av | 在线午夜| 日本三级线观看 视频 | 97精品超碰一区二区三区 | 亚洲精品一区二区网址 | 欧美激情免费在线 | 久久国产精品免费视频 | 免费爱爱视频 | 久久99精品久久久久久噜噜 | 亚洲成人一级片 | 欧美日韩在线一区二区三区 | 91精品国产91久久久久久吃药 | 亚洲综合中文字幕在线观看 | 成人免费精品视频 | 欧美极品一区二区 | 亚洲欧美在线观看视频 | h肉视频 |