最近一段時間與redis接觸比較頻繁。發現有些東西還是工作中經常會用到的,自己也花了點時間鞏固下。本篇文章主要是以總結性的方式梳理,因為redis的主題很大,任何一個技術點展開都是幾篇文章的量。也可以說這篇文章是個概覽。 ...
最近一段時間與redis接觸比較頻繁。發現有些東西還是工作中經常會用到的,自己也花了點時間鞏固下。本篇文章主要是以總結性的方式梳理,因為redis的主題很大,任何一個技術點展開都是幾篇文章的量。也可以說這篇文章是個概覽。
1.redis基本數據結構與短結構壓縮
瞭解redis的數據結構有助於瞭解每種數據結構的優劣勢,方便設計合理的cache結構。
1.1.redis提供5種數據結構
1.STRING:可以存儲字元串、浮點型、整型,如果是字元串可以執行字元串操作,如果是浮點型、整型也可以執行加減操作。redis會識別出它的具體類型。
2.LIST:鏈表,鏈表中的每個NODE包含一個字元串。可以對鏈表進行兩端推入、彈出操作。
3.SET:無序集合,不會存在相同的集合元素,集合的交集、並集、差集運算。
4.HASH:鍵值對的無需散列,增、刪、獲取鍵值。
5.ZSET:有序集合,根據一個浮點數分值來排序。
這幾種數據類型用起來場景還是比較明顯的,遇到複雜的cache場景我們需要結合這幾種數據結構一起來設計。
比如,購物車場景,我們首先需要兩個HASH來存儲,第一個HASH是用戶與購物車之間的關係,第二個HASH是購物車中的商品列表。
先通過userId獲取到shoppingCartId,然後再通過shoppingCartId就可以獲取到用戶購物車的ProductIds。然後再通過ProductIds就可以非同步讀取用戶購物車的商品信息。
通過userId獲取shoppingCartId,再通過shoppingCartId獲取ProductIds,這看似兩個步驟可以在一個redis command中執行完成。(藉助redis的pipeline管道。)
圖1:
設計cache的時候,可以稍微綜合的考慮下這幾種結構,如果沒有理想的結構再擴展下,將這幾種結構混搭下可以滿足複雜的cache結構。有一個平衡點就是過期時間的把握,集合沒有辦法設置具體item的過期時間,所以具體的數據對象還是需要設置expire,保證數據的新鮮度,比如,這裡的STRING:p100商品。
1.2.壓縮表與集合的整數集合編碼
redis為列表、集合、散列、有序集合提供了壓縮存儲的方式,在存儲數據不多的情況下用redis提供的短結構, 可以換來極大的存儲壓縮比。
redis的數據類型對應的短結構實現:list、zset、hash,這三個將使用ziplist,set使用集合整數編碼。
預設的存儲結構是以entry node 鏈表來存儲數據的。然而,entry node 是一個結構化的存儲。就拿list來說,entry node 前後包含了一個指針,形成雙向鏈表,中間是包含的數據。數據節點包含三個部分,當前字元串所用空間,當前字元串所剩空間,當前字元串的值。
這些鬆散性的結構帶來了一定的存儲開銷。redis提供了一個ziplist(壓縮表)的功能,一旦開啟壓縮表那麼原本用entry node的存儲結構將使用序列化的位元組序列來存儲。這有優勢也有劣勢,優勢就是存儲空間少了,劣勢就是不靈活了。存儲空間少了,不僅帶來空間利用率高,還有一個大的優勢就是執行master\slave repliaction 或者BGSAVE的時候都是有好處,存儲或者帶寬都會消耗小。
set背後使用hash來保證不會存在重覆的可以。當對set使用短結構存儲的時候,redis將使用整數集合編碼進行存儲。
還有一點,如果我們設置的最大壓縮限制超過之後redis將恢復entry node鏈表的是方式存儲。因為,如果你的壓縮表數據太多,讀取或者修改就會很影響性能。可能需要對整個壓縮列表進行解碼、編碼操作,等等。
2.redis 事務
redis 提供transaction 功能,你可以使用事務來處理一些數據一致性要求比較敏感的場景。redis的事務是沒有所謂的隔離級別這一說的,性能是才是它追求的目的。事務所包含的所有command,是一個接著一個被執行,這執行結束之前其他客戶端的所有請求都被block住。
使用redis事務比較簡單,它有一個表示事務開始的命令 multi,然後使用exec提交。
有兩點需要註意,在沒有執行exec的時候所有在multi之後的命令都不會被執行。預設情況下,redis收到multi命令之後會將用戶接下來的提交都暫時性的存放在一起queue里,直到接收到exec命令才會去執行queue里的所有命令。還有一點,有些redis客戶端為了提高性能,會將multi與exec的所有命令都暫時存在redis客戶端本地,然後一次性提交。這其實基於的是redis的pipeline 。
你也可以單獨使用pipeline,而不使用multi、exec事務。只是為了減少redis key的傳輸次數而已。如果不會產生數據一致性問題是可以這麼做的。
說到redis事務就不得不提redis事務的性能問題,所以現在結合redis來開發分散式事務來提供性能也是很常見的方案:https://github.com/Plen-wang/redis-lock 供參考。
3.redis兩種持久化原理(RDB、AOF)
redis支持兩種方式來持久化數據,第一種:snapshotting(鏡像或快照)也稱RDB、第二種:AOF(append-only file 文件追加)。
RDB:鏡像模式就是將某個時間段的所有記憶體數據直接寫入硬碟。
AOF:將執行的寫命令增量複製到硬碟裡面。
這兩種其實就是不同的側重,RDB是數據持久化,AOF是命令持久化,數據持久化比較直接,還原的時候直接恢複數據即可。命令持久化恢復的話需要執行一遍命令才行。
redis執行持久化操作取決於兩方面:預設是根據持久化配置來執行,還有就是用戶手動觸發。手動觸發有兩個命令:
SAVE:會block所有的用戶操作,知道持久化結束。
BGSAVE:後臺子進程執行,linux中使用fork命令開啟一個子進程副本,這個子進程副本與主進程共用一套記憶體空間,直到主進程或子進程對記憶體進行修改,被修改之後的記憶體區域將被子進程複製出來單獨使用。
RDB持久化的問題是會存在丟失數據的可能,AOF持久化最多丟失一秒內的命令。所以持久化結合這兩種來執行會在數據完整性和性能之間取的平衡。
4.redis 主從複製的基本原理
redis提供複製功能,這有很多好處。容災、擴展讀寫性能。
有兩個點需要註意:從伺服器一旦進行同步數據時會清空自己的所有數據。redis不支持主主複製。
複製過程大致如下:
1.從伺服器連接主伺服器,發送SYNC命令。
2.主伺服器執行BGSAVE,並使用緩存區記錄BGSAVE之後執行的所有寫命令。
3.BGSAVE執行完畢之後,向從伺服器發送快照文件。從節點丟棄所有本實例數據。載入主伺服器發送過來的快照數據。
4.快照發送完畢之後開始發送緩存區中的寫命令。從節點開始向常規的操作一樣執行增量複製。
5.開始進入常規的複製操作。
有個問題需要分享下,redis一旦占用的記憶體哪怕我們清楚了key,這部分記憶體還是無法還給操作系統的,這是redis的設計策略。雖然從redis info命令中查看的記憶體大小是沒用占用的,但是操作系統無法使用這部分記憶體。
還有個問題,所有redis伺服器如果要被覆制數據,也就是要執行BGSAVE,那麼至少需要保留30%-45%的空餘記憶體。因為BGSAVE執行的時候可能需要copy key出來,如果這個時候寫命令過多還需要給緩存區留有點空間。
5.redis 擴展讀性能
有了主從複製功能之後實際上擴展讀性能是比較容易的。利用主從鏈,將同步操作分派給同步節點,這樣主節點就可以專門只負責寫命令處理。
圖2:
複製節點專門處理複製功能,最下游的讀節點專門來接受讀請求。如果考慮主節點宕機問題,可以開啟複製節點的持久化功能,或者開啟讀節點的部分節點也行。主要是為了防止宕機可以快速恢復master。如果是異地雙活,可以把左右兩邊的所有讀節點開啟持久化,左邊一個機房,右邊一個機房,既可以雙活也可以恢復。(實際情況沒有這麼簡單,只是個思路)
讀節點是不能夠執行寫入命令的,這個才能保證將來的數據複製。
6.redis HA方案(sentinel)
redis 的高可用方案基於自己的一套sentinel 集群來管理。
圖3:
sentinel cluster 保持對redis集群的監控,如果sentinel-1發現master在某個時間段內沒有響應ping命令,就主觀斷定是可能宕機了。sentinel-2、sentinel-3如果都發現master是無響應的,那麼三個投票斷定master是客觀宕機了,做一次master、slave切換。同時會通過sentinel-sh腳本進行一些通知操作。
當然,redis-HA方案有好幾種,我們也可以用keepalived、VIP來實現,將master、backup、slave分離開來,master、backup自動VIP切換。