問題
del 和 unlink 有啥區(qū)別???為什么String類型刪除不會(huì)做異步刪除?
彬彬回答
DEL 和 UNLINK 都是同步的釋放 key 對象,區(qū)別是怎么釋放后面的 value 對象
DEL 每次都是同步釋放 value 部分,如果 value 很大,例如一個(gè) list 里很多元素,這會(huì)阻塞 Redis 工作線程。
為了規(guī)避這個(gè)問題,4.0 里引出了 UNLINK 命令,可以異步釋放 value 對象,放到一個(gè)子線程中。
這邊需要引出一個(gè)釋放的閾值,見后面解釋。
目前默認(rèn)的閾值是 64,例如只有一個(gè) list 里面含有超過 64 個(gè)元素,才會(huì)異步釋放,否則也是會(huì)同步釋放
不同的數(shù)據(jù)結(jié)構(gòu)的計(jì)算閾值的方式不一樣,不過大致遵循一個(gè)原則:就是要釋放多少塊內(nèi)存
即在小對象上使用 UNLINK 效果等同于 DEL,也是同步釋放,區(qū)別就是要多走幾個(gè)函數(shù)調(diào)用,例如判斷 list 里需要判斷列表的長度等
大 value 對象的釋放是異步的,放在一個(gè)子線程上,小對象之所以不異步釋放,是因?yàn)楫惒结尫?,主線程和子線程之間需要做一些同步操作(這是有代價(jià)的),然后小對象釋放,本身也很快就也不值得進(jìn)行異步釋放,內(nèi)存釋放也更及時(shí)。
即可能異步釋放,實(shí)際上會(huì)比同步釋放更慢,所以作者設(shè)置了個(gè) 64 的經(jīng)驗(yàn)值
所以如果是一個(gè)小對象,DEL 和 UNLINK 其實(shí)一樣;如果是一個(gè)大對象,UNLINK 會(huì)更加好。
所以大部分情況下都可以用 UNLINK 代替 DEL,而 Redis 其實(shí)也有個(gè)配置項(xiàng),可以控制將 DEL 默認(rèn)轉(zhuǎn)換為 UNLINK(實(shí)現(xiàn)上都是同一個(gè)函數(shù),只是入口 async 參數(shù)不同)
不過我們需要知道異步釋放的好處(不阻塞主線程)和它的壞處(需要進(jìn)行一些線程同步相關(guān)的操作,內(nèi)存釋放不及時(shí))。
至于說 string 為啥不異步釋放,主要是作者認(rèn)為它是一整塊內(nèi)存空間,計(jì)算閾值的時(shí)候 string 的結(jié)果固定是 1,那么就 <= 64,就是同步釋放。
在補(bǔ)充一點(diǎn),前面舉例是說的 list,底層是用的 quicklist,嚴(yán)格來說統(tǒng)計(jì)的是 quicklistNode 的節(jié)點(diǎn)數(shù)量,就不是列表元素?cái)?shù)量。
像 zset 那些如果用的 ziplist/listpack 編碼的話,這種計(jì)算出來的閾值是 1,就也不是元素?cái)?shù)量。如果是跳表編碼的話就是統(tǒng)計(jì)的元素?cái)?shù)量。
然后至于選擇的話,大部分情況可以無腦用 UNLINK,不過需要知道壞處。
例如對于每一次的 async delete,主線程給子線程提交任務(wù)時(shí)需要加鎖解鎖,bio 子線程消費(fèi)任務(wù)的時(shí)候也要加鎖解鎖,要做一些線程同步,還有線程上下文切換,這些都是可能會(huì)有的潛在的問題,如果小元素都異步釋放的話,的確代價(jià)可能會(huì)大,多線程做事情的確是會(huì)有這些麻煩。
可以多做壓測來驗(yàn)證環(huán)境里到底哪個(gè)好,不過大部分情況這些我們不用關(guān)系,只要寫代碼的時(shí)候有意識的注意大 key 的釋放就好。