banner
ShuWa

ShuWa

是进亦忧,退亦忧。然则何时而乐耶?
twitter

Redis データ型

データ型と適用シーン#

Redis は豊富なデータ型を提供しており、一般的なものは 5 種類あります:String(文字列)、Hash(ハッシュ)、List(リスト)、Set(集合)、Zset(ソートされた集合)。Redis のバージョンが更新されるにつれて、さらに 4 種類のデータ型がサポートされるようになりました:BitMap(2.2 版追加)、HyperLogLog(2.8 版追加)、GEO(3.2 版追加)、Stream(5.0 版追加)。

String#

String は最も基本的な key-value 構造で、key は一意の識別子、value は具体的な値です。value は実際には文字列だけでなく、数字(整数または浮動小数点数)も含むことができ、value が最大で格納できるデータの長さは 512M です。

内部実装#

String 型の底層データ構造の実装は主にintSDS(簡単な動的文字列)です。

SDS は C のネイティブ文字列と比較して:

  • SDS はテキストデータだけでなく、バイナリデータも保存できます。SDS は len 属性の値を使用して文字列が終了するかどうかを判断し、SDS のすべての API はバイナリデータを処理する方法で buf [] 配列に保存されたデータを処理します。したがって、SDS はテキストデータだけでなく、画像、音声、動画、圧縮ファイルなどのバイナリデータも保存できます。
  • **SDS の文字列長の取得の時間計算量は O (1)** です。C 言語の文字列は自身の長さを記録しないため、長さを取得する計算量は O (n) ですが、SDS 構造では len 属性で文字列の長さを記録しているため、計算量は O (1) です。
  • Redis の SDS API は安全で、文字列の結合によってバッファオーバーフローを引き起こしません。SDS は文字列を結合する前に SDS の空間が要件を満たしているかどうかを確認し、空間が不足している場合は自動的に拡張するため、バッファオーバーフローの問題は発生しません。

文字列オブジェクトの内部エンコーディングには 3 種類あります:intraw、およびembstrです。

もし文字列オブジェクトが整数値を保存しており、その整数値が long 型で表現できる場合、文字列オブジェクトは整数値を文字列オブジェクト構造の ptr 属性に保存し(void * を long に変換)、文字列オブジェクトのエンコーディングを int に設定します。

もし文字列オブジェクトが文字列を保存しており、その文字列の長さが 32 バイト以下(redis 2.+ バージョン)であれば、文字列オブジェクトは簡単な動的文字列(SDS)を使用してこの文字列を保存し、オブジェクトのエンコーディングを embstr に設定します。embstr エンコーディングは短い文字列を保存するための最適化されたエンコーディング方式です。

image

もし文字列オブジェクトが文字列を保存しており、その文字列の長さが 32 バイトを超える(redis 2.+ バージョン)場合、文字列オブジェクトは簡単な動的文字列(SDS)を使用してこの文字列を保存し、オブジェクトのエンコーディングを raw に設定します。

image

注意:embstr エンコーディングと raw エンコーディングの境界は redis の異なるバージョンで異なります:

  • redis 2.+ は32バイト
  • redis 3.0-4.0 は39バイト
  • redis 5.0 は44バイト

embstr と raw エンコーディングはどちらも SDS を使用して値を保存しますが、違いは **embstr は一度のメモリ割り当て関数を使用して redisObject と SDS を保存するための連続したメモリ空間を割り当てるのに対し、raw エンコーディングは 2 回のメモリ割り当て関数を呼び出して redisObject と SDS を保存するための 2 つの空間をそれぞれ割り当てます。Redis はこれにより多くの利点があります:

  • embstr エンコーディングは文字列オブジェクトを作成するために必要なメモリ割り当ての回数を raw エンコーディングの 2 回から 1 回に減らします;
  • embstr エンコーディングの文字列オブジェクトを解放するためにも、同様に 1 回のメモリ解放関数の呼び出しが必要です;
  • embstr エンコーディングの文字列オブジェクトのすべてのデータが連続したメモリに保存されているため、CPU キャッシュをより良く利用して性能を向上させることができます。

しかし、embstr にも欠点があります:

  • 文字列の長さが増加してメモリを再割り当てする必要がある場合、全体の redisObject と sds の両方を再割り当てする必要があるため、embstr エンコーディングの文字列オブジェクトは実際には読み取り専用です。redis は embstr エンコーディングの文字列オブジェクトに対して適切な変更プログラムを作成していません。embstr エンコーディングの文字列オブジェクトに対して任意の変更コマンド(例えば append)を実行すると、プログラムは最初にオブジェクトのエンコーディングを embstr から raw に変換し、その後に変更コマンドを実行します。

よく使われるコマンド#

# key-valueタイプの値を設定
> SET name lin
OK
# keyに基づいて対応するvalueを取得
> GET name
"lin"
# 特定のkeyが存在するかどうかを判断
> EXISTS name
(integer) 1
# keyが保存している文字列値の長さを返す
> STRLEN name
(integer) 3
# 特定のkeyに対応する値を削除
> DEL name
(integer) 1

# 複数のkey-valueタイプの値を一度に設定
> MSET key1 value1 key2 value2 
OK
# 複数のkeyに対応するvalueを一度に取得
> MGET key1 key2 
1) "value1"
2) "value2"

# key-valueタイプの値を設定
> SET number 0
OK
# keyに保存されている数字値を1増やす
> INCR number
(integer) 1
# keyに保存されている数字値に10を加える
> INCRBY number 10
(integer) 11
# keyに保存されている数字値を1減らす
> DECR number
(integer) 10
# keyに保存されている数字値から10を減らす
> DECRBY number 10
(integer) 0

# keyを60秒後に期限切れに設定(この方法は既存のkeyに対して期限を設定します)
> EXPIRE name  60 
(integer) 1
# データがどれくらいで期限切れになるかを確認
> TTL name 
(integer) 51

# key-valueタイプの値を設定し、そのkeyの期限を60秒に設定
> SET key  value EX 60
OK
> SETEX key  60 value
OK

# 存在しない場合は挿入(not exists)
>SETNX key value
(integer) 1

適用シーン#

キャッシュオブジェクト#
  • オブジェクト全体の JSON を直接キャッシュする。コマンド例:SET user:1 '{"name":"xiaolin", "age":18}'。
  • key を user:ID: 属性に分離し、MSET を使用して保存し、MGET で各属性値を取得する。コマンド例:MSET user:1 xiaolin user:1 18 user:2 xiaomei user:2 20。
