偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

面試官:Redis的事務(wù)滿(mǎn)足原子性嗎?

數(shù)據(jù)庫(kù) 其他數(shù)據(jù)庫(kù) Redis
熟悉redis的同學(xué)肯定知道,在redis中也存在事務(wù),那么它的事務(wù)也滿(mǎn)足原子性嗎?下面我們就來(lái)一探究竟。和數(shù)據(jù)庫(kù)事務(wù)類(lèi)似,redis事務(wù)也是用來(lái)一次性地執(zhí)行多條命令。

[[422510]]

談起數(shù)據(jù)庫(kù)的事務(wù)來(lái),估計(jì)很多同學(xué)的第一反應(yīng)都是ACID,而排在ACID中首位的A原子性,要求一個(gè)事務(wù)中的所有操作,要么全部完成,要么全部不完成。熟悉redis的同學(xué)肯定知道,在redis中也存在事務(wù),那么它的事務(wù)也滿(mǎn)足原子性嗎?下面我們就來(lái)一探究竟。

什么是Redis事務(wù)?

和數(shù)據(jù)庫(kù)事務(wù)類(lèi)似,redis事務(wù)也是用來(lái)一次性地執(zhí)行多條命令。使用起來(lái)也很簡(jiǎn)單,可以用MULTI開(kāi)啟一個(gè)事務(wù),然后將多個(gè)命令入隊(duì)到事務(wù)的隊(duì)列中,最后由EXEC命令觸發(fā)事務(wù),執(zhí)行事務(wù)中的所有命令??匆粋€(gè)簡(jiǎn)單的事務(wù)執(zhí)行例子:

  1. 127.0.0.1:6379> multi 
  2. OK 
  3. 127.0.0.1:6379> set name Hydra 
  4. QUEUED 
  5. 127.0.0.1:6379> set age 18 
  6. QUEUED 
  7. 127.0.0.1:6379> incr age 
  8. QUEUED 
  9. 127.0.0.1:6379> exec 
  10. 1) OK 
  11. 2) OK 
  12. 3) (integer) 19 

可以看到,在指令和操作數(shù)的數(shù)據(jù)類(lèi)型等都正常的情況下,輸入EXEC后所有命令被執(zhí)行成功。

Redis事務(wù)滿(mǎn)足原子性嗎?

如果要驗(yàn)證redis事務(wù)是否滿(mǎn)足原子性,那么需要在redis事務(wù)執(zhí)行發(fā)生異常的情況下進(jìn)行,下面我們分兩種不同類(lèi)型的錯(cuò)誤分別測(cè)試。

語(yǔ)法錯(cuò)誤

首先測(cè)試命令中有語(yǔ)法錯(cuò)誤的情況,這種情況多為命令的參數(shù)個(gè)數(shù)不正確或輸入的命令本身存在錯(cuò)誤。下面我們?cè)谑聞?wù)中輸入一個(gè)存在格式錯(cuò)誤的命令,開(kāi)啟事務(wù)并依次輸入下面的命令:

  1. 127.0.0.1:6379> multi 
  2. OK 
  3. 127.0.0.1:6379> set name Hydra 
  4. QUEUED 
  5. 127.0.0.1:6379> incr 
  6. (error) ERR wrong number of arguments for 'incr' command 
  7. 127.0.0.1:6379> set age 18 
  8. QUEUED 

輸入的命令incr后面沒(méi)有添加參數(shù),屬于命令格式不對(duì)的語(yǔ)法錯(cuò)誤,這時(shí)在命令入隊(duì)時(shí)就會(huì)立刻檢測(cè)出錯(cuò)誤并提示error。使用exec執(zhí)行事務(wù),查看結(jié)果輸出:

  1. 127.0.0.1:6379> exec 
  2. (error) EXECABORT Transaction discarded because of previous errors. 

