Redis事务深入解析和使用
作為關(guān)系型數(shù)據(jù)庫(kù)中一項(xiàng)非常重要的基礎(chǔ)功能——事務(wù),在 Redis 中是如何處理并使用的?
1.前言
事務(wù)指的是提供一種將多個(gè)命令打包,一次性按順序地執(zhí)行的機(jī)制,并且保證服務(wù)器只有在執(zhí)行完事務(wù)中的所有命令后,才會(huì)繼續(xù)處理此客戶(hù)端的其他命令。
事務(wù)也是其他關(guān)系型數(shù)據(jù)庫(kù),所必備的一項(xiàng)非常重要的能力。以支付的場(chǎng)景為例,正常情況下只有正常消費(fèi)完成之后,才會(huì)減去賬戶(hù)余額。但如果沒(méi)有事務(wù)的保障,可能會(huì)發(fā)生消費(fèi)失敗了,但依舊會(huì)把賬戶(hù)的余額給扣減了,我想這種情況應(yīng)該任何人都無(wú)法接受吧?所以事務(wù)是關(guān)系型數(shù)據(jù)庫(kù)中一項(xiàng)非常重要的基礎(chǔ)功能。
2.事務(wù)基本使用
事務(wù)在其他語(yǔ)言中,一般分為以下三個(gè)階段:
- 開(kāi)啟事務(wù)——Begin Transaction
- 執(zhí)行業(yè)務(wù)代碼,提交事務(wù)——Common Transaction
- 業(yè)務(wù)處理中出現(xiàn)異常,回滾事務(wù)——Rollback Transaction
以 Java 中的事務(wù)執(zhí)行為例:
// 開(kāi)啟事務(wù) begin(); try {//......// 提交事務(wù)commit(); } catch(Exception e) {// 回滾事務(wù)rollback(); }Redis 中的事務(wù)從開(kāi)始到結(jié)束也是要經(jīng)歷三個(gè)階段:
- 開(kāi)啟事務(wù)
- 命令入列
- 執(zhí)行事務(wù)/放棄事務(wù)
其中,開(kāi)啟事務(wù)使用 multi 命令,事務(wù)執(zhí)行使用 exec 命令,放棄事務(wù)使用 discard 命令。
1)開(kāi)啟事務(wù)
multi 命令用于開(kāi)啟事務(wù),實(shí)現(xiàn)代碼如下:
> multi OKmulti 命令可以讓客戶(hù)端從非事務(wù)模式狀態(tài),變?yōu)槭聞?wù)模式狀態(tài),如下圖所示:
注意:multi 命令不能嵌套使用,如果已經(jīng)開(kāi)啟了事務(wù)的情況下,再執(zhí)行 multi 命令,會(huì)提示如下錯(cuò)誤:
(error) ERR MULTI calls can not be nested
執(zhí)行效果,如下代碼所示:
127.0.0.1:6379> multi OK 127.0.0.1:6379> multi (error) ERR MULTI calls can not be nested當(dāng)客戶(hù)端是非事務(wù)狀態(tài)時(shí),使用 multi 命令,客戶(hù)端會(huì)返回結(jié)果 OK ,如果客戶(hù)端已經(jīng)是事務(wù)狀態(tài),再執(zhí)行 multi 命令會(huì) multi 命令不能嵌套的錯(cuò)誤,但不會(huì)終止客戶(hù)端為事務(wù)的狀態(tài),如下圖所示:
2)命令入列
客戶(hù)端進(jìn)入事務(wù)狀態(tài)之后,執(zhí)行的所有常規(guī) Redis 操作命令(非觸發(fā)事務(wù)執(zhí)行或放棄和導(dǎo)致入列異常的命令)會(huì)依次入列,命令入列成功后會(huì)返回 QUEUED ,如下代碼所示:
> multi OK > set k v QUEUED > get k QUEUED執(zhí)行流程如下圖所示:
注意:命令會(huì)按照先進(jìn)先出(FIFO)的順序出入列,也就是說(shuō)事務(wù)會(huì)按照命令的入列順序,從前往后依次執(zhí)行。
3)執(zhí)行事務(wù)/放棄事務(wù)
執(zhí)行事務(wù)的命令是 exec ,放棄事務(wù)的命令是 discard 。
執(zhí)行事務(wù)示例代碼如下:
放棄事務(wù)示例代碼如下:
> multi OK > set k v3 QUEUED > discard OK > get k "v2"執(zhí)行流程如下圖所示:
3.事務(wù)錯(cuò)誤&回滾
事務(wù)執(zhí)行中的錯(cuò)誤分為以下三類(lèi):
- 執(zhí)行時(shí)才會(huì)出現(xiàn)的錯(cuò)誤(簡(jiǎn)稱(chēng):執(zhí)行時(shí)錯(cuò)誤);
- 入列時(shí)錯(cuò)誤,不會(huì)終止整個(gè)事務(wù);
- 入列時(shí)錯(cuò)誤,會(huì)終止整個(gè)事務(wù)。
1)執(zhí)行時(shí)錯(cuò)誤
示例代碼如下:
> get k "v" > multi OK > set k v2 QUEUED > expire k 10s QUEUED > exec 1) OK 2) (error) ERR value is not an integer or out of range > get k "v2"執(zhí)行命令解釋如下圖所示:
從以上結(jié)果可以看出,即使事務(wù)隊(duì)列中某個(gè)命令在執(zhí)行期間發(fā)生了錯(cuò)誤,事務(wù)也會(huì)繼續(xù)執(zhí)行,直到事務(wù)隊(duì)列中所有命令執(zhí)行完成。
2)入列錯(cuò)誤不會(huì)導(dǎo)致事務(wù)結(jié)束
示例代碼如下:
> get k "v" > multi OK > set k v2 QUEUED > multi (error) ERR MULTI calls can not be nested > exec 1) OK > get k "v2"執(zhí)行命令解釋如下圖所示:
可以看出,重復(fù)執(zhí)行 multi 會(huì)導(dǎo)致入列錯(cuò)誤,但不會(huì)終止事務(wù),最終查詢(xún)的結(jié)果是事務(wù)執(zhí)行成功了。除了重復(fù)執(zhí)行 multi 命令,還有在事務(wù)狀態(tài)下執(zhí)行 watch 也是同樣的效果,下文會(huì)詳細(xì)講解關(guān)于 watch 的內(nèi)容。
3)入列錯(cuò)誤會(huì)導(dǎo)致事務(wù)結(jié)束
示例代碼如下:
> get k "v2" > multi OK > set k v3 QUEUED > set k (error) ERR wrong number of arguments for 'set' command > exec (error) EXECABORT Transaction discarded because of previous errors. > get k "v2"執(zhí)行命令解釋如下圖所示:
4)為什么不支持事務(wù)回滾?
Redis 官方文檔的解釋如下:
If you have a relational databases background, the fact that Redis commands can fail during a transaction, but still Redis will execute the rest of the transaction instead of rolling back, may look odd to you.
However there are good opinions for this behavior:
- Redis commands can fail only if called with a wrong syntax (and the problem is not detectable during the command queueing), or against keys holding the wrong data type: this means that in practical terms a failing command is the result of a programming errors, and a kind of error that is very likely to be detected during development, and not in production.
- Redis is internally simplified and faster because it does not need the ability to roll back.
An argument against Redis point of view is that bugs happen, however it should be noted that in general the roll back does not save you from programming errors. For instance if a query increments a key by 2 instead of 1, or increments the wrong key, there is no way for a rollback mechanism to help. Given that no one can save the programmer from his or her errors, and that the kind of errors required for a Redis command to fail are unlikely to enter in production, we selected the simpler and faster approach of not supporting roll backs on errors.
大概的意思是,作者不支持事務(wù)回滾的原因有以下兩個(gè):
- 他認(rèn)為 Redis 事務(wù)的執(zhí)行時(shí),錯(cuò)誤通常都是編程錯(cuò)誤造成的,這種錯(cuò)誤通常只會(huì)出現(xiàn)在開(kāi)發(fā)環(huán)境中,而很少會(huì)在實(shí)際的生產(chǎn)環(huán)境中出現(xiàn),所以他認(rèn)為沒(méi)有必要為 Redis 開(kāi)發(fā)事務(wù)回滾功能;
- 不支持事務(wù)回滾是因?yàn)檫@種復(fù)雜的功能和 Redis 追求的簡(jiǎn)單高效的設(shè)計(jì)主旨不符合。
這里不支持事務(wù)回滾,指的是不支持運(yùn)行時(shí)錯(cuò)誤的事務(wù)回滾。
4.監(jiān)控
watch 命令用于客戶(hù)端并發(fā)情況下,為事務(wù)提供一個(gè)樂(lè)觀(guān)鎖(CAS,Check And Set),也就是可以用 watch 命令來(lái)監(jiān)控一個(gè)或多個(gè)變量,如果在事務(wù)的過(guò)程中,某個(gè)監(jiān)控項(xiàng)被修改了,那么整個(gè)事務(wù)就會(huì)終止執(zhí)行。
watch 基本語(yǔ)法如下:
watch key [key …]
watch 示例代碼如下:
> watch k OK > multi OK > set k v2 QUEUED > exec (nil) > get k "v"從以上命令可以看出,如果 exec 返回的結(jié)果是 nil 時(shí),表示 watch 監(jiān)控的對(duì)象在事務(wù)執(zhí)行的過(guò)程中被修改了。從 get k 的結(jié)果也可以看出,在事務(wù)中設(shè)置的值 set k v2 并未正常執(zhí)行。
執(zhí)行流程如下圖所示:
注意: watch 命令只能在客戶(hù)端開(kāi)啟事務(wù)之前執(zhí)行,在事務(wù)中執(zhí)行 watch 命令會(huì)引發(fā)錯(cuò)誤,但不會(huì)造成整個(gè)事務(wù)失敗,如下代碼所示:
執(zhí)行命令解釋如下圖所示:
unwatch 命令用于清除所有之前監(jiān)控的所有對(duì)象(鍵值對(duì))。
unwatch 示例如下所示:
可以看出,即使在事務(wù)的執(zhí)行過(guò)程中,k 值被修改了,因?yàn)檎{(diào)用了 unwatch 命令,整個(gè)事務(wù)依然會(huì)順利執(zhí)行。
5.事務(wù)在程序中使用
以下是事務(wù)在 Java 中的使用,代碼如下:
import redis.clients.jedis.Jedis; import redis.clients.jedis.Transaction;public class TransactionExample {public static void main(String[] args) {// 創(chuàng)建 Redis 連接Jedis jedis = new Jedis("xxx.xxx.xxx.xxx", 6379);// 設(shè)置 Redis 密碼jedis.auth("xxx");// 設(shè)置鍵值jedis.set("k", "v");// 開(kāi)啟監(jiān)視 watchjedis.watch("k");// 開(kāi)始事務(wù)Transaction tx = jedis.multi();// 命令入列tx.set("k", "v2");// 執(zhí)行事務(wù)tx.exec();System.out.println(jedis.get("k"));jedis.close();} }6.小結(jié)
事務(wù)為多個(gè)命令提供一次性按順序執(zhí)行的機(jī)制,與 Redis 事務(wù)相關(guān)的命令有以下五個(gè):
- multi:開(kāi)啟事務(wù)
- exec:執(zhí)行事務(wù)
- discard:丟棄事務(wù)
- watch:為事務(wù)提供樂(lè)觀(guān)鎖實(shí)現(xiàn)
- unwatch:取消監(jiān)控(取消事務(wù)中的樂(lè)觀(guān)鎖)
正常情況下 Redis 事務(wù)分為三個(gè)階段:開(kāi)啟事務(wù)、命令入列、執(zhí)行事務(wù)。Redis 事務(wù)并不支持運(yùn)行時(shí)錯(cuò)誤的事務(wù)回滾,但在某些入列錯(cuò)誤,如 set key 或者是 watch 監(jiān)控項(xiàng)被修改時(shí),提供整個(gè)事務(wù)回滾的功能。
7.思考題
Redis 事務(wù)中如何解決并發(fā)修改的問(wèn)題?Redis 支持事務(wù)回滾嗎?使用 Redis 事務(wù)時(shí)會(huì)出現(xiàn)哪三種錯(cuò)誤?這三種錯(cuò)誤對(duì)事務(wù)有何影響?只有高手才能答對(duì)的問(wèn)題,你能答上來(lái)幾個(gè)?
8.參考&鳴謝
https://redis.io/topics/transactions
https://redisbook.readthedocs.io/en/latest/feature/transaction.html#id3
關(guān)注下方二維碼,訂閱更多精彩內(nèi)容
總結(jié)
以上是生活随笔為你收集整理的Redis事务深入解析和使用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: JVM史上最佳入门指南
- 下一篇: 面试突击 005 | Redis 是如何