一般的なカウント#

Redis はコマンドを単一スレッドで処理するため、コマンドの実行プロセスは原子的です。したがって、String データ型はカウントシーンに適しています。例えば、訪問回数、いいね、リツイート、在庫数などを計算するのに適しています。

分散ロック#

SET コマンドには NX パラメータがあり、「key が存在しない場合にのみ挿入」を実現できます。これを使用して分散ロックを実現できます:

  • key が存在しない場合、挿入成功が表示され、ロック成功を示すことができます;
  • key が存在する場合、挿入失敗が表示され、ロック失敗を示すことができます。

一般的に、分散ロックには期限を追加することがあり、分散ロックのコマンドは次のようになります:

SET lock_key unique_value NX PX 10000

ロック解除のプロセスは lock_key キーを削除することですが、乱雑に削除してはいけません。操作を実行するクライアントがロックを取得したクライアントであることを確認する必要があります。したがって、ロック解除の際には、最初にロックの unique_value がロックを取得したクライアントであるかどうかを確認し、そうであれば lock_key キーを削除します。

共有セッション情報#

通常、バックエンド管理システムを開発する際には、セッションを使用してユーザーのセッション(ログイン)状態を保存します。これらのセッション情報はサーバー側に保存されますが、これは単一システムアプリケーションにのみ適用され、分散システムの場合、このモデルは適用されなくなります。

したがって、Redis を使用してこれらのセッション情報を統一的に保存および管理する必要があります。これにより、リクエストがどのサーバーに送信されても、サーバーは同じ Redis から関連するセッション情報を取得します。これにより、分散システムでのセッション保存の問題が解決されます。

List#

List は単純な文字列のリストで、挿入順序に従って並べられ、List リストの先頭または末尾から要素を追加できます。リストの最大長は 2^32 - 1 であり、つまり各リストは 40 億以上の要素をサポートします。

内部実装#

List 型の底層データ構造は双方向リストまたは圧縮リストによって実装されています。

  • リストの要素数が 512 未満(デフォルト値、list-max-ziplist-entries で設定可能)で、リストの各要素の値が 64 バイト未満(デフォルト値、list-max-ziplist-value で設定可能)である場合、Redis は圧縮リストを List 型の底層データ構造として使用します;
  • リストの要素が上記の条件を満たさない場合、Redis は双方向リストを List 型の底層データ構造として使用します;

しかし、Redis 3.2 バージョン以降、List データ型の底層データ構造はquicklistのみで実装され、双方向リストと圧縮リストが置き換えられました。

よく使われるコマンド#

# 1つまたは複数の値valueをkeyリストの先頭(最左)に挿入し、最後の値が最前に来る
LPUSH key value [value ...] 
# 1つまたは複数の値valueをkeyリストの末尾(最右)に挿入
RPUSH key value [value ...]
# keyリストの先頭要素を削除して返す
LPOP key     
# keyリストの末尾要素を削除して返す
RPOP key 

# リストkeyの指定された範囲内の要素を返す。範囲はオフセットstartとstopで指定し、0から始まる
LRANGE key start stop

# keyリストの先頭から1つの要素をポップし、なければtimeout秒ブロックする。timeout=0の場合は常にブロックする
BLPOP key [key ...] timeout
# keyリストの末尾から1つの要素をポップし、なければtimeout秒ブロックする。timeout=0の場合は常にブロックする
BRPOP key [key ...] timeout

適用シーン#

メッセージキュー#

メッセージキューはメッセージを保存および取得する際に、メッセージの順序を保ち、重複メッセージを処理し、メッセージの信頼性を保証する必要があります。
Redis の List と Stream の 2 つのデータ型は、メッセージキューのこれら 3 つの要件を満たすことができます。

List がメッセージキューとして持つ欠点は?

List は複数の消費者が同じメッセージを消費することをサポートしていません。なぜなら、消費者がメッセージを引き出すと、そのメッセージは List から削除され、他の消費者が再度消費することができなくなるからです。

1 つのメッセージが複数の消費者によって消費されるようにするには、複数の消費者を消費グループにまとめる必要があります。これにより、複数の消費者が同じメッセージを消費できるようになりますが、List 型は消費グループの実装をサポートしていません。

Hash#

Hash はキーと値のペア(key - value)の集合であり、value の形式は次のようになります:value=[{field1,value1},...{fieldN,valueN}]。Hash はオブジェクトの保存に特に適しています。

内部実装#

Hash 型の底層データ構造は圧縮リストまたはハッシュテーブルによって実装されています:

  • ハッシュ型の要素数が 512 未満(デフォルト値、hash-max-ziplist-entries で設定可能)で、すべての値が 64 バイト未満(デフォルト値、hash-max-ziplist-value で設定可能)である場合、Redis は圧縮リストを Hash 型の底層データ構造として使用します;
  • ハッシュ型の要素が上記の条件を満たさない場合、Redis はハッシュテーブルを Hash 型の底層データ構造として使用します。

Redis 7.0 では、圧縮リストデータ構造は廃止され、listpack データ構造によって実装されました。

よく使われるコマンド#

# ハッシュテーブルkeyのキーと値を保存
HSET key field value   
# ハッシュテーブルkeyに対応するfieldキーの値を取得
HGET key field

# ハッシュテーブルkeyに複数のキーと値を保存
HMSET key field value [field value...] 
# ハッシュテーブルkeyの複数のfieldキーの値を一度に取得
HMGET key field [field ...]       
# ハッシュテーブルkeyからfieldキーの値を削除
HDEL key field [field ...]    

# ハッシュテーブルkeyのfieldの数を返す
HLEN key       
# ハッシュテーブルkeyのすべてのキーと値を返す
HGETALL key 

# ハッシュテーブルkeyのfieldキーの値に増分nを加える
HINCRBY key field n                         

適用シーン#

キャッシュオブジェクト#

Hash 型の(key,field,value)の構造はオブジェクトの(オブジェクト id,属性,値)の構造に似ており、オブジェクトを保存するために使用できます。

# ハッシュテーブルuid:1のキーと値を保存
> HMSET uid:1 name Tom age 15
2
# ハッシュテーブルuid:2のキーと値を保存
> HMSET uid:2 name Jerry age 13
2
# ハッシュテーブルユーザーidが1のすべてのキーと値を取得
> HGETALL uid:1
1) "name"
2) "Tom"
3) "age"
4) "15"
ショッピングカート#

