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

Redis篇:事務(wù)和Lua 腳本的使用

存儲(chǔ) 存儲(chǔ)軟件 Redis
現(xiàn)在多數(shù)秒殺,抽獎(jiǎng),搶紅包等大并發(fā)高流量的功能一般都是基于 redis 實(shí)現(xiàn),然而在選擇 redis 的時(shí)候,我們也要了解 redis 如何保證服務(wù)正確運(yùn)行的原理.

[[436743]]

本文轉(zhuǎn)載自微信公眾號(hào)「潛行前行」,作者cscw 。轉(zhuǎn)載本文請(qǐng)聯(lián)系潛行前行公眾號(hào)。

 現(xiàn)在多數(shù)秒殺,抽獎(jiǎng),搶紅包等大并發(fā)高流量的功能一般都是基于 redis 實(shí)現(xiàn),然而在選擇 redis 的時(shí)候,我們也要了解 redis 如何保證服務(wù)正確運(yùn)行的原理

前言

  • redis 如何實(shí)現(xiàn)高性能和高并發(fā)
  • reids 事務(wù)的 ACID 原理
  • WATCH、EXEC 命令實(shí)現(xiàn) redis 事務(wù)
  • lua 實(shí)現(xiàn) redis事務(wù)
  • 搶紅包方案

redis 如何實(shí)現(xiàn)高性能和高并發(fā)

  • redis 是一個(gè)內(nèi)存數(shù)據(jù)庫(kù),讀寫(xiě)非常高效。除了開(kāi)啟 AOF,RDB 異步線程去持久化數(shù)據(jù),基本沒(méi)有磁盤(pán)I/O消耗,性能方面是比 mysql,oracle 快很多
  • redis 自己實(shí)現(xiàn)一套簡(jiǎn)單高效的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu):動(dòng)態(tài)字符串(SDS),鏈表,字典,跳躍鏈表,整數(shù)集合和壓縮列表。然后在這個(gè)基礎(chǔ)上去實(shí)現(xiàn)用戶(hù)能操作的對(duì)象:字符串,列表,哈希,集合,有序集合等對(duì)象
  • reactor 模式的網(wǎng)絡(luò)事件處理器。它使用了 I/O 多路復(fù)用去同時(shí)監(jiān)控多個(gè)套接字,這是一種高效的I/O模型。reactor 相關(guān)知識(shí)可以看下這篇文章框架篇:見(jiàn)識(shí)一下linux高性能網(wǎng)絡(luò)IO+Reactor模型
  • 事件處理器是單線執(zhí)行的,這大大減少CPU的上下文切換,和對(duì)資源鎖的競(jìng)爭(zhēng)問(wèn)題,極大提高redis服務(wù)處理速度(至于為啥使用單線程,因?yàn)镃PU夠用了,它的性能瓶頸在內(nèi)存而不是CPU)
  • Redis直接自己構(gòu)建了VM 機(jī)制 ,因?yàn)橐话愕南到y(tǒng)調(diào)用系統(tǒng)函數(shù)的話,會(huì)浪費(fèi)一定的時(shí)間去移動(dòng)和請(qǐng)求

reids 事務(wù)的 ACID 原理

redis 的事務(wù)需要先劃分出三個(gè)階段

  • 事務(wù)開(kāi)啟,使用 MULTI 可以標(biāo)志著執(zhí)行該命令的客戶(hù)端從非事務(wù)狀態(tài)切換至事務(wù)狀態(tài)redis> MULTI
  • 命令入隊(duì),MULTI開(kāi)啟事務(wù)之后,非 WATCH、EXEC、DISCARD、MULTI 等特殊命令;客戶(hù)端的命令不會(huì)被立即執(zhí)行,而是放入一個(gè)事務(wù)隊(duì)列
  • 執(zhí)行事務(wù)或者丟棄。如果收到 EXEC 的命令,事務(wù)隊(duì)列里的命令將會(huì)被執(zhí)行。如果是 DISCARD 則事務(wù)被丟棄

命令入隊(duì)過(guò)程如果出錯(cuò)(如使用了不存在的命令),則事務(wù)隊(duì)列會(huì)被拒接執(zhí)行

執(zhí)行事務(wù)期間出現(xiàn)了異常(如命令和操作的數(shù)據(jù)類(lèi)型不匹配),事務(wù)隊(duì)列的里的命令還是繼續(xù)執(zhí)行下去,直到全部命令執(zhí)行完。不會(huì)回滾

