banner
Violet

Violet's Blog

A Web <Developer />. Code_ for Fun.
x
github
bilibili
email

Redisの基本とデータ構造

概要#

Redis とは何か#

Redis はインメモリの高速キャッシュデータベースです。正式名称は:Remote Dictionary Server(リモート辞書サーバー)です。

Redis は C 言語で書かれたキー・バリュー型ストレージシステムで、string、list、set、zset、hash などのデータ型をサポートしています。

Redis はキャッシュ、イベントの発行または購読、高速キューなどのシナリオで使用できます。メモリベースで、永続化が可能です。

Redis の利点#

  • 読み書き性能が優れている
  • 豊富なデータ型
  • 原子性
  • 豊富な機能(発行・購読、通知、期限切れ)
  • 永続化をサポート(RDB および AOF 方式での永続化)
  • 分散型

Redis でできること#

  • データキャッシュ
  • 限定ビジネス
  • カウンター(incrby)
  • 分散ロック(Redisson を基にした分散ロックの実装)
  • レート制限(Redis の List をメッセージキューとして使用)
  • メッセージキュー

インストール / 起動#

インストール#

Centos8 に Redis をインストールする🔗

起動#

# フロントグラウンドで起動
redis-server
# バックグラウンドで起動
brew services start redis
# 停止
brew services stop redis

接続#

redis-cli

デフォルトポート 6379

Redis はデフォルトで 16 のデータベースを持ち、0-15 です

select 1を使用して切り替え

基本コマンド#

  • keys * 現在のデータベースにどのようなキーがあるかを確認する(16 のデータベースがあります)
  • exists key このキーが存在するかどうかを判断する
  • type key このキーのタイプを確認する
  • del key または unlink key 特定のキーを削除する
    • unlinkは非同期削除
  • expire key 10 キーの有効期限を秒で設定する
  • ttl key キーが何秒で期限切れになるかを確認する -1 は永遠に期限切れにならない -2 はすでに期限切れ
  • select データベースを切り替える
  • dbsize 現在のデータベースにあるキーの数を確認する
  • flushdb 現在のデータベースのすべてのキーをクリアする

基本データ型(5 種類)#

string(文字列)#

  • string 型はバイナリセーフで、任意のデータを含むことができます
  • string は Redis の基本データ型で、1 つの値の最大は 512Mb です

コマンド#

  • set <key> <value>
  • get <key> 確認する
  • append <key> <value> キーの後に値を追加する
    • このキーが存在しない場合は作成されます
  • strlen <key> このキーに対応する値の長さを取得する
  • setnx <key> <value> キーが存在しない場合のみ作成する
  • incr <key> このキーの値を + 1 する
  • decr <key> 値を - 1 する
  • incrby/decrby <key> <num> キーの値に num を加算または減算する

これらの操作は原子性操作であり、操作中に他のスレッドによって中断されることはありません。なぜなら Redis はシングルスレッドだからです。

  • mset <key> <value> <key> <value> <key> <value> 一度に複数のキーを設定する
  • mget <key> <key> <key> 一度に複数のキーを取得する
  • msetnx <key> <value> <key> <value> 一度に複数のキーを設定する、キーが存在しない場合
    • 原子性操作 1 つが失敗するとすべてが失敗する
  • getrange <key> <開始> <終了> キーの中の第 <開始> から < 終了 > までのすべての値を取得する
  • setrange <key> <offset> <value> キーの offset 以降に値を上書き設定する
  • setex <key> <有効期限> <value> キーを設定する際に有効期限を設定する
  • get set <key> <value> 以前の値を取得し、その後新しい値を設定する

データ構造#

Arraylist

使用シーン#

  1. キャッシュ:Redis を MySQL キャッシュとして使用し、MySQL の読み書き負荷を軽減する
  2. カウンター:Redis のシングルスレッドの利点
  3. セッション:spring session + redis によるセッション共有

list(リスト)#

単一キーの多値、基盤は双方向リスト

コマンド#

  • lpush/rpush <key> <v1> <v2> <v3> 双方向リストの左右から値をプッシュする
  • lrange <key> 0 -1 リストを列挙し、すべてを取得する
  • rpop/lpop <key> 左右から値を取得して削除する
  • rpoplpush <k1> <k2> k1 の右から 1 つの値をポップし、k2 の左にプッシュする
  • lindex <key> index index 番目の値を取得する
  • llen <k> 長さを取得する
  • linsert before/after <v1> <v2> v1 の前 / 後に v2 を挿入する
  • lrem <key> <n> <v> 左から n 個の値を削除する
  • lset <k> <index> <v> index を v に設定する

データ構造#

