Redis對(duì)象共享池,性能優(yōu)化小細(xì)節(jié)
如果你仔細(xì)研究過(guò) Redis 中各種實(shí)現(xiàn)細(xì)節(jié),你會(huì)發(fā)現(xiàn)為了性能,Redis 真的是不遺余力。
作為一種高性能的鍵值存儲(chǔ)系統(tǒng),Redis 廣泛用于緩存、會(huì)話管理、消息隊(duì)列等多種場(chǎng)景。
為了提高 Redis 在處理大量數(shù)據(jù)時(shí)的性能和效率,Redis 設(shè)計(jì)并實(shí)現(xiàn)了對(duì)象共享池(Shared Object Pool)這一內(nèi)部機(jī)制。
那么接下來(lái)松哥就和大家詳細(xì)說(shuō)一說(shuō) Redis 中的對(duì)象共享池。
一 設(shè)計(jì)目的
Redis 的對(duì)象共享池主要用于復(fù)用一些常用的數(shù)據(jù)對(duì)象,以減少內(nèi)存的開(kāi)銷(xiāo)。
在 Redis 中,一些常用的數(shù)據(jù)對(duì)象,主要是小整數(shù)(如 0 到 9999)等,是不會(huì)被改變的,因此可以安全地共享使用而無(wú)需重復(fù)創(chuàng)建。
例如你設(shè)置 set k1 99 和 set k2 99,這時(shí) k1 和 k2 其實(shí)指向的是同一個(gè)對(duì)象。
通過(guò)共享這些對(duì)象,Redis 能夠顯著降低內(nèi)存的使用量,并減少對(duì)象的創(chuàng)建和銷(xiāo)毀時(shí)間,從而提升整體性能。
二 工作原理
在 Redis 服務(wù)器啟動(dòng)時(shí),會(huì)預(yù)先創(chuàng)建并存儲(chǔ)一些常用的對(duì)象到一個(gè)全局的哈希表中,這個(gè)哈希表就是對(duì)象共享池。
當(dāng) Redis 需要處理一個(gè)鍵值對(duì)時(shí),會(huì)首先檢查這個(gè)鍵值對(duì)中的值是否已經(jīng)在對(duì)象共享池中。如果已存在,Redis 將直接引用該對(duì)象,而不是創(chuàng)建一個(gè)新的對(duì)象。
三 支持的對(duì)象類(lèi)型
目前主要是支持小整型,也就是 0~9999 之間的整數(shù),浮點(diǎn)型數(shù)據(jù)不支持。
四 應(yīng)用場(chǎng)景
對(duì)象共享池在多種場(chǎng)景下都能顯著提升 Redis 的性能和效率,特別是在處理大量重復(fù)數(shù)據(jù)時(shí)。例如,在 Web 應(yīng)用中,許多緩存的鍵值對(duì)可能包含相同的值,通過(guò)對(duì)象共享池,這些值可以被多個(gè)鍵值對(duì)共享,從而節(jié)省大量?jī)?nèi)存。
注意事項(xiàng)
- 只讀性:對(duì)象共享池中的對(duì)象是只讀的,不可修改。如果應(yīng)用程序需要修改這些對(duì)象,Redis 會(huì)將其復(fù)制并創(chuàng)建一個(gè)新的對(duì)象進(jìn)行操作。因此,在使用共享對(duì)象時(shí),需要注意對(duì)象的可修改性。
- 內(nèi)存策略:當(dāng) Redis 設(shè)置了最大內(nèi)存值(maxmemory)并啟用了 LRU(最近最少使用)等相關(guān)淘汰策略時(shí),對(duì)象共享池可能會(huì)被禁用。這是因?yàn)樵趦?nèi)存緊張的情況下,共享對(duì)象可能不再是最優(yōu)選擇。
五 實(shí)際案例
為了通過(guò)實(shí)際案例證明 Redis 中對(duì)象共享池的存在,我們可以結(jié)合 Redis 的內(nèi)部機(jī)制和一些實(shí)際操作來(lái)進(jìn)行分析。雖然 Redis 的官方文檔沒(méi)有直接提及“對(duì)象共享池”這一術(shù)語(yǔ),但我們可以從 Redis 如何處理整數(shù)對(duì)象的共享中看到其背后的共享機(jī)制。
假設(shè)我們有一個(gè) Redis 服務(wù)器,它用于存儲(chǔ)和訪問(wèn)大量的鍵值對(duì)。在這些鍵值對(duì)中,有一部分鍵對(duì)應(yīng)的值是常見(jiàn)的小整數(shù)。
我們來(lái)執(zhí)行以下命令:
SET k1 1
OBJECT REFCOUNT k1
SET k2 1
OBJECT REFCOUNT k1
圖片
這里我要跟大家解釋下。
OBJECT REFCOUNT 命令理論上可以查看某一個(gè) key 對(duì)應(yīng)的 value 被引用的次數(shù)。
所以我們期望第一次執(zhí)行 OBJECT REFCOUNT k1 的時(shí)候返回 1,第二次執(zhí)行 OBJECT REFCOUNT k1 的時(shí)候返回 2,但是實(shí)際上卻并非如此,每次都是返回 2^31-1。
雖然這里并沒(méi)有返回我們想要的值,但是大家可以看到,OBJECT REFCOUNT k1 返回的值確實(shí)和 value 為字符串的 key 的返回值是不同的。
松哥來(lái)解釋下原因。
通過(guò)分析 Redis 源碼,松哥發(fā)現(xiàn)新版本的 redis 中 OBJ_SHARED_INTEGERS 變量定義了共享整數(shù) 10000,并且定義不被銷(xiāo)毀的全局對(duì)象的引用數(shù)量 OBJ_SHARED_REFCOUNT 為 INT_MAX,INT_MAX = 2^31 - 1 =2147483647。
源碼位置在:https://github.com/redis/redis/blob/unstable/src/server.h#L903。
圖片
并且從源碼中可以看到當(dāng)把一個(gè)對(duì)象設(shè)置為共享時(shí)候就會(huì)把 refcount 設(shè)置為 INT_MAX。
源碼位置在:https://github.com/redis/redis/blob/unstable/src/object.c#L56。
圖片
從這里就能看出,如果某個(gè)對(duì)象的引用次數(shù)是 2^31-1,那么就說(shuō)明這個(gè)對(duì)象是一個(gè)引用對(duì)象。
Redis 實(shí)戰(zhàn)
Redis 博大精深,然而很多時(shí)候我們說(shuō)到 Redis,卻只知道緩存或者分布式鎖,面試的時(shí)候也只能從這兩個(gè)角度去準(zhǔn)備。
但是在實(shí)際面試中,Redis 這塊能夠發(fā)揮的地方可太多了:
- Redis 中 String 類(lèi)型使用了什么樣的數(shù)據(jù)結(jié)構(gòu)?
- 為什么每種數(shù)據(jù)類(lèi)型幾乎都設(shè)計(jì)了兩種以上的數(shù)據(jù)結(jié)構(gòu)?
- 為什么要延遲雙刪?原因是什么
- RedLock 解決了什么問(wèn)題,為什么現(xiàn)在又被廢棄了?現(xiàn)在用什么?
- watchdog 什么情況下會(huì)失效?
- Redis 掛了怎么辦?
- 如何實(shí)現(xiàn)百萬(wàn)級(jí)排行榜?
- 。。。
還有很多,我就不一一列舉了。