華為技術專家深度解析Redis惰性刪除原理

      網友投稿 1302 2025-04-02

      Lazy Free會影響緩存替換嗎?


      redis緩存淘汰是為了在Redis server內存使用量超過閾值時,篩選一些冷數(shù)據(jù),從Redis server中刪除。我們在前兩節(jié)課,LRU和LFU在最后淘汰數(shù)據(jù)時,都會刪除被淘汰數(shù)據(jù)。

      但它們在刪除淘汰數(shù)據(jù)時,會根據(jù)如下配置項決定是否啟用Lazy Free(惰性刪除)

      惰性刪除,Redis 4.0后功能,使用后臺線程執(zhí)行刪除數(shù)據(jù)的任務,避免了刪除操作阻塞主線程。但后臺線程異步刪除數(shù)據(jù)能及時釋放內存嗎?它會影響到redis緩存的正常使用嗎?

      1 配置惰性刪除

      當Redis server希望啟動惰性刪除時,需在redis.conf設置惰性刪除相關配置項,包括如下場景:

      默認值都是no。所以,要在緩存淘汰時啟用,就要將lazyfree-lazy-eviction置yes。Redis server在啟動過程中進行配置參數(shù)初始化時,會根據(jù)redis.conf,設置全局變量server的lazyfree_lazy_eviction成員變量。

      若看到對server.lazyfree_lazy_eviction變量值進行條件判斷,那就是Redis根據(jù)lazyfree-lazy-eviction配置項,決定是否執(zhí)行惰性刪除。

      2 被淘汰數(shù)據(jù)的刪除過程

      華為技術專家深度解析Redis惰性刪除原理

      getMaxmemoryState負責執(zhí)行數(shù)據(jù)淘汰,篩選出被淘汰的鍵值對后,就要開始刪除被淘汰的數(shù)據(jù):

      為被淘汰的key創(chuàng)建一個SDS對象,然后調用propagateExpire:

      Redis server可能針對緩存淘汰場景啟用惰性刪除,propagateExpire會根據(jù)全局變量server.lazyfree_lazy_eviction決定刪除操作對應命令:

      lazyfree_lazy_eviction=1(啟用緩存淘汰時的惰性刪除),則刪除操作對應UNLINK命令

      否則,就是DEL命令

      因為這些命令經常使用,所以Redis為這些命令創(chuàng)建共享對象,即sharedObjectsStruct結構體,并用一個全局變量shared表示

      ![image-20211225010625978](/Users/apple/Library/Application Support/typora-user-images/image-20211225010625978.png)

      ![image-20211225011045661](/Users/apple/Library/Application Support/typora-user-images/image-20211225011045661.png)

      在該結構體中包含了指向共享對象的指針,這其中就包括了unlink和del命令對象。

      然后,propagateExpire在為刪除操作創(chuàng)建命令對象時,就使用了shared變量中的unlink或del對象:

      接著,propagateExpire會判斷Redis server是否啟用AOF日志:

      若啟用,則propagateExpire會調用feedAppendOnlyFile,把被淘汰key的刪除操作記錄到AOF文件,保證后續(xù)使用AOF文件進行Redis數(shù)據(jù)庫恢復時,可以和恢復前保持一致。通過實現(xiàn)。

      然后,propagateExpire調用propagate,把刪除操作同步給從節(jié)點,以保證主從節(jié)點的數(shù)據(jù)一致。propagate流程:

      接下來,performEvictions就會開始執(zhí)行刪除。

      performEvictions根據(jù)server是否啟用了惰性刪除,分別執(zhí)行:

      Case1:若server啟用惰性刪除,performEvictions調用dbAsyncDelete異步刪除

      Case2:若server未啟用惰性刪除,performEvictions調用dbSyncDelete同步刪除

      performEvictions在調用刪除函數(shù)前,都會調用zmalloc_used_memory計算當前使用內存量。然后,它在調用刪除函數(shù)后,會再次調用zmalloc_used_memory函數(shù)計算此時的內存使用量,并計算刪除操作導致的內存使用量差值,這個差值就是通過刪除操作而被釋放的內存量。

      performEvictions最后把這部分釋放的內存量和已釋放內存量相加,得到最新內存釋放量:

      所以performEvictions在選定被刪除的KV對后,可通過異步或同步操作來完成數(shù)據(jù)的實際刪除。那數(shù)據(jù)異步刪除和同步刪除到底如何執(zhí)行的?

      3 數(shù)據(jù)刪除操作

      刪除操作包含兩步:

      將被淘汰的KV對從哈希表剔除,這哈希表既可能是設置了過期key的哈希表,也可能是全局哈希表

      釋放被淘汰KV對所占用的內存空間

      若這倆操作一起做,就是同步刪除;只做1,而2由后臺線程執(zhí)行,就是異步刪除。

      Redis使用dictGenericDelete實現(xiàn)了這倆操作。

      **首先,dictGenericDelete函數(shù)會先在哈希表中查找要刪除的key。**它會計算被刪除key的哈希值,然后根據(jù)哈希值找到key所在的哈希桶。

      因為不同key的哈希值可能相同,而Redis的哈希表是采用了鏈式哈希(你可以回顧下第3講中介紹的鏈式哈希),所以即使我們根據(jù)一個key的哈希值,定位到了它所在的哈希桶,我們也仍然需要在這個哈希桶中去比對查找,這個key是否真的存在。

      也正是由于這個原因,dictGenericDelete函數(shù)緊接著就會在哈希桶中,進一步比對查找要刪除的key。如果找到了,它就先把這個key從哈希表中去除,也就是把這個key從哈希桶的鏈表中去除。

      然后,dictGenericDelete會根據(jù)入參nofree,決定是否實際釋放K和V的內存空間:

      dictGenericDelete根據(jù)nofree決定執(zhí)行同步or異步刪除。

      dictDelete V.S dictUnlink

      給dictGenericDelete傳遞的nofree參數(shù)值是0 or 1:

      nofree=0:同步刪除

      nofree=1,異步刪除

      好了,到這里,我們就了解了同步刪除和異步刪除的基本代碼實現(xiàn)。下面我們就再來看下,在剛才介紹的performEvictions函數(shù)中,它在刪除鍵值對時,所調用的dbAsyncDelete和dbSyncDelete這兩個函數(shù),是如何使用dictDelete和dictUnlink來實際刪除被淘汰數(shù)據(jù)的。

      基于異步刪除的數(shù)據(jù)淘汰

      由dbAsyncDelete執(zhí)行:

      調用dictDelete

      調用dictUnlink:

      被淘汰的KV對只是在全局哈希表中被移除,其占用內存空間還是沒有實際釋放。所以dbAsyncDelete會調用lazyfreeGetFreeEffort,計算釋放被淘汰KV對內存空間的開銷:

      若開銷較小,dbAsyncDelete直接在主I/O線程中進行同步刪除

      否則,dbAsyncDelete創(chuàng)建惰性刪除任務,并交給后臺線程完成

      雖dbAsyncDelete說是執(zhí)行惰性刪除,但在實際執(zhí)行過程中,會使用lazyfreeGetFreeEffort評估刪除開銷。

      lazyfreeGetFreeEffort根據(jù)要刪除的KV對的類型計算刪除開銷:

      若KV對類型屬于List、Hash、Set和Sorted Set這四種集合類型中的一種,且未使用緊湊型內存結構,則該KV對的刪除開銷就等于集合中的元素個數(shù)

      否則,刪除開銷等于1

      舉個例子,如下代碼就展示了azyfreeGetFreeEffort計算List和Set類型鍵值對的刪除開銷:KV對是Set類型,同時它是使用哈希表結構而不是整數(shù)集合來保存數(shù)據(jù)的話,那么它的刪除開銷就是Set中的元素個數(shù)。

      這樣,當dbAsyncDelete通過lazyfreeGetFreeEffort計得被淘汰KV對的刪除開銷后:

      把刪除開銷和宏定義LAZYFREE_THRESHOLD(默認64)比較。

      當被淘汰KV對為包含超過64個元素的集合類型時,dbAsyncDelete才會調用bioCreateBackgroundJob實際創(chuàng)建后臺任務執(zhí)行惰性刪除。

      若被淘汰KV對不是集合類型或是集合類型但包含元素個數(shù)≤64個,則dbAsyncDelete調用dictFreeUnlinkedEntry釋放KV對所占的內存空間。

      dbAsyncDelete使用后臺任務或主IO線程釋放內存空間:

      基于異步刪除的數(shù)據(jù)淘汰過程,實際上會根據(jù)要刪除的KV對包含的元素個數(shù),決定是使用后臺線程還是主線程執(zhí)行刪除操作。

      主線程如何知道后臺線程釋放的內存空間,已滿足待釋放空間的大小?performEvictions在調用dbAsyncDelete或dbSyncDelete前后,都會統(tǒng)計已使用內存量,并計算調用刪除函數(shù)前后的差值,這就能知曉已釋放的內存空間大小。

      此外,performEvictions在調用dbAsyncDelete后,會再主動檢測當前內存使用量,是否已滿足最大內存容量要求。一旦滿足,performEvictions就會停止淘汰數(shù)據(jù)的執(zhí)行流程。

      可以。主線程決定淘汰這個 key 后,會先把這個 key 從「全局哈希表」剔除,然后評估釋放內存代價,如符合條件,則丟到「后臺線程」執(zhí)行「釋放內存」操作。

      之后就可繼續(xù)處理客戶端請求,盡管后臺線程還未完成釋放內存,但因 key 已被全局哈希表剔除,所以主線程已查詢不到該 key,對客戶端無影響。

      同步刪除的數(shù)據(jù)淘汰

      dbSyncDelete實現(xiàn)。

      首先,調用dictDelete,在過期key的哈希表中刪除被淘汰的KV對

      再次調用dictDelete,在全局哈希表中刪除被淘汰的KV對

      dictDelete調用dictGenericDelete同步釋放KV對的內存空間時,最終分別調用dictFreeKey、dictFreeVal和zfree釋放K、V和KV對所對應的哈希項這三者所占內存空間。

      它們根據(jù)操作的哈希表類型,調用相應valDestructor和keyDestructor函數(shù)釋放內存:

      為方便能找到最終進行內存釋放操作的函數(shù),以全局哈希表為例,看當操作全局哈希表時,KV對的dictFreeVal和dictFreeKey兩個宏定義對應的函數(shù)。

      全局哈希表是在initServer中創(chuàng)建:

      dbDictType是個dictType類型結構體:

      dbDictType作為全局哈希表,保存:

      SDS類型的key

      多種數(shù)據(jù)類型的value

      所以,dbDictType類型哈希表的K和V釋放函數(shù),分別是dictSdsDestructor和dictObjectDestructor:

      dictSdsDestructor直接調用sdsfree,釋放SDS字符串占用的內存空間。dictObjectDestructor會調用decrRefCount,執(zhí)行釋放操作:

      decrRefCount會判斷待釋放對象的引用計數(shù)。:

      只有當引用計數(shù)為1,才會根據(jù)待釋放對象的類型,調用具體類型的釋放函數(shù)來釋放內存空間

      否則,decrRefCount只是把待釋放對象的引用計數(shù)減1

      若待釋放對象的引用計數(shù)為1,且String類型,則decrRefCount調用freeStringObject執(zhí)行最終的內存釋放操作。

      若對象是List類型,則decrRefCount調用freeListObject最終釋放內存:

      基于同步刪除的數(shù)據(jù)淘汰過程,就是通過dictDelete將被淘汰KV對從全局哈希表移除,并通過dictFreeKey、dictFreeVal和zfree釋放內存空間。

      釋放v空間的函數(shù)是decrRefCount,根據(jù)V的引用計數(shù)和類型,最終調用不同數(shù)據(jù)類型的釋放函數(shù)來完成內存空間釋放。

      基于異步刪除的數(shù)據(jù)淘汰,它通過后臺線程執(zhí)行的函數(shù)是lazyfreeFreeObjectFromBioThread函數(shù),該函數(shù)實際上也是調用了decrRefCount釋放內存空間。

      總結

      Redis 4.0后提供惰性刪除功能,所以Redis緩存淘汰數(shù)據(jù)時,就會根據(jù)是否啟用惰性刪除,決定是執(zhí)行同步刪除還是異步的惰性刪除。

      同步刪除還是異步的惰性刪除,都會先把被淘汰的KV對從哈希表中移除。然后,同步刪除就會緊接著調用dictFreeKey、dictFreeVal和zfree分別釋放key、value和鍵值對哈希項的內存空間。而異步的惰性刪除,則是把空間釋放任務交給了后臺線程完成。

      雖惰性刪除是由后臺線程異步完成,但后臺線程啟動后會監(jiān)聽惰性刪除的任務隊列,一旦有惰性刪除任務,后臺線程就會執(zhí)行并釋放內存空間。所以,從淘汰數(shù)據(jù)釋放內存空間的角度來說,惰性刪除并不影響緩存淘汰時的空間釋放要求。

      后臺線程需通過同步機制獲取任務,這過程會引入一些額外時間開銷,會導致內存釋放不像同步刪除那樣非常及時。這也是Redis在被淘汰數(shù)據(jù)是小集合(元素不超過64個)時,仍使用主線程進行內存釋放的考慮因素。

      Redis 專家

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:每次打開或者保存文檔的時候時間太久(文檔保存速度太慢)
      下一篇:包含生產管理系統(tǒng)視頻的詞條
      相關文章
      亚洲精品国产字幕久久不卡| 亚洲成a∨人片在无码2023| 国产亚洲精品影视在线| 亚洲制服丝袜一区二区三区| 久久亚洲AV无码精品色午夜麻豆 | 亚洲啪啪综合AV一区| 亚洲伊人久久精品影院| 中文字幕久久亚洲一区| 久久亚洲精品无码观看不卡| 亚洲人成无码网WWW| 国产精品亚洲精品日韩已方| 亚洲人成无码网WWW| 亚洲综合图色40p| 亚洲精品乱码久久久久久蜜桃不卡| 亚洲人成影院在线无码按摩店| 亚洲乱色熟女一区二区三区丝袜| 国产偷v国产偷v亚洲高清| 亚洲av无码国产精品色午夜字幕 | 亚洲一本一道一区二区三区| 国产99在线|亚洲| 亚洲人成网站在线在线观看| 亚洲AV性色在线观看| 另类图片亚洲校园小说区| 亚洲成网777777国产精品| 国产亚洲日韩一区二区三区| 亚洲综合无码AV一区二区| 亚洲成A∨人片在线观看不卡| 亚洲av无码无在线观看红杏| 亚洲综合精品香蕉久久网97| 亚洲精品综合久久中文字幕| 99久久婷婷国产综合亚洲| 亚洲国产高清国产拍精品| 日韩亚洲国产综合久久久| 久久久久亚洲爆乳少妇无| 亚洲精品美女久久久久99| 久久精品国产亚洲av影院| 亚洲人成片在线观看| 亚洲第一成年免费网站| 亚洲精品国产福利一二区| 亚洲精品国偷自产在线| 亚洲精品无码久久毛片波多野吉衣|