要素が少ない場合は ziplist、要素が多くなると複数の ziplist を組み合わせて quicklist になります。

使用シーン#

  1. メッセージキュー

set(集合)#

set 集合の要素は重複できず、基盤はハッシュテーブルで、追加、削除、変更、検索はすべて O (1) の複雑度です。

コマンド#

  • sadd <k> <v1> <v2> 追加する
  • smembers <k> この集合にあるすべての値を取得する
  • sismember <k> <v> この値が存在するかどうかを判断する
  • scard <k> この集合の長さを返す
  • srem <k> <v> 削除する
  • spop <k> ランダムに 1 つを取得して削除する
  • srandmember <k> <n> ランダムに n 個を取得し、削除しない
  • smove <k1> <k2> <v> k1 から値を取り出し、k2 に移動する
  • sinter <k1> <k2> 交差を返す
  • sunion <k1> <k2> 和集合を返す
  • sdiff <k1> <k2> 差集合を返す

使用シーン#

  1. いいね、リツイート、コレクションなど

hash(ハッシュ)#

java の map<string,object> に似ており、key はオブジェクト名、value の中には field と value があります。

コマンド#

  • hset <k> <field> <value> キーとキーの中の field と value を設定する
  • hget <k> <field> k の中の field の値を取得する
  • hmset <k> <field1> <v1> <field2> <v2> 一度に複数を設定する
  • hexists <k1> <field> キーの中の field が存在するか確認する
  • hkeys <key> このキーのすべての field を取得する
  • hvals <key> このキーのすべての value を取得する、field はない
  • hincrby <k> <f> <n> f の value に n を加算する
  • hsetnx <k> <f> <v> field が存在しない場合に value を設定する

データ構造#

field-value の長さが短く、個数が少ない場合は ziplist を使用し、そうでない場合は hashtable を使用します。

使用シーン#

  1. キャッシュ

Zset(ソート済み集合)#

ソート済み集合

各要素にはスコア(score)があり、このスコアを使用して集合をソートします。スコアは重複可能です。

加重ソート set は、Java の優先キューに似ていますが、Redis ではキューを set に置き換えています。

コマンド#

  • zadd <k> <score> <k> <score> <k> <score> キーとスコアを追加し、スコアに基づいて順位を決定します。
  • zrange <k> <start> <stop> withscores スコアが開始と終了の間にある要素を取得します。
  • zrangebyscore <k> <min> <max> 最小スコアと最大スコアの間の要素を返します。
  • zincrby <k> <n> <v> 要素 v に n のスコアを追加します。
  • zrem <k> <v> 削除します。
  • zcount <k> <min> <max> min スコアと max スコアの間の要素の数をカウントします。
  • zrank <k> <v> この要素の順位を取得します。0 から始まります。

データ構造#

ハッシュと跳躍リスト

使用シーン#

  1. ランキング

特殊データ型(3 種類)#

以下の内容の学習はすべてここから来ており、コードスニペットを使用しています。

原文アドレス🔗

HyperLogLogs(基数統計)#

基数とは何か#

2 つの集合の全集

例えば:A = {1,2,3,4,5,6} ; B = {5,6,7,8,9} ; 基数 = {1,2,3,4,7,8,9} 、すなわち重複しない要素。

使用シーン#

統計とカウント、登録 IP 数の統計、毎日の訪問 IP 数、ページのリアルタイム UV、オンラインユーザー数などに使用されます。

利点#

基数推定アルゴリズムに基づいており、基数を比較的正確に推定できます。少量の固定メモリを使用して集合内のユニークな要素を識別できます。

このアルゴリズムは必ずしも正確ではなく、0.81% の誤差率があります。基数統計は、訪問者数の統計など、一定の許容誤差を受け入れるシーンに適しています。

コマンド#

127.0.0.1:6379> pfadd key1 a b c d e f g h i	# 第一組の要素を作成
(integer) 1
127.0.0.1:6379> pfcount key1					# 要素の基数をカウント
(integer) 9
127.0.0.1:6379> pfadd key2 c j k l m e g a		# 第二組の要素を作成
(integer) 1
127.0.0.1:6379> pfcount key2
(integer) 8
127.0.0.1:6379> pfmerge key3 key1 key2			# 二組をマージ:key1 key2 -> key3 和集合
OK
127.0.0.1:6379> pfcount key3
(integer) 13

Bitmap(ビットストレージ)#

Bitmap はバイナリで情報を記録し、0 と 1 の 2 つの状態のみを持ちます。

使用シーン#

  1. ユーザーのログイン情報を統計、ログインと未ログイン
  2. ユーザーのいいね情報を統計、いいねしたと未いいね
  3. 従業員の打刻情報を統計、打刻したと未打刻

