redis學習筆記
目錄
- 一、redis和memcached的區別
- 三、關於redis Sentinel和Cluster
- 七、關於redis的事務概念
- 九、redis的key驅逐策略
最近在學習redis,覺得redis確實是分散式系統中的一個利器,於是看了很多官方文件,帶著一些問題,結合平時專案中使用情況作了一些總結,本文不適合redis初學者,初學者可以檢視ofollow,noindex" target="_blank">Redis 命令參考 先學習下redis。
一、redis和memcached的區別
以下來自Stack Overflow的一個問答memcached-vs-redis ,已經說的十分清楚了:
redis比memcached更強大、更流行、更受支援。Memcached只能做Redis能做的一小部分事情。即使在它們重疊的一些地方,redis也能做的更好。
以下是詳細對比:
- 速度:兩者都非常快。基準測試因工作負載、版本和許多其他因素而異,但通常顯示redis與memcached一樣快或幾乎一樣快。我推薦使用redis,但並不是因為memcached慢
-
記憶體使用:
- memcached:你可以指定快取大小,當插入專案時,守護程序會快速增長到略大於此大小。除了重新啟動memcached之外,從來沒有真正的方法可以回收這些空間。您所有的鍵都可以過期,您可以重新整理資料庫,它仍然會使用您配置它時使用的全部RAM。
- redis:設定最大大小取決於您。Redis使用的記憶體永遠不會超過它必須使用的記憶體,它會將不再使用的記憶體還給您。
- 我將100000個大約2KB字串(大約200MB)的隨機句子儲存到這兩個字串中。Memcached記憶體的使用增長到大約225MB。Redis記憶體的使用增長到大約228MB。重新整理後,redis下降到大約29MB, memcached保持在~225MB。它們在儲存資料方面也同樣高效,但redis能夠回收資料。
- 持久化:這對redis來說是一個明顯的勝利,因為它在預設情況下是這樣做的,並且持久化有很多可配置項。Memcached沒有在沒有第三方工具的情況下轉儲到磁碟的機制。
- 擴充套件: 在您需要一個以上的例項作為快取之前,這兩種方法都為您提供了大量的空間。Redis提供了一些工具來幫助您做這些事,而memcached不提供這些工具。
二、批量匯入資料
有時候我們需要批量匯入一些資料,這時候可以通過將命令寫入文字中,然後通過管道匯入redis,文字中的命令不需要顯示的分隔符結尾,內容如下:
set key1 value1 set key2 value2 set key3 value3
匯入命令如下:cat command.txt|redis-cli -h 10.10.23.15
也可以加上--pipe
批量執行命令
三、關於redis Sentinel和Cluster
redis的哨兵是官方作為redis高可用的解決方案,針對的是redis的主從故障轉移,沒有分片的功能。如果你的資料一個redis例項能夠完全存放時,那麼redis Sentinel是一種不錯的方案,能夠監視redis叢集,並提供故障轉移的功能。但是如果一臺redis不能完全放下你的資料時,你必須選擇擴充套件redis,有很多解決方案,早期很多采用了客戶端分片或者代理的形式,在redis3.0官方支援叢集,這種方案是服務端分片,一開始不理解哨兵和叢集的關係,以為哨兵是用來做高可用,叢集只是分片,但是其實叢集本身就是自治的,並且已經有了高可用的功能,在配置叢集的時候,可以配置主節點和相應的從節點,叢集會相互探測節點的存活,在主節點下線時進行故障轉移,等於說,搭建叢集事並不需要也不應該才讓哨兵介入。
四、提高redis效能
這裡我看到了一篇十分不錯的文章:Redis效能問題排查解決手冊 ,內容十分不錯,在排查redis效能問題的時候可以當一個手冊來看,我主要關注的點如下:
- slowlog: 這個命令用來查redis的慢查詢日誌,之前在優化mysql時,慢查詢日誌非常有用,讓我對這個比較敏感,另外redis的慢查詢日誌是儲存在記憶體中的,在重啟的時候,日誌會丟失。像對於redis來說,一般的命令執行非常快,瓶頸主要會在網路io,所以設定10ms是比較保守的,一個命令如果要
- 命令複雜度:redis在處理客戶端請求時是單執行緒的,所以redis對cpu非常敏感,如果一個命令非常耗時就會導致其他的命令被阻塞,因此我們得十分注意redis命令的複雜度,例如集合的運算和list的隨機存取時,在用到複雜度高的命令時需要十分注意value的大小,像list的lset命令是O(n)級別的,如果能始終控制list在幾百以下,那麼經常使用也是完全沒問題的,但是如果上萬的場景,如果這樣的需求,是不是應該考慮做列表的切片,或者採用zset來代替。
- 批量執行命令:redis的很多命令都提供了批量執行的版本,可以用這些命令很容易優化,上面也說了redis的主要瓶頸在io,所以如果可以用批量執行命令的話,儘量批量執行。對於不同型別的命令,redis還提供了流水線,流水線是一種客戶端行為,你開啟流水線之後傳送的命令會被客戶端快取著,當你告知客戶端傳送命令時,命令會被批量傳送到redis,執行完後批量返回,只有一次往返,提高了吞吐量。
這裡想說下zset的兩個命令:ZRANGE key start stop [WITHSCORES]
和ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
,zrange的複雜度O(log(N)+M),N為有序集的基數,M為被結果集的基數沒有太多疑問。但是ZRANGEBYSCORE這個命令的服務度,如果沒有後面的[LIMIT offset count]那麼他和zange的複雜度是一樣的,但是如果加上後面的分頁引數他的複雜度實際是O(log(N)+M+offset),試想這樣的場景:zset有200000個元素,且分數一樣(這樣的場景是存在的,見:redis實戰),查詢排名199990到200000的元素,兩者的複雜度天差地別,實現的分頁功能確實一樣的。所以,一共要小心這種隱藏的坑。
五、redis的鎖
在實現一個需要加鎖的操作時,redis提供了三種方案:
- 事務: 樂觀鎖,適合衝突很少的場景
- 分散式鎖: 悲觀鎖,適合衝突頻繁的場景,如果衝突頻繁事務的重試次數為激增,大量消耗cpu
- lua: 適合效能瓶頸的優化,是一種鎖的優化手段
六、lua指令碼和事務
redis中lua指令碼十分簡單,可以把它理解為redis中的儲存過程。在資料庫中,尤其是mysql,很多公司都禁止使用儲存過程,因為不好維護,有很多人喜歡把邏輯寫在儲存過程(其實我覺得如果儲存過程中只寫簡單的sql,並且有統一的公司規範也是比較容易維護的,主要是有些人寫在程式碼中,有些人寫儲存過程,而且邏輯寫經常寫在儲存過程,所以直接禁止比較好)。
對於redis的lua來說,我覺得在有些情況下是很有必要的,因為redis單執行緒處理客戶端命令的這一個特點,lua指令碼的執行也是原子性的,在很多的需要事務的場景都可以採用lua來代替,比如:獲取key a的值,如果a>10,那麼設定b為0。採用lua可以減少網路的往返次數,在對效能有極致要求的情況下,採用lua提高几倍的吞吐量是非常有用的手段。
但是我認為在大多數情況下都不應該使用lua,儘量採用redis本身支援的事務和流水線等功能來提高吞吐量,理由如下:
- lua對叢集支援不友好:一般場景下事務的效能已經足夠了,如果採用lua之後又在達到瓶頸,那麼勢必要考慮叢集來橫向擴充套件了,lua如果涉及到多個鍵分佈在不同的例項上,叢集會直接返回失敗,上面說過叢集不支援多鍵命令,同樣不支援key在不同例項的lua指令碼。如果要支援這個lua指令碼,那麼你要保證鍵都在一個例項。詳情參見:Keys hash tags
- lua可維護性:lua雖然很簡單,但是不是團隊中每個人都會,如果裡面寫了很多的邏輯,可維護性比較差
我的建議還是儘量不使用lua,如果有必要使用lua可以作為後期效能瓶頸的優化方案。
另外在說說事務和lua,事務在某些場景還是lua不能代替的,如:在事務watch之後,我需要檢查下記憶體中的變數a,如果a大於0,那麼執行事務。lua的上下文是在redis中,它能看到的只是整個redis,所以lua並不能完全代替事務。
七、關於redis的事務概念
很多人都知道redis的事務概念,也知道資料庫中的事務,但是這兩個事務是沒有什麼關聯的。redis的事務保證了操作的原子性,在操作執行的過程中,操作不會被打斷,例如操作 1,2,3 即使1失敗了,2和3照樣會執行。redis事務的原子性和資料庫事務的原子性也沒有太大關係,如果說非要對應那麼更加對應的是資料庫事務的隔離性,資料庫事務的原子性和隔離性定義如下(摘自360百科):
原子性: 整個事務中的所有操作,要麼全部完成,要麼全部不完成,不可能停滯在中間某個環節。事務在執行過程中發生錯誤,會被回滾到事務開始前的狀態,就像這個事務從來沒有執行過一樣。
redis沒有回滾,如果失敗也繼續執行
隔離性:隔離狀態執行事務,使它們好像是系統在給定時間內執行的唯一操作。如果有兩個事務,執行在相同的時間內,執行相同的功能,事務的隔離性將確保每一事務在系統中認為只有該事務在使用系統。這種屬性有時稱為序列化,為了防止事務操作間的混淆,必須序列化或序列化請求,使得在同一時間僅有一個請求用於同一資料。
redis單執行緒保證了執行過程中不會被另一個事務打擾
八、redis的zset
在使用的過程中覺得zset的功能實在是強大,因為他有序性的特點,能提供很多功能。有必要好好學習底層的實現(hashmap+skiplist),非常有用的資料結構。
九、redis的key驅逐策略
memcache預設是基於lru驅逐,而redis的預設是關閉的,如果記憶體滿了,redis會拒絕執行寫命令。在編寫程式的時候需要知道redis有沒有配置lru,如果配置了那麼你的key都是不可靠的,有可能會丟失,這就是快取的典型場景。對於快取來說,最好給你的每個key都設定上過期時間。
十、redis部署方案
關於redis的部署方案有很多,需要針對具體的應用場景,主要考慮的是成本,一個備份節點沒用到,成本很高,另外如果作為資料庫必須持久化
- 如果只是快取,無高可用: 單機版,(如果做了持久化,要考慮資料的過期問題,主要是程式的考慮)
- 快取+高可用:一主一從+哨兵(只在從節點持久化)
- 快取+高可用+高效能:一主多從+哨兵 客戶端做讀寫分離(所有的讀寫分離都需要考慮一致性的問題)
- 資料庫,一個例項能放下所有資料:可以採用主從(雙機持久化)+哨兵,
- 資料庫+一個例項不能放下所有資料:叢集+主從+雙機持久化
方案無非是 持久化、主從高可用、讀寫分離、叢集等組合,需要根據實際的業務來部署,建議在微服務架構下不要採用一套方案,而是多個方案部署,比如快取部署一套,記憶體資料庫方案部署一套,針對高併發場景單一致性要求不高的部署讀寫分離。每個服務之間用到的資料庫在最好隔開,如果使用同一個快取就選擇不同的db,有利於後期遷移資料。關於部署方案可以參考阿里雲redis文件 ,非常值得學習。
這裡有一點疑問,我在網上看到很多都說主從採用鏈式複製,包括阿里雲的讀寫分離也採用了這樣一個方案,好處顯而易見,但是不知道自己搭建的話怎麼實現,我看官網文件的sentinel都是星式複製,找不到相關的部署方法,如果有知道的,望大神告知!