天天看點

Redis4.0新特性(二)-Lazy Free

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

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

繼續閱讀