ユーザー id を key、商品 id を field、商品数量を value とすることで、ショッピングカートの 3 つの要素を構成します。以下のコマンドが関与します:

  • 商品を追加:HSET cart:{ユーザー id} {商品 id} 1
  • 数量を追加:HINCRBY cart:{ユーザー id} {商品 id} 1
  • 商品の総数:HLEN cart:{ユーザー id}
  • 商品を削除:HDEL cart:{ユーザー id} {商品 id}
  • ショッピングカートのすべての商品を取得:HGETALL cart:{ユーザー id}

前述のように、商品 ID を Redis に保存しただけでは、商品具体情報を表示する際に、商品 id を使用してデータベースを再度照会し、完全な商品情報を取得する必要があります。

Set#

Set 型は順序なしユニークなキーと値の集合です。保存順序は挿入の先後に従いません。

1 つの集合は最大で 2^32-1 個の要素を保存できます。数学における集合の概念に似ており、交差、和、差などをサポートしています。したがって、Set 型は集合内の追加、削除、変更、検索をサポートするだけでなく、複数の集合の交差、和、差を計算することもできます。

内部実装#

Set 型の底層データ構造はハッシュテーブルまたは整数集合によって実装されています:

  • 集合内の要素がすべて整数で、要素数が 512 未満(デフォルト値、set-maxintset-entries で設定可能)である場合、Redis は整数集合を Set 型の底層データ構造として使用します;
  • 集合内の要素が上記の条件を満たさない場合、Redis はハッシュテーブルを Set 型の底層データ構造として使用します。

よく使われるコマンド#

# 集合keyに要素を追加し、要素が存在する場合は無視し、keyが存在しない場合は新規作成
SADD key member [member ...]
# 集合keyから要素を削除
SREM key member [member ...] 
# 集合keyのすべての要素を取得
SMEMBERS key
# 集合keyの要素数を取得
SCARD key

# member要素が集合keyに存在するかどうかを判断
SISMEMBER key member

# 集合keyからcount個の要素をランダムに選択し、要素はkeyから削除されない
SRANDMEMBER key [count]
# 集合keyからcount個の要素をランダムに選択し、要素はkeyから削除される
SPOP key [count]

# 交差計算
SINTER key [key ...]
# 交差結果を新しい集合destinationに保存
SINTERSTORE destination key [key ...]

# 和計算
SUNION key [key ...]
# 和結果を新しい集合destinationに保存
SUNIONSTORE destination key [key ...]

# 差計算
SDIFF key [key ...]
# 差結果を新しい集合destinationに保存
SDIFFSTORE destination key [key ...]

適用シーン#

Set 型はデータの重複排除やユニーク性の保証に適しており、複数の集合の交差、差、和を計算するためにも使用できますが、ここには潜在的なリスクがあります。Set の差、和、交差の計算は計算量が高く、データ量が大きい場合、これらの計算を直接実行するとRedis インスタンスがブロックされる可能性があります。

いいね#

Set 型はユーザーが 1 つのいいねを付けることを保証できるため、以下のようなシーンを例に挙げます。key は記事 id、value はユーザー id です。

# uid:1ユーザーが記事article:1にいいねを付ける
> SADD article:1 uid:1
(integer) 1
# uid:2ユーザーが記事article:1にいいねを付ける
> SADD article:1 uid:2
(integer) 1
# uid:3ユーザーが記事article:1にいいねを付ける
> SADD article:1 uid:3
(integer) 1
uid:1はarticle:1の記事のいいねを取り消しました。

> SREM article:1 uid:1
(integer) 1
article:1の記事のすべてのいいねユーザーを取得:

> SMEMBERS article:1
1) "uid:3"
2) "uid:2"
article:1の記事のいいねユーザー数を取得:

> SCARD article:1
(integer) 2
ユーザーuid:1が記事article:1にいいねを付けたかどうかを判断:

> SISMEMBER article:1 uid:1
(integer) 0  # 0が返されるといいねしていないことを示し、1が返されるといいねしていることを示す
共通のフォロー#

Set 型は交差計算をサポートしているため、共通の友人や公式アカウントを計算するために使用できます。

key はユーザー id、value はフォローしている公式アカウントの id です。

# uid:1ユーザーが公式アカウントid 5、6、7、8、9をフォロー
> SADD uid:1 5 6 7 8 9
(integer) 5
# uid:2ユーザーが公式アカウントid 7、8、9、10、11をフォロー
> SADD uid:2 7 8 9 10 11
(integer) 5

uid:1とuid:2が共通してフォローしている公式アカウント:

# 共通のフォローを取得
> SINTER uid:1 uid:2
1) "7"
2) "8"
3) "9"

uid:2にuid:1がフォローしている公式アカウントを推薦:

> SDIFF uid:1 uid:2
1) "5"
2) "6"
特定の公式アカウントがuid:1またはuid:2によって同時にフォローされているかどうかを確認:

> SISMEMBER uid:1 5
(integer) 1 # 0が返されるとフォローしていることを示し
> SISMEMBER uid:2 5
(integer) 0 # 0が返されるとフォローしていないことを示す
抽選活動#

特定の活動で当選したユーザー名を保存します。Set 型は重複排除機能があるため、同じユーザーが 2 回当選することはありません。

keyは抽選活動名、valueは従業員名で、すべての従業員名を抽選箱に入れます:

>SADD lucky Tom Jerry John Sean Marry Lindy Sary Mark
(integer) 5
重複当選を許可する場合はSRANDMEMBERコマンドを使用します。

# 1つの一等賞を抽選:
> SRANDMEMBER lucky 1
1) "Tom"
# 2つの二等賞を抽選:
> SRANDMEMBER lucky 2
1) "Mark"
2) "Jerry"
# 3つの三等賞を抽選:
> SRANDMEMBER lucky 3
1) "Sary"
2) "Tom"
3) "Jerry"
重複当選を許可しない場合はSPOPコマンドを使用します。

# 一等賞1つを抽選
> SPOP lucky 1
1) "Sary"
# 二等賞2つを抽選
> SPOP lucky 2
1) "Jerry"
2) "Mark"
# 三等賞3つを抽選
> SPOP lucky 3
1) "John"
2) "Sean"
3) "Lindy"

Zset#

Zset 型(ソートされた集合型)は Set 型に対してスコア(得点)というソート属性が追加されています。ソートされた集合 ZSet にとって、各保存される要素は 2 つの値から構成されます。1 つはソートされた集合の要素値、もう 1 つはソート値です。

