原子性
原子性是數據庫的事務中的特性。在數據庫事務的情景下,原子性指的是:一個事務(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中的事務其實是要保證批量操作的原子性。
四、Redis為什么這么快
1、完全基於內存,絕大部分請求是純粹的內存操作,非常快速。數據存在內存中,類似於HashMap,HashMap的優勢就是查找和操作的時間復雜度都是O(1);
2、數據結構簡單,對數據操作也簡單,Redis中的數據結構是專門進行設計的;
3、采用單線程,避免了不必要的上下文切換和競爭條件,也不存在多進程或者多線程導致的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的性能消耗;
4、使用多路I/O復用模型,非阻塞IO;
5、使用底層模型不同,它們之間底層實現方式以及與客戶端之間通信的應用協議不一樣,Redis直接自己構建了VM 機制 ,因為一般的系統調用系統函數的話,會浪費一定的時間去移動和請求;
以上幾點都比較好理解,下邊我們針對多路 I/O 復用模型進行簡單的探討:
(1)多路 I/O 復用模型
多路I/O復用模型是利用 select、poll、epoll 可以同時監察多個流的 I/O 事件的能力,在空閑的時候,會把當前線程阻塞掉,當有一個或多個流有 I/O 事件時,就從阻塞態中喚醒,於是程序就會輪詢一遍所有的流(epoll 是只輪詢那些真正發出了事件的流),並且只依次順序的處理就緒的流,這種做法就避免了大量的無用操作。
這里“多路”指的是多個網絡連接,“復用”指的是復用同一個線程。采用多路 I/O 復用技術可以讓單個線程高效的處理多個連接請求(盡量減少網絡 IO 的時間消耗),且 Redis 在內存中操作數據的速度非常快,也就是說內存內的操作不會成為影響Redis性能的瓶頸,主要由以上幾點造就了 Redis 具有很高的吞吐量。