Redis4.0新增了非常實用的lazy free特性,從根本上解決Big Key(主要指定元素較多集合類型Key)删除的風險。筆者在redis運維中也遇過幾次Big Key删除帶來可用性和性能故障。
本文分為以下幾節說明redis lazy free:
lazy free的定義
我們為什麼需要lazy free
lazy free的使用
lazy free的監控
lazy free實作的簡單分析
lazy free可譯為惰性删除或延遲釋放;當删除鍵的時候,redis提供異步延時釋放key記憶體的功能,把key釋放操作放在bio(Background I/O)單獨的子線程進行中,減少删除big key對redis主線程的阻塞。有效地避免删除big key帶來的性能和可用性問題。
Redis是single-thread程式(除少量的bio任務),當運作一個耗時較大的請求時,會導緻所有請求排隊等待redis不能響應其他請求,引起性能問題,甚至叢集發生故障切換。而redis删除大的集合鍵時,就屬于這類比較耗時的請求。通過測試來看,删除一個100萬個元素的集合鍵,耗時約1000ms左右。以下測試,删除一個100萬個字段的hash鍵,耗時1360ms;處理此DEL請求期間,其他請求完全被阻塞。
測試估算,可參考;和硬體環境、Redis版本和負載等因素有關
redis4.0有lazy free功能後,這類主動或被動的删除big key時,和一個O(1)指令的耗時一樣,亞毫秒級傳回; 把真正釋放redis元素耗時動作交由bio背景任務執行。在redis4.0前,沒有lazy free功能;DBA隻能通過取巧的方法,類似scan big key,每次删除100個元素;但在面對“被動”删除鍵的場景,這種取巧的删除就無能為力。
例如:我們生産Redis Cluster大叢集,業務緩慢地寫入一個帶有TTL的2000多萬個字段的Hash鍵,當這個鍵過期時,redis開始被動清理它時,導緻redis被阻塞20多秒,目前分片主節點因20多秒不能處理請求,并發生主庫故障切換。
lazy free的使用分為2類:第一類是與DEL指令對應的主動删除,第二類是過期key删除、maxmemory key驅逐淘汰删除。
UNLINK指令是與DEL一樣删除key功能的lazy free實作。唯一不同時,UNLINK在删除集合類鍵時,如果集合鍵的元素個數大于64個(詳細後文),會把真正的記憶體釋放操作,給單獨的bio來操作。示例如下:使用UNLINK指令删除一個大鍵mylist, 它包含200萬個元素,但用時隻有0.03毫秒
注意:DEL指令,還是阻塞的删除操作。
通過對FLUSHALL/FLUSHDB添加ASYNC異步清理選項,redis在清理整個執行個體或DB時,操作都是異步的。
lazy free應用于被動删除中,目前有4種場景,每種場景對應一個配置參數; 預設都是關閉。
注意:從測試來看lazy free回收記憶體效率還是比較高的; 但在生産環境請結合實際情況,開啟被動删除的lazy free 觀察redis記憶體使用情況。
針對redis記憶體使用達到maxmeory,并設定有淘汰政策時;在被動淘汰鍵時,是否采用lazy free機制;
因為此場景開啟lazy free, 可能使用淘汰鍵的記憶體釋放不及時,導緻redis記憶體超用,超過maxmemory的限制。此場景使用時,請結合業務測試。
針對設定有TTL的鍵,達到過期後,被redis清理删除時是否采用lazy free機制;
此場景建議開啟,因TTL本身是自适應調整的速度。
針對有些指令在處理已存在的鍵時,會帶有一個隐式的DEL鍵的操作。如rename指令,當目标鍵已存在,redis會先删除目标鍵,如果這些目标鍵是一個big key,那就會引入阻塞删除的性能問題。 此參數設定就是解決這類問題,建議可開啟。
針對slave進行全量資料同步,slave在加載master的RDB檔案前,會運作flushall來清理自己的資料場景,
參數設定決定是否采用異常flush機制。如果記憶體變動不大,建議可開啟。可減少全量同步耗時,進而減少主庫因輸出緩沖區爆漲引起的記憶體使用增長。
lazy free能監控的資料名額,隻有一個值:lazyfree_pending_objects,表示redis執行lazy free操作,在等待被實際回收内容的鍵個數。并不能展現單個大鍵的元素個數或等待lazy free回收的記憶體大小。
是以此值有一定參考值,可監測redis lazy free的效率或堆積鍵數量; 比如在flushall async場景下會有少量的堆積。
antirez為實作lazy free功能,對很多底層結構和關鍵函數都做了修改;該小節隻介紹lazy free的功能實作邏輯;代碼主要在源檔案lazyfree.c和bio.c中。
unlink指令入口函數unlinkCommand()和del調用相同函數delGenericCommand()進行删除KEY操作,使用lazy辨別是否為lazyfree調用。如果是lazyfree,則調用dbAsyncDelete()函數。
但并非每次unlink指令就一定啟用lazy free,redis會先判斷釋放KEY的代價(cost),當cost大于LAZYFREE_THRESHOLD才進行lazy free.
釋放key代價計算函數lazyfreeGetFreeEffort(),集合類型鍵,且滿足對應編碼,cost就是集合鍵的元數個數,否則cost就是1.
舉例:
1一個包含100元素的list key, 它的free cost就是100
2 一個512MB的string key, 它的free cost是1
是以可以看出,redis的lazy free的cost計算主要時間複雜度相關。
lazyfreeGetFreeEffort()函數代碼
dbAsyncDelete()函數的部分代碼
在bio中實際調用lazyfreeFreeObjectFromBioThread()函數釋放key
當flushall/flushdb帶上async,函數emptyDb()調用emptyDbAsync()來進行整個執行個體或DB的lazy free邏輯處理。
emptyDbAsync處理邏輯如下:
在bio中實際調用lazyfreeFreeDatabaseFromBioThread函數釋放db
被動删除4個場景,redis在每個場景調用時,都會判斷對應的參數是否開啟,如果參數開啟,則調用以上對應的lazy free函數處理邏輯實作。
因為Redis是單個主線程處理,antirez一直強調”Lazy Redis is better Redis”。而lazy free的本質就是把某些cost較高的(主要時間複制度,占用主線程cpu時間片)較高删除操作,從redis主線程剝離,讓bio子線程來處理,極大地減少主線阻塞時間。進而減少删除導緻性能和穩定性問題。
原文釋出時間為:2017-11-12
本文作者:RogerZhuo