RedisのString型は元々このようなメモリを占有します

RedisのString型は元々このようなメモリを占有します

Long 型を保存すると大量のメモリが消費されますが、Redis のメモリ オーバーヘッドはどこに費やされるのでしょうか?

1. シーン紹介

ここで、画像 ID に従って画像ストレージ オブジェクト ID を迅速に見つけることができる必要がある画像ストレージ システムを開発するとします。画像IDと画像格納オブジェクトIDのサンプルデータは以下のとおりです。

photo_id: 1101000060
photo_obj_id: 3302000080

このシナリオでは、ピクチャ ID とピクチャ ストレージ オブジェクト ID がたまたま 1 対 1 の関係にあり、これは典型的な「キー単一値」モデルです。Redis の String 型は、「データを含むデータ」の格納形式を提供します。 1 つのキーが 1 つの値に対応します。これは、このシナリオに最適です。

String型を使うことに決めたら、実際の戦闘を通してメモリ使用量を見てみましょう。まず次のコマンドで Redis に接続します。

この記事で使用した Redis サーバーと以下のソース コードはどちらもバージョン 6.2.4 です。

redis-cli -h 127.0.0.1 -p 6379

次に、次のコマンドを実行して、Redis の初期メモリ使用量を表示します。

127.0.0.1:6379> info memory
# Memory
used_memory:871840

次に、10 個のデータを挿入します。

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

メモリを再度確認します。

127.0.0.1:6379> info memory
# Memory
used_memory:872528

10 枚の写真を保存するために 688 バイトのメモリが使用されていることがわかります。画像 ID および画像ストレージ オブジェクト ID レコードは平均 68 バイトを使用します。

しかし、問題は、ピクチャ ID とそのストレージ オブジェクト ID のセットのレコードが実際には 16 バイトしか必要としないことです。ピクチャID、ピクチャ格納オブジェクトIDはともに10桁であり、8バイトのLong型で2の64乗の最大値を表現でき、必ず10桁を表現できる。このように、必要なバイト数は 16 バイトだけですが、なぜ String 型は 68 バイトを使用するのでしょうか?

それを確認するには、基になる String 型の実装から始める必要があります。

2. String 型の基礎となる実装

保存するデータに文字が含まれる場合、String 型は Simple Dynamic String (SDS) 構造で保存されます。

2.1 SDS

SDS の構造はsds.hファイルで定義されていますが、Redis バージョン 3.2 以降、SDS は 1 つのデータ構造から 5 つのデータ構造に変更されました。