ソートされた集合は集合が重複メンバーを持たない特性を保持しています(得点は重複可能)が、異なるのはソートされた集合の要素はソート可能であることです。

内部実装#

Zset 型の底層データ構造は圧縮リストまたはスキップリストによって実装されています:

  • ソートされた集合の要素数が 128 未満で、各要素の値が 64 バイト未満の場合、Redis は圧縮リストを Zset 型の底層データ構造として使用します;
  • ソートされた集合の要素が上記の条件を満たさない場合、Redis はスキップリストを Zset 型の底層データ構造として使用します;

Redis 7.0 では、圧縮リストデータ構造は廃止され、listpack データ構造によって実装されました。

よく使われるコマンド#

Zsetのよく使われる操作:

# ソートされた集合keyにスコア付き要素を追加
ZADD key score member [[score member]...]   
# ソートされた集合keyから要素を削除
ZREM key member [member...]                 
# ソートされた集合keyにおける要素memberのスコアを返す
ZSCORE key member
# ソートされた集合keyにおける要素の数を返す
ZCARD key 

# ソートされた集合keyにおける要素memberのスコアにincrementを加える
ZINCRBY key increment member 

# 昇順でソートされた集合keyのstartインデックスからstopインデックスまでの要素を取得
ZRANGE key start stop [WITHSCORES]
# 降順でソートされた集合keyのstartインデックスからstopインデックスまでの要素を取得
ZREVRANGE key start stop [WITHSCORES]

# 指定されたスコア範囲内のメンバーを返す。スコアは低い順にソートされる。
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

# 指定されたメンバー範囲内のメンバーを返す。辞書順で正順に並べられ、スコアは同じでなければならない。
ZRANGEBYLEX key min max [LIMIT offset count]
# 指定されたメンバー範囲内のメンバーを返す。辞書順で逆順に並べられ、スコアは同じでなければならない
ZREVRANGEBYLEX key max min [LIMIT offset count]
Zsetの演算操作(Set型と比較して、ZSet型は差集計演算をサポートしていません):

# 和計算(同じ要素のスコアを加算)、numberkeysは合計いくつのkey
ZUNIONSTORE destkey numberkeys key [key...] 
# 交差計算(同じ要素のスコアを加算)、numberkeysは合計いくつのkey
ZINTERSTORE destkey numberkeys key [key...]

適用シーン#

ランキング#
 arcticle:1の記事は200いいねを獲得しました
> ZADD user:xiaolin:ranking 200 arcticle:1
(integer) 1
# arcticle:2の記事は40いいねを獲得しました
> ZADD user:xiaolin:ranking 40 arcticle:2
(integer) 1
# arcticle:3の記事は100いいねを獲得しました
> ZADD user:xiaolin:ranking 100 arcticle:3
(integer) 1
# arcticle:4の記事は50いいねを獲得しました
> ZADD user:xiaolin:ranking 50 arcticle:4
(integer) 1
# arcticle:5の記事は150いいねを獲得しました
> ZADD user:xiaolin:ranking 150 arcticle:5
(integer) 1
記事arcticle:4に新しいいいねを追加するには、ZINCRBYコマンドを使用します(ソートされた集合keyの要素memberのスコアにincrementを加える):

> ZINCRBY user:xiaolin:ranking 1 arcticle:4
"51"
特定の記事のいいね数を確認するには、ZSCOREコマンドを使用します(ソートされた集合keyにおける要素数を返す):

> ZSCORE user:xiaolin:ranking arcticle:4
"50"
小林のいいね数が最も多い3つの記事を取得するには、ZREVRANGEコマンドを使用します(降順でソートされた集合keyのstartインデックスからstopインデックスまでの要素を取得):

# WITHSCORESはスコアも表示されることを示します
> ZREVRANGE user:xiaolin:ranking 0 2 WITHSCORES
1) "arcticle:1"
2) "200"
3) "arcticle:5"
4) "150"
5) "arcticle:3"
6) "100"
小林の100いいねから200いいねの範囲の記事を取得するには、ZRANGEBYSCOREコマンドを使用します(指定されたスコア範囲内のメンバーを返す。スコアは低い順にソートされる):

> ZRANGEBYSCORE user:xiaolin:ranking 100 200 WITHSCORES
1) "arcticle:3"
2) "100"
3) "arcticle:5"
4) "150"
5) "arcticle:1"
6) "200"

BitMap#

Bitmap、すなわちビットマップは、連続した二進数の配列(0 と 1)であり、オフセット(offset)を通じて要素を位置づけることができます。BitMap は最小の単位 bit を使用して 0|1 の設定を行い、特定の要素の値または状態を示します。時間計算量は O (1) です。

bit はコンピュータにおける最小の単位であるため、これを使用して保存すると非常にスペースを節約でき、特にデータ量が大きく、二値統計を使用するシーンに適しています。

内部実装#

Bitmap 自体は String 型を底層データ構造として実装された二値状態を統計するデータ型です。

String 型はバイナリのバイト配列として保存されるため、Redis はバイト配列の各 bit を利用して、要素の二値状態を示すために Bitmap を考えることができます。

bitmap 基本操作:#


# 値を設定する。valueは0または1のみ
SETBIT key offset value

# 値を取得
GETBIT key offset

# 指定された範囲内の値が1である個数を取得
# startとendはバイト単位
BITCOUNT key start end
bitmap演算操作:

# BitMap間の演算
# operationsはビット演算子、列挙値
  AND 与運算 &
  OR 或運算 |
  XOR 排他或運算 ^
  NOT 取反 ~
# resultは計算結果が保存されるkey
# key1 … keynは演算に参加するkeyで、複数指定可能、空白で区切る。not演算は1つのkeyのみ
# BITOPが異なる長さの文字列を処理する場合、短い方の文字列の不足部分は0と見なされます。返り値はdestkeyに保存される文字列の長さ(バイト単位)で、入力keyの中で最も長い文字列の長さと等しくなります。
BITOP [operations] [result] [key1] [keyn…]

# 指定key内で指定value(0/1)が最初に出現する位置を返す
BITPOS [key] [value]

適用シーン#

出席統計#
例えば、ID 100のユーザーの2022年6月の出席状況を統計する場合、次の手順で操作できます。

第一歩、次のコマンドを実行して、ユーザーが6月3日に出席したことを記録します。

SETBIT uid:sign:100:202206 2 1
第二歩、そのユーザーが6月3日に出席したかどうかを確認します。