WATCH 可用于監(jiān)控 redis 變量值,在命令 EXEC 之前;redis 里的數(shù)據(jù)是有機(jī)會(huì)被其他客戶(hù)端的命令修改的。使用 WATCH,監(jiān)控的變量被修改后,執(zhí)行 EXEC 時(shí)則會(huì)返回執(zhí)行失敗的 nil 回復(fù)

  1. redis> WATCH "name" 
  2. OK 
  3. redis> MULTI   ### 此時(shí)name已被其他客戶(hù)端的命令修改 
  4. OK 
  5. redis> SET "name" "lwl" 
  6. QUEUED 
  7. redis> EXEC 
  8. (nil) 

從嚴(yán)格意義上來(lái)說(shuō),redis 是沒(méi)有事務(wù)的。因?yàn)槭聞?wù)必須具備四個(gè)特點(diǎn):原子性(Atomicity),一致性(Consistency),隔離性(Isolation),持久性(Durability)。然后 redis 是做不到這四點(diǎn),只是具備其中一些特征,redis的事務(wù)是個(gè)偽事務(wù),而且不支持回滾。下面將為各位同學(xué)一一道來(lái)

原子性

從上面可以,事務(wù)的異常會(huì)發(fā)生在EXEC命令執(zhí)行前、后

EXEC命令執(zhí)行前:在命令入隊(duì)時(shí)就報(bào)錯(cuò),(如內(nèi)存不足,命令名稱(chēng)錯(cuò)誤),redis 就會(huì)報(bào)錯(cuò)并且記錄下這個(gè)錯(cuò)誤。此時(shí),客戶(hù)還能繼續(xù)提交命令操作;等到執(zhí)行EXEC時(shí),redis 就會(huì)拒絕執(zhí)行所有提交的命令操作,返回事務(wù)失敗的結(jié)果 nil

EXEC命令執(zhí)行后:命令和操作的數(shù)據(jù)類(lèi)型不匹配,但 redis 實(shí)例沒(méi)有檢查出錯(cuò)誤。在執(zhí)行完 EXEC 命令以后,redis 實(shí)際執(zhí)行這些指令,就會(huì)報(bào)錯(cuò)。此時(shí)事務(wù)是不會(huì)回滾的,但事務(wù)隊(duì)列的命令還是繼續(xù)被執(zhí)行。事務(wù)的原子性無(wú)法保證

EXEC執(zhí)行時(shí),發(fā)生故障:如果 redis 開(kāi)啟了 AOF 日志,那么,只會(huì)有部分的事務(wù)操作被記錄到 AOF 日志中。需要使用 redis-check-aof 工具檢查 AOF 日志文件,這個(gè)工具可以把未完成的事務(wù)操作從 AOF 文件中去除。事務(wù)的原子性得到保證

一致性

EXEC命令執(zhí)行前:入隊(duì)報(bào)錯(cuò)事務(wù)會(huì)被放棄執(zhí)行,具有一致性

EXEC命令執(zhí)行后:實(shí)際執(zhí)行時(shí)報(bào)錯(cuò),錯(cuò)誤的執(zhí)行不會(huì)執(zhí)行,正確的指令可以正常執(zhí)行,一致性可以保證

EXEC執(zhí)行時(shí),發(fā)生故障:RDB 模式,RDB 快照不會(huì)在事務(wù)執(zhí)行時(shí)執(zhí)行,事務(wù)結(jié)果不會(huì)保存在RDB;AOF 模式,可以使用 redis-check-aof 工具檢查 AOF 日志文件,把未完成的事務(wù)操作從 AOF 文件中去除。可以保證一致性

隔離性

EXEC 命令前執(zhí)行,隔離性需要通過(guò) WATCH 機(jī)制保證。因?yàn)?EXEC 命令執(zhí)行前,其他客戶(hù)端命令可以被執(zhí)行,相關(guān)變量會(huì)被修改;但可以使用 WATCH 機(jī)制監(jiān)控相關(guān)變量。一旦相關(guān)變量被修改,則 EXEC 后則事務(wù)失敗返回;具有隔離性

EXEC 命令之后,隔離性可以保證。因?yàn)?redis 是單線程執(zhí)行,事務(wù)隊(duì)列里的命令和其他客戶(hù)端的命令只能二選一被順序執(zhí)行,因此具有隔離性

持久性

如果 redis 沒(méi)有使用 RDB 或 AOF,事務(wù)的持久化是不存在的

使用 RDB 模式,那么在一個(gè)事務(wù)執(zhí)行后,而下一次的 RDB 快照還未執(zhí)行前,如果發(fā)生了實(shí)例宕機(jī),數(shù)據(jù)丟失,這種情況下,事務(wù)修改的數(shù)據(jù)也是不能保證持久化

