背景
Redis是單進程的,為了發揮多核的優勢,我們redis集群采用在單台機器上部署多個redis實例,同時對持久化方式做了改進,采用rdb和增量的aof相結合的方式,appendfsync配置為no。那這樣會有一個時段同時進行aof和rdb,當aof和rdb同時進行時,監控系統報警說某些請求Timeout,我們超時設置為2s。另外我們的機器分為兩種類型,cpu配置不同,一種是4核,另外一種是12核,timeout發生在4核的機器上。
分析
解決這個問題還是走了不少彎路的,所以在這兒和大家分享一下。經過一番分析找到一下可能導致慢的原因
CPU問題?
因為timeout的問題主要出現在4核的機器上,12核的機器沒有問題,所以首先懷疑cpu問題。我用info commandstats查看各個命令執行時間都是us級別,但4核的比12核的慢了近50%。如果是cpu撐不了,這個值應該至少在ms級別。同時profiler分析了redis執行bgsave的函數耗時。在MakeFile中添加-pg,注意編譯和鏈接都需要加,我開始只在編譯階段加了,沒有效果。
觀察都lzf和crc確實很占用cpu時間是最多的。crc采用一種增量的方式,而不是每次全量,性能應該很高。那么關閉compress和crc以后是否能解決呢?關閉以后問題依舊看來不是cpu的問題。
IO問題?
關閉aof以后問題解決,這當中也做了很多嘗試,調整bgsave子進程的進程和IO有優先級,將主進程和子進程分別綁定在不同的cpu上,都不管用一度懷疑不是IO的問題。
在這里介紹幾個工具
既然write(2)是buffered IO,寫先到page cache中,那究竟是什么阻塞了呢?write是不是無限制地使用pagecache,這一點不清楚,這一段時間就狂看各種linux內核相關文檔和看源碼。
發現阻塞write(2)有幾種情況:
彎路
說說其中的彎路,由於我的粗心放過了一個重大的線索,同事告訴我一個link http://www.redis.io/topics/latency,其中有一節就講到了aof被大量IO阻塞的問題,作者提到fdatasysnc會阻塞write(2),但我們設置appendfsync為no,不會出現fdatasync和write(2)的阻塞
那究竟是什么阻塞了write呢,曾一度懷疑是dirty page超過可用內存的40%導致的擁塞等待。
但后來在一台64G內存redis只占用了7G內存的機器上遇到aof阻塞,排除了擁塞等待,那還有什么原因呢?
我查看redis的pullrequest https://github.com/antirez/redis/pull/1862,發現idning也遇到了類似問題,提出aof采用單獨異步線程write的方案,不過作者有點不感冒沒有merge,但他提出可以把文件系統的日志模式改為writeback模式不寫日志,我試了會有所改觀但不會解決問題。我依然知道到底是什么阻塞了write,這時候我把write的代碼整體讀了一篇然后用systemtap檢測每一個可疑的高耗時函數
鎖定在__wait_on_bit_lock,再根據調用堆棧發現有很多地方調用它,比如是read的時候,等待時候把數據從磁盤加載到page中等等。最終定位到一個可疑之處
write的時候都會先定位寫那個page並嘗試lock,而flush dirty page的時候也會先lock住。如果pdflush時,rdb產生大量的IO,那么他有可能嘗試占用lock從而 __grab_cache_page等待很長時間。關於這一點我還不是很確定,歡迎大家幫助我確認。
Redis是單進程的,為了發揮多核的優勢,我們redis集群采用在單台機器上部署多個redis實例,同時對持久化方式做了改進,采用rdb和增量的aof相結合的方式,appendfsync配置為no。那這樣會有一個時段同時進行aof和rdb,當aof和rdb同時進行時,監控系統報警說某些請求Timeout,我們超時設置為2s。另外我們的機器分為兩種類型,cpu配置不同,一種是4核,另外一種是12核,timeout發生在4核的機器上。
分析
解決這個問題還是走了不少彎路的,所以在這兒和大家分享一下。經過一番分析找到一下可能導致慢的原因
- CPU,rdb過程會有lzf compress以及crc會消耗cpu
- bgsave的fork,fork需要copy page entry table到子進程中去,這個過程是阻塞的
- IO問題,bgsave產生大量的IO,但這是在子進程中完成,應該不會影響主進程吧,在此處埋個伏筆
CPU問題?
因為timeout的問題主要出現在4核的機器上,12核的機器沒有問題,所以首先懷疑cpu問題。我用info commandstats查看各個命令執行時間都是us級別,但4核的比12核的慢了近50%。如果是cpu撐不了,這個值應該至少在ms級別。同時profiler分析了redis執行bgsave的函數耗時。在MakeFile中添加-pg,注意編譯和鏈接都需要加,我開始只在編譯階段加了,沒有效果。
- GPROF=-pg
- FINAL_CFLAGS=$(STD) $(WARN) $(GPROF) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS)
- # redis-server
- $(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ)
- $(REDIS_LD) $(GPROF) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a $(FINAL_LIBS)
觀察都lzf和crc確實很占用cpu時間是最多的。crc采用一種增量的方式,而不是每次全量,性能應該很高。那么關閉compress和crc以后是否能解決呢?關閉以后問題依舊看來不是cpu的問題。
IO問題?
關閉aof以后問題解決,這當中也做了很多嘗試,調整bgsave子進程的進程和IO有優先級,將主進程和子進程分別綁定在不同的cpu上,都不管用一度懷疑不是IO的問題。
在這里介紹幾個工具
- renice 調整進程的優先級
- ionice 調整進程的IO優先級,當然這是針對block層的IO schedule,為啥不生效,后來才發現,ionice對async write的不起作用,cfq是將async write請求在同一請求隊列中不屬於進程自己的隊列,write(2)一般情況是寫入page cache就立馬返回,而不是等待真正落入磁盤
- taskset 將指定進程綁定到指定的cpu上
既然write(2)是buffered IO,寫先到page cache中,那究竟是什么阻塞了呢?write是不是無限制地使用pagecache,這一點不清楚,這一段時間就狂看各種linux內核相關文檔和看源碼。
發現阻塞write(2)有幾種情況:
- fsync(fdatasync) 會阻塞write
- pdflush會阻塞write
- 還有當dirty page超過可用內存的40%,這時候就會調用schedule_timeout阻塞200ms。write並不是僅僅copy到page cache就返回了,他還會檢查dirty page的使用比例,如超過了會嘗試阻塞地寫入磁盤,如果此時不能完全寫入,就會調用schedule_timeout從而產生iotimewait,此時使用top觀察確實有90%的CPU使用是iotimewait
彎路
說說其中的彎路,由於我的粗心放過了一個重大的線索,同事告訴我一個link http://www.redis.io/topics/latency,其中有一節就講到了aof被大量IO阻塞的問題,作者提到fdatasysnc會阻塞write(2),但我們設置appendfsync為no,不會出現fdatasync和write(2)的阻塞
那究竟是什么阻塞了write呢,曾一度懷疑是dirty page超過可用內存的40%導致的擁塞等待。
但后來在一台64G內存redis只占用了7G內存的機器上遇到aof阻塞,排除了擁塞等待,那還有什么原因呢?
我查看redis的pullrequest https://github.com/antirez/redis/pull/1862,發現idning也遇到了類似問題,提出aof采用單獨異步線程write的方案,不過作者有點不感冒沒有merge,但他提出可以把文件系統的日志模式改為writeback模式不寫日志,我試了會有所改觀但不會解決問題。我依然知道到底是什么阻塞了write,這時候我把write的代碼整體讀了一篇然后用systemtap檢測每一個可疑的高耗時函數
- probe kernel.function("__wait_on_bit_lock").return {
- if(isinstr(execname(),"redis-server")){
- time = gettimeofday_us() - @entry(gettimeofday_us())
- if(time > 100000){
- printf("trace:%s,pid:%d,function:%s,cost:%d\n",ctime(gettimeofday_s()),pid(),probefunc(),time);
- print_stack(backtrace());
- }
- }
- }
鎖定在__wait_on_bit_lock,再根據調用堆棧發現有很多地方調用它,比如是read的時候,等待時候把數據從磁盤加載到page中等等。最終定位到一個可疑之處
- page = __grab_cache_page(mapping,index,&cached_page,&lru_pvec);
write的時候都會先定位寫那個page並嘗試lock,而flush dirty page的時候也會先lock住。如果pdflush時,rdb產生大量的IO,那么他有可能嘗試占用lock從而 __grab_cache_page等待很長時間。關於這一點我還不是很確定,歡迎大家幫助我確認。
作者原話:那是因為你要求Redis保證fsync語義,RDB沒有。 如果你想處理爆發我建議在絕對需要的時候提供延遲fsync超過2秒的技巧
解決Latency的正確流程
Redis的作者在 http://www.redis.io/topics/latency,已經總結了很多種latency的情況,遇到latency一定要先看它,是否有負責自己的場景,比如我提的問題就是Latency due to AOF and disk I/O,我開始使用了pstack和strace但是都沒有定位到阻塞的原因,其實strace可以定位到,但是使用方式不對導致沒有捕獲到真正的原因。redis2.6以后還提供了watchdog功能,通過config set watchdog打開該功能,就能在日志中打印出慢查詢的堆棧。