GETBIT uid:sign:100:202206 2 
第三歩、そのユーザーの6月の出席回数を統計します。

BITCOUNT uid:sign:100:202206
これにより、そのユーザーの6月の出席状況がわかります。

次のコマンドを実行して、userID = 100の2022年6月の最初の打刻日を取得できます:

BITPOS uid:sign:100:202206 1
連続出席ユーザー数#

3 日間連続して打刻したユーザー数を統計する場合、3 つのビットマップを AND 演算し、結果を destmap に保存し、その後 destmap に対して BITCOUNT を実行して統計を取ります。次のコマンドを実行します:

# AND演算
BITOP AND destmap bitmap:01 bitmap:02 bitmap:03
# bitが1の個数を統計
BITCOUNT destmap

たとえ 1 日に 1 億のデータが生成されても、Bitmap が占有するメモリはそれほど大きくなく、約 12MB のメモリを占有します(10^8/8/1024/1024)。7 日間の Bitmap のメモリコストは約 84MB です。また、Bitmap に期限を設定して、Redis が期限切れの打刻データを削除し、メモリを節約することをお勧めします。

HyperLogLog#

HyperLogLog は不正確な重複カウントを提供します。Redis 内では、各 HyperLogLog キーはわずか 12KB のメモリを消費し、約 2^64 個の異なる要素の基数を計算できます。要素が増えるほどメモリを消費する Set や Hash 型と比較して、HyperLogLog は非常にスペースを節約します。

内部実装#

よく使われるコマンド#

# 指定された要素をHyperLogLogに追加
PFADD key element [element ...]

# 指定されたHyperLogLogの基数の推定値を返す。
PFCOUNT key [key ...]

# 複数のHyperLogLogを1つのHyperLogLogにマージ
PFMERGE destkey sourcekey [sourcekey ...]

適用シーン#

百万レベルのウェブページ UV カウント#
UVを統計する際、PFADDコマンド(HyperLogLogに新しい要素を追加するために使用)を使用して、アクセスページの各ユーザーをHyperLogLogに追加できます。

PFADD page1:uv user1 user2 user3 user4 user5
次に、PFCOUNTコマンドを使用してpage1のUV値を直接取得できます。このコマンドの役割はHyperLogLogの統計結果を返すことです。

PFCOUNT page1:uv

GEO#

主に地理的位置情報を保存し、保存された情報に対して操作を行うために使用されます。
日常生活の中で、私たちは「近くのレストラン」を検索したり、タクシーアプリで車を呼んだりすることが増えています。これらはすべて位置情報サービス(Location-Based Service、LBS)に基づくアプリケーションに依存しています。LBS アプリケーションがアクセスするデータは、人や物に関連する一組の緯度と経度の情報であり、隣接する緯度と経度の範囲を照会できる必要があります。GEO は LBS サービスのシーンで非常に適しています。

内部実装#

GEO 自体は新しい底層データ構造を設計しておらず、直接 Sorted Set 集合型を使用しています。

GEO 型は GeoHash エンコーディング方法を使用して、緯度と経度を Sorted Set の要素の重みスコアに変換しています。この中の 2 つの重要なメカニズムは「2 次元マップの範囲を区切る」と「範囲をエンコードする」ことです。一組の緯度と経度が特定の範囲に落ちると、その範囲のエンコード値を使用して表現し、エンコード値を Sorted Set 要素の重みスコアとして使用します。

これにより、緯度と経度を Sorted Set に保存し、Sorted Set が提供する「重みによる順序付き範囲検索」の特性を利用して、LBS サービスで頻繁に使用される「近くを検索する」ニーズを実現できます。

よく使われるコマンド#

# 指定された地理空間位置を保存します。1つまたは複数の経度(longitude)、緯度(latitude)、位置名(member)を指定されたkeyに追加できます。
GEOADD key longitude latitude member [longitude latitude member ...]

# 指定されたkeyからすべての指定された名前(member)の位置(経度と緯度)を返します。存在しない場合はnilを返します。
GEOPOS key member [member ...]

# 2つの指定された位置間の距離を返します。
GEODIST key member1 member2 [m|km|ft|mi]

# ユーザーが指定した緯度と経度の座標に基づいて、指定された範囲内の地理位置集合を取得します。
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]

適用シーン#

次のコマンドを実行すると、ID 33の車両の現在の緯度と経度の位置をGEO集合に保存できます:
> GEOADD cars:locations 116.034579 39.030452 33

ユーザーが自分の近くの配車を探したい場合、LBSアプリケーションはGEORADIUSコマンドを使用できます。
たとえば、LBSアプリケーションが次のコマンドを実行すると、Redisは入力されたユーザーの緯度情報(116.054579、39.030452)に基づいて、この緯度を中心に5キロ以内の車両情報を検索し、LBSアプリケーションに返します。

> GEORADIUS cars:locations 116.054579 39.030452 5 km ASC COUNT 10

Stream#

Redis Stream は Redis 5.0 バージョンで新たに追加されたデータ型で、メッセージキューのために特別に設計されたデータ型です。
メッセージキューを完璧に実現するために使用され、メッセージの永続化、全体的にユニークな ID の自動生成、ack 確認メッセージのモード、消費グループモードなどをサポートし、メッセージキューをより安定かつ信頼性の高いものにします。

よく使われるコマンド#

Stream メッセージキュー操作コマンド:

XADD:メッセージを挿入し、順序を保証し、全体的にユニークなIDを自動生成します;
XLEN :メッセージの長さを照会します;
XREAD:メッセージを読み取るために使用され、IDに基づいてデータを読み取ることができます;
XDEL :メッセージIDに基づいてメッセージを削除します;
DEL :全体のStreamを削除します;
XRANGE :範囲メッセージを読み取ります;
XREADGROUP:消費グループ形式でメッセージを読み取ります;
XPENDINGとXACK:
XPENDINGコマンドは、各消費グループ内のすべての消費者の「読み取られたが未確認の」メッセージを照会するために使用できます;
XACKコマンドは、メッセージキューにメッセージ処理が完了したことを確認するために使用されます;

適用シーン#

プロデューサーは XADD コマンドを使用してメッセージを挿入します:

# *はRedisが挿入されたデータに自動的に全体的にユニークなIDを生成することを示します
# 名前がmymqのメッセージキューにメッセージを挿入し、メッセージのキーはname、値はxiaolinです
> XADD mymq * name xiaolin
"1654254953808-0"