AOF 模式,因?yàn)?AOF 模式的三種配置選項(xiàng) no、everysec 和 always 都會(huì)存在數(shù)據(jù)丟失的情況。所以,事務(wù)的持久性屬性也還是得不到保證

總結(jié)

redis 的事務(wù)機(jī)制可以保證一致性和隔離性;但是無(wú)法保證持久性;具備了一定的原子性,但不支持回滾

WATCH、EXEC 命令實(shí)現(xiàn) redis 事務(wù)

  1. redis> WATCH "map" 
  2. OK 
  3. redis> MULTI  
  4. OK 
  5. redis> HSET map "csc" "lwl"   
  6. QUEUED 
  7. redis> HGET map "csc" 
  8. QUEUED 
  9. redis> EXEC 
  10. 1) OK 
  11. 2) "lwl"   

lua 實(shí)現(xiàn) redis 事務(wù)

除了 MULTI、WATCH、EXEC 命令,還有其他的方式可做到 redis 原子性和隔離性嗎?有的,lua 腳本;redis 內(nèi)置了lua的執(zhí)行環(huán)境,并自帶了一些 lua 函數(shù)庫(kù)。redis 執(zhí)行 lua 時(shí),會(huì)啟動(dòng)一個(gè)偽客戶(hù)端去執(zhí)行腳本里的 redis 命令

一致性,原子性,持久性 和 MULTI,EXEC 過(guò)程相似:如果 lua 存在錯(cuò)誤的命令名稱(chēng),事務(wù)會(huì)執(zhí)行失敗。如果在執(zhí)行 redis 命令過(guò)程出現(xiàn)異常,之前正常執(zhí)行的命令也不會(huì)回滾

lua 腳本被當(dāng)做一命令集合一起被執(zhí)行,且 redis 是單線處理機(jī)制,因此不需要 WATCH 保證隔離性,天然具備隔離性

Lua調(diào)用Redis指令: redis.call("命令名稱(chēng)",參數(shù)1,參數(shù)2)

優(yōu)點(diǎn)

減少網(wǎng)絡(luò)開(kāi)銷(xiāo):可以將多個(gè)請(qǐng)求通過(guò)腳本的形式一次發(fā)送,減少網(wǎng)絡(luò)時(shí)延

原子操作:Redis會(huì)將整個(gè)腳本作為一個(gè)整體執(zhí)行,中間不會(huì)被其他請(qǐng)求插入。在腳本運(yùn)行過(guò)程中無(wú)需擔(dān)心會(huì)出現(xiàn)競(jìng)態(tài)條件

可重復(fù)使用:客戶(hù)端發(fā)送的腳本會(huì)永久存在 redis 中,這樣其他客戶(hù)端可以復(fù)用這一腳本,而不需要使用代碼完成相同的邏輯

搶紅包方案

問(wèn)題關(guān)鍵點(diǎn)

  • 一:用戶(hù)是否參與過(guò)活動(dòng),不可重復(fù)參與
  • 二:紅包數(shù)量有限;而且一個(gè)可搶的紅包,保證不能讓多個(gè)人同時(shí)搶到
  • 三:持久化存儲(chǔ)紅包與用戶(hù)的關(guān)系
  • 四:如何保證 步驟一到步驟三的原子性和隔離性

關(guān)鍵點(diǎn)一

redis 的集合對(duì)象 set 是無(wú)序且唯一的。set 集合由整數(shù)集合或字典實(shí)現(xiàn)的,添加,刪除,查找的復(fù)雜度基本視為 O(1),存放的最大對(duì)象個(gè)數(shù)是2^32 - 1 (4294967295)

使用 set 集合保存參加過(guò)的用戶(hù),每次用戶(hù)參與活動(dòng)時(shí)先判斷是否在 set 里。不在則可以搶紅包

如果是用戶(hù)可以重復(fù)參與多次的場(chǎng)景,則使用哈希對(duì)象,key存用戶(hù)對(duì)象,value 存放參與次數(shù)。使用 INCR 原子操作增加 value,如果返回?cái)?shù)值 > 上限,說(shuō)明搶的次數(shù)用完

關(guān)鍵點(diǎn)二

使用 list 或者 set 存放事先創(chuàng)建好的有限個(gè)紅包;因?yàn)?redis 是單線程操作,同一時(shí)間,多人搶紅包,只會(huì)有一個(gè)人成功。而紅包是事先生成的,消費(fèi)用完即止,不存在超發(fā)的可能

使用 list 列表存放紅包

  • 因?yàn)榧t包金額大小不一,為增加搶到紅包大小的隨機(jī)性,需要先shuffle一次,再 LPUSH 入隊(duì)列
  • RPOP 出隊(duì)列一個(gè)紅包,如果返回不為nil,則代表獲取成功,繼續(xù)下一步,反之則說(shuō)明已搶完,返回