在這種情況下,只要事務(wù)中的一條命令有語(yǔ)法錯(cuò)誤,在執(zhí)行exec后就會(huì)直接返回錯(cuò)誤,包括語(yǔ)法正確的命令在內(nèi)的所有命令都不會(huì)被執(zhí)行。對(duì)此進(jìn)行驗(yàn)證,看一下在事務(wù)中其他指令執(zhí)行情況,查看set命令的執(zhí)行結(jié)果,全部為空,說(shuō)明指令沒(méi)有被執(zhí)行。

  1. 127.0.0.1:6379> get name 
  2. (nil) 
  3. 127.0.0.1:6379> get age 
  4. (nil) 

此外,如果存在命令本身拼寫(xiě)錯(cuò)誤、或輸入了一個(gè)不存在的命令等情況,也屬于語(yǔ)法錯(cuò)誤的情況,執(zhí)行事務(wù)時(shí)會(huì)直接報(bào)錯(cuò)。

運(yùn)行錯(cuò)誤

運(yùn)行錯(cuò)誤是指輸入的指令格式正確,但是在命令執(zhí)行期間出現(xiàn)的錯(cuò)誤,典型場(chǎng)景是當(dāng)輸入?yún)?shù)的數(shù)據(jù)類(lèi)型不符合命令的參數(shù)要求時(shí),就會(huì)發(fā)生運(yùn)行錯(cuò)誤。例如下面的例子中,對(duì)一個(gè)string類(lèi)型的值執(zhí)行列表的操作,報(bào)錯(cuò)如下:

  1. 127.0.0.1:6379> set key1 value1 
  2. OK 
  3. 127.0.0.1:6379> lpush key1 value2 
  4. (error) WRONGTYPE Operation against a key holding the wrong kind of value 

這種錯(cuò)誤在redis實(shí)際執(zhí)行指令前是無(wú)法被發(fā)現(xiàn)的,只能當(dāng)真正執(zhí)行才能夠被發(fā)現(xiàn),因此這樣的命令是可以被事務(wù)隊(duì)列接收的,不會(huì)和上面的語(yǔ)法錯(cuò)誤一樣立即報(bào)錯(cuò)。

具體看一下當(dāng)事務(wù)中存在運(yùn)行錯(cuò)誤的情況,在下面的事務(wù)中,嘗試對(duì)string類(lèi)型數(shù)據(jù)進(jìn)行incr自增操作:

  1. 127.0.0.1:6379> multi 
  2. OK 
  3. 127.0.0.1:6379> set name Hydra 
  4. QUEUED 
  5. 127.0.0.1:6379> set age eighteen 
  6. QUEUED 
  7. 127.0.0.1:6379> incr age 
  8. QUEUED 
  9. 127.0.0.1:6379> del name 
  10. QUEUED 

redis一直到這里都沒(méi)有提示存在錯(cuò)誤,執(zhí)行exec看一下結(jié)果輸出:

  1. 127.0.0.1:6379> exec 
  2. 1) OK 
  3. 2) OK 
  4. 3) (error) ERR value is not an integer or out of range 
  5. 4) (integer) 1 

運(yùn)行結(jié)果可以看到,雖然incr age這條命令出現(xiàn)了錯(cuò)誤,但是它前后的命令都正常執(zhí)行了,再看一下這些key對(duì)應(yīng)的值,確實(shí)證明了其余指令都執(zhí)行成功:

  1. 127.0.0.1:6379> get name 
  2. (nil) 
  3. 127.0.0.1:6379> get age 
  4. "eighteen" 

階段性結(jié)論

對(duì)上面的事務(wù)的運(yùn)行結(jié)果進(jìn)行一下分析:

  • 存在語(yǔ)法錯(cuò)誤的情況下,所有命令都不會(huì)執(zhí)行
  • 存在運(yùn)行錯(cuò)誤的情況下,除執(zhí)行中出現(xiàn)錯(cuò)誤的命令外,其他命令都能正常執(zhí)行

