我們都知道,在面試的環節中,會有各種千奇百怪的問題,最經典的就是各種數據庫,這種中間件,還有就是底層原理,還有就是關于緩存數據庫這塊,今天了不起就來說說這個某東最喜歡問的一個內容,那就是關于 redis 的一些問題,比如 Redis 為什么快?
Redis
redis是一個key-value存儲系統。
和Memcached類似,它支持存儲的value類型相對更多。
包括string(字符串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型)。
這些數據類型都支持push/pop、add/remove及取交集并集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎上,redis支持各種不同方式的排序。與memcached一樣,為了保證效率,數據都是緩存在內存中。
區別的是redis會周期性的把更新的數據寫入磁盤或者把修改操作寫入追加的記錄文件,并且在此基礎上實現了master-slave(主從)同步。
Redis 為什么快?
- 純內存訪問
- 單線程,避免上下文的切換
- 漸進式ReHash,緩存時間戳
這也是我們在面試中經常會被問到的內容,而我們的基礎回答都是前兩個,一個是純內存訪問,一個就是單線程,但是如果你在面試的時候,只是回答了這兩個,那么只能說你是對 Redis 有了一些簡單的了解,如果評分制,那么你只能算是及格,如果你想拿到滿分,那么第三個,就必不可少了。
其實 Redis 快的原因,是因為 Redis 的內部,采用了很多效率高的機制,就比如我們說的第三個,漸進式的ReHash 和緩存時間戳。
什么是漸進式 ReHash
我們在開篇中也說了,Redis是一個 key-value 存儲系統。
這樣就不得不提一下另外的一個概念了,全局Hash表
為了實現從鍵到值的快速訪問,Redis使用了一個全局哈希表來保存所有鍵值對。而一個 Hash 表,其實本質上就是一個數組,數組的每一個元素稱為一個 Hash 桶,所以,這也是為什么很多人稱 Hash 表是由多個 Hash 桶組成的,而每個 Hash 桶中保存了鍵值對數據。
圖片
如果他們出現了 Hash 沖突了,那么就會使用連表的方式進行解決。
一般的,當我們插入數據的時候,數組的長度不會很長,但是當我們在不斷的往內部插入數據的過程中,就會擴容,比如我們擴容是N倍,這個時候就會涉及到我們原有數據元素的移動,而這個過程,我們流稱之為 ReHash 了。
但是隨著移動,就會伴生出一些問題,比如我們的元素非常的多,上千條,甚至上萬條,那么如果元素移動的話,一次性移動上千甚至上萬條的時候,就必然的導致一種情況的出現,那都不用說,IO阻塞了, 因為元素的移動,就有可能導致我們的元素從1的位置,挪到了4的位置,甚至挪動到n的位置,數據只要移動,那么久一定會出現阻塞的問題,一旦開始阻塞了,那 Redis 的速度,必然會下降。
所以為了解決這個阻塞的問題,所以為了解決這個問題,所以 Redis 使用的是漸進式的 ReHash。
而這個漸進式的 ReHash 其實原理也不復雜,總結大白話就是把一次大量拷貝的開銷,分攤到多次處理請求的過程中,避免耗時操作,保證數據的快速訪問。
那么他這個分攤請求是怎么處理的呢?
首先、Redis 默認使用了兩個全局哈希表: 哈希表 1 和哈希表 2。一開始,當你剛插入數據時,默認使用哈希表1,此時的哈希表 2 并沒有被分配空間。隨著數據逐步增多,Redis 開始執行 ReHash。
- 給哈希表 2 分配更大的空間,例如是當前哈希表 1 大小的兩倍
- 把哈希表 1 中的數據重新映射并拷貝到哈希表 2 中
- 釋放哈希表 1的空間
在上面的第二步涉及大量的數據拷貝,如果一次性把哈希表 1 中的數據都遷移完,會造成 Redis 線程阻塞,無法服務其他請求。此時,Redis 就無法快速訪問數據了。
圖片
在Redis 開始執行 ReHash,Redis仍然正常處理客戶端請求,但是要加入一個額外的處理處理
第1個請求時,把哈希表 1中的第1個索引位置上的所有 entries 拷貝到哈希表 2 中
處理第2個請求時,把哈希表1中的第2個索引位置上的所有 entries 拷貝到哈希表 2中
如此循環,直到把所有的索引位置的數據都拷貝到哈希表 2 中。
這樣就實現了剛才了不起說的,把一次大量拷貝的開銷,分攤到多次處理請求的過程中,避免耗時操作,保證數據的快速訪問。
關于 漸進式的 ReHash 就說完了,那么這個緩存時間戳又是用來干嘛的呢?
緩存時間戳
這個緩存時間戳,也是 Redis 快的另外一個主要的原因,幾乎是 ReHash 并列的呀。
我們在開發中使用時間戳,一般都是使用的 System 的方法,也就是 currentTimeMillis()來獲取時間戳的,但是這是我們在 JAVA 代碼中的,而 Redis 顯然不能這么用,因為每一次獲取系統時間戳都是一次系統調用(涉及到上下文切換),所以系統調用相對來說是比較費時間的,作為單線程的 Redis 承受不起,所以它需要對時間進行緩存,由一個定時任務,每毫秒更新一次時間緩存獲取時間都是從緩存中直接拿。
而這就是 緩存時間戳,所以,在面試中如果有面試官問到 Redis 為什么這么快的時候,你知道應該怎么回答了么?