消費者は XREAD コマンドを使用してメッセージキューからメッセージを読み取ることができ、メッセージ ID を指定し、このメッセージ ID の次のメッセージから読み取りを開始します(注意:入力メッセージ ID の次のメッセージから読み取るのではなく、入力 ID のメッセージを照会します)。

# IDが1654254953807-0のメッセージから、以降のすべてのメッセージを読み取ります(例では合計1件)。
> XREAD STREAMS mymq 1654254953807-0
1) 1) "mymq"
   2) 1) 1) "1654254953808-0"
         2) 1) "name"
            2) "xiaolin"

ブロック読み取りを実現したい場合(データがない場合、ブロックする)、XREAD を呼び出す際に BLOCK 設定項目を設定し、BRPOP のブロック読み取り操作を実現できます。

たとえば、次のコマンドでは、BLOCK 10000 の設定項目が設定され、10000 の単位はミリ秒であり、XREAD が最新のメッセージを読み取る際に、メッセージが到着しない場合、XREAD は 10000 ミリ秒(すなわち 10 秒)ブロックし、その後に戻ります。

# コマンドの最後の「$」記号は最新のメッセージを読み取ることを示します
> XREAD BLOCK 10000 STREAMS mymq $
(nil)
(10.00s)

Stream は XGROUP を使用して消費グループを作成できます。消費グループを作成した後、Stream は XREADGROUP コマンドを使用して消費グループ内の消費者がメッセージを読み取ることを可能にします。

2 つの消費グループを作成し、これらの消費グループが mymq メッセージキューのメッセージを消費し、すべて最初のメッセージから読み取るように指定します:

# group1という名前の消費グループを作成し、0-0は最初のメッセージから読み取ることを示します。
> XGROUP CREATE mymq group1 0-0
OK
# group2という名前の消費グループを作成し、0-0は最初のメッセージから読み取ることを示します。
> XGROUP CREATE mymq group2 0-0
OK

消費グループ group1 内の消費者 consumer1 が mymq メッセージキューからすべてのメッセージを読み取るコマンドは次のようになります:

# コマンドの最後のパラメータ「>」は、最初の未消費のメッセージから読み取ることを示します。
> XREADGROUP GROUP group1 consumer1 STREAMS mymq >
1) 1) "mymq"
   2) 1) 1) "1654254953808-0"
         2) 1) "name"
            2) "xiaolin"

メッセージキュー内のメッセージは、消費グループ内の 1 つの消費者が読み取ると、他の消費者が再度読み取ることはできません。すなわち、同じ消費グループ内の消費者は同じメッセージを消費できません。
ただし、異なる消費グループの消費者は同じメッセージを消費できます(ただし、前提条件として、異なる消費グループが同じ位置からメッセージを読み取るように指定されている必要があります)。
消費グループを使用する目的は、グループ内の複数の消費者がメッセージの読み取りを共同で分担することです。したがって、通常は各消費者が一部のメッセージを読み取るようにして、メッセージの読み取り負荷を複数の消費者間で均等に分配します。

Stream に基づくメッセージキューが、消費者が故障またはダウンした後に再起動しても、未処理のメッセージを読み取ることを保証するにはどうすればよいですか?

Streams は内部キュー(PENDING List とも呼ばれる)を使用して、消費グループ内の各消費者が読み取ったメッセージを保持します。消費者がXACKコマンドを使用して Streams に「メッセージが処理完了した」と通知するまで、メッセージは保持されます。

以上で、Stream に基づくメッセージキューについての説明は終了です。まとめると:

  • メッセージの順序:XADD/XREAD
  • ブロック読み取り:XREAD block
  • 重複メッセージ処理:Stream は XADD コマンドを使用する際に、全体的にユニークな ID を自動生成します;
  • メッセージの信頼性:内部で PENDING List を使用してメッセージを自動保存し、XPENDING コマンドを使用して消費グループが読み取ったが未確認のメッセージを確認し、消費者が XACK でメッセージを確認します;
  • 消費グループ形式でデータを消費することをサポートします。

Redis の Stream メッセージキューと専門のメッセージキューとの違いは何ですか?

専門のメッセージキューは、2 つの大きな要件を満たす必要があります:

  • メッセージが失われないこと。
  • メッセージが蓄積できること。
  1. Redis Stream メッセージは失われる可能性がありますか?

メッセージキューは、実際には 3 つの部分に分かれています:プロデューサー、キュー中間ウェア、消費者
Redis Stream メッセージキューは、3 つの環境でデータを失わないことを保証できますか?

  • Redis プロデューサーはメッセージを失うことはありますか?プロデューサーはメッセージを失うことはありません。プロデューサーが異常な状況に対して適切に処理を行うかどうかに依存します。メッセージが生成され、MQ の中間ウェアに送信される過程で、正常に(MQ 中間ウェア)からの ack 確認応答を受け取ることができれば、送信成功を示します。したがって、戻り値と異常を適切に処理し、異常が発生した場合はメッセージを再送信することで、この段階でメッセージが失われることはありません。
  • Redis 消費者はメッセージを失うことはありますか?ありません。なぜなら、Stream(MQ 中間ウェア)は内部キュー(PENDING List とも呼ばれる)を使用して、消費グループ内の各消費者が読み取ったメッセージを保持しますが、未確認のメッセージです。消費者は再起動後、XPENDING コマンドを使用して読み取ったが未確認のメッセージを確認できます。消費者がビジネスロジックを実行した後、消費確認の XACK コマンドを送信することで、メッセージが失われないことを保証できます。
  • Redis メッセージ中間ウェアはメッセージを失うことはありますか?あります。Redis は以下の 2 つのシナリオでデータを失う可能性があります:
    • AOF 永続化が 1 秒ごとにディスクに書き込むように設定されているが、この書き込みプロセスは非同期であり、Redis がダウンした場合、データが失われる可能性があります。
    • 主従複製も非同期であり、主従切り替え時にもデータが失われる可能性があります。
  1. Redis Stream メッセージは蓄積できますか?

Redis のデータはすべてメモリに保存されているため、メッセージが蓄積されると、Redis のメモリが持続的に増加し、マシンのメモリ上限を超えると OOM のリスクに直面します。
そのため、Redis の Stream は、キューの最大長を指定できる機能を提供しており、これによりこの状況を回避します。
キューの最大長を指定すると、キューの長さが上限を超えると、古いメッセージが削除され、新しいメッセージが固定長で保持されます。このように見ると、Stream はメッセージが蓄積される場合、最大長を指定した場合でも、メッセージが失われる可能性があります。
しかし、Kafka や RabbitMQ などの専門のメッセージキューは、データをディスクに保存しているため、メッセージが蓄積される場合、単にディスクスペースを多く占有するだけです。

