Redis爲什麼變慢了?一文講透如何排查Redis性能問題 | 萬字長文

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis 作爲優秀的內存數據庫,其擁有非常高的性能,單個實例的 OPS 能夠達到 10W 左右。但也正因此如此,當我們在使用 Redis 時,如果發現操作延遲變大的情況,就會與我們的預期不符。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你也許或多或少地,也遇到過以下這些場景:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Redis 上執行同樣的命令,爲什麼有時響應很快,有時卻很慢?","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲什麼 Redis 執行 SET、DEL 命令耗時也很久?","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲什麼我的 Redis 突然慢了一波,之後又恢復正常了?","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲什麼我的 Redis 穩定運行了很久,突然從某個時間點開始變慢了?","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"...","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你並不清楚 Redis 內部的實現原理,那麼在排查這種延遲問題時就會一頭霧水。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你也遇到了以上情況,那麼,這篇文章將會給你一個「全面」的問題排查思路,並且針對這些導致變慢的場景,我還會給你一個高效的解決方案。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"在正文開始之前,我需要提醒你的是,這篇文章很長,涵蓋的 Redis 知識點也非常廣,全篇文章接近 2W 字,如果此時你的閱讀環境不適合專注閱讀,我建議你先收藏此文章,然後在合適的時間專注閱讀這篇文章。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你能耐心且認真地讀完這篇文章,我可以保證,你對 Redis 的性能調優將會有非常大的收穫。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你準備好了,那就跟着我的思路開始吧!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ea/ea3e484f08ebe9dde9b117a994d218e8.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"Redis真的變慢了嗎?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,在開始之前,你需要弄清楚 Redis 是否真的變慢了?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你發現你的業務服務 API 響應延遲變長,首先你需要先排查服務內部,究竟是哪個環節拖慢了整個服務。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比較高效的做法是,在服務內部集成","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"鏈路追蹤","attrs":{}},{"type":"text","text":",也就是在服務訪問外部依賴的出入口,記錄下每次請求外部依賴的響應延時。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a3/a33803fc385125d42f4b2b65d341fa31.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你發現確實是操作 Redis 的這條鏈路耗時變長了,那麼此刻你需要把焦點關注在業務服務到 Redis 這條鏈路上。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從你的業務服務到 Redis 這條鏈路變慢的原因可能也有 2 個:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"業務服務器到 Redis 服務器之間的網絡存在問題,例如網絡線路質量不佳,網絡數據包在傳輸時存在延遲、丟包等情況","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"Redis 本身存在問題,需要進一步排查是什麼原因導致 Redis 變慢","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通常來說,第一種情況發生的概率比較小,如果是服務器之間網絡存在問題,那部署在這臺業務服務器上的所有服務都會發生網絡延遲的情況,此時你需要聯繫網絡運維同事,讓其協助解決網絡問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們這篇文章,重點關注的是第二種情況。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也就是從 Redis 角度來排查,是否存在導致變慢的場景,以及都有哪些因素會導致 Redis 的延遲增加,然後針對性地進行優化。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"排除網絡原因,如何確認你的 Redis 是否真的變慢了?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,你需要對 Redis 進行基準性能測試,瞭解你的 Redis 在生產環境服務器上的基準性能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"什麼是基準性能?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"簡單來講,基準性能就是指 Redis 在一臺負載正常的機器上,其最大的響應延遲和平均響應延遲分別是怎樣的?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲什麼要測試基準性能?我參考別人提供的響應延遲,判斷自己的 Redis 是否變慢不行嗎?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"答案是否定的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲 Redis 在不同的軟硬件環境下,它的性能是各不相同的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如,我的機器配置比較低,當延遲爲 2ms 時,我就認爲 Redis 變慢了,但是如果你的硬件配置比較高,那麼在你的運行環境下,可能延遲是 0.5ms 時就可以認爲 Redis 變慢了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,你只有瞭解了你的 Redis 在生產環境服務器上的基準性能,才能進一步評估,當其延遲達到什麼程度時,才認爲 Redis 確實變慢了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"具體如何做?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了避免業務服務器到 Redis 服務器之間的網絡延遲,你需要直接在 Redis 服務器上測試實例的響應延遲情況。執行以下命令,就可以測試出這個實例 60 秒內的最大響應延遲:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"$ redis-cli -h 127.0.0.1 -p 6379 --intrinsic-latency 60\nMax latency so far: 1 microseconds.\nMax latency so far: 15 microseconds.\nMax latency so far: 17 microseconds.\nMax latency so far: 18 microseconds.\nMax latency so far: 31 microseconds.\nMax latency so far: 32 microseconds.\nMax latency so far: 59 microseconds.\nMax latency so far: 72 microseconds.\n\n1428669267 total runs (avg latency: 0.0420 microseconds / 42.00 nanoseconds per run).\nWorst run took 1429x longer than the average latency.","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從輸出結果可以看到,這 60 秒內的最大響應延遲爲 72 微秒(0.072毫秒)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你還可以使用以下命令,查看一段時間內 Redis 的最小、最大、平均訪問延遲:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"$ redis-cli -h 127.0.0.1 -p 6379 --latency-history -i 1\nmin: 0, max: 1, avg: 0.13 (100 samples) -- 1.01 seconds range\nmin: 0, max: 1, avg: 0.12 (99 samples) -- 1.01 seconds range\nmin: 0, max: 1, avg: 0.13 (99 samples) -- 1.01 seconds range\nmin: 0, max: 1, avg: 0.10 (99 samples) -- 1.01 seconds range\nmin: 0, max: 1, avg: 0.13 (98 samples) -- 1.00 seconds range\nmin: 0, max: 1, avg: 0.08 (99 samples) -- 1.01 seconds range\n...","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上輸出結果是,每間隔 1 秒,採樣 Redis 的平均操作耗時,其結果分佈在 0.08 ~ 0.13 毫秒之間。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"瞭解了基準性能測試方法,那麼你就可以按照以下幾步,來判斷你的 Redis 是否真的變慢了:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"在相同配置的服務器上,測試一個正常 Redis 實例的基準性能","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"找到你認爲可能變慢的 Redis 實例,測試這個實例的基準性能","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"如果你觀察到,這個實例的運行延遲是正常 Redis 基準性能的 2 倍以上,即可認爲這個 Redis 實例確實變慢了 ","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"確認是 Redis 變慢了,那如何排查是哪裏發生了問題呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面跟着我的思路,我們從易到難,一步步來分析可能導致 Redis 變慢的因素。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"使用複雜度過高的命令","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,第一步,你需要去查看一下 Redis 的慢日誌(slowlog)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis 提供了慢日誌命令的統計功能,它記錄了有哪些命令在執行時耗時比較久。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"查看 Redis 慢日誌之前,你需要設置慢日誌的閾值。例如,設置慢日誌的閾值爲 5 毫秒,並且保留最近 500 條慢日誌記錄:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"# 命令執行耗時超過 5 毫秒,記錄慢日誌\nCONFIG SET slowlog-log-slower-than 5000\n# 只保留最近 500 條慢日誌\nCONFIG SET slowlog-max-len 500","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"設置完成之後,所有執行的命令如果操作耗時超過了 5 毫秒,都會被 Redis 記錄下來。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時,你可以執行以下命令,就可以查詢到最近記錄的慢日誌:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"127.0.0.1:6379> SLOWLOG get 5\n1) 1) (integer) 32693 # 慢日誌ID\n 2) (integer) 1593763337 # 執行時間戳\n 3) (integer) 5299 # 執行耗時(微秒)\n 4) 1) \"LRANGE\" # 具體執行的命令和參數\n 2) \"user_list:2000\"\n 3) \"0\"\n 4) \"-1\"\n2) 1) (integer) 32692\n 2) (integer) 1593763337\n 3) (integer) 5044\n 4) 1) \"GET\"\n 2) \"user_info:1000\"\n...","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過查看慢日誌,我們就可以知道在什麼時間點,執行了哪些命令比較耗時。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你的應用程序執行的 Redis 命令有以下特點,那麼有可能會導致操作延遲變大:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"經常使用 O(N) 以上覆雜度的命令,例如 SORT、SUNION、ZUNIONSTORE 聚合類命令","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"使用 O(N) 複雜度的命令,但 N 的值非常大","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一種情況導致變慢的原因在於,Redis 在操作內存數據時,時間複雜度過高,要花費更多的 CPU 資源。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二種情況導致變慢的原因在於,Redis 一次需要返回給客戶端的數據過多,更多時間花費在數據協議的組裝和網絡傳輸過程中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,我們還可以從資源使用率層面來分析,如果你的應用程序操作 Redis 的 OPS 不是很大,但 Redis 實例的 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"CPU 使用率卻很高","attrs":{}},{"type":"text","text":",那麼很有可能是使用了複雜度過高的命令導致的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除此之外,我們都知道,Redis 是單線程處理客戶端請求的,如果你經常使用以上命令,那麼當 Redis 處理客戶端請求時,一旦前面某個命令發生耗時,就會導致後面的請求發生排隊,對於客戶端來說,響應延遲也會變長。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0c/0ce7e8c0f53edbaf17a2c13d91c66882.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"針對這種情況如何解決呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"答案很簡單,你可以使用以下方法優化你的業務:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"儘量不使用 O(N) 以上覆雜度過高的命令,對於數據的聚合操作,放在客戶端做","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"執行 O(N) 命令,保證 N 儘量的小(推薦 N <= 300),每次獲取儘量少的數據,讓 Redis 可以及時處理返回","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"操作bigkey","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你查詢慢日誌發現,並不是複雜度過高的命令導致的,而都是 SET / DEL 這種簡單命令出現在慢日誌中,那麼你就要懷疑你的實例否寫入了 bigkey。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis 在寫入數據時,需要爲新的數據分配內存,相對應的,當從 Redis 中刪除數據時,它會釋放對應的內存空間。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果一個 key 寫入的 value 非常大,那麼 Redis 在","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"分配內存時就會比較耗時","attrs":{}},{"type":"text","text":"。同樣的,當刪除這個 key 時,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"釋放內存也會比較耗時","attrs":{}},{"type":"text","text":",這種類型的 key 我們一般稱之爲 bigkey。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時,你需要檢查你的業務代碼,是否存在寫入 bigkey 的情況。你需要評估寫入一個 key 的數據大小,儘量避免一個 key 存入過大的數據。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果已經寫入了 bigkey,那有沒有什麼辦法可以掃描出實例中 bigkey 的分佈情況呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"答案是可以的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis 提供了掃描 bigkey 的命令,執行以下命令就可以掃描出,一個實例中 bigkey 的分佈情況,輸出結果是以類型維度展示的:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"$ redis-cli -h 127.0.0.1 -p 6379 --bigkeys -i 0.01\n\n...\n-------- summary -------\n\nSampled 829675 keys in the keyspace!\nTotal key length in bytes is 10059825 (avg len 12.13)\n\nBiggest string found 'key:291880' has 10 bytes\nBiggest list found 'mylist:004' has 40 items\nBiggest set found 'myset:2386' has 38 members\nBiggest hash found 'myhash:3574' has 37 fields\nBiggest zset found 'myzset:2704' has 42 members\n\n36313 strings with 363130 bytes (04.38% of keys, avg size 10.00)\n787393 lists with 896540 items (94.90% of keys, avg size 1.14)\n1994 sets with 40052 members (00.24% of keys, avg size 20.09)\n1990 hashs with 39632 fields (00.24% of keys, avg size 19.92)\n1985 zsets with 39750 members (00.24% of keys, avg size 20.03)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從輸出結果我們可以很清晰地看到,每種數據類型所佔用的最大內存 / 擁有最多元素的 key 是哪一個,以及每種數據類型在整個實例中的佔比和平均大小 / 元素數量。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其實,使用這個命令的原理,就是 Redis 在內部執行了 SCAN 命令,遍歷整個實例中所有的 key,然後針對 key 的類型,分別執行 STRLEN、LLEN、HLEN、SCARD、ZCARD 命令,來獲取 String 類型的長度、容器類型(List、Hash、Set、ZSet)的元素個數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏我需要提醒你的是,當執行這個命令時,要注意 2 個問題:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"對線上實例進行 bigkey 掃描時,Redis 的 OPS 會突增,爲了降低掃描過程中對 Redis 的影響,最好控制一下掃描的頻率,指定 -i 參數即可,它表示掃描過程中每次掃描後休息的時間間隔,單位是秒","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"掃描結果中,對於容器類型(List、Hash、Set、ZSet)的 key,只能掃描出元素最多的 key。但一個 key 的元素多,不一定表示佔用內存也多,你還需要根據業務情況,進一步評估內存佔用情況","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那針對 bigkey 導致延遲的問題,有什麼好的解決方案呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏有兩點可以優化:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"業務應用盡量避免寫入 bigkey","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"如果你使用的 Redis 是 4.0 以上版本,用 UNLINK 命令替代 DEL,此命令可以把釋放 key 內存的操作,放到後臺線程中去執行,從而降低對 Redis 的影響","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"如果你使用的 Redis 是 6.0 以上版本,可以開啓 lazy-free 機制(lazyfree-lazy-user-del = yes),在執行 DEL 命令時,釋放內存也會放到後臺線程中執行","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但即便可以使用方案 2,我也不建議你在實例中存入 bigkey。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是因爲 bigkey 在很多場景下,依舊會產生性能問題。例如,bigkey 在分片集羣模式下,對於數據的遷移也會有性能影響,以及我後面即將講到的數據過期、數據淘汰、透明大頁,都會受到 bigkey 的影響。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"集中過期","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你發現,平時在操作 Redis 時,並沒有延遲很大的情況發生,但在某個時間點突然出現一波延時,其現象表現爲:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"變慢的時間點很有規律,例如某個整點,或者每間隔多久就會發生一波延遲。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果是出現這種情況,那麼你需要排查一下,業務代碼中是否存在設置大量 key 集中過期的情況。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果有大量的 key 在某個固定時間點集中過期,在這個時間點訪問 Redis 時,就有可能導致延時變大。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲什麼集中過期會導致 Redis 延遲變大?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這就需要我們瞭解 Redis 的過期策略是怎樣的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis 的過期數據採用被動過期 + 主動過期兩種策略:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"被動過期:只有當訪問某個 key 時,才判斷這個 key 是否已過期,如果已過期,則從實例中刪除","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"主動過期:Redis 內部維護了一個定時任務,默認每隔 100 毫秒(1秒10次)就會從全局的過期哈希表中隨機取出 20 個 key,然後刪除其中過期的 key,如果過期 key 的比例超過了 25%,則繼續重複此過程,直到過期 key 的比例下降到 25% 以下,或者這次任務的執行耗時超過了 25 毫秒,纔會退出循環","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注意,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這個主動過期 key 的定時任務,是在 Redis 主線程中執行的","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也就是說如果在執行主動過期的過程中,出現了需要大量刪除過期 key 的情況,那麼此時應用程序在訪問 Redis 時,必須要等待這個過期任務執行結束,Redis 纔可以服務這個客戶端請求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時就會出現,應用訪問 Redis 延時變大。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果此時需要過期刪除的是一個 bigkey,那麼這個耗時會更久。而且,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這個操作延遲的命令並不會記錄在慢日誌中","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲慢日誌中","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"只記錄一個命令真正操作內存數據的耗時","attrs":{}},{"type":"text","text":",而 Redis 主動刪除過期 key 的邏輯,是在命令真正執行之前執行的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,此時你會看到,慢日誌中沒有操作耗時的命令,但我們的應用程序卻感知到了延遲變大,其實時間都花費在了刪除過期 key 上,這種情況我們需要尤爲注意。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/25/25dad196bad2e2bb79edc2059acde8f1.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那遇到這種情況,如何分析和排查?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時,你需要檢查你的業務代碼,是否存在集中過期 key 的邏輯。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一般集中過期使用的是 expireat / pexpireat 命令,你需要在代碼中搜索這個關鍵字。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"排查代碼後,如果確實存在集中過期 key 的邏輯存在,但這種邏輯又是業務所必須的,那此時如何優化,同時又不對 Redis 有性能影響呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一般有兩種方案來規避這個問題:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"集中過期 key 增加一個隨機過期時間,把集中過期的時間打散,降低 Redis 清理過期 key 的壓力","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"如果你使用的 Redis 是 4.0 以上版本,可以開啓 lazy-free 機制,當刪除過期 key 時,把釋放內存的操作放到後臺線程中執行,避免阻塞主線程","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一種方案,在設置 key 的過期時間時,增加一個隨機時間,僞代碼可以這麼寫:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"# 在過期時間點之後的 5 分鐘內隨機過期掉\nredis.expireat(key, expire_time + random(300))","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣一來,Redis 在處理過期時,不會因爲集中刪除過多的 key 導致壓力過大,從而避免阻塞主線程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二種方案,Redis 4.0 以上版本,開啓 lazy-free 機制:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"# 釋放過期 key 的內存,放到後臺線程執行\nlazyfree-lazy-expire yes","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,除了業務層面的優化和修改配置之外,你還可以通過運維手段及時發現這種情況。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"運維層面,你需要把 Redis 的各項運行狀態數據監控起來,在 Redis 上執行 INFO 命令就可以拿到這個實例所有的運行狀態數據。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這裏我們需要重點關注 expired_keys 這一項,它代表整個實例到目前爲止,累計刪除過期 key 的數量。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你需要把這個指標監控起來,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"當這個指標在很短時間內出現了突增","attrs":{}},{"type":"text","text":",需要及時報警出來,然後與業務應用報慢的時間點進行對比分析,確認時間是否一致,如果一致,則可以確認確實是因爲集中過期 key 導致的延遲變大。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"實例內存達到上限","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你的 Redis 實例設置了內存上限 maxmemory,那麼也有可能導致 Redis 變慢。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當我們把 Redis 當做純緩存使用時,通常會給這個實例設置一個內存上限 maxmemory,然後設置一個數據淘汰策略。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而當實例的內存達到了 maxmemory 後,你可能會發現,在此之後每次寫入新數據,操作延遲變大了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是爲什麼?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原因在於,當 Redis 內存達到 maxmemory 後,每次寫入新的數據之前,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Redis 必須先從實例中踢出一部分數據,讓整個實例的內存維持在 maxmemory 之下","attrs":{}},{"type":"text","text":",然後才能把新數據寫進來。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個踢出舊數據的邏輯也是需要消耗時間的,而具體耗時的長短,要取決於你配置的淘汰策略:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"allkeys-lru:不管 key 是否設置了過期,淘汰最近最少訪問的 key","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"volatile-lru:只淘汰最近最少訪問、並設置了過期時間的 key","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"allkeys-random:不管 key 是否設置了過期,隨機淘汰 key","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"volatile-random:只隨機淘汰設置了過期時間的 key","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"allkeys-ttl:不管 key 是否設置了過期,淘汰即將過期的 key","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"noeviction:不淘汰任何 key,實例內存達到 maxmeory 後,再寫入新數據直接返回錯誤","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"allkeys-lfu:不管 key 是否設置了過期,淘汰訪問頻率最低的 key(4.0+版本支持)","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"volatile-lfu:只淘汰訪問頻率最低、並設置了過期時間 key(4.0+版本支持)","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"具體使用哪種策略,我們需要根據具體的業務場景來配置。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一般最常使用的是 allkeys-lru / volatile-lru 淘汰策略,它們的處理邏輯是,每次從實例中隨機取出一批 key(這個數量可配置),然後淘汰一個最少訪問的 key,之後把剩下的 key 暫存到一個池子中,繼續隨機取一批 key,並與之前池子中的 key 比較,再淘汰一個最少訪問的 key。以此往復,直到實例內存降到 maxmemory 之下。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要注意的是,Redis 的淘汰數據的邏輯與刪除過期 key 的一樣,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"也是在命令真正執行之前執行的","attrs":{}},{"type":"text","text":",也就是說它也會增加我們操作 Redis 的延遲,而且,寫 OPS 越高,延遲也會越明顯。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,如果此時你的 Redis 實例中還存儲了 bigkey,那麼","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"在淘汰刪除 bigkey 釋放內存時,也會耗時比較久","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/36/367a42fe57927cee24476747d2f42e43.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看到了麼?bigkey 的危害到處都是,這也是前面我提醒你儘量不存儲 bigkey 的原因。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"針對這種情況,如何解決呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我給你 4 個方面的優化建議:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"避免存儲 bigkey,降低釋放內存的耗時","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"淘汰策略改爲隨機淘汰,隨機淘汰比 LRU 要快很多(視業務情況調整)","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"拆分實例,把淘汰 key 的壓力分攤到多個實例上","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"如果使用的是 Redis 4.0 以上版本,開啓 layz-free 機制,把淘汰 key 釋放內存的操作放到後臺線程中執行(配置 lazyfree-lazy-eviction = yes)","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"fork耗時嚴重","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了保證 Redis 數據的安全性,我們可能會開啓後臺定時 RDB 和 AOF rewrite 功能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但如果你發現,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"操作 Redis 延遲變大,都發生在 Redis 後臺 RDB 和 AOF rewrite 期間","attrs":{}},{"type":"text","text":",那你就需要排查,在這期間有可能導致變慢的情況。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當 Redis 開啓了後臺 RDB 和 AOF rewrite 後,在執行時,它們都需要主進程創建出一個子進程進行數據的持久化。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主進程創建子進程,會調用操作系統提供的 fork 函數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而 fork 在執行過程中,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"主進程需要拷貝自己的內存頁表給子進程","attrs":{}},{"type":"text","text":",如果這個實例很大,那麼這個拷貝的過程也會比較耗時。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而且這個 fork 過程會消耗大量的 CPU 資源,在完成 fork 之前,整個 Redis 實例會被阻塞住,無法處理任何客戶端請求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果此時你的 CPU 資源本來就很緊張,那麼 fork 的耗時會更長,甚至達到秒級,這會嚴重影響 Redis 的性能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那如何確認確實是因爲 fork 耗時導致的 Redis 延遲變大呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可以在 Redis 上執行 INFO 命令,查看 latest","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"fork","attrs":{}},{"type":"text","text":"usec 項,單位微秒。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"# 上一次 fork 耗時,單位微秒\nlatest_fork_usec:59477","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個時間就是主進程在 fork 子進程期間,整個實例阻塞無法處理客戶端請求的時間。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你發現這個耗時很久,就要警惕起來了,這意味在這期間,你的整個 Redis 實例都處於不可用的狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了數據持久化會生成 RDB 之外,當主從節點第一次建立數據同步時,主節點也創建子進程生成 RDB,然後發給從節點進行一次全量同步,所以,這個過程也會對 Redis 產生性能影響。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/53/53e020351d0f7483480735a9e103a9fe.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要想避免這種情況,你可以採取以下方案進行優化:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"控制 Redis 實例的內存:儘量在 10G 以下,執行 fork 的耗時與實例大小有關,實例越大,耗時越久","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"合理配置數據持久化策略:在 slave 節點執行 RDB 備份,推薦在低峯期執行,而對於丟失數據不敏感的業務(例如把 Redis 當做純緩存使用),可以關閉 AOF 和 AOF rewrite","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"Redis 實例不要部署在虛擬機上:fork 的耗時也與系統也有關,虛擬機比物理機耗時更久","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"降低主從庫全量同步的概率:適當調大 repl-backlog-size 參數,避免主從全量同步","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"開啓內存大頁","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了上面講到的子進程 RDB 和 AOF rewrite 期間,fork 耗時導致的延時變大之外,這裏還有一個方面也會導致性能問題,這就是操作系統是否開啓了","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"內存大頁機制","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"什麼是內存大頁?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們都知道,應用程序向操作系統申請內存時,是按","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"內存頁","attrs":{}},{"type":"text","text":"進行申請的,而常規的內存頁大小是 4KB。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Linux 內核從 2.6.38 開始,支持了","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"內存大頁機制","attrs":{}},{"type":"text","text":",該機制允許應用程序以 2MB 大小爲單位,向操作系統申請內存。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"應用程序每次向操作系統申請的內存單位變大了,但這也意味着申請內存的耗時變長。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這對 Redis 會有什麼影響呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當 Redis 在執行後臺 RDB 和 AOF rewrite 時,採用 fork 子進程的方式來處理。但主進程 fork 子進程後,此時的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"主進程依舊是可以接收寫請求的","attrs":{}},{"type":"text","text":",而進來的寫請求,會採用 Copy On Write(寫時複製)的方式操作內存數據。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也就是說,主進程一旦有數據需要修改,Redis 並不會直接修改現有內存中的數據,而是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"先將這塊內存數據拷貝出來,再修改這塊新內存的數據","attrs":{}},{"type":"text","text":",這就是所謂的「寫時複製」。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"寫時複製你也可以理解成,誰需要發生寫操作,誰就需要先拷貝,再修改。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣做的好處是,父進程有任何寫操作,並不會影響子進程的數據持久化(子進程只持久化 fork 這一瞬間整個實例中的所有數據即可,不關心新的數據變更,因爲子進程只需要一份內存快照,然後持久化到磁盤上)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是請注意,主進程在拷貝內存數據時,這個階段就涉及到新內存的申請,如果此時操作系統開啓了內存大頁,那麼在此期間,客戶端即便只修改 10B 的數據,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Redis 在申請內存時也會以 2MB 爲單位向操作系統申請,申請內存的耗時變長,進而導致每個寫請求的延遲增加,影響到 Redis 性能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同樣地,如果這個寫請求操作的是一個 bigkey,那主進程在拷貝這個 bigkey 內存塊時,一次申請的內存會更大,時間也會更久。可見,bigkey 在這裏又一次影響到了性能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/40/4020506b7da20ae3bd4c2e76b4c867ba.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那如何解決這個問題?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"很簡單,你只需要關閉內存大頁機制就可以了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,你需要查看 Redis 機器是否開啓了內存大頁:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"$ cat /sys/kernel/mm/transparent_hugepage/enabled\n[always] madvise never","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果輸出選項是 always,就表示目前開啓了內存大頁機制,我們需要關掉它:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"$ echo never > /sys/kernel/mm/transparent_hugepage/enabled","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其實,操作系統提供的內存大頁機制,其優勢是,可以在一定程序上降低應用程序申請內存的次數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是對於 Redis 這種對性能和延遲極其敏感的數據庫來說,我們希望 Redis 在每次申請內存時,耗時儘量短,所以我不建議你在 Redis 機器上開啓這個機制。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"開啓AOF","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前面我們分析了 RDB 和 AOF rewrite 對 Redis 性能的影響,主要關注點在 fork 上。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其實,關於數據持久化方面,還有影響 Redis 性能的因素,這次我們重點來看 AOF 數據持久化。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你的 AOF 配置不合理,還是有可能會導致性能問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當 Redis 開啓 AOF 後,其工作原理如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"Redis 執行寫命令後,把這個命令寫入到 AOF 文件內存中(write 系統調用)","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"Redis 根據配置的 AOF 刷盤策略,把 AOF 內存數據刷到磁盤上(fsync 系統調用)","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了保證 AOF 文件數據的安全性,Redis 提供了 3 種刷盤機制:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"appendfsync always:主線程每次執行寫操作後立即刷盤,此方案會佔用比較大的磁盤 IO 資源,但數據安全性最高","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"appendfsync no:主線程每次寫操作只寫內存就返回,內存數據什麼時候刷到磁盤,交由操作系統決定,此方案對性能影響最小,但數據安全性也最低,Redis 宕機時丟失的數據取決於操作系統刷盤時機","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"appendfsync everysec:主線程每次寫操作只寫內存就返回,然後由後臺線程每隔 1 秒執行一次刷盤操作(觸發fsync系統調用),此方案對性能影響相對較小,但當 Redis 宕機時會丟失 1 秒的數據","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面我們依次來分析,這幾個機制對性能的影響。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你的 AOF 配置爲 appendfsync always,那麼 Redis 每處理一次寫操作,都會把這個命令寫入到磁盤中才返回,整個過程都是在主線程執行的,這個過程必然會加重 Redis 寫負擔。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原因也很簡單,操作磁盤要比操作內存慢幾百倍,採用這個配置會嚴重拖慢 Redis 的性能,因此我不建議你把 AOF 刷盤方式配置爲 always。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們接着來看 appendfsync no 配置項。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這種配置下,Redis 每次寫操作只寫內存,什麼時候把內存中的數據刷到磁盤,交給操作系統決定,此方案對 Redis 的性能影響最小,但當 Redis 宕機時,會丟失一部分數據,爲了數據的安全性,一般我們也不採取這種配置。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你的 Redis 只用作純緩存,對於數據丟失不敏感,採用配置 appendfsync no 也是可以的。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看到這裏,我猜你肯定和大多數人的想法一樣,選比較折中的方案 appendfsync everysec 就沒問題了吧?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個方案優勢在於,Redis 主線程寫完內存後就返回,具體的刷盤操作是放到後臺線程中執行的,後臺線程每隔 1 秒把內存中的數據刷到磁盤中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種方案既兼顧了性能,又儘可能地保證了數據安全,是不是覺得很完美?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"但是,這裏我要給你潑一盆冷水了,採用這種方案你也要警惕一下,因爲這種方案還是存在導致 Redis 延遲變大的情況發生,甚至會阻塞整個 Redis。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是爲什麼?我把 AOF 最耗時的刷盤操作,放到後臺線程中也會影響到 Redis 主線程?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你試想這樣一種情況:當 Redis 後臺線程在執行 AOF 文件刷盤時,如果此時磁盤的 IO 負載很高,那這個後臺線程在執行刷盤操作(fsync系統調用)時就會被阻塞住。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時的主線程依舊會接收寫請求,緊接着,主線程又需要把數據寫到文件內存中(write 系統調用),","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"但此時的後臺子線程由於磁盤負載過高,導致 fsync 發生阻塞,遲遲不能返回,那主線程在執行 write 系統調用時,也會被阻塞住","attrs":{}},{"type":"text","text":",直到後臺線程 fsync 執行完成後,主線程執行 write 才能成功返回。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看到了麼?在這個過程中,主線程依舊有阻塞的風險。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,儘管你的 AOF 配置爲 appendfsync everysec,也不能掉以輕心,要警惕磁盤壓力過大導致的 Redis 有性能問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/54/54d128ba902795a6000bc66cb7905729.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那什麼情況下會導致磁盤 IO 負載過大?以及如何解決這個問題呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我總結了以下幾種情況,你可以參考進行問題排查:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"子進程正在執行 AOF rewrite,這個過程會佔用大量的磁盤 IO 資源","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"有其他應用程序在執行大量的寫文件操作,也會佔用磁盤 IO 資源","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於情況1,說白了就是,Redis 的 AOF 後臺子線程刷盤操作,撞上了子進程 AOF rewrite!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這怎麼辦?難道要關閉 AOF rewrite 纔行?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"幸運的是,Redis 提供了一個配置項,當子進程在 AOF rewrite 期間,可以讓後臺子線程不執行刷盤(不觸發 fsync 系統調用)操作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這相當於在 AOF rewrite 期間,臨時把 appendfsync 設置爲了 none,配置如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"# AOF rewrite 期間,AOF 後臺子線程不進行刷盤操作\n# 相當於在這期間,臨時把 appendfsync 設置爲了 none\nno-appendfsync-on-rewrite yes","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當然,開啓這個配置項,在 AOF rewrite 期間,如果實例發生宕機,那麼此時會丟失更多的數據,性能和數據安全性,你需要權衡後進行選擇。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果佔用磁盤資源的是其他應用程序,那就比較簡單了,你需要定位到是哪個應用程序在大量寫磁盤,然後把這個應用程序遷移到其他機器上執行就好了,避免對 Redis 產生影響。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當然,如果你對 Redis 的性能和數據安全都有很高的要求,那麼我建議從","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"硬件層面","attrs":{}},{"type":"text","text":"來優化,更換爲 SSD 磁盤,提高磁盤的 IO 能力,保證 AOF 期間有充足的磁盤資源可以使用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"綁定CPU","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"很多時候,我們在部署服務時,爲了提高服務性能,降低應用程序在多個 CPU 核心之間的上下文切換帶來的性能損耗,通常採用的方案是進程綁定 CPU 的方式提高性能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但在部署 Redis 時,如果你需要綁定 CPU 來提高其性能,我建議你仔細斟酌後再做操作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲什麼?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲 Redis 在綁定 CPU 時,是有很多考究的,如果你不瞭解 Redis 的運行原理,隨意綁定 CPU 不僅不會提高性能,甚至有可能會帶來相反的效果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們都知道,一般現代的服務器會有多個 CPU,而每個 CPU 又包含多個物理核心,每個物理核心又分爲多個邏輯核心,每個物理核下的邏輯核共用 L1/L2 Cache。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而 Redis Server 除了主線程服務客戶端請求之外,還會創建子進程、子線程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中子進程用於數據持久化,而子線程用於執行一些比較耗時操作,例如異步釋放 fd、異步 AOF 刷盤、異步 lazy-free 等等。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你把 Redis 進程只綁定了一個 CPU 邏輯核心上,那麼當 Redis 在進行數據持久化時,fork 出的子進程會繼承父進程的 CPU 使用偏好。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"而此時的子進程會消耗大量的 CPU 資源進行數據持久化(把實例數據全部掃描出來需要耗費CPU),這就會導致子進程會與主進程發生 CPU 爭搶,進而影響到主進程服務客戶端請求,訪問延遲變大。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這就是 Redis 綁定 CPU 帶來的性能問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那如何解決這個問題呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你確實想要綁定 CPU,可以優化的方案是,不要讓 Redis 進程只綁定在一個 CPU 邏輯核上,而是綁定在多個邏輯核心上,而且,綁定的多個邏輯核心最好是同一個物理核心,這樣它們還可以共用 L1/L2 Cache。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當然,即便我們把 Redis 綁定在多個邏輯核心上,也只能在一定程度上緩解主線程、子進程、後臺線程在 CPU 資源上的競爭。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲這些子進程、子線程還是會在這多個邏輯核心上進行切換,存在性能損耗。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何再進一步優化?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可能你已經想到了,我們是否可以讓主線程、子進程、後臺線程,分別綁定在固定的 CPU 核心上,不讓它們來回切換,這樣一來,他們各自使用的 CPU 資源互不影響。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其實,這個方案 Redis 官方已經想到了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis 在 6.0 版本已經推出了這個功能,我們可以通過以下配置,對主線程、後臺線程、後臺 RDB 進程、AOF rewrite 進程,綁定固定的 CPU 邏輯核心:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"# Redis Server 和 IO 線程綁定到 CPU核心 0,2,4,6\nserver_cpulist 0-7:2\n\n# 後臺子線程綁定到 CPU核心 1,3\nbio_cpulist 1,3\n\n# 後臺 AOF rewrite 進程綁定到 CPU 核心 8,9,10,11\naof_rewrite_cpulist 8-11\n\n# 後臺 RDB 進程綁定到 CPU 核心 1,10,11\n# bgsave_cpulist 1,10-1","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你使用的正好是 Redis 6.0 版本,就可以通過以上配置,來進一步提高 Redis 性能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏我需要提醒你的是,一般來說,Redis 的性能已經足夠優秀,除非你對 Redis 的性能有更加嚴苛的要求,否則不建議你綁定 CPU。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上面的分析你也能看出,綁定 CPU 需要你對計算機體系結構有非常清晰的瞭解,否則謹慎操作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們繼續分析還有什麼場景會導致 Redis 變慢。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"使用Swap","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你發現 Redis 突然變得非常慢,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"每次的操作耗時都達到了幾百毫秒甚至秒級","attrs":{}},{"type":"text","text":",那此時你就需要檢查 Redis 是否使用到了 Swap,在這種情況下 Redis 基本上已經無法提供高性能的服務了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"什麼是 Swap?爲什麼使用 Swap 會導致 Redis 的性能下降?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你對操作系統有些瞭解,就會知道操作系統爲了緩解內存不足對應用程序的影響,允許把一部分內存中的數據換到磁盤上,以達到應用程序對內存使用的緩衝,這些內存數據被換到磁盤上的區域,就是 Swap。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"問題就在於,當內存中的數據被換到磁盤上後,Redis 再訪問這些數據時,就需要從磁盤上讀取,訪問磁盤的速度要比訪問內存慢幾百倍!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"尤其是針對 Redis 這種對性能要求極高、性能極其敏感的數據庫來說,這個操作延時是無法接受的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時,你需要檢查 Redis 機器的內存使用情況,確認是否存在使用了 Swap。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可以通過以下方式來查看 Redis 進程是否使用到了 Swap:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"# 先找到 Redis 的進程 ID\n$ ps -aux | grep redis-server\n\n# 查看 Redis Swap 使用情況\n$ cat /proc/$pid/smaps | egrep '^(Swap|Size)'","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輸出結果如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"Size: 1256 kB\nSwap: 0 kB\nSize: 4 kB\nSwap: 0 kB\nSize: 132 kB\nSwap: 0 kB\nSize: 63488 kB\nSwap: 0 kB\nSize: 132 kB\nSwap: 0 kB\nSize: 65404 kB\nSwap: 0 kB\nSize: 1921024 kB\nSwap: 0 kB\n...","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個結果會列出 Redis 進程的內存使用情況。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每一行 Size 表示 Redis 所用的一塊內存大小,Size 下面的 Swap 就表示這塊 Size 大小的內存,有多少數據已經被換到磁盤上了,如果這兩個值相等,說明這塊內存的數據都已經完全被換到磁盤上了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果只是少量數據被換到磁盤上,例如每一塊 Swap 佔對應 Size 的比例很小,那影響並不是很大。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果是幾百兆甚至上 GB 的內存被換到了磁盤上","attrs":{}},{"type":"text","text":",那麼你就需要警惕了,這種情況 Redis 的性能肯定會急劇下降。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時的解決方案是:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"增加機器的內存,讓 Redis 有足夠的內存可以使用","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"整理內存空間,釋放出足夠的內存供 Redis 使用,然後釋放 Redis 的 Swap,讓 Redis 重新使用內存","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"釋放 Redis 的 Swap 過程通常要重啓實例,爲了避免重啓實例對業務的影響,一般會先進行主從切換,然後釋放舊主節點的 Swap,重啓舊主節點實例,待從庫數據同步完成後,再進行主從切換即可。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可見,當 Redis 使用到 Swap 後,此時的 Redis 性能基本已達不到高性能的要求(你可以理解爲武功被廢),所以你也需要提前預防這種情況。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"預防的辦法就是,你需要對 Redis 機器的內存和 Swap 使用情況進行監控,在內存不足或使用到 Swap 時報警出來,及時處理。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"碎片整理","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis 的數據都存儲在內存中,當我們的應用程序頻繁修改 Redis 中的數據時,就有可能會導致 Redis 產生內存碎片。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"內存碎片會降低 Redis 的內存使用率,我們可以通過執行 INFO 命令,得到這個實例的內存碎片率:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"# Memory\nused_memory:5709194824\nused_memory_human:5.32G\nused_memory_rss:8264855552\nused_memory_rss_human:7.70G\n...\nmem_fragmentation_ratio:1.45","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個內存碎片率是怎麼計算的?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"很簡單,mem","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"fragmentation","attrs":{}},{"type":"text","text":"ratio = used","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"memory","attrs":{}},{"type":"text","text":"rss / used_memory。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中 used","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"memory 表示 Redis 存儲數據的內存大小,而 used","attrs":{}},{"type":"text","text":"memory_rss 表示操作系統實際分配給 Redis 進程的大小。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果 mem","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"fragmentation","attrs":{}},{"type":"text","text":"ratio > 1.5,說明內存碎片率已經超過了 50%,這時我們就需要採取一些措施來降低內存碎片了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"解決的方案一般如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"如果你使用的是 Redis 4.0 以下版本,只能通過重啓實例來解決","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"如果你使用的是 Redis 4.0 版本,它正好提供了自動碎片整理的功能,可以通過配置開啓碎片自動整理","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"但是,開啓內存碎片整理,它也有可能會導致 Redis 性能下降。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原因在於,Redis 的碎片整理工作是也在","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"主線程","attrs":{}},{"type":"text","text":"中執行的,當其進行碎片整理時,必然會消耗 CPU 資源,產生更多的耗時,從而影響到客戶端的請求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,當你需要開啓這個功能時,最好提前測試評估它對 Redis 的影響。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis 碎片整理的參數配置如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"# 開啓自動內存碎片整理(總開關)\nactivedefrag yes\n\n# 內存使用 100MB 以下,不進行碎片整理\nactive-defrag-ignore-bytes 100mb\n\n# 內存碎片率超過 10%,開始碎片整理\nactive-defrag-threshold-lower 10\n# 內存碎片率超過 100%,盡最大努力碎片整理\nactive-defrag-threshold-upper 100\n\n# 內存碎片整理佔用 CPU 資源最小百分比\nactive-defrag-cycle-min 1\n# 內存碎片整理佔用 CPU 資源最大百分比\nactive-defrag-cycle-max 25\n\n# 碎片整理期間,對於 List/Set/Hash/ZSet 類型元素一次 Scan 的數量\nactive-defrag-max-scan-fields 1000","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你需要結合 Redis 機器的負載情況,以及應用程序可接受的延遲範圍進行評估,合理調整碎片整理的參數,儘可能降低碎片整理期間對 Redis 的影響。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"網絡帶寬過載","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果以上產生性能問題的場景,你都規避掉了,而且 Redis 也穩定運行了很長時間,但在某個時間點之後開始,操作 Redis 突然開始變慢了,而且一直持續下去,這種情況又是什麼原因導致?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時你需要排查一下 Redis 機器的網絡帶寬是否過載,是否存在某個實例把整個機器的網路帶寬佔滿的情況。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"網絡帶寬過載的情況下,服務器在 TCP 層和網絡層就會出現數據包發送延遲、丟包等情況。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis 的高性能,除了操作內存之外,就在於網絡 IO 了,如果網絡 IO 存在瓶頸,那麼也會嚴重影響 Redis 的性能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果確實出現這種情況,你需要及時確認佔滿網絡帶寬 Redis 實例,如果屬於正常的業務訪問,那就需要及時擴容或遷移實例了,避免因爲這個實例流量過大,影響這個機器的其他實例。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"運維層面,你需要對 Redis 機器的各項指標增加監控,包括網絡流量,在網絡流量達到一定閾值時提前報警,及時確認和擴容。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"其他原因","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"好了,以上這些方面就是如何排查 Redis 延遲問題的思路和路徑。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了以上這些,還有一些比較小的點,你也需要注意一下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"1) 頻繁短連接","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你的業務應用,應該使用長連接操作 Redis,避免頻繁的短連接。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"頻繁的短連接會導致 Redis 大量時間耗費在連接的建立和釋放上,TCP 的三次握手和四次揮手同樣也會增加訪問延遲。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2) 運維監控","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前面我也提到了,要想提前預知 Redis 變慢的情況發生,必不可少的就是做好完善的監控。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"監控其實就是對採集 Redis 的各項運行時指標,通常的做法是監控程序定時採集 Redis 的 INFO 信息,然後根據 INFO 信息中的狀態數據做數據展示和報警。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏我需要提醒你的是,在寫一些監控腳本,或使用開源的監控組件時,也不能掉以輕心。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在寫監控腳本訪問 Redis 時,儘量採用長連接的方式採集狀態信息,避免頻繁短連接。同時,你還要注意控制訪問 Redis 的頻率,避免影響到業務請求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在使用一些開源的監控組件時,最好了解一下這些組件的實現原理,以及正確配置這些組件,防止出現監控組件發生 Bug,導致短時大量操作 Redis,影響 Redis 性能的情況發生。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們當時就發生過,DBA 在使用一些開源組件時,因爲配置和使用問題,導致監控程序頻繁地與 Redis 建立和斷開連接,導致 Redis 響應變慢。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"3)其它程序爭搶資源","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後需要提醒你的是,你的 Redis 機器最好專項專用,只用來部署 Redis 實例,不要部署其他應用程序,儘量給 Redis 提供一個相對「安靜」的環境,避免其它程序佔用 CPU、內存、磁盤資源,導致分配給 Redis 的資源不足而受到影響。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"好了,以上就是我總結的在使用 Redis 過程中,常見的可能導致延遲、甚至阻塞的問題場景,以及如何快速定位和分析這些問題,並且針對性地提供瞭解決方案。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏我也彙總成了思維導圖,方便你在排查 Redis 性能問題時,快速地去分析和定位。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f3/f3ce2d49fd3f146a4ea12511a9fbff48.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏再簡單總結一下,Redis 的性能問題,既涉及到了業務開發人員的使用方面,也涉及到了 DBA 的運維方面。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作爲業務開發人員,我們需要了解 Redis 的基本原理,例如各個命令執行的時間複雜度、數據過期策略、數據淘汰策略等,從而更合理地使用 Redis 命令,並且結合業務場景進行優化。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作爲 DBA 和運維人員,需要了解 Redis 運行機制,例如數據持久化、內存碎片整理、進程綁核配置。除此之外,還需要了解操作系統相關知識,例如寫時複製、內存大頁、Swap 機制等等。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同時,DBA 在部署 Redis 時,需要提前對進行容量規劃,預留足夠的機器資源,還要對 Redis 機器和實例做好完善的監控,這樣才能儘可能地保證 Redis 的穩定運行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"後記","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你能耐心地看到這裏,想必你肯定已經對 Redis 的性能調優有了很大的收穫。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你應該也發現了,Redis 的性能問題,涉及到的知識點非常廣,幾乎涵蓋了 CPU、內存、網絡、甚至磁盤的方方面面,同時,你還需要了解計算機的體系結構,以及操作系統的各種機制。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從資源使用角度來看,包含的知識點如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CPU 相關:使用複雜度過高命令、數據的持久化,都與耗費過多的 CPU 資源有關","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"內存相關:bigkey 內存的申請和釋放、數據過期、數據淘汰、碎片整理、內存大頁、內存寫時複製都與內存息息相關","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"磁盤相關:數據持久化、AOF 刷盤策略,也會受到磁盤的影響","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"網絡相關:短連接、實例流量過載、網絡流量過載,也會降低 Redis 性能","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"計算機系統:CPU 結構、內存分配,都屬於最基礎的計算機系統知識","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"操作系統:寫時複製、內存大頁、Swap、CPU 綁定,都屬於操作系統層面的知識","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"沒想到吧?Redis 爲了把性能做到極致,涉及到了這麼多項優化。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果這篇文章內容,你能吸收 90% 以上,說明你對 Redis 原理、計算機基礎、操作系統都已經有了較爲深刻的理解。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你能吸收 50% 左右,那你可以好好梳理一下,哪些方面是自己的知識盲區,這樣可以針對性地去學習。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你吸收的只在 30% 以下,那麼你可以先從 Redis 的基本原理出發,先了解 Redis 的各種機制,進而思考 Redis 爲了提高性能,爲什麼使用這些機制?這些機制又是利用了計算機和操作系統的哪些特性去做的?進而一步步地去擴充你的知識體系,這是一個非常高效的學習路徑。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於篇幅限制,關於 Redis 的很多細節無法全部展開,其實,這篇文章提到的每一個導致 Redis 性能問題的場景,如果展開來講,都可以寫出一篇文章出來。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如,關於 Redis 進程綁定 CPU,以及操作系統使用 Swap,其實這些還涉及到了非一致性內存訪問 NUMA 架構的影響,其中也有很多細節沒有展開來講。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這篇文章主要站在一個宏觀層面進行性能分析,如果你想了解更多細節,歡迎關注我的公衆號「水滴與銀彈」,我會在後續的文章中,帶你深度剖析 Redis 的運行原理,以及產生性能問題的更多細節。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"關注「水滴與銀彈」公衆號,第一時間獲取優質技術乾貨。7年資深後端研發,用簡單的方式把技術講清楚。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章