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>双方向リストの左右から値をプッシュする
  • Large <key> 0 -1リストを列挙し、すべてを取得する
  • rpop/lpop <key>左右から値を取得して削除する
  • rpoplpush <k1> <k2>k1 の右側から 1 つの値をポップし、k2 の左側にプッシュする
  • lindex <key> indexインデックスの値を取得する
  • llen <k>長さを取得する
  • linsert before/after <v1> <v2>v1 の前 / 後に v2 を挿入する
  • lrem <key> <n> <v>リストから n 個の値を削除する
  • lset <k> <index> <v>インデックスを v に設定する

データ構造#

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

使用シーン#

  1. メッセージキュー

set(集合)#

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

コマンド#

  • sadd <k> <v1> <v2>追加する
  • smember <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 の value を取得する
  • 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		# 2番目の要素グループを作成
(integer) 1
127.0.0.1:6379> pfcount key2
(integer) 8
127.0.0.1:6379> pfmerge key3 key1 key2			# 2つのグループをマージ: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"
  • geodist2 地点間の距離を取得し、単位を指定できます。
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はそれぞれ unint8、unint16、unint32、unint64 で全体の 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は終了バイトです。

エントリ構造#

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

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

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

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