通過(guò)分析我們知道了redis中的事務(wù)是不滿(mǎn)足原子性的,在運(yùn)行錯(cuò)誤的情況下,并沒(méi)有提供類(lèi)似數(shù)據(jù)庫(kù)中的回滾功能。那么為什么redis不支持回滾呢,官方文檔給出了說(shuō)明,大意如下:

  • redis命令失敗只會(huì)發(fā)生在語(yǔ)法錯(cuò)誤或數(shù)據(jù)類(lèi)型錯(cuò)誤的情況,這一結(jié)果都是由編程過(guò)程中的錯(cuò)誤導(dǎo)致,這種情況應(yīng)該在開(kāi)發(fā)環(huán)境中檢測(cè)出來(lái),而不是生產(chǎn)環(huán)境
  • 不使用回滾,能使redis內(nèi)部設(shè)計(jì)更簡(jiǎn)單,速度更快
  • 回滾不能避免編程邏輯中的錯(cuò)誤,如果想要將一個(gè)鍵的值增加2卻只增加了1,這種情況即使提供回滾也無(wú)法提供幫助

基于以上原因,redis官方選擇了更簡(jiǎn)單、更快的方法,不支持錯(cuò)誤回滾。這樣的話(huà),如果在我們的業(yè)務(wù)場(chǎng)景中需要保證原子性,那么就要求了開(kāi)發(fā)者通過(guò)其他手段保證命令全部執(zhí)行成功或失敗,例如在執(zhí)行命令前進(jìn)行參數(shù)類(lèi)型的校驗(yàn),或在事務(wù)執(zhí)行出現(xiàn)錯(cuò)誤時(shí)及時(shí)做事務(wù)補(bǔ)償。

提到其他方式,相信很多小伙伴都聽(tīng)說(shuō)使用Lua腳本來(lái)保證操作的原子性,例如在分布式鎖中通常使用的就是Lua腳本,那么,神奇的Lua腳本真的能保證原子性嗎?

簡(jiǎn)單的Lua腳本入門(mén)

在驗(yàn)證lua腳本的原子性之前,我們需要對(duì)它做一個(gè)簡(jiǎn)單的了解。redis從2.6版本開(kāi)始支持執(zhí)行l(wèi)ua腳本,它的功能和事務(wù)非常類(lèi)似,一段lua腳本被視作一條命令執(zhí)行,這樣將多條redis命令寫(xiě)入lua,即可實(shí)現(xiàn)類(lèi)似事務(wù)的執(zhí)行結(jié)果。我們先看一下下面幾個(gè)常用的命令。

EVAL 命令

最常用的EVAL用于執(zhí)行一段腳本,它的命令的格式如下:

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

簡(jiǎn)單解釋一下其中的參數(shù):

  • script是一段lua腳本程序
  • numkeys指定后續(xù)參數(shù)有幾個(gè)key,如沒(méi)有key則為0
  • key [key …]表示腳本中用到的redis中的鍵,在lua腳本中通過(guò)KEYS[i]的形式獲取
  • arg [arg …]表示附加參數(shù),在lua腳本中通過(guò)ARGV[i]獲取

看一個(gè)簡(jiǎn)單的例子:

  1. 127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 value1 vauel2 
  2. 1) "key1" 
  3. 2) "key2" 
  4. 3) "value1" 
  5. 4) "vauel2" 

在上面的命令中,雙引號(hào)中是lua腳本程序,后面的2表示存在兩個(gè)key,分別是key1和key2,之后的參數(shù)是附加參數(shù)value1和value2。

如果想要使用lua腳本執(zhí)行set命令,可以寫(xiě)成這樣:

  1. 127.0.0.1:6379> EVAL "redis.call('SET', KEYS[1], ARGV[1]);" 1 name Hydra 
  2. (nil) 

這里使用了redis內(nèi)置的lua函數(shù)redis.call來(lái)完成set命令,這里打印的執(zhí)行結(jié)果nil是因?yàn)闆](méi)有返回值,如果不習(xí)慣的話(huà),其實(shí)我們可以在腳本中添加return 0;的返回語(yǔ)句。

