概要#
Redis とは何か#
Redis はメモリ高速キャッシュデータベースです。正式名称は:Remote Dictionary Server(リモートデータサービス)です。
Redis は C 言語で書かれており、キー・バリューのストレージシステムで、string、list、set、zset、hash のデータ型をサポートしています。
Redis はキャッシュ、イベントの発行または購読、高速キューなどのシナリオで使用できます。メモリベースで、永続化が可能です。
Redis の利点#
- 読み書き性能が優れている
- 豊富なデータ型
- 原子性
- 豊富な機能(発行・購読、通知、期限切れ)
- 永続化をサポート(RDB および AOF 方式の永続化)
- 分散型
Redis でできること#
- データキャッシュ
- 限定ビジネス
- カウンター(incrby)
- 分散ロック(Redisson を基にした分散ロックの実装)
- レート制限(Redis の List をメッセージキューとして使用)
- メッセージキュー
インストール / 起動#
インストール#
起動#
# フロントグラウンドで起動
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
使用シーン#
- キャッシュ:Redis を MySQL キャッシュとして使用し、MySQL の読み書きの負担を軽減する
- カウンター:Redis はシングルスレッドの利点
- セッション: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 を形成する
使用シーン#
- メッセージキュー
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>
差を返す
使用シーン#
- いいね、リツイート、コレクションなど
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 を使用する
使用シーン#
- キャッシュ
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 から始まる)
データ構造#
ハッシュ、スキップリスト
使用シーン#
- ランキング
特殊データ型(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 つの状態のみを持ちます。
使用シーン#
- ユーザーのログイン情報を統計、ログインと未ログイン
- ユーザーのいいね情報を統計、いいねしたとしない
- 従業員の打刻情報を統計、打刻したとしない
コマンド#
-
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
指定されたメンバーの一定の半径内にある他のメンバーを表示します。
Redis のデータ構造#
- 5 つの基本データ構造:string(文字列)、list(リスト)、set(集合)、hash(ハッシュ)、zset(ソート済み集合)
- 3 つの特殊データ構造:HyperLogLogs(基数統計)、Bitmap(ビットストレージ)、Geospatial(地理位置)
対応関係#
動的文字列 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 は
len
とalloc
を使用して空間の事前割り当てと遅延空間の解放を実現します。- 空間の事前割り当て:文字列の空間を拡張する際、必要以上に拡張し、文字列の長さが増加する際に必要なメモリの再割り当て回数を減らします。文字列が 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 と異なることはなく、主な違いはハッシュ衝突の解決と拡張です。
ハッシュ衝突#
チェインアドレス法を使用します。
拡張#
拡張手順:
- 元のハッシュテーブルの占めるスペースのサイズに基づいて新しいハッシュテーブルを作成し、サイズは元のハッシュテーブルの 2 倍にします(収縮も元のサイズの半分にします)。
- ハッシュアルゴリズムを使用してインデックス値を再計算します。
- 新しいテーブルが作成され、値が割り当てられた後、元のテーブルを削除します。
拡張が発生する条件
- Redis が
bgsave
またはbgrewriteaof
コマンドを実行していない場合、負荷係数が 1 以上である。 - Redis が
bgsave
またはbgrewriteaof
コマンドを実行していない場合、負荷係数が 5 以上である。
負荷係数 = ハッシュテーブル内のノード数 / ハッシュテーブルのサイズ
整数集合 IntSet#
intset は Redis の集合型の底層実装の 1 つであり、集合が整数値要素のみを含み、要素数が少ない場合、Redis は intset を集合キーの底層実装として使用します。
メモリ構造#
encoding
エンコーディング方式length
整数の個数を保存します。contents
実際に数値を保存する連続メモリ領域を指します。
拡張#
intset の底層は配列であり、新しく挿入された要素のサイズが元の配列の最大要素のサイズを超えると、全体の配列を拡張する必要があります。各要素が占めるスペースのサイズは最大要素のサイズに変更されます。
拡張は行われますが、縮小は行われません!
スキップリスト ZSkipList#
スキップリストの目的は、パフォーマンスを向上させることです。スキップリストは対数時間内に挿入、検索、削除を完了できます。しかし、スキップリストは占めるスペースが大きく、時間を節約するデータ構造に分類されます。
スキップリストとは何か#
従来のリンクリストでは、要素を検索するには、最初から最後までリスト全体を走査する必要があり、時間複雑度は O (n) です。スキップリストは多層インデックスを追加することで走査の長さを減らし、検索速度を向上させます。スキップリストはバランス木やハッシュテーブルに似た効果を実現します。
なぜバランス木を使用せず、スキップリストを使用するのか