亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

 

1、場景介紹

假設(shè)現(xiàn)在我們要開發(fā)一個圖片存儲系統(tǒng),要求這個系統(tǒng)能夠根據(jù)圖片 ID 快速查找到圖片存儲對象 ID。圖片 ID 和圖片存儲對象 ID 的樣例數(shù)據(jù)如下:

photo_id: 1101000060
photo_obj_id: 3302000080

在這種場景下,圖片 ID 和圖片存儲對象 ID 剛好是一對一的關(guān)系,是典型的“鍵 - 單值”模式,redis 的 String 類型提供了“一個鍵對應(yīng)一個值的數(shù)據(jù)”的保存形式,在這種場景下剛好適用。

確定使用 String 類型后,接下來我們通過實戰(zhàn),來看看它的內(nèi)存使用情況。首先通過下面命令連接上 Redis。

本文我使用的 Redis Server 及下文源碼都是 6.2.4 版本。

redis-cli -h 127.0.0.1 -p 6379

然后執(zhí)行下面的命令查看 Redis 的初始內(nèi)存使用情況。

127.0.0.1:6379> info memory
# Memory
used_memory:871840

接著插入 10 條數(shù)據(jù):

10.118.32.170:0> set 1101000060 3302000080
10.118.32.170:0> set 1101000061 3302000081
10.118.32.170:0> set 1101000062 3302000082
10.118.32.170:0> set 1101000063 3302000083
10.118.32.170:0> set 1101000064 3302000084
10.118.32.170:0> set 1101000065 3302000085
10.118.32.170:0> set 1101000066 3302000086
10.118.32.170:0> set 1101000067 3302000087
10.118.32.170:0> set 1101000068 3302000088
10.118.32.170:0> set 1101000069 3302000089

再次查看內(nèi)存:

127.0.0.1:6379> info memory
# Memory
used_memory:872528

可以看到,存儲 10 個圖片,內(nèi)存使用了 688 個字節(jié)。一個圖片 ID 和圖片存儲對象 ID 的記錄平均用了 68 字節(jié)。

但問題是,一組圖片 ID 及其存儲對象 ID 的記錄,實際只需要 16 字節(jié)就可以了。圖片 ID 和圖片存儲對象 ID 都是 10 位數(shù),而 8 字節(jié)的 Long 類型最大可以表示 2 的 64 次方的數(shù)值,肯定可以表示 10 位數(shù)。這樣算下來只需 16 字節(jié)就可以了,為什么 String 類型卻用了 68 字節(jié)呢?

為了一探究竟,我們不得不從 String 類型的底層實現(xiàn)扒起。

2、String 類型的底層實現(xiàn)

當(dāng)你保存的數(shù)據(jù)中包含字符時,String 類型就會用簡單動態(tài)字符串(Simple Dynamic String,SDS)結(jié)構(gòu)體來保存。

2.1 SDS

SDS 的結(jié)構(gòu)定義在sds.h文件中,在 Redis 3.2 版本之后,SDS 由一種數(shù)據(jù)結(jié)構(gòu)變成了 5 種數(shù)據(jù)結(jié)構(gòu)。

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) hisdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) hisdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) hisdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) hisdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) hisdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

這 5 種數(shù)據(jù)結(jié)構(gòu)依次存儲不同長度的內(nèi)容,Redis 會根據(jù) SDS 存儲的內(nèi)容長度來選擇不同的結(jié)構(gòu)。

  • • sdshdr5:存儲大小為 32 字節(jié)(2 的 5 次方),只被應(yīng)用在了 Redis 中的 key 中。
  • • sdshdr8:存儲大小為 256 字節(jié)(2 的 8 次方)。
  • • sdshdr16:存儲大小為 64KB(2 的 16 次方)。
  • • sdshdr32:存儲大小為 4GB(2 的 32 次方)。
  • • sdshdr64:存儲大小為 2 的 64 次方字節(jié)。

以 sdshdr8 為例。

 

  • • buf:字節(jié)數(shù)組,保存實際數(shù)據(jù)。為了表示字節(jié)數(shù)組的結(jié)束,Redis 會自動在數(shù)組最后加一個'',這就會額外占用 1 個字節(jié)的開銷。
  • • len:占 4 個字節(jié),表示 buf 的已用長度,不包括''。
  • • alloc:也占 4 個字節(jié),表示 buf 的實際分配長度,不包括''。
  • • flags:占 1 個字節(jié),標(biāo)記當(dāng)前字節(jié)數(shù)組的屬性,是sdshdr8還是sdshdr16等。(flags 值的定義可以看下面代碼)

在源碼sds.h中,flags 值定義如下:

#define HI_SDS_TYPE_5  0 
#define HI_SDS_TYPE_8  1
#define HI_SDS_TYPE_16 2
#define HI_SDS_TYPE_32 3
#define HI_SDS_TYPE_64 4

2.2 RedisObject

因為 Redis 的數(shù)據(jù)類型有很多,而且,不同數(shù)據(jù)類型都有些相同的元數(shù)據(jù)要記錄,所以,值對象并不是直接存儲,而是被包裝成redisObject對象,它的定義如下。