コマンド#

  • setbit <key> <offset> <value> データを書き込む。ここで key は任意、offset は整数のみ、value は 0 または 1 のみです。

    従業員の 1 週間の打刻状況を統計する場合、key は sign に設定し、offset は 0-6 に設定し、打刻は 0、未打刻は 1 とします。

127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 1
(integer) 0
  • getbit <key> <offset> を使用して特定のキーの状態を確認し、0 または 1 を返します。
127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 5
(integer) 0
  • bitcount <key> を使用して特定のデータの合計をカウントします。
127.0.0.1:6379> bitcount sign
(integer) 3

geospatial(地理位置)#

ユーザーが地理的位置情報を推測できるようにし、2 地点間の距離などを計算します。

コマンド#

  • geoadd 位置を追加します。
127.0.0.1:6379> geoadd china:city 118.76 32.04 nanjing 112.55 37.86 taiyuan 123.43 41.80 shenyang
(integer) 3
127.0.0.1:6379> geoadd china:city 144.05 22.52 shengzhen 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 3
  • geopop 指定したメンバーの地理的位置を取得します。
127.0.0.1:6379> geopos china:city taiyuan nanjing
1) 1) "112.54999905824661255"
   1) "37.86000073876942196"
2) 1) "118.75999957323074341"
   1) "32.03999960287850968"
  • geodist 2 地点間の距離を取得し、単位を指定できます。
127.0.0.1:6379> geodist china:city taiyuan shenyang m
"1026439.1070"
127.0.0.1:6379> geodist china:city taiyuan shenyang km
"1026.4391"
  • georadius

ここに詳細な説明があります🔗

近くの人々を取得し、すべての近くの人々の住所、位置を取得し、半径を通じてクエリします。

  • georadiusbymember

link🔗

指定されたメンバーの一定の半径範囲内の他のメンバーを表示します。

Redis のデータ構造#

  • 5 つの基本データ構造:string(文字列)、list(リスト)、set(集合)、hash(ハッシュ)、zset(ソート済み集合)
  • 3 つの特殊データ構造:HyperLogLogs(基数統計)、Bitmap(ビットストレージ)、Geospatial(地理位置)

対応関係#

画像出典

image

動的文字列 SDS#

Redis は C の文字列を使用せず、独自にデータ構造を定義しています。すなわち、シンプルダイナミックストリング(Simple Dynamic String SDS)です。Redis の String の基盤データ構造は SDS を使用しています。

SDS のメモリ構造#

  • len SDS が保存する文字列の長さを保持します。
  • buf[] 配列が文字列の各要素を保存します。
  • alloc uint8、uint16、uint32、uint64 で全体の SDS を表します。
  • flags 常に 1 文字で、低位の 3 ビットがヘッダーのタイプを示します。

SDS の利点#

  • O (1) の複雑度で文字列の長さを取得:Redis では文字列の長さを取得するには len 属性を読み取るだけで済みますが、C では文字列の長さを取得するには全体を走査する必要があり、O (n) の複雑度になります。strlen <K>を使用して文字列の長さを取得します。
  • バッファオーバーフローを防ぐ:C では、strcatを使用して 2 つの文字列を結合する際、十分なメモリが割り当てられていないとバッファオーバーフローを引き起こします。SDS は len に基づいて結合後の長さがメモリ要件を満たすかどうかを推測します。満たさない場合は、まず拡張し、その後結合することでバッファオーバーフローを防ぎます。
  • 文字列のメモリ再割り当て回数を減らす:SDS はlenallocを使用してスペースの事前割り当て遅延スペース解放を実現します。
    • スペースの事前割り当て:文字列のスペースを拡張する際、必要以上に拡張し、文字列の長さが増加する際のメモリ再割り当て回数を減らします。文字列が 1M 未満の場合、Redis は文字列サイズの 2 倍のスペースを割り当て、1M を超える場合は追加で 1M のスペースを割り当てます。
    • 遅延スペース解放:文字列の長さが減少した場合、すぐにメモリを返却せず、alloc属性を使用してこれらのスペースを記録し、後で使用します。この方法がスペースを無駄にすると思われる場合は、未使用スペースの定期的な解放を自分で設定できます。
  • バイナリセーフ:C では空文字を使用して文字列の終了を示します。しかし、Redis に保存されるバイナリファイル(画像など)には空文字が含まれている可能性があり、C では正しく読み取れません。SDS はlen属性で示される長さを使用して文字列の終了を判断します。

圧縮リスト ZipList#

ziplist は、ストレージ効率を向上させるために設計された特殊なエンコーディングの双方向リストです。文字列または整数を保存するために使用できます。

