網(wǎng)站開發(fā)建設(shè)方案書嵌入式培訓(xùn)
? ? ? ? 在實(shí)際應(yīng)用中,你是否遇到過這樣的情況,本來Redis運(yùn)行的好好的,響應(yīng)也挺正常,但突然就變慢了,響應(yīng)時(shí)間增加了,這不僅會影響用戶體驗(yàn),還會牽連其他系統(tǒng)。
? ? ? ? 那如何排查Redis變慢的情況呢?首先有個問題需要確定,就是確定Redis是否真的變慢了。
Redis基線性能
? ? ? ? 要判斷Redis是否變慢了,一個最直接的方法就是查看Redis的響應(yīng)時(shí)間。
? ? ? ? 大部分情況下,Redis的延遲很低,但是在某些情況下,Redis會出現(xiàn)很高的延遲,可能會達(dá)到幾秒甚至更長,不過持續(xù)的時(shí)間又不長,這到底是怎么情況呢?如果出現(xiàn)了響應(yīng)延遲到秒級別就可以確定Redis變慢了。
? ? ? ? 首先需要先確定Redis的延遲絕對值,但是在不同運(yùn)行環(huán)境下,Redis的絕對性能是不同的。所以就需要當(dāng)前環(huán)境的基線性能,所謂基線性能,就是一個系統(tǒng)在低壓力、無干擾下的基本性能,這個性能只由當(dāng)前軟硬件環(huán)境配置決定。
? ? ? ? 基線性能可以通過Redis提供的命令來確定,具體為在redis-cli中添加--intrinsic-latency選項(xiàng),可以用來檢測和統(tǒng)計(jì)Redis在運(yùn)行期間內(nèi)的最大延遲,這就可以作為基線性能。
redis-cli -h localhost --intrinsic-latency 120
Max latency so far: 1 microseconds.
Max latency so far: 29 microseconds.
Max latency so far: 31 microseconds.
Max latency so far: 34 microseconds.
Max latency so far: 53 microseconds.
Max latency so far: 68 microseconds.
Max latency so far: 103 microseconds.
Max latency so far: 106 microseconds.
Max latency so far: 142 microseconds.
Max latency so far: 158 microseconds.
Max latency so far: 164 microseconds.
Max latency so far: 273 microseconds.
Max latency so far: 296 microseconds.
Max latency so far: 673 microseconds.
Max latency so far: 946 microseconds.
Max latency so far: 2138 microseconds.
Max latency so far: 2234 microseconds.
Max latency so far: 16164 microseconds.2383205581 total runs (avg latency: 0.0504 microseconds / 50.35 nanoseconds per run).
Worst run took 321018x longer than the average latency.
? ? ? ? 在自己的電腦上運(yùn)行命令后,會打印120秒內(nèi)檢測到的最大延遲,可以看到這里的最大延遲為16164微妙,16ms左右。一般情況下,檢測120s的時(shí)長已經(jīng)夠了。
? ? ? ? 一般來說,運(yùn)行時(shí)響應(yīng)時(shí)間和基線性能做對比,如果響應(yīng)時(shí)間達(dá)到了基線性能的2倍以上,就可以認(rèn)定Redis變慢了。
Redis變慢的原因
????????一旦發(fā)現(xiàn)變慢了,接下來就要查找原因解決這個問題了。這個過程要基于Redis本身的工作原理,并結(jié)合和它交互的操作系統(tǒng)、存儲以及網(wǎng)絡(luò)等外部系統(tǒng)的關(guān)鍵機(jī)制,在借助一些輔助工具來定位問題,并指定行之有效的解決方案。
????????Redis變慢的原因有幾下幾點(diǎn),如圖所示:
? ? ? ? 慢查詢指令
????????慢查詢命令,指在Redis中執(zhí)行速度慢的命令,這會導(dǎo)致Redis延遲增加。Redis提供的命令操作很多,并不是所有命令都慢,這和命令操作的復(fù)雜度有關(guān),所以我們必須知道不同命令的復(fù)雜度。
? ? ? ? 比如,操作的value為String類型時(shí),由于操作的事hash表,這個操作的復(fù)雜度是固定的,都是O(1),除非出現(xiàn)了hash碰撞嚴(yán)重。但操作的數(shù)據(jù)類型為集合時(shí),如果集合中包含大量的元素,那這個操作復(fù)雜度是比較高的,會比較耗時(shí)。
? ? ? ? Redis官網(wǎng)提供了各個命令的復(fù)雜度:Commands | Redishttps://redis.io/commands/? ? ? ? 如果有復(fù)雜的操作,就要考慮是否需要用簡單的命令來替換。如果使用keys命令,查詢大量的key,也會導(dǎo)致慢查詢,因?yàn)樾枰獟呙杷械逆I值對,所以生產(chǎn)環(huán)境要禁用這樣的命令。
? ? ? ? 刪除key操作
? ? ? ? 如果Redis中key過期了是會被自動刪除的,這也是Redis的回收機(jī)制,Redis 的 key可以設(shè)置過期時(shí)間,默認(rèn)情況下,每100ms就會掃描一次,刪除過期的key,具體如下:
- 采樣ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP個數(shù)的key,并將其中過期的key全部刪除;
- 如果超過25%的key過期了,則重復(fù)刪除過程,直到過期key的比例降至25%以下。
? ? ? ? 如果觸發(fā)了第二條規(guī)則,Redis就會一直刪除以釋放內(nèi)存空間。注意,刪除操作是阻塞的(Redis 4.0提供了異步機(jī)制減少阻塞)。所以一旦觸發(fā)了就會一直刪除key,這樣一來,就沒辦法給客戶端提供服務(wù)了。
? ? ? ? 磁盤I/O:AOF
? ? ? ? 為了保證數(shù)據(jù)的可靠性,Redis提供了AOF和RDB兩種機(jī)制(想具體了解請查看:Redis持久化(AOF、RDB)用到的寫時(shí)復(fù)制到底是什么-CSDN博客)。其中AOF提供了三種寫回策略:always、everysec、no,這三種寫回策略依賴文件系統(tǒng)來完成,也就是write和fsync。
????????write把日志寫到內(nèi)核的緩沖區(qū),就可以返回了,并不需要等待日志實(shí)際寫回磁盤;而fsync需要把日志記錄寫回到磁盤才能返回,時(shí)間較長。
????????當(dāng)寫回策略配置為everysec和always時(shí),Redis需要調(diào)用fsync把日志寫回磁盤。但是這兩種寫回策略的具體情況不一樣。但不管怎么說,都會涉及到寫回磁盤,而且fsync通常比較耗時(shí),如果Redis主線程執(zhí)行寫回,就會造成阻塞。
? ? ? ? 另外AOF日志重寫時(shí),也容易阻塞主線程,所以Redis使用了子線程來完成該操作。但是AOF重寫會對磁盤又大量的IO操作,同時(shí)fsync又需要等到數(shù)據(jù)寫到磁盤才能返回,所以,當(dāng)AOF重寫的壓力比較大時(shí),就會導(dǎo)致fsync阻塞,雖然fsync由后臺子線程完成,但是主線程會監(jiān)測fsync的執(zhí)行進(jìn)度。
????????當(dāng)主線程使用子線程執(zhí)行了一次fsync,需要再次把接收的數(shù)據(jù)寫回磁盤,如果主線程發(fā)現(xiàn)上一次的fsync還沒有執(zhí)行完,那么它就會阻塞。所以,如果后臺線程執(zhí)行fsync頻繁阻塞的話,主線程也會阻塞,導(dǎo)致Redis性能變慢。
? ? ? ? 內(nèi)存大頁
? ? ? ? 內(nèi)存大頁機(jī)制也會影響Redis性能。Linux內(nèi)核從2.6.38開始支持內(nèi)存大頁,支持2MB大小的內(nèi)存頁分配,而常規(guī)的也大小是4KB。你可能會說,Redis是內(nèi)存數(shù)據(jù)庫,內(nèi)存大頁對Redis不是有好處的嗎,減少了內(nèi)存的分配,但是任何事都有兩面性,這時(shí)一個權(quán)衡的過程。
? ? ? ? Redis為了提高可靠性,提供了持久化的機(jī)制。這個過程需要額外的線程來執(zhí)行,所以不會阻塞主線程為客戶端提供服務(wù)。如果在持久化的過程中,客戶端修改了數(shù)據(jù),Redis會才用寫時(shí)復(fù)制(Copy On Write)機(jī)制,數(shù)據(jù)一旦修改了不會直接修改內(nèi)存中的數(shù)據(jù),而是復(fù)制一份,然后再進(jìn)行修改。
? ? ? ? 如果采用了內(nèi)存大頁,那Redis就需要拷貝該大頁。如果關(guān)閉了內(nèi)存大頁,那需要拷貝的頁數(shù)據(jù)只有4kb,可見內(nèi)存大頁會復(fù)制大量數(shù)據(jù)。
????????所以正常情況下,關(guān)閉內(nèi)存大頁就可以了。
? ? ? ? swap操作
? ? ? ? 操作系統(tǒng)swap是將內(nèi)存數(shù)據(jù)在內(nèi)存和磁盤來回?fù)Q入核換出的機(jī)制,涉及到磁盤的讀寫。所以一旦涉及到了swap,其性能都會收到磁盤性能的影響。
? ? ? ? Redis是內(nèi)存數(shù)據(jù)庫,內(nèi)存使用量大,如果用Redis保存海量數(shù)據(jù),而且沒有控制好內(nèi)存使用量,就可能會觸發(fā)swap機(jī)制,從而影響性能。一旦觸發(fā)swap機(jī)制,Redis需要操作磁盤才能完成,這回極大的降低Redis的性能。
? ? ? ? 關(guān)于海量數(shù)據(jù)的處理請參考:面對海量數(shù)據(jù)Redis如何應(yīng)對-CSDN博客
? ? ? ? Redis對于swap的排查有現(xiàn)成的命令,具體這里不做詳細(xì)介紹了。
? ? ? ? 內(nèi)存碎片
? ? ? ? 內(nèi)存碎片很好理解,明明有內(nèi)存空間,但是申請時(shí)就是無法分配需要的內(nèi)存空間,這對于使用內(nèi)存的Redis來說無疑影響巨大。
? ? ? ? 內(nèi)存碎片的原因
- 內(nèi)存不是按需分配的,操作系統(tǒng)為了減少內(nèi)存的分配次數(shù),每次都是按照固定大小進(jìn)行內(nèi)存分配的,例如8字節(jié)、16字節(jié)、32字節(jié)等,比如申請了20字節(jié)的內(nèi)存,但是實(shí)際上會分配32字節(jié),此時(shí)如果在寫入5字節(jié)數(shù)據(jù),就不用再申請內(nèi)存了,減少了內(nèi)存申請的次數(shù)。
- Redis鍵值對大小不一和刪除操作的影響,內(nèi)存分配器只能按照固定大小分配內(nèi)存。所以,分配的內(nèi)存空間一般都會比申請的空間大一些,不會完全一直,這本身就會造成一定碎片,降低內(nèi)存空存儲效率。鍵值對會被修改或刪除,這會導(dǎo)致空間的擴(kuò)容和釋放。一方面,如果修改后的鍵值對變大或變小了,就需要占用額外的空間或釋放不用的空間。另一方面,刪除的鍵值對就不在需要內(nèi)存空間了,此時(shí)會把空間釋放出來,變成空閑空間。
? ? ? ? 如何判斷內(nèi)存碎片
????????Redis是內(nèi)存數(shù)據(jù)庫,內(nèi)存利用率的高低直接關(guān)系到Redis運(yùn)行效率的高低。為了讓用戶能監(jiān)控到實(shí)時(shí)的內(nèi)存使用情況,Redis自身提供了info命令,可以用來查詢內(nèi)存使用的詳細(xì)信息
info memory
# Memory
used_memory:1143520
used_memory_human:1.09M
used_memory_rss:1114112
used_memory_rss_human:1.06M
used_memory_peak:1144672
used_memory_peak_human:1.09M
used_memory_peak_perc:99.90%
used_memory_overhead:1096754
used_memory_startup:1079248
used_memory_dataset:46766
used_memory_dataset_perc:72.76%
allocator_allocated:1098416
allocator_active:1076224
allocator_resident:1076224
total_system_memory:8589934592
total_system_memory_human:8.00G
used_memory_lua:37888
used_memory_lua_human:37.00K
used_memory_scripts:0
used_memory_scripts_human:0B
number_of_cached_scripts:0
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction
allocator_frag_ratio:1.1
allocator_frag_bytes:18446744073709529424
allocator_rss_ratio:1.00
allocator_rss_bytes:0
rss_overhead_ratio:1.04
rss_overhead_bytes:37888
mem_fragmentation_ratio:1.01
mem_fragmentation_bytes:15696
mem_not_counted_for_evict:0
mem_replication_backlog:0
mem_clients_slaves:0
mem_clients_normal:16986
mem_aof_buffer:0
mem_allocator:libc
active_defrag_running:0
lazyfree_pending_objects:0
????????mem_fragmentation_ratio指標(biāo)表示內(nèi)存碎片化率,該值大于1但小于1.5時(shí),這種情況是正常的,因?yàn)閮?nèi)存碎片是無法避免的。如果該值大于1.5,就表名內(nèi)存碎片化率比較嚴(yán)重了,超過了50%,這時(shí)需要采取措施來處理內(nèi)存碎片化。
? ? ? ? Redis從4.0版本后提供了專門清理內(nèi)存碎片化的參數(shù),通過該參數(shù)來設(shè)置內(nèi)存碎片化的清理的開始和結(jié)束時(shí)機(jī),以及占用CPU比例,從而減少碎片清理對Redis本身請求處理的性能影響。
config set activedefrag yes
????????這個命令只是啟動了自動清理功能,但具體什么時(shí)候清理,會受下面兩個參數(shù)的控制。這兩個參數(shù)分別設(shè)置了觸發(fā)內(nèi)存清理的一個條件,如果同時(shí)滿足這兩個條件,就開始清理,清理過程中,只要有一個條件不滿足,就停止清理。
- active-defrag-ignore-bytes 100mb:表示內(nèi)存碎片的字節(jié)數(shù)達(dá)到100MB時(shí),就開始清理。
- active-defrag-threshold-lower 10:表示內(nèi)存碎片空間占操作系統(tǒng)分配給Redis的總空間比例達(dá)到10%時(shí),開始清理。
總結(jié)
? ? ? ? 關(guān)于Redis變慢的問題排查就介紹到這里,不知道你有沒有清晰的思路,歡迎關(guān)注并留言討論。