バックグラウンド
- 同様のユーザーセンターがあり、user_id + id番号をキーとして何百万ものユーザーがredisに保存されています。キーを照合するクエリのプレフィックスとしてuser_を使用する必要がありますが、この運用コマンドを実行するとサービスがフリーズし、redisにリンクタイムアウトが発生します。最後の行で見つかった問題は、キーで見つかったキーが多すぎるために発生した問題です。特定の理由は彼の注文の原則からです
- 最終的な解決策は、scanコマンドを使用することです。
キー
前書き
- あいまい一致は、単純な正則化、ページングなし、カーソルなしで実行できます。それは力ずくの探索と全探索です。
- 利点は便利であり、唯一の欠点です。Redisはシングルスレッドです。つまり、スレッドにクエリが多すぎると、クエリ時間が長くなり、他のスレッドがブロックまたはタイムアウトします。クエリの時間の複雑さはO(n)です
スキャン
前書き
- スキャンの複雑さはO(n)で、カーソルはスレッドをブロックせずに段階的なクエリに使用できます
- ファジーマッチングはキーと同じように実行できますが、返されたカーソルを移動する必要があるたびに、制限を使用してエントリの最大数を制限できます。これは、(http://doc.redisfans.com/key/scan .html#scan)
- カーソルによって返されるデータは、空の場合もあれば、複数ある場合もあります。返されたカーソルが0でない限り、データがなくなったわけではありません。
- ただし、まだ問題があります。つまり、重複するキー値を返す可能性があります。これには、アプリケーションで重複排除を実行する必要があります
。2つのデータ構造自体を繰り返すことができないため、ストレージまたはマップにsetを使用できます。
SCAN内部探索
-
グローバルredisはキーと値のストレージを使用し、その基礎となるデータ構造のdict辞書を使用します。辞書の内部ストレージはJavaのハッシュマップに似ており、その最下層は配列とリンクリストによって実現されます。
-
dictに格納するキーは、以下の配列の添え字です。配列の次の表は、ハッシュ値を計算することによって取得されます。リンクされたリストがあるのは、ハッシュの競合のためです。
-
scanを使用する場合、scanのカーソルは配列の添え字になりますが、ハッシュの計算後に格納されるため、配列に順次格納されることはなく、配列のセクションに値が存在する場合と存在しない場合があります。値。スロットに複数の値があることも可能です。そのため、以下に示すように、インクリメンタルプロセスに複数のゼロスキャンがある理由です。
-
配列の添え字の順序に従うのが便利な場合、展開後に再ハッシュする必要があるため、展開した場合はどうすればよいですか。配列の添え字の位置が変更されます。このプロセスでは、展開前にスキャンによって返されたカーソルは正確ではありません。まだ?
-
テーブルの拡張のためのRedisのソリューションは以下である:代わり端に第一次元アレイのビット0から横断する、それが使用
横断する高次キャリー加算を。トラバーサルにこのような特別な方法を使用する理由は、ディクショナリの拡張と縮小を考慮する
ときに、スロットトラバーサルの重複と省略を回避するためです。 -
ハイビットキャリー加算を選択する主な理由は、その拡張特性です。これは、hashMapに似ています。これを使用します。
* JavaのHashMapには拡張の概念があります。loadFactorがしきい値に達すると
、サイズの2倍の新しい配列を再割り当てする必要があります。 、次にすべての要素を新しい配列にリハッシュします。Rehashは、
配列の長さに対する要素のハッシュ値の剰余演算です。長さが変更されたため、各要素が接続されているスロットも変更された可能性があります
。また、配列の長さは2 n(展開時に容量が2 n になる理由)であるため、モジュロ演算はビットAND演算と同等です。
抽象的に言えば、開始スロットの2進数がxxxであるとすると、スロット内の要素は
0xxxおよび1xxx(xxx + 8)に再ハッシュされます。辞書の長さが16ビットから32ビットに拡張される
と、バイナリスロットxxxxの要素は0xxxxおよび1xxxx(xxxx + 16)に再ハッシュされます。*
a mod 8 = a & (8-1) = a & 7
a mod 16 = a & (16-1) = a & 15
a mod 32 = a & (32-1) = a & 31
次の図は、縮小と拡大の比較です
したがって、redisはトラバースに高次キャリー加算を使用します。
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); Java 1.8计算hash的方法
- また、拡張時にはコピーとリハッシュが実行され、redis内のデータ量が多いため、1回限りのリハッシュによりストールの問題が発生します。したがって、redisはプログレッシブリハッシュを使用します。つまり、リハッシュは一度に実行されずにゆっくりと続行されるため、これもスキャンプロセス中に注意すべき点です。つまり、新旧両方の辞書がスキャンされ、最終的にマージされて返されます。
- 鍵についてもう一度考えますが、上記の問題を考慮する必要はありませんか?、常に毎回全量をスキャンするため、拡張について心配する必要はありません。
総括する
- Redisスキャンとキーの使用
- スキャン内部スキャンの概要
- dictの基本的なデータ構造と拡張の一般的なプロセス
参照
「
Redis Deep Adventure」のreidsコマンドリファレンス:http://doc.redisfans.com/key/scan.html#scan