ziplist のメモリ構造#

  • zlbytes ziplist が占めるメモリのバイト数を保存します。
  • zltail ziplist の最後のエントリのオフセットを保存し、最後のエントリを迅速に特定するために使用します。これにより、迅速なポップ操作が可能になります。
  • zllen ziplist のエントリの数を保存します。
  • zlend 終了バイトです。

Entry 構造#

  • prevlen 前のエントリのサイズ
  • encoding 現在のエントリのタイプと長さ
  • entry-data エントリデータ

PS:entry-data に整数データが保存されている場合、encoding は entrydata と結合され、この場合の entry 構造は となります。

ziplist の利点#

メモリを節約します。

  • 通常のリストでは、各要素が占めるスペースのサイズが一致し、現在のリストの最大要素が占めるスペースのサイズとなり、スペースの無駄が生じます。
  • ziplist は特殊なデータ構造を使用して、各要素が自身のサイズと同じスペースを占めるようにし、スペースの無駄を減らします。
  • 伝統的なリストはメモリ内でアドレスが連続していますが、ziplist は占めるサイズが不一致であるため、アドレスが連続せず、prelenフィールドを使用して前の要素の長さを保存して走査問題を解決します。

ziplist の欠点#

メモリスペースを事前に確保せず、リスト内の要素が削除された後、リストの無駄なスペースが即座に返却されるため、リストを変更するたびにスペースを再割り当てする必要があります。

クイックリスト QuickList#

ziplist をノードとする双方向リストです。QuickList を理解する方法:List<List<ziplist>> quickList

quicklist のメモリ構造#

  • quicklistNode リスト内のノードで、quicklist ではこのノードが ziplist です。
  • quickListLZF ziplist が LZ4 アルゴリズムで圧縮された後、quickListLZF構造に包装できます。
  • quicklistBookmark quicklist の末尾に位置します。
  • quicklist
    • head、tail は先頭と末尾のポインタを指します。
    • len はリスト内のノードを示します。
    • count は quicklist 内の ziplist のエントリ数を示します。
    • fill は各 quicklist 内の ziplist が占める最大スペースを制御します。
    • compress は各 ziplist を LZ4 アルゴリズムで圧縮してスペースを節約するかどうかを制御します。
  • quicklistIter イテレータ
  • quicklistEntry ziplist 内のエントリをラップします。

ハッシュテーブル - Dict-HashTable#

ハッシュテーブルはほとんどの言語の HashTable と同様ですが、主にハッシュ衝突の解決と拡張に違いがあります。

ハッシュ衝突#

チェーンアドレス法を使用します。

拡張#

拡張手順:

  1. 元のハッシュテーブルの占有スペースサイズに基づいて新しいハッシュテーブルを作成し、サイズは元のハッシュテーブルの 2 倍にします(縮小も元のサイズの半分にします)。
  2. ハッシュアルゴリズムを使用してインデックス値を再計算します。
  3. 新しいテーブルが作成され、値が割り当てられた後、元のテーブルを削除します。

拡張が発生する条件

  1. Redis がbgsaveまたはbgrewriteaofコマンドを実行していない場合、負荷因子が 1 以上です。
  2. Redis がbgsaveまたはbgrewriteaofコマンドを実行していない場合、負荷因子が 5 以上です。

負荷因子 = ハッシュテーブル内のノード数 / ハッシュテーブルのサイズ

整数集合 IntSet#

intset は Redis の集合型の底層実装の 1 つであり、集合が整数値要素のみを含み、要素数が少ない場合、Redis は intset を集合キーの底層実装として使用します。

メモリ構造#

  • encoding エンコーディング方式
  • length 整数の個数を保存します。
  • contents 実際に保存される数値の連続メモリ領域を指します。

拡張#

intset の底層は配列であり、新しく挿入される要素のサイズが元の配列の最大要素のサイズを超えると、全体の配列を拡張する必要があります。各要素が占めるスペースのサイズは最大要素のサイズに変更されます。

拡張は行われますが、縮小は行われません!

跳表 ZSkipList#

跳躍表の目的は、性能を向上させることです。跳躍表は、対数時間内に挿入、検索、削除を完了できます。しかし、跳躍表は占めるスペースが大きく、時間を節約するためのデータ構造です。

跳躍表とは何か#

従来のリンクリストでは、要素を検索する必要がある場合、検索要素を見つけるまでリスト全体を先頭から走査する必要があります。時間複雑度は O (n) です。跳躍表は多層インデックスを追加することで走査の長さを減少させ、検索速度を向上させます。跳躍表は平衡木やハッシュテーブルに似た効果を実現しています。

なぜ平衡木を使用せず、跳躍表を使用するのか

リンク🔗

画像出典

image

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