set 集合中有兩個(gè)指令非常適合在搶紅包、抽獎(jiǎng)的場(chǎng)景使用

  • SPOP key [count] 移除并返回集合中的一個(gè)隨機(jī)元素
  • SRANDMEMBER key [count] 返回集合中一個(gè)或多個(gè)隨機(jī)數(shù);需要再調(diào) SREM 移除一遍
  • 將所有的紅包通過(guò) SADD 添加到 set 中,然后通過(guò)隨機(jī)命令獲取對(duì)應(yīng)的紅包即可

如果有謝謝惠顧之類(lèi)的落空選項(xiàng),生成對(duì)應(yīng)的無(wú)效紅包、獎(jiǎng)品放入 set 或 list 即可

搶紅包一般是有時(shí)效性,正好可以配合 redis 的 key 的失效時(shí)間使用。使得搶紅包功能很完美的解決

關(guān)鍵點(diǎn)三

使用額外的 list 列表保存用戶(hù)與紅包的關(guān)系,用戶(hù)搶到紅包后,將對(duì)應(yīng)的關(guān)系 LPUSH 入隊(duì)列,然后服務(wù)去消費(fèi)拉取數(shù)據(jù)批量保存到數(shù)據(jù)庫(kù)即可

關(guān)鍵點(diǎn)四

使用 lua 腳本實(shí)現(xiàn)即可

  1. -- 參數(shù):KEYS[1]-紅包list,KEYS[2]-用戶(hù)和紅包的消費(fèi)list,KEYS[3]-去重的哈希對(duì)象,KEYS[4]-用戶(hù)ID 
  2. -- 函數(shù):嘗試獲得紅包,如果成功,則返回json字符串,如果不成功,則返回nil 
  3. -- 返回值:nil 或者 json字符串,{"userId":"用戶(hù)ID","id":"紅包ID"} 
  4. -- 如果用戶(hù)已搶過(guò)紅包,則返回nil 
  5.  
  6. -- 步驟一,攔截重復(fù)參與 
  7. if redis.call('hexists', KEYS[3], KEYS[4]) == 1 then 
  8.   return nil 
  9. else 
  10.   -- 步驟二,先取出一個(gè)紅包 
  11.   local lunkMoney = redis.call('rpop', KEYS[1]); 
  12.   if luckMoney then 
  13.     local data = cjson.decode(luckMoney); 
  14.     data['userId'] = KEYS[4]; -- 加入用戶(hù)ID信息 
  15.     local re = cjson.encode(data); 
  16.     -- 把用戶(hù)ID放到去重的哈希,value設(shè)置為 1 
  17.     redis.call('hset', KEYS[3], KEYS[4], 1); 
  18.     -- 步驟三: 用戶(hù)和紅包放到已消費(fèi)隊(duì)列里 
  19.     redis.call('lpush', KEYS[2], re); 
  20.     return re; 
  21.   end 
  22. end 
  23. return nil 

參考文章

redis事務(wù)一致性問(wèn)題?

Redis的ACID屬性

搶紅包設(shè)計(jì)

騰訊二面:Redis 事務(wù)支持 ACID 么?

 

責(zé)任編輯:武曉燕 來(lái)源: 潛行前行
相關(guān)推薦

2023-04-04 07:52:26

RedisLua腳本

2024-01-09 07:25:31

2019-08-06 14:06:19

數(shù)據(jù)庫(kù)工具技術(shù)

2025-02-28 08:21:36

C語(yǔ)言C++Java

2022-08-03 08:17:00

Redis事務(wù)內(nèi)存

2023-05-05 08:08:06

JavaRedis事務(wù)

2011-08-23 09:56:52

UnicodeLua

2011-08-25 13:22:40

CEGUILua腳本

2011-08-23 09:44:28

LUA腳本

2011-08-25 09:55:27

2024-12-30 07:20:00

Redis數(shù)據(jù)庫(kù)MySQL

2024-08-13 17:35:27

2022-03-08 07:22:48

Redis腳本分布式鎖

2024-01-18 11:54:44

Redis事務(wù)命令

2011-08-24 14:26:08

Lua游戲腳本

2011-08-30 10:28:11

MySQL ProxyLUA

2024-03-29 08:56:47

2022-04-26 21:49:55

Spring事務(wù)數(shù)據(jù)庫(kù)

2021-08-01 07:19:16

語(yǔ)言OpenrestyNginx

2020-10-19 06:43:53

Redis腳本原子
點(diǎn)贊
收藏

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