/* 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 つのデータ構造には、異なる長さのコンテンツが順番に格納されており、Redis は SDS に格納されているコンテンツの長さに応じて異なる構造を選択します。

  • sdshdr5: ストレージ サイズは 32 バイト (2 の 5 乗) で、Redis のキーでのみ使用されます。
  • sdshdr8: ストレージサイズは 256 バイト (2 の 8 乗) です。
  • sdshdr16:ストレージサイズは64KB(2の16乗)です。
  • sdshdr32: ストレージ サイズは 4GB (2 の 32 乗) です。
  • sdshdr64: ストレージ サイズは 2 の 64 乗バイトです。

sdshdr8 を例に挙げます。

  • buf: バイト配列。実際のデータを保存します。バイト配列の終わりを示すために、Redis は配列の最後に 1 を自動的に追加します'\0'。これにより、さらに 1 バイトのオーバーヘッドがかかります。
  • len: 4 バイト。 buf の使用長を示します。含まれません'\0'
  • alloc: これも 4 バイトを占め、含まれない buf の実際の割り当て長を示します'\0'
  • flags: 1 バイトを占有し、現在のバイト配列の属性をマークします (はいsdshdr8などsdshdr16)。(フラグ値の定義は次のコードで確認できます)

ソース コードではsds.h、フラグの値は次のように定義されています。

#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 Redisオブジェクト

Redis には多くのデータ型があり、異なるデータ型でも記録するメタデータは同じであるため、値オブジェクトは直接格納されず、オブジェクトにパッケージ化され、その定義は次のようになりますredisObject

typedef struct redisObject {
    
    
    unsigned type:4;//对象类型(4位=0.5字节)
    unsigned encoding:4;//编码(4位=0.5字节)
    unsigned lru:LRU_BITS;//记录对象最后一次被应用程序访问的时间(24位=3字节)
    int refcount;//引用计数。等于0时表示可以被垃圾回收(32位=4字节)
    void *ptr;//指向底层实际的数据存储结构,如:sds等(8字节)
} robj;

以下のことが理解に役立ちます。

メモリ領域を節約するために、Redis はいくつかの最適化も行いました。

Long 型の整数を保存すると、RedisObject 内のポインターが整数データに直接割り当てられるため、整数を指す追加のポインターは必要ありません。この格納方法は通常 int エンコード方式とも呼ばれます。

文字列データを保存する場合、文字列が 44 バイト以下の場合、RedisObject 内のメタデータ、ポインター、SDS は連続したメモリ領域となるため、メモリの断片化を回避できます。このレイアウトは embstr エンコーディングとも呼ばれます。

文字列が 44 バイトを超えると、SDS 内のデータ量が増加し始めます。Redis は SDS と RedisObject を一緒に配置しなくなり、SDS に独立したスペースを割り当て、SDS 構造を指すポインターを使用します。このレイアウトは raw エンコード モードと呼ばれます。

OBJECT ENCODING コマンドを使用して、データベース キーの値オブジェクトのエンコーディングを表示します。

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 の追加のメタデータ オーバーヘッドがわかっているので、String 型のメモリ使用量を計算できるようになりました。

画像格納オブジェクトのIDはLong型の整数なので、intエンコードされたRedisObjectで直接保存できます。int エンコードされた各 RedisObject のメタデータ部分は 8 バイトを占め、ポインター部分には 8 バイトの整数が直接割り当てられます。ピクチャ ID は sdshdr5 データ構造を使用して保存され、10 ビットのピクチャ ID に 16 バイトが割り当てられ、ターミネータ '\0' が 1 バイトを占めます。

合計 34 バイトが占有されます。前述の画像 ID と画像ストレージ オブジェクト ID のレコードと比較すると、平均使用量 68 バイトは少し大きいですが、追加のオーバーヘッドはどこに行くのでしょうか。

2.3 グローバルハッシュテーブル

キーから値への高速アクセスを実現するために、Redis はハッシュ テーブルを使用してすべてのキーと値のペアを保存します。このハッシュ テーブルにはすべてのキーと値のペアが格納されるため、グローバル ハッシュ テーブルとも呼ばれます。ハッシュ テーブルの各項目は dictEntry 構造であり、キーと値のペアを指すために使用されます。dictEntry 構造体には、キー、値、次の dictEntry をそれぞれ指す 8 バイトのポインターが 3 つあります。次の図に示すように、3 つのポインターは合計 24 バイトです。

jemalloc がメモリを割り当てるときは、2 の N 乗に最も近い値が割り当てられます。例えば。6 バイトのスペースを申請した場合、jemalloc は実際には 2 の 4 乗、つまり 8 バイトのスペースを割り当てます。24 バイトのスペースを申請した場合、jemalloc は 32 バイトを割り当てます。

最終的に分析したメモリ オーバーヘッドは 66 バイトで、上記のシナリオの平均値 68 に比較的近い値でした。

やっと

String 型は非常に多くのメモリを消費するため、メモリを節約するための良い解決策はありますか?

この記事の内容を一週間かけて準備してみましたので、参考になりましたら「見る」をクリックしていただけますか?あなたの「いいね!」に作者は興奮して夜も眠れなくなります。

以下の内容にご興味がございましたら、公式アカウント「学生陽テクノノート」のフォローもよろしくお願いいたします!

参考文献

  • この記事の一部のコマンドについては、新人チュートリアルを参照してください: https://www.runoob.com/redis/redis-tutorial.html
  • Redis のキーも SDS タイプです。https://www.cnblogs.com/lonely-wolf/p/14261486.html を参照してください。
  • SDS の定義については、https://juejin.cn/post/6844903936520880135#Heading-6 を参照してください。
  • 記事の概要はGeek Time「Redisコア技術と実戦」を参照してください。
  • 「Redisの設計と実装」

おすすめ

転載: blog.csdn.net/yang237061644/article/details/128626006