知其所以然~redis的原子性

張佔嶺發表於2018-07-12

原子性

原子性是資料庫的事務中的特性。在資料庫事務的情景下,原子性指的是:一個事務(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 , 回滾是沒有辦法處理這些情況的。

相關文章