typedef struct redisObject {
    unsigned type:4;//對象類型(4位=0.5字節(jié))
    unsigned encoding:4;//編碼(4位=0.5字節(jié))
    unsigned lru:LRU_BITS;//記錄對象最后一次被應(yīng)用程序訪問的時間(24位=3字節(jié))
    int refcount;//引用計數(shù)。等于0時表示可以被垃圾回收(32位=4字節(jié))
    void *ptr;//指向底層實際的數(shù)據(jù)存儲結(jié)構(gòu),如:sds等(8字節(jié))
} robj;

下面可以幫助我們理解:

 

為了節(jié)省內(nèi)存空間,Redis 還做了一些優(yōu)化。

當(dāng)保存的是 Long 類型整數(shù)時,RedisObject 中的指針就直接賦值為整數(shù)數(shù)據(jù)了,這樣就不用額外的指針再指向整數(shù)了。這種保存方式通常也叫作 int 編碼方式。

當(dāng)保存的是字符串?dāng)?shù)據(jù),并且字符串小于等于 44 字節(jié)時,RedisObject 中的元數(shù)據(jù)、指針和 SDS 是一塊連續(xù)的內(nèi)存區(qū)域,這樣就可以避免內(nèi)存碎片。這種布局方式也被稱為 embstr 編碼方式。

當(dāng)字符串大于 44 字節(jié)時,SDS 的數(shù)據(jù)量就開始變多了,Redis 就不再把 SDS 和 RedisObject 布局在一起了,而是會給 SDS 分配獨(dú)立的空間,并用指針指向 SDS 結(jié)構(gòu)。這種布局方式被稱為 raw 編碼模式。

 

使用 OBJECT ENCODING 命令可以查看一個數(shù)據(jù)庫鍵的值對象的編碼:

127.0.0.1:6379> SET msg "hello world"
OK
127.0.0.1:6379> OBJECT ENCODING msg
"embstr"
127.0.0.1:6379> SET story "long long long ago..."
OK
127.0.0.1:6379> OBJECT ENCODING story
"raw"
127.0.0.1:6379> SADD numbers 1 3 5
(integer) 3
127.0.0.1:6379> OBJECT ENCODING numbers
"intset"
127.0.0.1:6379> SADD numbers "seven"
(integer) 1
127.0.0.1:6379> OBJECT ENCODING numbers
"hashtable"

注意這個命令SET story "long long long ago...",省略號指的是省略了很多字符。

知道了 SDS 和 RedisObject 額外元數(shù)據(jù)開銷,現(xiàn)在,我們就可以計算 String 類型的內(nèi)存使用量了。

圖片存儲對象 ID 是 Long 類型整數(shù),所以可以直接用 int 編碼的 RedisObject 保存。每個 int 編碼的 RedisObject 元數(shù)據(jù)部分占 8 字節(jié),指針部分被直接賦值為 8 字節(jié)的整數(shù)了。圖片 ID 使用 sdshdr5 數(shù)據(jù)結(jié)構(gòu)來保存,會為 10 位的圖片 ID 分配 16 個字節(jié),結(jié)束符 '' 占 1 個字節(jié)。

 

共占用 34 個字節(jié)。與上文所說的一個圖片 ID 和圖片存儲對象 ID 的記錄平均用了 68 字節(jié)相差有點大啊,另外的開銷去哪兒了?

2.3 全局哈希表

為了實現(xiàn)從鍵到值的快速訪問,Redis 使用了一個哈希表來保存所有鍵值對。因為這個哈希表保存了所有的鍵值對,所以,也稱為全局哈希表。哈希表的每一項是一個 dictEntry 的結(jié)構(gòu)體,用來指向一個鍵值對。dictEntry 結(jié)構(gòu)中有三個 8 字節(jié)的指針,分別指向 key、value 以及下一個 dictEntry,三個指針共 24 字節(jié),如下圖所示:

 

jemalloc 在分配內(nèi)存時,會分配一個最接近 2 的 N 次方的數(shù)值。舉個例子。如果你申請 6 字節(jié)空間,jemalloc 實際會分配 2 的 4 次方即 8 字節(jié)空間;如果你申請 24 字節(jié)空間,jemalloc 則會分配 32 字節(jié)。

最終我們分析出來的內(nèi)存開銷,為 66 字節(jié),比較接近上文場景中的平均值 68 了。

最后

既然 String 類型這么占內(nèi)存,那么你有好的方案來節(jié)省內(nèi)存嗎?

這篇文章內(nèi)容我準(zhǔn)備了一周,如果對你有幫助,可以點個贊嗎?你的點贊會讓作者興奮得一晚上睡不著覺。

對后面的內(nèi)容感興趣,也可以關(guān)注公眾號「楊同學(xué)technotes」,感謝支持!

參考資料

  • • 文中的一些命令,參考菜鳥教程:https://www.runoob.com/redis/redis-tutorial.html
  • • Redis 的 key 也是 SDS 類型的,參考:https://www.cnblogs.com/lonely-wolf/p/14261486.html
  • • SDS 的定義,參考:https://juejin.cn/post/6844903936520880135#heading-6
  • • 文章大綱,參考極客時間《Redis核心技術(shù)與實戰(zhàn)》
  • • 《Redis設(shè)計與實現(xiàn)》

分享到:
標(biāo)簽:Redis
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定