したがって、Redis をキューとして使用する場合、直面する 2 つの問題があります:

  • Redis 自体がデータを失う可能性がある;
  • メッセージが圧迫されると、メモリリソースが逼迫する;

したがって、Redis をメッセージキューとして使用できるかどうかは、ビジネスシーンによって異なります:

  • ビジネスシーンが十分にシンプルで、データの損失に敏感でなく、メッセージの蓄積の可能性が比較的小さい場合、Redis をキューとして使用することは完全に可能です。
  • ビジネスに大量のメッセージがあり、メッセージの蓄積の可能性が高く、データの損失を受け入れられない場合は、専門のメッセージキュー中間ウェアを使用するべきです。

Redis データ構造#

キーと値のペア#

Redis のキーと値のペアの key は文字列オブジェクトであり、value は文字列オブジェクトであるか、集合データ型のオブジェクト(例えば List オブジェクト、Hash オブジェクト、Set オブジェクト、Zset オブジェクト)であることができます。

これらのキーと値のペアは Redis にどのように保存されているのでしょうか?

Redis はすべてのキーと値のペアを保存するために「ハッシュテーブル」を使用しています。ハッシュテーブルの最大の利点は、O (1) の時間計算量でキーと値のペアを迅速に見つけることができることです。ハッシュテーブルは実際には配列であり、配列の要素はハッシュバケットと呼ばれます。

Redis のハッシュバケットはどのようにキーと値のペアデータを保存しているのでしょうか?

ハッシュバケットはキーと値データへのポインタ(dictEntry*)を保存します。これにより、ポインタを介してキーと値データを見つけることができ、キーと値のペアの値は文字列オブジェクトや集合データ型のオブジェクトを保存できるため、キーと値のペアのデータ構造では、実際の値そのものを直接保存するのではなく、void * key と void * value ポインタを保存し、それぞれ実際のキーオブジェクトと値オブジェクトを指します。これにより、値が集合データであっても、void * value ポインタを介して見つけることができます。

SDS#

データ構造内の各メンバー変数をそれぞれ紹介します:

  • len は文字列の長さを記録します。これにより、文字列の長さを取得する際には、このメンバー変数の値を返すだけで済み、時間計算量は O(1)になります。
  • alloc は文字配列に割り当てられたスペースの長さです。これにより、文字列を変更する際に、alloc - len を使用して残りのスペースの大きさを計算し、変更要件を満たすかどうかを判断できます。満たさない場合、SDS のスペースは自動的に変更に必要なサイズに拡張され、その後実際の変更操作が実行されるため、SDS を使用すると、手動で SDS のスペースサイズを変更する必要はなく、前述のようなバッファオーバーフローの問題も発生しません。
  • flags は異なるタイプの SDS を示します。合計で 5 つのタイプが設計されており、それぞれ sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64 です。後で違いを説明します。
  • buf [] は実際のデータを保存する文字配列です。文字列だけでなく、バイナリデータも保存できます。

SDS は C のネイティブ文字列と比較して:

  • SDS はテキストデータだけでなく、バイナリデータも保存できます。SDS は len 属性の値を使用して文字列が終了するかどうかを判断し、SDS のすべての API はバイナリデータを処理する方法で buf [] 配列に保存されたデータを処理します。したがって、SDS はテキストデータだけでなく、画像、音声、動画、圧縮ファイルなどのバイナリデータも保存できます。
  • **SDS の文字列長の取得の時間計算量は O (1)** です。C 言語の文字列は自身の長さを記録しないため、長さを取得する計算量は O (n) ですが、SDS 構造では len 属性で文字列の長さを記録しているため、計算量は O (1) です。
  • Redis の SDS API は安全で、文字列の結合によってバッファオーバーフローを引き起こしません。SDS は文字列を結合する前に SDS の空間が要件を満たしているかどうかを確認し、空間が不足している場合は自動的に拡張するため、バッファオーバーフローの問題は発生しません。
  • メモリスペースを節約します。SDS 構造内には flags メンバー変数があり、SDS のタイプを示します。
    Redis は合計で 5 つのタイプを設計しており、それぞれ sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64 です。これら 5 つのタイプの主な違いは、データ構造内の len と alloc メンバー変数のデータ型が異なることです。
    sdshdr16 タイプの len と alloc のデータ型は uint16_t であり、文字配列の長さと割り当てられたスペースのサイズは 2 の 16 乗を超えることはできません。
    SDS が異なるタイプの構造体を設計した理由は、異なるサイズの文字列を柔軟に保存し、メモリスペースを効果的に節約するためです。たとえば、小さな文字列を保存する場合、構造のヘッダーが占めるスペースも少なくなります。
    異なるタイプの構造体を設計するだけでなく、Redis はプログラミング上でメモリスペースを節約するために、__attribute__ ((packed))を構造体の宣言に使用しています。これは、コンパイラに構造体のコンパイルプロセスでの最適化整列をキャンセルし、実際の占有バイト数に従って整列するように指示する役割を果たします。

リスト#

list 構造はリストヘッダーポインタ head、リストの尾ノード tail、リストノードの数 len、さらにカスタム実装可能な dup、free、match 関数を提供します。
リストのメモリは連続していないため、CPU キャッシュをうまく利用できず、リストノードの値にはリストノード構造ヘッダーの割り当てが必要で、メモリオーバーヘッドが大きくなります。そのため、Redis 3.0 ではList オブジェクトはデータ量が少ない場合に「圧縮リスト」を底層データ構造の実装として採用します。圧縮リストはメモリスペースを節約し、メモリコンパクト型のデータ構造です。
Redis 5.0 では新しいデータ構造 listpack が設計され、圧縮リストのコンパクトなメモリレイアウトを引き継ぎ、最終的に最新の Redis バージョンでは Hash オブジェクトと Zset オブジェクトの底層データ構造の 1 つである圧縮リストが listpack によって置き換えられました。

圧縮リスト#

圧縮リストは Redis がメモリを節約するために開発したもので、連続したメモリブロックで構成される順序型データ構造で、配列に似ています。
圧縮リストには欠点もあります:

  • 多くの要素を保存できないため、クエリ効率が低下します;
  • 新しい要素を追加したり、特定の要素を変更したりする際に、圧縮リストが占有するメモリ空間を再割り当てする必要があり、連鎖更新の問題を引き起こす可能性があります。

