redis scan 效率太慢_Redis 基础、高级特性与性能调优(下)
數(shù)據(jù)淘汰機制
Redis提供了5種數(shù)據(jù)淘汰策略:
- volatile-lru:使用LRU算法進行數(shù)據(jù)淘汰(淘汰上次使用時間最早的,且使用次數(shù)最少的key),只淘汰設定了有效期的key
- allkeys-lru:使用LRU算法進行數(shù)據(jù)淘汰,所有的key都可以被淘汰
- volatile-random:隨機淘汰數(shù)據(jù),只淘汰設定了有效期的key
- allkeys-random:隨機淘汰數(shù)據(jù),所有的key都可以被淘汰
- volatile-ttl:淘汰剩余有效期最短的key
最好為Redis指定一種有效的數(shù)據(jù)淘汰策略以配合maxmemory設置,避免在內存使用滿后發(fā)生寫入失敗的情況。
一般來說,推薦使用的策略是volatile-lru,并辨識Redis中保存的數(shù)據(jù)的重要性。對于那些重要的,絕對不能丟棄的數(shù)據(jù)(如配置類數(shù)據(jù)等),應不設置有效期,這樣Redis就永遠不會淘汰這些數(shù)據(jù)。對于那些相對不是那么重要的,并且能夠熱加載的數(shù)據(jù)(比如緩存最近登錄的用戶信息,當在Redis中找不到時,程序會去DB中讀取),可以設置上有效期,這樣在內存不夠時Redis就會淘汰這部分數(shù)據(jù)。
配置方法:
maxmemory-policy volatile-lru #默認是noeviction,即不進行數(shù)據(jù)淘汰Pipelining
Pipelining
Redis提供許多批量操作的命令,如MSET/MGET/HMSET/HMGET等等,這些命令存在的意義是減少維護網絡連接和傳輸數(shù)據(jù)所消耗的資源和時間。
例如連續(xù)使用5次SET命令設置5個不同的key,比起使用一次MSET命令設置5個不同的key,效果是一樣的,但前者會消耗更多的RTT(Round Trip Time)時長,永遠應優(yōu)先使用后者。
然而,如果客戶端要連續(xù)執(zhí)行的多次操作無法通過Redis命令組合在一起,例如:
SET a "abc"INCR bHSET c name "hi"此時便可以使用Redis提供的pipelining功能來實現(xiàn)在一次交互中執(zhí)行多條命令。
使用pipelining時,只需要從客戶端一次向Redis發(fā)送多條命令(以rn)分隔,Redis就會依次執(zhí)行這些命令,并且把每個命令的返回按順序組裝在一起一次返回,比如:
$ (printf "PINGrnPINGrnPINGrn"; sleep 1) | nc localhost 6379+PONG+PONG+PONG大部分的Redis客戶端都對Pipelining提供支持,所以開發(fā)者通常并不需要自己手工拼裝命令列表。
Pipelining的局限性
Pipelining只能用于執(zhí)行連續(xù)且無相關性的命令,當某個命令的生成需要依賴于前一個命令的返回時,就無法使用Pipelining了。
通過Scripting功能,可以規(guī)避這一局限性
事務與Scripting
Pipelining能夠讓Redis在一次交互中處理多條命令,然而在一些場景下,我們可能需要在此基礎上確保這一組命令是連續(xù)執(zhí)行的。
比如獲取當前累計的PV數(shù)并將其清0
> GET vCount12384> SET vCount 0OK如果在GET和SET命令之間插進來一個INCR vCount,就會使客戶端拿到的vCount不準確。
Redis的事務可以確保復數(shù)命令執(zhí)行時的原子性。也就是說Redis能夠保證:一個事務中的一組命令是絕對連續(xù)執(zhí)行的,在這些命令執(zhí)行完成之前,絕對不會有來自于其他連接的其他命令插進去執(zhí)行。
通過MULTI和EXEC命令來把這兩個命令加入一個事務中:
> MULTIOK> GET vCountQUEUED> SET vCount 0QUEUED> EXEC1) 123842) OKRedis在接收到MULTI命令后便會開啟一個事務,這之后的所有讀寫命令都會保存在隊列中但并不執(zhí)行,直到接收到EXEC命令后,Redis會把隊列中的所有命令連續(xù)順序執(zhí)行,并以數(shù)組形式返回每個命令的返回結果。
可以使用DISCARD命令放棄當前的事務,將保存的命令隊列清空。
需要注意的是,Redis事務不支持回滾:
如果一個事務中的命令出現(xiàn)了語法錯誤,大部分客戶端驅動會返回錯誤,2.6.5版本以上的Redis也會在執(zhí)行EXEC時檢查隊列中的命令是否存在語法錯誤,如果存在,則會自動放棄事務并返回錯誤。
但如果一個事務中的命令有非語法類的錯誤(比如對String執(zhí)行HSET操作),無論客戶端驅動還是Redis都無法在真正執(zhí)行這條命令之前發(fā)現(xiàn),所以事務中的所有命令仍然會被依次執(zhí)行。在這種情況下,會出現(xiàn)一個事務中部分命令成功部分命令失敗的情況,然而與RDBMS不同,Redis不提供事務回滾的功能,所以只能通過其他方法進行數(shù)據(jù)的回滾。
通過事務實現(xiàn)CAS
Redis提供了WATCH命令與事務搭配使用,實現(xiàn)CAS樂觀鎖的機制。
假設要實現(xiàn)將某個商品的狀態(tài)改為已售:
if(exec(HGET stock:1001 state) == "in stock") exec(HSET stock:1001 state "sold");這一偽代碼執(zhí)行時,無法確保并發(fā)安全性,有可能多個客戶端都獲取到了”in stock”的狀態(tài),導致一個庫存被售賣多次。
使用WATCH命令和事務可以解決這一問題:
exec(WATCH stock:1001);if(exec(HGET stock:1001 state) == "in stock") { exec(MULTI); exec(HSET stock:1001 state "sold"); exec(EXEC);}WATCH的機制是:在事務EXEC命令執(zhí)行時,Redis會檢查被WATCH的key,只有被WATCH的key從WATCH起始時至今沒有發(fā)生過變更,EXEC才會被執(zhí)行。如果WATCH的key在WATCH命令到EXEC命令之間發(fā)生過變化,則EXEC命令會返回失敗。
Scripting
通過EVAL與EVALSHA命令,可以讓Redis執(zhí)行LUA腳本。這就類似于RDBMS的存儲過程一樣,可以把客戶端與Redis之間密集的讀/寫交互放在服務端進行,避免過多的數(shù)據(jù)交互,提升性能。
Scripting功能是作為事務功能的替代者誕生的,事務提供的所有能力Scripting都可以做到。Redis官方推薦使用LUA Script來代替事務,前者的效率和便利性都超過了事務。
關于Scripting的具體使用,本文不做詳細介紹,請參考官方文檔
https://redis.io/commands/eval
Redis性能調優(yōu)
盡管Redis是一個非常快速的內存數(shù)據(jù)存儲媒介,也并不代表Redis不會產生性能問題。
前文中提到過,Redis采用單線程模型,所有的命令都是由一個線程串行執(zhí)行的,所以當某個命令執(zhí)行耗時較長時,會拖慢其后的所有命令,這使得Redis對每個任務的執(zhí)行效率更加敏感。
針對Redis的性能優(yōu)化,主要從下面幾個層面入手:
- 最初的也是最重要的,確保沒有讓Redis執(zhí)行耗時長的命令
- 使用pipelining將連續(xù)執(zhí)行的命令組合執(zhí)行
- 操作系統(tǒng)的Transparent huge pages功能必須關閉:
- 如果在虛擬機中運行Redis,可能天然就有虛擬機環(huán)境帶來的固有延遲。可以通過./redis-cli –intrinsic-latency 100命令查看固有延遲。同時如果對Redis的性能有較高要求的話,應盡可能在物理機上直接部署Redis。
- 檢查數(shù)據(jù)持久化策略
- 考慮引入讀寫分離機制
長耗時命令
Redis絕大多數(shù)讀寫命令的時間復雜度都在O(1)到O(N)之間,在文本和官方文檔中均對每個命令的時間復雜度有說明。
通常來說,O(1)的命令是安全的,O(N)命令在使用時需要注意,如果N的數(shù)量級不可預知,則應避免使用。例如對一個field數(shù)未知的Hash數(shù)據(jù)執(zhí)行HGETALL/HKEYS/HVALS命令,通常來說這些命令執(zhí)行的很快,但如果這個Hash中的field數(shù)量極多,耗時就會成倍增長。
又如使用SUNION對兩個Set執(zhí)行Union操作,或使用SORT對List/Set執(zhí)行排序操作等時,都應該嚴加注意。
避免在使用這些O(N)命令時發(fā)生問題主要有幾個辦法:
- 不要把List當做列表使用,僅當做隊列來使用
- 通過機制嚴格控制Hash、Set、Sorted Set的大小
- 可能的話,將排序、并集、交集等操作放在客戶端執(zhí)行
- 絕對禁止使用KEYS命令
- 避免一次性遍歷集合類型的所有成員,而應使用SCAN類的命令進行分批的,游標式的遍歷
Redis提供了SCAN命令,可以對Redis中存儲的所有key進行游標式的遍歷,避免使用KEYS命令帶來的性能問題。同時還有SSCAN/HSCAN/ZSCAN等命令,分別用于對Set/Hash/Sorted Set中的元素進行游標式遍歷。SCAN類命令的使用請參考官方文檔:
https://redis.io/commands/scan
Redis提供了Slow Log功能,可以自動記錄耗時較長的命令。相關的配置參數(shù)有兩個:
slowlog-log-slower-than xxxms #執(zhí)行時間慢于xxx毫秒的命令計入Slow Logslowlog-max-len xxx #Slow Log的長度,即最大紀錄多少條Slow Log使用SLOWLOG GET [number]命令,可以輸出最近進入Slow Log的number條命令。
使用SLOWLOG RESET命令,可以重置Slow Log
網絡引發(fā)的延遲
- 盡可能使用長連接或連接池,避免頻繁創(chuàng)建銷毀連接
- 客戶端進行的批量數(shù)據(jù)操作,應使用Pipeline特性在一次交互中完成。具體請參照本文的Pipelining章節(jié)
數(shù)據(jù)持久化引發(fā)的延遲
Redis的數(shù)據(jù)持久化工作本身就會帶來延遲,需要根據(jù)數(shù)據(jù)的安全級別和性能要求制定合理的持久化策略:
- AOF + fsync always的設置雖然能夠絕對確保數(shù)據(jù)安全,但每個操作都會觸發(fā)一次fsync,會對Redis的性能有比較明顯的影響
- AOF + fsync every second是比較好的折中方案,每秒fsync一次
- AOF + fsync never會提供AOF持久化方案下的最優(yōu)性能
- 使用RDB持久化通常會提供比使用AOF更高的性能,但需要注意RDB的策略配置
- 每一次RDB快照和AOF Rewrite都需要Redis主進程進行fork操作。fork操作本身可能會產生較高的耗時,與CPU和Redis占用的內存大小有關。根據(jù)具體的情況合理配置RDB快照和AOF Rewrite時機,避免過于頻繁的fork帶來的延遲
Swap引發(fā)的延遲
當Linux將Redis所用的內存分頁移至swap空間時,將會阻塞Redis進程,導致Redis出現(xiàn)不正常的延遲。Swap通常在物理內存不足或一些進程在進行大量I/O操作時發(fā)生,應盡可能避免上述兩種情況的出現(xiàn)。
/proc//smaps文件中會保存進程的swap記錄,通過查看這個文件,能夠判斷Redis的延遲是否由Swap產生。如果這個文件中記錄了較大的Swap size,則說明延遲很有可能是Swap造成的。
數(shù)據(jù)淘汰引發(fā)的延遲
當同一秒內有大量key過期時,也會引發(fā)Redis的延遲。在使用時應盡量將key的失效時間錯開。
引入讀寫分離機制
Redis的主從復制能力可以實現(xiàn)一主多從的多節(jié)點架構,在這一架構下,主節(jié)點接收所有寫請求,并將數(shù)據(jù)同步給多個從節(jié)點。
在這一基礎上,我們可以讓從節(jié)點提供對實時性要求不高的讀請求服務,以減小主節(jié)點的壓力。
尤其是針對一些使用了長耗時命令的統(tǒng)計類任務,完全可以指定在一個或多個從節(jié)點上執(zhí)行,避免這些長耗時命令影響其他請求的響應。
關于讀寫分離的具體說明,請參見后續(xù)章節(jié)
主從復制與集群分片
主從復制
Redis支持一主多從的主從復制架構。一個Master實例負責處理所有的寫請求,Master將寫操作同步至所有Slave。
借助Redis的主從復制,可以實現(xiàn)讀寫分離和高可用:
- 實時性要求不是特別高的讀請求,可以在Slave上完成,提升效率。特別是一些周期性執(zhí)行的統(tǒng)計任務,這些任務可能需要執(zhí)行一些長耗時的Redis命令,可以專門規(guī)劃出1個或幾個Slave用于服務這些統(tǒng)計任務
- 借助Redis Sentinel可以實現(xiàn)高可用,當Master crash后,Redis Sentinel能夠自動將一個Slave晉升為Master,繼續(xù)提供服務
啟用主從復制非常簡單,只需要配置多個Redis實例,在作為Slave的Redis實例中配置:
slaveof 192.168.1.1 6379 #指定Master的IP和端口當Slave啟動后,會從Master進行一次冷啟動數(shù)據(jù)同步,由Master觸發(fā)BGSAVE生成RDB文件推送給Slave進行導入,導入完成后Master再將增量數(shù)據(jù)通過Redis Protocol同步給Slave。之后主從之間的數(shù)據(jù)便一直以Redis Protocol進行同步
使用Sentinel做自動failover
Redis的主從復制功能本身只是做數(shù)據(jù)同步,并不提供監(jiān)控和自動failover能力,要通過主從復制功能來實現(xiàn)Redis的高可用,還需要引入一個組件:Redis Sentinel
Redis Sentinel是Redis官方開發(fā)的監(jiān)控組件,可以監(jiān)控Redis實例的狀態(tài),通過Master節(jié)點自動發(fā)現(xiàn)Slave節(jié)點,并在監(jiān)測到Master節(jié)點失效時選舉出一個新的Master,并向所有Redis實例推送新的主從配置。
Redis Sentinel需要至少部署3個實例才能形成選舉關系。
關鍵配置:
另外需要注意的是,Redis Sentinel實現(xiàn)的自動failover不是在同一個IP和端口上完成的,也就是說自動failover產生的新Master提供服務的IP和端口與之前的Master是不一樣的,所以要實現(xiàn)HA,還要求客戶端必須支持Sentinel,能夠與Sentinel交互獲得新Master的信息才行。
集群分片
為何要做集群分片:
- Redis中存儲的數(shù)據(jù)量大,一臺主機的物理內存已經無法容納
- Redis的寫請求并發(fā)量大,一個Redis實例以無法承載
當上述兩個問題出現(xiàn)時,就必須要對Redis進行分片了。
Redis的分片方案有很多種,例如很多Redis的客戶端都自行實現(xiàn)了分片功能,也有向Twemproxy這樣的以代理方式實現(xiàn)的Redis分片方案。然而首選的方案還應該是Redis官方在3.0版本中推出的Redis Cluster分片方案。
本文不會對Redis Cluster的具體安裝和部署細節(jié)進行介紹,重點介紹Redis Cluster帶來的好處與弊端。
Redis Cluster的能力
- 能夠自動將數(shù)據(jù)分散在多個節(jié)點上
- 當訪問的key不在當前分片上時,能夠自動將請求轉發(fā)至正確的分片
- 當集群中部分節(jié)點失效時仍能提供服務
其中第三點是基于主從復制來實現(xiàn)的,Redis Cluster的每個數(shù)據(jù)分片都采用了主從復制的結構,原理和前文所述的主從復制完全一致,唯一的區(qū)別是省去了Redis Sentinel這一額外的組件,由Redis Cluster負責進行一個分片內部的節(jié)點監(jiān)控和自動failover。
Redis Cluster分片原理
Redis Cluster中共有16384個hash slot,Redis會計算每個key的CRC16,將結果與16384取模,來決定該key存儲在哪一個hash slot中,同時需要指定Redis Cluster中每個數(shù)據(jù)分片負責的Slot數(shù)。Slot的分配在任何時間點都可以進行重新分配。
客戶端在對key進行讀寫操作時,可以連接Cluster中的任意一個分片,如果操作的key不在此分片負責的Slot范圍內,Redis Cluster會自動將請求重定向到正確的分片上。
hash tags
在基礎的分片原則上,Redis還支持hash tags功能,以hash tags要求的格式明明的key,將會確保進入同一個Slot中。例如:{uiv}user:1000和{uiv}user:1001擁有同樣的hash tag {uiv},會保存在同一個Slot中。
使用Redis Cluster時,pipelining、事務和LUA Script功能涉及的key必須在同一個數(shù)據(jù)分片上,否則將會返回錯誤。如要在Redis Cluster中使用上述功能,就必須通過hash tags來確保一個pipeline或一個事務中操作的所有key都位于同一個Slot中。
有一些客戶端(如Redisson)實現(xiàn)了集群化的pipelining操作,可以自動將一個pipeline里的命令按key所在的分片進行分組,分別發(fā)到不同的分片上執(zhí)行。但是Redis不支持跨分片的事務,事務和LUA Script還是必須遵循所有key在一個分片上的規(guī)則要求。主從復制 vs 集群分片
在設計軟件架構時,要如何在主從復制和集群分片兩種部署方案中取舍呢?
從各個方面看,Redis Cluster都是優(yōu)于主從復制的方案
- Redis Cluster能夠解決單節(jié)點上數(shù)據(jù)量過大的問題
- Redis Cluster能夠解決單節(jié)點訪問壓力過大的問題
- Redis Cluster包含了主從復制的能力
那是不是代表Redis Cluster永遠是優(yōu)于主從復制的選擇呢?
并不是。
軟件架構永遠不是越復雜越好,復雜的架構在帶來顯著好處的同時,一定也會帶來相應的弊端。采用Redis Cluster的弊端包括:
- 維護難度增加。在使用Redis Cluster時,需要維護的Redis實例數(shù)倍增,需要監(jiān)控的主機數(shù)量也相應增加,數(shù)據(jù)備份/持久化的復雜度也會增加。同時在進行分片的增減操作時,還需要進行reshard操作,遠比主從模式下增加一個Slave的復雜度要高。
- 客戶端資源消耗增加。當客戶端使用連接池時,需要為每一個數(shù)據(jù)分片維護一個連接池,客戶端同時需要保持的連接數(shù)成倍增多,加大了客戶端本身和操作系統(tǒng)資源的消耗。
- 性能優(yōu)化難度增加。你可能需要在多個分片上查看Slow Log和Swap日志才能定位性能問題。
- 事務和LUA Script的使用成本增加。在Redis Cluster中使用事務和LUA Script特性有嚴格的限制條件,事務和Script中操作的key必須位于同一個分片上,這就使得在開發(fā)時必須對相應場景下涉及的key進行額外的規(guī)劃和規(guī)范要求。如果應用的場景中大量涉及事務和Script的使用,如何在保證這兩個功能的正常運作前提下把數(shù)據(jù)平均分到多個數(shù)據(jù)分片中就會成為難點。
所以說,在主從復制和集群分片兩個方案中做出選擇時,應該從應用軟件的功能特性、數(shù)據(jù)和訪問量級、未來發(fā)展規(guī)劃等方面綜合考慮,只在確實有必要引入數(shù)據(jù)分片時再使用Redis Cluster。
下面是一些建議:
綜合上面幾點考慮,如果單臺主機的可用物理內存完全足以支撐對Redis的容量需求,且Redis面臨的并發(fā)寫壓力距離Benchmark值還尚有距離,建議采用主從復制的架構,可以省去很多不必要的麻煩。同時,如果應用中大量使用pipelining和事務,也建議盡可能選擇主從復制架構,可以減少設計和開發(fā)時的復雜度。
Redis Java客戶端的選擇
Redis的Java客戶端很多,官方推薦的有三種:Jedis、Redisson和lettuce。
在這里對Jedis和Redisson進行對比介紹
Jedis:
- 輕量,簡潔,便于集成和改造
- 支持連接池
- 支持pipelining、事務、LUA Scripting、Redis Sentinel、Redis Cluster
- 不支持讀寫分離,需要自己實現(xiàn)
- 文檔差(真的很差,幾乎沒有……)
Redisson:
- 基于Netty實現(xiàn),采用非阻塞IO,性能高
- 支持異步請求
- 支持連接池
- 支持pipelining、LUA Scripting、Redis Sentinel、Redis Cluster
- 不支持事務,官方建議以LUA Scripting代替事務
- 支持在Redis Cluster架構下使用pipelining
- 支持讀寫分離,支持讀負載均衡,在主從復制和Redis Cluster架構下都可以使用
- 內建Tomcat Session Manager,為Tomcat 6/7/8提供了會話共享功能
- 可以與Spring Session集成,實現(xiàn)基于Redis的會話共享
- 文檔較豐富,有中文文檔
對于Jedis和Redisson的選擇,同樣應遵循前述的原理,盡管Jedis比起Redisson有各種各樣的不足,但也應該在需要使用Redisson的高級特性時再選用Redisson,避免造成不必要的程序復雜度提升。
本文轉自k8s中文社區(qū)
總結
以上是生活随笔為你收集整理的redis scan 效率太慢_Redis 基础、高级特性与性能调优(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python自动点击脚本_[Python
- 下一篇: kafka 削峰_从面试角度一文学完 K