文/小碼農談IT
資料庫有事務,那Redis也有事務。有的人說Redis事務其實不算事務,應該叫具有命令打包功能。那麼,元芳,你怎麼看?
什麼是事務
百科裡面有這樣一段話,事務應該具有4個屬性:原子性、一致性、隔離性、持久性。這四個屬性通常稱為ACID特性。我們重點來關注下原子性和隔離性。
原子性(atomicity)。一個事務是一個不可分割的工作單位,事務中包括的操作要麼都做,要麼都不做。隔離性(isolation)。一個事務的執行不能被其他事務幹擾。即一個事務內部的操作及使用的數據對並發的其他事務是隔離的,並發執行的各個事務之間不能互相干擾。
學過資料庫的都明白,資料庫的事務運行有三種模式:自動提交事務,顯式事務,隱性事務。
自動提交事務:每條單獨的語句都是一個事務,每個語句都隱含一個commit。一般執行單條的SQL操作都是自動提交事務。
顯式事務:以begin transaction 開始,以commit 或 rollback 結束。一般我們代碼中業務邏輯時開啟事務對應的就是這種模式。
隱性事務: 在前一個事務完成時,新事務隱式啟動,但每個事務仍以commit或rollback顯示結束。
那麼Redis是不是也有多種運行模式呢?狹隘地說其實並沒有。
Redis事務
Redis事務顧名思義,就是Redis中的事務。Redis 事務可以一次執行多個命令, 並且帶有以下三個重要的保證:
批量操作在發送 EXEC 命令前被放入隊列緩存。收到 EXEC 命令後進入事務執行,事務中任意命令執行失敗,其餘的命令依然被執行。在事務執行過程,其他客戶端提交的命令請求不會插入到事務執行命令序列中。一個事務從開始到執行會經歷以下三個階段:開始事務,命令入隊,執行事務。
看,Redis的事務就是這麼簡單粗暴。也就是說事務開啟,N條Redis操作先入隊,然後全部入隊完畢後執行exec命令批量執行。其中如果有哪個操作失敗或者錯誤了,剩下的操作繼續執行,並且失敗之前執行的操作也不會自動回滾,這個其實已經不保證執行的原子性了。注意,這點是和DB事務的最大差別。這也是很多人把Redis事務不叫做事務而只叫做具有命令打包功能的原因。引用一個例子來說明。
總結特點:
1.不支持回滾:沒有實現錯誤直接回滾的功能;
2.不保證原子性:redis同一個事務中如果有一條命令執行失敗,其後的命令仍然會被執行,沒有自動支持回滾;
3.隔離性:只保證了事務執行的隔離性。
為什麼不支持回滾呢?其實官方還有個比較合理的說法,覺得還挺有意思:
只有當被調用的Redis命令有語法錯誤時,這條命令才會執行失敗(在將這個命令放入事務隊列期間,Redis能夠發現此類問題),或者對某個鍵執行不符合其數據類型的操作:實際上,這就意味著只有程序錯誤才會導致Redis命令執行失敗,這種錯誤很有可能在程序開發期間發現,一般很少在生產環境發現。Redis已經在系統內部進行功能簡化,這樣可以確保更快的運行速度,因為Redis不需要事務回滾的能力。
watch命令其實是一個樂觀鎖機制
那既然稱作是事務,肯定是要能支持回滾的吧,不然沒有什麼意義了。筆者認為回滾的話可以有兩種方式。
其一,在操作命令被放入隊列的時候也就是在exec之前就判斷邏輯上的錯誤並不執行exec命令,則事務不會被執行;也可以配合DISCARD命令取消事務,放棄執行事務塊內的所有命令。當然,這樣的做法似乎對語法上的錯誤意義不大,因為操作入隊的時候並沒有直接被執行(版本不同存在爭議),也就無法判斷執行是否失敗。(另一種說法是:如果將某個命令放入隊列時發生錯誤,那麼大多數客戶端將會中止事務,並且丟棄這個事務。比如,由於INCR命令的語法錯誤,Redis根本就沒有將這個命令放入事務執行隊列。如果已經被放入了並執行exec,則不管是都存在錯誤,會將事務一直全部執行下去。);
其二,可以使用watch命令來手動回滾。值得一說的是,watch命令保證了執行的原子性(支持整個隊列不執行=自動回滾)和隔離性。但其還是偏向於隔離性的支持,對於語法錯誤導致的失敗仍然還是無法支持自動回滾的,這也再次印證了官方的那個觀點:只有程序錯誤才會導致Redis命令執行失敗,這種錯誤很有可能在程序開發期間發現,一般很少在生產環境發現。
Redis Watch 命令用於監視一個(或多個) key ,如果在事務執行之前這個(或這些) key 被其他命令所改動,那麼事務將被打斷。
Redis使用WATCH命令實現事務的「檢查再設置」(CAS)行為。作為WATCH命令的參數的鍵會受到Redis的監控,Redis能夠檢測到它們的變化。在執行EXEC命令之前,如果Redis檢測到至少有一個鍵被修改了,那麼整個事務便會中止運行,然後EXEC命令會返回一個Null值,提醒用戶事務運行失敗。
我們引用一個例子來說明watch命令。
例子中用戶A對key為balance的值進行了監控,之後用戶B對balance進行了賦值操作,注意此時用戶A監控的balance已經被其他用戶修改了。而後用戶A開啟事務,對balance進行了一次減15的操作,執行exec。事務執行完畢我們查詢balance結果,其值並沒有改變。說明事務被中止或者說被回滾了。那麼問題來了,事務中對debt的加15操作有執行成功嗎?顯然應該也是不執行的。
再次總結特點:
1.支持破壞隔離性時的回滾來同時保證原子性=Redis執行中的Redis自帶的分布式樂觀鎖;
2.語法錯誤導致的原子性破壞還是不支持自動回滾(版本不同有爭議);
好了,很驚訝的是,這其實就是一個活生生的樂觀鎖的例子,回想我們之前Redis實現的單體樂觀鎖,樂觀鎖的原理都一樣。Redis用樂觀鎖機制實現了Redis事務,但是樂觀鎖的弊端也是存在的,在高並發的情況下,失敗的可能性很高;不管是什麼,合適的才是最好的,這個就留給觀者去評判了。
Redis腳本也是事務型
Redis還有個腳本,根據定義,Redis腳本也是事務型的。我們可以想到我們之前用腳本實現的分布式鎖。因此,可以通過Redis事務實現的功能,同樣也可以通過Redis腳本來實現,而且通常腳本更簡單、更快速。
那麼大家都把Redis事務應用在什麼場景呢?