したがって、Redis オブジェクト(List オブジェクト、Hash オブジェクト、Zset オブジェクト)に含まれる要素の数が少ない場合や、要素の値が小さい場合にのみ圧縮リストを底層データ構造として使用します。

構造設計#

圧縮リストのヘッダーには 3 つのフィールドがあります:

  • zlbytes は圧縮リスト全体が占有するメモリバイト数を記録します;
  • zltail は圧縮リストの「尾部」ノードが開始アドレスから何バイト離れているか、つまりリストの尾のオフセットを記録します;
  • zllen は圧縮リストに含まれるノードの数を記録します;
  • zlend は圧縮リストの終了点を示し、固定値 0xFF(10 進数 255)です。

圧縮リスト内で、最初の要素と最後の要素を検索する場合、ヘッダーの 3 つのフィールド(zllen)の長さを直接使用して位置を特定でき、計算量は O (1) です。他の要素を検索する場合は、これほど効率的ではなく、1 つずつ検索する必要があるため、その場合の計算量は O (N) になります。したがって、圧縮リストは多くの要素を保存するのには適していません。

さらに、圧縮リストノード(entry)の構成は次のようになります:

  • prevlen は「前のノード」の長さを記録し、後ろから前に遡るために使用されます;
  • encoding は現在のノードの実際のデータの「タイプと長さ」を記録します。タイプは主に 2 種類:文字列と整数です。
  • data は現在のノードの実際のデータを記録し、タイプと長さは encoding によって決まります。

このように、データのサイズとタイプに応じて異なるスペースサイズを割り当てる設計思想は、Redis がメモリを節約するために採用したものです。

prevlen と encoding がどのようにデータのサイズとタイプに応じて異なるスペースサイズを割り当てるかをそれぞれ説明します。

圧縮リストの各ノード内の prevlen 属性は「前のノードの長さ」を記録しており、prevlen 属性のスペースサイズは前のノードの長さに依存します。たとえば:

  • 前のノードの長さが 254 バイト未満の場合、prevlen 属性は 1 バイトのスペースを使用してこの長さ値を保存する必要があります;
  • 前のノードの長さが 254 バイト以上の場合、prevlen 属性は 5 バイトのスペースを使用してこの長さ値を保存する必要があります;

encoding 属性のスペースサイズはデータが文字列か整数か、または文字列の長さに依存します。以下の図(下の図の content は実際のデータ、つまり本文の data フィールドを示します):

  • 現在のノードのデータが整数の場合、encoding は 1 バイトのスペースを使用してエンコードされ、つまり encoding の長さは 1 バイトです。encoding を通じて整数タイプが確認されると、整数データの実際のサイズが確認できます。たとえば、encoding が int16 整数であることが確認されれば、data の長さは int16 のサイズになります。
  • 現在のノードのデータが文字列の場合、文字列の長さに応じて、encoding は 1 バイト / 2 バイト / 5 バイトのスペースを使用してエンコードされます。encoding の最初の 2 ビットはデータのタイプを示し、残りのビットは文字列データの実際の長さ、つまり data の長さを示します。

連鎖更新#

圧縮リストに新しい要素を追加したり、特定の要素を変更したりする際に、スペースが不足している場合、圧縮リストが占有するメモリ空間を再割り当てする必要があります。そして、新しく挿入された要素が大きい場合、後続の要素の prevlen が占有するスペースがすべて変更される可能性があり、「連鎖更新」問題を引き起こし、各要素のスペースがすべて再割り当てされ、圧縮リストのパフォーマンスが低下します。
したがって、圧縮リストは保存するノードの数が少ないシーンでのみ使用されます。ノードの数が十分に少ない場合、連鎖更新が発生しても受け入れられます。

ハッシュテーブル#

ハッシュテーブルは配列(dictEntry **table)であり、配列の各要素は「ハッシュテーブルノード(dictEntry)」へのポインタです。
dictEntry 構造には、キーと値へのポインタだけでなく、次のハッシュテーブルノードへのポインタも含まれており、このポインタは複数のハッシュ値が同じキーと値のペアをリンクするために使用され、ハッシュ衝突の問題を解決します。これが連鎖ハッシュです。

ハッシュ衝突#

連鎖ハッシュ#

実装方法は、各ハッシュテーブルノードに next ポインタを持たせ、次のハッシュテーブルノードを指すために使用します。したがって、複数のハッシュテーブルノードは next ポインタを使用して単方向リストを構成し、同じハッシュバケットに割り当てられた複数のノードはこの単方向リストで接続され、ハッシュ衝突の問題を解決します。

連鎖ハッシュの限界も明らかで、リストの長さが増加するにつれて、特定の位置のデータを照会するのにかかる時間が増加します。結局、リストの照会の時間計算量は O (n) です。

この問題を解決するには、rehash を行う必要があります。つまり、ハッシュテーブルのサイズを拡張する必要があります。

Rehash#

ハッシュテーブルを実際に使用する際、Redis は dict 構造体を定義しており、この構造体には 2 つのハッシュテーブル(ht [2])が定義されています。2 つのハッシュテーブルが定義されている理由は、rehash を行う際に 2 つのハッシュテーブルが必要になるからです。
通常のサービスリクエスト段階では、挿入されたデータはすべて「ハッシュテーブル 1」に書き込まれ、この時点で「ハッシュテーブル 2」はまだスペースが割り当てられていません。

データが徐々に増加し、rehash 操作がトリガーされると、このプロセスは 3 つのステップに分かれます:

  • 「ハッシュテーブル 2」にスペースを割り当てます。通常、「ハッシュテーブル 1」の 2 倍のサイズになります;
  • 「ハッシュテーブル 1」のデータを「ハッシュテーブル 2」に移行します;
  • 移行が完了した後、「ハッシュテーブル 1」のスペースは解放され、「ハッシュテーブル 2」が「ハッシュテーブル 1」として設定され、その後「ハッシュテーブル 2」に新しい空のハッシュテーブルが作成され、次回の rehash の準備が整います。
    image
    2 つ目のステップには問題があります。「ハッシュテーブル 1」のデータ量が非常に大きい場合、「ハッシュテーブル 2」に移行する際に、大量のデータコピーが関与し、この時点で Redis がブロックされ、他のリクエストにサービスを提供できなくなる可能性があります。

トリガー条件:
image
rehash 操作をトリガー

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。