面試竟被問到Redis事務,觸及知識盲區(qū),臉都綠了
前言
前幾天有讀者說自己面試被問到Redis的事務,雖然不常用,但是面試竟然被問到,平時自己沒有注意Redis的事務這一塊,面試的時候被問到非常不好受。
雖然,這位讀者面試最后算是過了,但是薪資方面沒有拿到自己理想的薪資。
其實這個也是正常的,一般面試被問到爛大街的,誰還問你啊,專門挑一些不常見的來問你,就是為了壓你的薪資。
所以在這里寫一篇文章對Redis的事務進行詳細的講解,估計對Redis事務從理解到原理深入這一篇就夠了。
以后面試都不用擔心了再被問道Redis的事務了,這一篇主要講解Redis事務原理和實操的演練,理解理論的同時也通過實操來證實理論。
事務介紹
Redis事務是一組命令的集合,將多個命令進行打包,然后這些命令會被順序的添加到隊列中,并且按順序的執(zhí)行這些命令。
「Redis事務中沒有像Mysql關系型數據庫事務隔離級別的概念,不能保證原子性操作,也沒有像Mysql那樣執(zhí)行事務失敗會進行回滾操作」。
這個與Redis的特點:「快速、高效」有著密切的關聯,「因為一些列回滾操作、像事務隔離級別那這樣加鎖、解鎖,是非常消耗性能的」。所以,Redis中執(zhí)行事務的流程只需要簡單的下面三個步驟:
- 開始事務(MULTI)
- 命令入隊
- 執(zhí)行事務(EXEC)、撤銷事務(DISCARD )
在Redis中事務的實現主要是通過如下的命令實現的:
命令 | 功能描述 |
---|---|
MULTI | 「事務開始的命令」,執(zhí)行該命令后,后面執(zhí)行的對Redis數據類型的「操作命令都會順序的放進隊列中」,等待執(zhí)行EXEC命令后隊列中的命令才會被執(zhí)行 |
DISCARD | 「放棄執(zhí)行隊列中的命令」,你可以理解為Mysql的回滾操作,「并且將當前的狀態(tài)從事務狀態(tài)改為非事務狀態(tài)」。 |
EXEC | 執(zhí)行該命令后「表示順序執(zhí)行隊列中的命令」,執(zhí)行完后并將結果顯示在客戶端,「將當前狀態(tài)從事務狀態(tài)改為非事務狀態(tài)」。若是執(zhí)行該命令之前有key被執(zhí)行WATCH命令并且又被其它客戶端修改,那么就會放棄執(zhí)行隊列中的所有命令,在客戶端顯示報錯信息,若是沒有修改就會執(zhí)行隊列中的所有命令。 |
WATCH key | 表示指定監(jiān)視某個key,「該命令只能在MULTI命令之前執(zhí)行」,如果監(jiān)視的key被其他客戶端修改,「EXEC將會放棄執(zhí)行隊列中的所有命令」 |
UNWATCH | 「取消監(jiān)視之前通過WATCH 命令監(jiān)視的key」,通過執(zhí)行EXEC 、DISCARD 兩個命令之前監(jiān)視的key也會被取消監(jiān)視 |
以上就是一個Redis事務的執(zhí)行過程包含的命令,下面就來詳細的圍繞著這幾個命令進行講解。
開始事務
MULTI 命令表示事務的開始,當看到OK表示已經進入事務的狀態(tài):
該命令執(zhí)行后客戶端會將「當前的狀態(tài)從非事務狀態(tài)修改為事務狀態(tài)」,這一狀態(tài)的切換是將客戶端的flags屬性中打開REDIS_MULTI來完成的,該命令可以理解關系型數據庫Mysql的BEGIN TRANCATION語句:
命令入隊
執(zhí)行完MULTI命令后,后面執(zhí)行的操作Redis五種類型的命令都會按順序的進入命令隊列中,該部分也是真正的業(yè)務邏輯的部分。
Redis客戶端的命令執(zhí)行后若是當前狀態(tài)處于事務狀態(tài)命令就會進入隊列中,并且返回QUEUED字符串,表示該命令已經進入了命令隊列中,并且「事務隊列是以先進先出(FIFO)的方式保存入隊的命令」的。
若是當前狀態(tài)是非事務狀態(tài)就會立即執(zhí)行命令,并將結果返回客戶端。在事務狀態(tài)「執(zhí)行操作事務的命令就會被立即執(zhí)行」,如EXEC、DISCARD、UNWATCH。
結合上面的分析,Redis執(zhí)行命令的流程如下圖所示:
事務的命令隊列中有三個參數分別是:「要執(zhí)行的命令」、「命令的參數」、「參數的個數」。例如:通過執(zhí)行如下的命令:
- redis> MULTI
- OK
- redis> SET name "黎杜"
- QUEUED
- redis> GET name
- QUEUED
那么對應上面的隊列中三個參數如下表格所示:
執(zhí)行的命令 | 命令的參數 | 參數的個數 |
---|---|---|
SET | ["name", "黎杜"] | 2 |
GET | ["name"] | 1 |
執(zhí)行事務
當客戶端執(zhí)行EXEC命令的時候,上面的命令隊列就會被按照先進先出的順序被執(zhí)行,當然執(zhí)行的結果有成功有失敗,這個后面分析。
上面說到當客戶端處于非事務的狀態(tài)命令發(fā)送到服務端會被立即執(zhí)行,若是客戶端處于事務狀態(tài)命令就會被放進命令隊列。
命令入隊的時候,會按照順序進入隊列,隊列以先進先出的特點來執(zhí)行隊列中的命令。
若是客戶端處于事務狀態(tài),執(zhí)行的是EXEC、DISCARD、UNWATCH這些操作事務的命令,也會被立即執(zhí)行。
正常執(zhí)行
還是上面的例子,執(zhí)行如下的代碼:
- redis> MULTI
- OK
- redis> SET name "黎杜"
- QUEUED
- redis> GET name
- QUEUED
所有的命令進入了隊列,當最后執(zhí)行EXEC,首先會執(zhí)行SET命令,然后執(zhí)行GET命令,并且執(zhí)行后的結果也會進入一個隊列中保存,最后返回給客戶端:
回復的類型 | 回復的內容 |
---|---|
status code reply | OK |
bulk reply | "黎杜" |
所以最后你會在客戶端看到「OK、黎杜」,這樣的結果顯示,這個也就是一個事務成功執(zhí)行的過程。
至此一個事務就完整的執(zhí)行完成,并且此時客戶端也從事務狀態(tài)更改為非事務狀態(tài)。
放棄事務
當然你也可以放棄執(zhí)行該事務,只要你再次執(zhí)行DISCARD操作就會放棄執(zhí)行此次的事務。具體代碼如下所示:
- redis> MULTI
- OK
- redis> SET name "黎杜"
- QUEUED
- redis> GET name
- QUEUED
- redis> DISCARD // 放棄執(zhí)行事務
- OK
DISCARD命令取消一個事務的時候,就會將命令隊列清空,并且將客戶端的狀態(tài)從事務狀態(tài)修改為非事務的狀態(tài)。
「Redis的事務是不可重復的」,當客戶端處于事務狀態(tài)的時候,再次向服務端發(fā)送MULTI命令時,直接就會向客戶端返回錯誤。
WATCH 命令
WATCH命令是在MULTI命令之前執(zhí)行的,表示監(jiān)視任意數量的key,與它對應的命令就是UNWATCH命令,取消監(jiān)視的key。
WATCH命令有點「類似于樂觀鎖機制」,在事務執(zhí)行的時候,若是被監(jiān)視的任意一個key被更改,則隊列中的命令不會被執(zhí)行,直接向客戶端返回(nil)表示事務執(zhí)行失敗。
下面我們來演示一下WATCH命令的操作流程,具體實現代碼如下:
- redis> WATCH num
- OK
- redis> MULTI
- OK
- redis> incrby num 10
- QUEUED
- redis> decrby num 1
- QUEUED
- redis> EXEC // 執(zhí)行成功
這個是WATCH命令的正常的操作流程,若是在其它的客戶端,修改了被監(jiān)視的任意key,就會放棄執(zhí)行該事務,如下圖所示:
客戶端一 | 客戶端二 |
---|---|
WATCH num | |
MULTI | |
incrby num 10 | get num |
decrby num 1 | |
EXEC | |
執(zhí)行失敗,返回(nil) |
WATCH命令的底層實現中保存了watched_keys 字典,「字典的鍵保存的是監(jiān)視的key,值是一個鏈表,鏈表中的每個節(jié)點值保存的是監(jiān)視該key的客戶端」。
若是某個客戶端不再監(jiān)視某個key,該客戶端就會從鏈表中脫離。如client3,通過執(zhí)行UNWATCH命令,不再監(jiān)視key1:
錯誤處理
上面說到Redis是沒有回滾機制的,那么執(zhí)行的過程,若是不小心敲錯命令,Redis的命令發(fā)送到服務端沒有被立即執(zhí)行,所以是暫時發(fā)現不到該錯誤。
那么在Redis中的錯誤處理主要分為兩類:「語法錯誤」、「運行錯誤」。下面主要來講解一下這兩類錯誤的區(qū)別。
語法錯誤
比如執(zhí)行命令的時候,命令的不存在或者錯誤的敲錯命令、參數的個數不對等都會導致語法錯誤。
下面來演示一下,執(zhí)行下面的四個命令,前后的兩個命令是正確的,中間的兩個命令是錯誤的,如下所示:
- 127.0.0.1:6379> multi
- OK
- 127.0.0.1:6379> set num 1
- QUEUED
- 127.0.0.1:6379> set num
- (error) ERR wrong number of arguments for 'set' command
- 127.0.0.1:6379> ssset num 3
- (error) ERR unknown command 'ssset'
- 127.0.0.1:6379> set num 2
- QUEUED
- 127.0.0.1:6379> exec
- (error) EXECABORT Transaction discarded because of previous errors.
語法錯誤是在Redis語法檢測的時候就能發(fā)現的,所以當你執(zhí)行錯誤命令的時候,也會即使的返回錯誤的提示。
最后,即使命令進入隊列,只要存在語法錯誤,該隊列中的命令都不會被執(zhí)行,會直接向客戶端返回事務執(zhí)行失敗的提示。
運行錯誤
執(zhí)行時使用不同類型的操作命令操作不同數據類型就會出現運行時錯誤,這種錯誤時Redis在不執(zhí)行命令的情況下,是無法發(fā)現的。
- 127.0.0.1:6379> multi
- OK
- 127.0.0.1:6379> set num 3
- QUEUED
- 127.0.0.1:6379> sadd num 4
- QUEUED
- 127.0.0.1:6379> set num 6
- QUEUED
- 127.0.0.1:6379> exec
- 1) OK
- 2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
- 3) OK
- 127.0.0.1:6379> get num
- "6"
這樣就會導致,正確的命令被執(zhí)行,而錯誤的命令不會不執(zhí)行,這也顯示出Redis的事務并不能保證數據的一致性,因為中間出現了錯誤,有些語句還是被執(zhí)行了。
這樣的結果只能程序員自己根據之前執(zhí)行的命令,自己一步一步正確的回退,所謂自己的爛攤子,自己收拾。
Redis事務與Mysql事務
我們知道關系性數據庫Mysql中具有事務的四大特性:「原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)」。
但是Redis的事務為了保證Redis除了客戶端的請求高效,去除了傳統(tǒng)關系型數據庫的「事務回滾、加鎖、解鎖」這些消耗性能的操作,Redis的事務實現簡單。
原子性中Redis的事務只能保證單個命令的原子性,多個命令就無法保證,如上面索道的運行時錯誤,即使中間有運行時錯誤出現也會正確的執(zhí)行后面正確的命令,不具有回滾操作。
既然沒有了原子性,數據的一致性也就無法保證,這些都需要程序員自己手動去實現。
Reids在進行事務的時候,不會被中斷知道事務的運行結束,也具有一定的隔離性,并且Redis也能持久化數據。