原子性
原子性是資料庫的事務中的特性。在資料庫事務的情景下,原子性指的是:一個事務(transaction)中的所有操作,要麼全部完成,要麼全部不完成,不會結束在中間某個環節。
對于Redis而言,指令的原子性指的是:一個操作的不可以再分,操作要麼執行,要麼不執行。
Redis操作原子性的原因
Redis的操作之是以是原子性的,是因為Redis是單線程的。
由于對作業系統相關的知識不是很熟悉,從上面這句話并不能真正了解Redis操作是原子性的原因,進一步查閱程序與線程的概念及其差別。
程序與線程
-
程序
計算機中已執行程式的實體。比如,一個啟動了的php-fpm,就是一個程序。
-
線程
作業系統能夠進行運算排程的最小單元。它被包含在程序之中,是程序的實際運作機關。一條線程指的是程序中一個單一順序的控制流,一個程序中可以并發多個線程,每條線程并行執行不同的任務。比如,mysql運作時,mysql啟動後,該mysql服務就是一個程序,而mysql的連接配接、查詢的操作,就是線程。
程序與線程的差別
- 資源(如打開檔案):程序間的資源互相獨立,同一程序的各線程間共享資源。某程序的線程在其他程序不可見。
- 通信:程序間通信:消息傳遞、同步、共享記憶體、遠端過程調用、管道。線程間通信:直接讀寫程序資料段(需要程序同步和互斥手段的輔助,以保證資料的一緻性)。
-
排程和切換:線程上下文切換比程序上下文切換要快得多。
線程,是作業系統最小的執行單元,在單線程程式中,任務一個一個地做,必須做完一個任務後,才會去做另一個任務。
Redis在并發中的表現
Redis的API是原子性的操作,那麼多個指令在并發中也是原子性的嗎?
看看下面這段代碼:
$redis= newRedis();
$redis->connect('127.0.0.1',6379);
for($i= 0;$iget('val');
$num++;
$redis->set('val',$num);
usleep(10000);
}
用兩個終端執行上面的程式,發現val的結果是小于2000的值,那麼可以知道,在程式中執行多個Redis指令并非是原子性的,這也和普通資料庫的表現是一樣的。
如果想在上面的程式中實作原子性,可以将get和set改成單指令操作,比如incr,或者使用Redis的事務,或者使用Redis+Lua的方式實作。
原子性總結
綜上所述,對Redis來說,執行get、set以及eval等API,都是一個一個的任務,這些任務都會由Redis的線程去負責執行,任務要麼執行成功,要麼執行失敗,這就是Redis的指令是原子性的原因。
Redis本身提供的所有API都是原子操作,Redis中的事務其實是要保證批量操作的原子性。
事務
MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事務相關的指令。事務可以一次執行多個指令, 并且帶有以下兩個重要的保證:
- 事務是一個單獨的隔離操作:事務中的所有指令都會序列化、按順序地執行。事務在執行的過程中,不會被其他用戶端發送來的指令請求所打斷。
- 事務是一個原子操作:事務中的指令要麼全部被執行,要麼全部都不執行。
事務的關鍵字
-
EXEC 指令負責觸發并執行事務中的所有指令:
如果用戶端在使用 MULTI 開啟了一個事務之後,卻因為斷線而沒有成功執行 EXEC ,那麼事務中的所有指令都不會被執行。
-
另一方面,如果用戶端成功在開啟事務之後執行 EXEC ,那麼事務中的所有指令都會被執行。
當使用 AOF 方式做持久化的時候, Redis 會使用單個 write(2) 指令将事務寫入到磁盤中。
然而,如果 Redis 伺服器因為某些原因被管理者殺死,或者遇上某種硬體故障,那麼可能隻有部分事務指令會被成功寫入到磁盤中。
如果 Redis 在重新啟動時發現 AOF 檔案出了這樣的問題,那麼它會退出,并彙報一個錯誤。
使用redis-check-aof程式可以修複這一問題:它會移除 AOF 檔案中不完整事務的資訊,確定伺服器可以順利啟動。
從 2.2 版本開始,Redis 還可以通過樂觀鎖(optimistic lock)實作 CAS (check-and-set)操作,具體資訊請參考文檔的後半部分。
事務的語句
> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1
為什麼 Redis 不支援復原(roll back)
如果你有使用關系式資料庫的經驗, 那麼 “Redis 在事務失敗時不進行復原,而是繼續執行餘下的指令”這種做法可能會讓你覺得有點奇怪。
以下是這種做法的優點:
- Redis 指令隻會因為錯誤的文法而失敗(并且這些問題不能在入隊時發現),或是指令用在了錯誤類型的鍵上面:這也就是說,從實用性的角度來說,失敗的指令是由程式設計錯誤造成的,而這些錯誤應該在開發的過程中被發現,而不應該出現在生産環境中。
-
因為不需要對復原進行支援,是以 Redis 的内部可以保持簡單且快速。
有種觀點認為 Redis 處理事務的做法會産生 bug , 然而需要注意的是, 在通常情況下, 復原并不能解決程式設計錯誤帶來的問題。 舉個例子, 如果你本來想通過 INCR 指令将鍵的值加上 1 , 卻不小心加上了 2 , 又或者對錯誤類型的鍵執行了 INCR , 復原是沒有辦法處理這些情況的。
作者:倉儲大叔,張占嶺,
榮譽:微軟MVP
QQ:853066980
支付寶掃一掃,為大叔打賞!