SCRIPT LOAD 和 EVALSHA命令

這兩個(gè)命令放在一起是因?yàn)樗鼈円话愠蓪?duì)使用。先看SCRIPT LOAD,它用于把腳本加載到緩存中,返回SHA1校驗(yàn)和,這時(shí)候只是緩存了命令,但是命令沒(méi)有被馬上執(zhí)行,看一個(gè)例子:

  1. 127.0.0.1:6379> SCRIPT LOAD "return redis.call('GET', KEYS[1]);" 
  2. "228d85f44a89b14a5cdb768a29c4c4d907133f56" 

這里返回了一個(gè)SHA1的校驗(yàn)和,接下來(lái)就可以使用EVALSHA來(lái)執(zhí)行腳本了:

  1. 127.0.0.1:6379> EVALSHA "228d85f44a89b14a5cdb768a29c4c4d907133f56" 1 name 
  2. "Hydra" 

這里使用這個(gè)SHA1值就相當(dāng)于導(dǎo)入了上面緩存的命令,在之后再拼接numkeys、key、arg等參數(shù),命令就能夠正常執(zhí)行了。

其他命令

使用SCRIPT EXISTS命令判斷腳本是否被緩存:

  1. 127.0.0.1:6379> SCRIPT EXISTS 228d85f44a89b14a5cdb768a29c4c4d907133f56 
  2. 1) (integer) 1 

使用SCRIPT FLUSH命令清除redis中的lua腳本緩存:

  1. 127.0.0.1:6379> SCRIPT FLUSH 
  2. OK 
  3. 127.0.0.1:6379> SCRIPT EXISTS 228d85f44a89b14a5cdb768a29c4c4d907133f56 
  4. 1) (integer) 0 

可以看到,執(zhí)行了SCRIPT FLUSH后,再次通過(guò)SHA1值查看腳本時(shí)已經(jīng)不存在。最后,還可以使用SCRIPT KILL命令殺死當(dāng)前正在運(yùn)行的 lua 腳本,但是只有當(dāng)腳本沒(méi)有執(zhí)行寫(xiě)操作時(shí)才會(huì)生效。

從這些操作看來(lái),lua腳本具有下面的優(yōu)點(diǎn):

  • 多次網(wǎng)絡(luò)請(qǐng)求可以在一次請(qǐng)求中完成,減少網(wǎng)絡(luò)開(kāi)銷(xiāo),減少了網(wǎng)絡(luò)延遲
  • 客戶(hù)端發(fā)送的腳本會(huì)存在redis中,其他客戶(hù)端可以復(fù)用這一腳本,而不需要再重復(fù)編碼完成相同的邏輯

Java代碼中使用lua腳本

在Java代碼中可以使用Jedis中封裝好的API來(lái)執(zhí)行l(wèi)ua腳本,下面是一個(gè)使用Jedis執(zhí)行l(wèi)ua腳本的例子:

  1. public static void main(String[] args) { 
  2.     Jedis jedis = new Jedis("127.0.0.1", 6379); 
  3.     String script="redis.call('SET', KEYS[1], ARGV[1]);" 
  4.             +"return redis.call('GET', KEYS[1]);"
  5.     List<String> keys= Arrays.asList("age"); 
  6.     List<String> values= Arrays.asList("eighteen"); 
  7.     Object result = jedis.eval(script, keys, values); 
  8.     System.out.println(result); 

執(zhí)行上面的代碼,控制臺(tái)打印了get命令返回的結(jié)果:

  1. eighteen 

簡(jiǎn)單的鋪墊完成后,我們來(lái)看一下lua腳本究竟能否實(shí)現(xiàn)回滾級(jí)別的原子性。對(duì)上面的代碼進(jìn)行改造,插入一條運(yùn)行錯(cuò)誤的命令:

  1. public static void main(String[] args) { 
  2.     Jedis jedis = new Jedis("127.0.0.1", 6379); 
  3.     String script="redis.call('SET', KEYS[1], ARGV[1]);" 
  4.             +"redis.call('INCR', KEYS[1]);" 
  5.             +"return redis.call('GET', KEYS[1]);"
  6.     List<String> keys= Arrays.asList("age"); 
  7.     List<String> values= Arrays.asList("eighteen"); 
  8.     Object result = jedis.eval(script, keys, values); 
  9.     System.out.println(result); 

查看執(zhí)行結(jié)果:

再到客戶(hù)端執(zhí)行一下get命令:

  1. 127.0.0.1:6379> get age 
  2. "eighteen" 

也就是說(shuō),雖然程序拋出了異常,但異常前的命令還是被正常的執(zhí)行了且沒(méi)有被回滾。再試試直接在redis客戶(hù)端中運(yùn)行這條指令:

  1. 127.0.0.1:6379> flushall 
  2. OK 
  3. 127.0.0.1:6379> eval "redis.call('SET', KEYS[1], ARGV[1]);redis.call('INCR', KEYS[1]);return redis.call('GET', KEYS[1])" 1 age eight 
  4. (error) ERR Error running script (call to f_c2ea9d5c8f60735ecbedb47efd42c834554b9b3b): @user_script:1: ERR value is not an integer or out of range 
  5. 127.0.0.1:6379> get age 
  6. "eight" 

同樣,錯(cuò)誤之前的指令仍然沒(méi)有被回滾,那么我們之前經(jīng)常聽(tīng)說(shuō)的Lua腳本保證原子性操作究竟是怎么回事呢?

其實(shí),在redis中是使用的同一個(gè)lua解釋器來(lái)執(zhí)行所有命令,也就保證了當(dāng)一段lua腳本在執(zhí)行時(shí),不會(huì)有其他腳本或redis命令同時(shí)執(zhí)行,保證了操作不會(huì)被其他指令插入或打擾,實(shí)現(xiàn)的僅僅是這種程度上的原子操作。

但是遺憾的是,如果lua腳本運(yùn)行時(shí)出錯(cuò)并中途結(jié)束,之后的操作不會(huì)進(jìn)行,但是之前已經(jīng)發(fā)生的寫(xiě)操作不會(huì)撤銷(xiāo),所以即使使用了lua腳本,也不能實(shí)現(xiàn)類(lèi)似數(shù)據(jù)庫(kù)回滾的原子性。

本文基于redis 5.0.3 進(jìn)行測(cè)試

官方文檔相關(guān)說(shuō)明:https://redis.io/topics/transactions

本文轉(zhuǎn)載自微信公眾號(hào)「碼農(nóng)參上」

【編輯推薦】

 

責(zé)任編輯:姜華 來(lái)源: 碼農(nóng)參上
相關(guān)推薦

2021-06-07 17:12:22

線(xiàn)程安全Atomic

2023-01-05 12:30:32

Redis

2021-04-12 21:34:29

Redis故障數(shù)據(jù)

2020-03-06 15:36:01

Redis內(nèi)存宕機(jī)

2023-09-01 15:27:31

2021-11-02 09:05:25

Redis

2024-04-03 00:00:00

Redis集群代碼

2019-02-27 09:28:15

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

2024-01-22 10:07:48

Redis持久化功能緩存擊穿

2020-11-06 07:11:40

內(nèi)存虛擬Redis

2020-07-02 07:52:11

RedisHash映射

2015-08-13 10:29:12

面試面試官

2021-06-03 14:00:35

PolarDB

2021-06-02 16:30:33

PolarDB原子性數(shù)據(jù)庫(kù)

2022-07-18 13:59:43

Redis單線(xiàn)程進(jìn)程

2024-04-09 10:40:04

2022-07-06 13:48:24

RedisSentinel機(jī)制

2024-10-22 16:39:07

2024-02-28 10:14:47

Redis數(shù)據(jù)硬盤(pán)

2022-07-26 08:40:42

Java并發(fā)工具類(lèi)
點(diǎn)贊
收藏

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