序文
最近、Redis関連の知識を学び、AliのRedis開発仕様とRedis開発および運用と保守の本を読みました。これは、使用仕様、ピットコマンド、実際のプロジェクト操作、および操作と保守の構成の4つの方向に分かれています。Redisを使用する際の21の注意点を整理しました。皆さんのお役に立てば幸いです。一緒に学びましょう。
パブリックアカウント:カタツムリを拾う少年
1.Redisの使用仕様
1.1、主要な仕様ポイント
Redisキーを設計するときは、次の点に注意する必要があります。
- キーの競合が上書きされないように、ビジネス名の前にキーを付け、コロンで区切ります。たとえば、live:rank:1
- キーのセマンティクスを明確にするために、キーの長さは可能な限り30文字未満にする必要があります。
- キーには、スペース、改行、一重引用符と二重引用符、その他のエスケープ文字などの特殊文字を含めることはできません。
- Redisキーは、使用されていないキーを時間内にクリーンアップまたは削除できるようにttlを設定しようとします。
1.2、値指定の要点
Redisの値を任意に設定することはできません。
最初のポイントは、多数のbigKeyが格納されている場合、問題が発生し、クエリが遅くなったり、メモリが過剰に増加したりするなどの問題が発生することです。
- 文字列型の場合、単一の値のサイズは10k以内に制御されます。
- ハッシュ、リスト、セット、zsetタイプの場合、要素の数は通常5000を超えません。
2番目のポイントは、適切なデータ型を選択することです。多くの小さなパートナーは、set andgetを備えたRedisのString型のみを使用しています。実際、Redisは豊富なデータ構造タイプを提供しており、一部のビジネスシナリオはhash、zset
他のデータ結果を待つのにより適しています。
反例:
set user:666:name jay
set user:666:age 18
复制代码
正例
hmset user:666 name jay age 18
复制代码
1.3。キーの有効期限を設定し、さまざまなビジネスのキーに注意を払い、有効期限を少し広げてみてください
- Redisデータはメモリに保存され、メモリリソースは非常に貴重であるためです。
- 通常、Redisはデータベースではなくキャッシュとして使用するため、キーのライフサイクルは長すぎないようにする必要があります。
- したがって、有効期限を設定するには、通常、expireを使用することをお勧めします。
ある時点で多数のキーが期限切れになると、Redisがスタックしたり、期限切れになったときにアバランシェをキャッシュしたりする可能性があります。したがって、一般に、さまざまなビジネスのキーの有効期限は分散する必要があります。同じビジネスの場合、時間にランダムな値を追加して、有効期限を広げることもできます。
1.4。効率を向上させるために、バッチ操作を使用することをお勧めします
SQLを毎日作成する場合、バッチ操作の方が効率的であることは誰もが知っています。一度に50個のアイテムを更新する方が、50回ループして毎回1個のアイテムを更新するよりも効率的です。実際、Redisの操作コマンドも同じです。
Redisクライアントによって実行されるコマンドは、次の4つのプロセスに分けることができます。1。コマンドの送信-> 2。コマンドのキューイング-> 3。コマンドの実行-> 4。結果を返します。1と4はRRT(コマンド実行ラウンドトリップ時間)と呼ばれます。Redisは、Mget、msetなどのバッチ操作コマンドを提供します。これにより、RRTを効果的に節約できます。ただし、ほとんどのコマンドはhgetallなどのバッチ操作をサポートしておらず、mhgetallは存在しません。パイプラインはこの問題を解決できます。
パイプラインとは何ですか?一連のRedisコマンドをアセンブルし、RTTを介してRedisに送信し、この一連のRedisコマンドの実行結果を順番にクライアントに返すことができます。
まず、パイプラインを使用せずにn個のコマンドを実行したモデルを見てみましょう。
パイプラインを使用してn個のコマンドを実行すると、プロセス全体で1 RTTが必要になり、モデルは次のようになります。
2.Redisに落とし穴があるコマンド
2.1。注意O(n)
コマンドの複雑さ、などhgetall
、smember
、lrange
等
Redisはシングルスレッドでコマンドを実行するためです。hgetallやsmemberなどのコマンドの時間計算量はO(n)です。nが増加し続けると、Redis CPUは急上昇し続け、他のコマンドの実行をブロックします。
hgetall、smember、lrangeなどのコマンドは必ずしも利用できないわけではなく、データ量を総合的に評価し、nの値を明確にしてから決定する必要があります。たとえば、hgetallの場合、ハッシュ要素nがさらにある場合は、最初にhscanを使用できます。
2.2Redisのmonitorコマンドを注意して使用する
Redis Monitorコマンドは、Redisサーバーが受信したコマンドをリアルタイムで出力するために使用されます。クライアントがRedisサーバーに対して実行したコマンド操作を知りたい場合は、Monitorコマンドを使用して表示できますが、通常、デバッグに使用されます。本番環境では使用しないでください。使用してください。監視コマンドにより、redisのメモリが急増し続ける可能性があるためです。
モニターモデルはJiangziで、Redisサーバーで実行されたすべてのコマンドを出力します。一般的に、RedisサーバーのQPSは非常に高く、つまり、モニターコマンドが実行された場合、Redisサーバーはの出力バッファーにあります。モニタークライアント。多くの「インベントリ」があり、Redisのメモリを大量に消費します。
2.3.keysコマンドは実稼働環境では使用できません
Redis Keysコマンドは、特定のパターンに一致するすべてのキーを検索するために使用されます。特定のタイプのキーがRedisにいくつあるかを確認したい場合、多くの友人は次のようにkeysコマンドを使用することを考えています。
keys key前缀*
复制代码
ただし、rediskeys
はマッチングをトラバースO(n)
します。複雑なのは、データベース内のデータが多いほど遅くなることです。redisはシングルスレッドであることがわかっています。データが多い場合、keys命令により、redisスレッドがブロックされ、オンラインサービスも一時停止します。サービスは、命令が実行されるまで再開されません。したがって、通常、実稼働環境では、keysコマンドを使用しないでください。公式文書には次のようにも記載されています。
警告:KEYSは、実稼働環境でのみ細心の注意を払って使用する必要があるコマンドと見なしてください。大規模なデータベースに対して実行すると、パフォーマンスが低下する可能性があります。このコマンドは、デバッグと、キースペースレイアウトの変更などの特別な操作を目的としています。通常のアプリケーションコードではKEYSを使用しないでください。キースペースのサブセットでキーを見つける方法を探している場合は、セットの使用を検討してください。
実際、keysコマンドのようなパターンマッチング機能を提供するscanコマンドを使用できます。その複雑さもO(n)ですが、カーソルを介して段階的に実行され、redisスレッドをブロックしません。ただし、一定の繰り返しの可能性があり、クライアントで1回重複排除する必要があります。
scanはインクリメンタル反復コマンドをサポートし、インクリメンタル反復コマンドにも欠点があります。たとえば、SMEMBERSコマンドを使用すると、現在setキーに含まれているすべての要素を返すことができますが、SCANなどのインクリメンタル反復コマンドの場合はキーのインクリメンタル反復中に、キーは変更される可能性があるため、増分反復コマンドは、返された要素に対して限定的な保証しか提供できません。
2.4fluxhall、flushdbの使用を禁止する
- Flushallコマンドは、Redisサーバー全体のデータをクリアするために使用されます(すべてのデータベースのすべてのキーを削除します)。
- Flushdbコマンドは、現在のデータベース内のすべてのキーをクリアするために使用されます。
これらの2つのコマンドはアトミックであり、実行を終了しません。実行が開始されると、実行は失敗しません。
2.5delコマンドの使用に注意してください
キーを削除するために一般的に使用するコマンドは何ですか?直接デルですか?キーを削除すると、delコマンドを直接使用できます。しかし、デルの時間計算量について考えたことはありますか?状況ごとに説明しましょう。
- 文字列型のキーを削除すると、時間計算量は
O(1)
、直接delできます。 - List / Hash / Set / ZSetタイプを削除すると、その複雑さは
O(n)
、nは要素の数を表します。
したがって、List / Hash / Set / ZSetキーを削除すると、要素が多いほど遅くなります。nが非常に大きい場合は、特に注意してください。メインスレッドがブロックされます。では、delを使用しない場合、どのように削除すればよいでしょうか。
- リストタイプの場合は、
lpop或者rpop
すべての要素が削除されるまで実行できます。- ハッシュ/セット/ Zセットタイプの場合は、最初
hscan/sscan/scan
にクエリを実行してから、実行してhdel/srem/zrem
各要素を順番に削除できます。
2.6 SORT、SINTER、およびその他の非常に複雑なコマンドの使用は避けてください。
より複雑なコマンドを実行すると、より多くのCPUリソースが消費され、メインスレッドがブロックされます。したがってSORT、SINTER、SINTERSTORE、ZUNIONSTORE、ZINTERSTORE
、このような集計コマンドの実行は避ける必要があります。通常は、実行のためにクライアント側に配置することをお勧めします。
3.実際の戦闘とピット回避操作を計画する
3.1分散ロックを使用する際の注意点
分散ロックは、実際には、共有リソースに一緒にアクセスするために分散システムのさまざまなプロセスを制御するロックの実現です。スパイク付きの注文や赤い封筒の取得などのビジネスシナリオには、分散ロックが必要です。主に次の点に注意して、Redisを分散ロックとして使用することがよくあります。
3.1.1 2つのコマンドSETNX + EXPIREは別々に記述されています(典型的なエラー実装例)
if(jedis.setnx(key_resource_id,lock_value) == 1){ //加锁
expire(key_resource_id,100); //设置过期时间
try {
do something //业务请求
}catch(){
}
finally {
jedis.del(key_resource_id); //释放锁
}
}
复制代码
場合はsetnx
ロックが実行され、時間が実行されようとしている、プロセスがクラッシュしたり、ニーズがメンテナンスのために再起動が期限切れに、ロックは「不滅」になり、他のスレッドがロックを取得することはできません一般的なので、分散ロックは、このようにすることはできません。
3.1.2 SETNX +値の値は有効期限です(一部の小規模なパートナーはこの方法で実装しますが、落とし穴があります)
long expires = System.currentTimeMillis() + expireTime; //系统时间+设置的过期时间
String expiresStr = String.valueOf(expires);
// 如果当前锁不存在,返回加锁成功
if (jedis.setnx(key_resource_id, expiresStr) == 1) {
return true;
}
// 如果锁已经存在,获取锁的过期时间
String currentValueStr = jedis.get(key_resource_id);
// 如果获取到的过期时间,小于系统当前时间,表示已经过期
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
// 锁已过期,获取上一个锁的过期时间,并设置现在锁的过期时间(不了解redis的getSet命令的小伙伴,可以去官网看下哈)
String oldValueStr = jedis.getSet(key_resource_id, expiresStr);
if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
// 考虑多线程并发的情况,只有一个线程的设置值和当前值相同,它才可以加锁
return true;
}
}
//其他情况,均返回加锁失败
return false;
}
复制代码
このスキームのデメリット:
- 有効期限はクライアント自体によって生成されます。分散環境では、各クライアントの時刻を同期する必要があります。
- 所有者の一意のIDはなく、他のクライアントによって解放/ロック解除される可能性があります。
- ロックの有効期限が切れると、複数の同時クライアントが同時に要求し、すべて実行され
jedis.getSet()
ます。最終的に、ロックを正常にロックできるのは1つのクライアントだけですが、クライアントロックの有効期限は他のクライアントによって上書きされる可能性があります。
3.1.3:SET拡張コマンド(SET EX PX NX)(起こりうる問題に注意してください)
if(jedis.set(key_resource_id, lock_value, "NX", "EX", 100s) == 1){ //加锁
try {
do something //业务处理
}catch(){
}
finally {
jedis.del(key_resource_id); //释放锁
}
}
复制代码
このソリューションにはまだ問題がある可能性があります。
- ロックの有効期限が切れて解放され、ビジネスはまだ実行されていません。
- ロックが別のスレッドによって誤って削除されました。
3.1.4 SET EX PX NX +は、一意のランダム値を確認して再度削除します(誤って削除する問題は解決されましたが、ロックが期限切れになり、ビジネスが実行されないという問題があります)
if(jedis.set(key_resource_id, uni_request_id, "NX", "EX", 100s) == 1){ //加锁
try {
do something //业务处理
}catch(){
}
finally {
//判断是不是当前线程加的锁,是才释放
if (uni_request_id.equals(jedis.get(key_resource_id))) {
jedis.del(lockKey); //释放锁
}
}
}
复制代码
ここで、現在のスレッドによって追加および解放されたロックがアトミック操作ではないかどうかを判断します。jedis.del()を呼び出してロックを解放すると、ロックは現在のクライアントに属していない可能性があり、他のユーザーによって追加されたロックが解放されます。
通常、代わりにluaスクリプトを使用します。luaスクリプトは次のとおりです。
if redis.call('get',KEYS[1]) == ARGV[1] then
return redis.call('del',KEYS[1])
else
return 0
end;
复制代码
3.1.5 Redissonフレームワーク+ Redlockアルゴリズムは、ロックの期限切れリリース、ビジネスの不完全な実行+スタンドアロンの問題の問題を解決します
Redissonは、Watch dog
ソリューションを使用してロックの有効期限と解放の問題を解決し、ビジネスは実行されません。Redissonの概略図は次のとおりです。
上記の分散ロックには、まだスタンドアロンの問題があります。
スレッド1がRedisマスターノードでロックを取得したが、ロックされたキーがスレーブノードに同期されていない場合。ちょうどこの時点で、マスターノードに障害が発生すると、スレーブノードがマスターノードにアップグレードされます。スレッド2は同じキーのロックを取得できますが、スレッド1はすでにロックを取得しており、ロックのセキュリティが失われます。
スタンドアロンの問題の場合は、Redlockアルゴリズムを使用できます。興味のある友達は私の記事、7つの解決策を読むことができます!Redis分散ロックの正しい使用姿勢について話し合う
3.2キャッシュコヒーレンシに関する注意
- 読み取り要求の場合は、最初にキャッシュを読み取り、次にデータベースを読み取ります
- 書き込み要求の場合は、最初にデータベースを更新してから、キャッシュに書き込みます
- データを更新するたびに、キャッシュをクリアする必要があります
- キャッシュは通常、特定の有効期限の無効化を設定する必要があります
- 整合性要件が高い場合は、biglog + MQを使用して保証できます。
興味のある友人は私の記事を読むことができます:並行環境では、最初にデータベースを操作するべきですか、それとも最初にキャッシュを操作するべきですか?
3.3頻繁に設定されたカバレッジによる、以前に設定された有効期限の無効化を回避するために、Redisの容量を適切に評価します。
Redisのすべてのデータ構造タイプで有効期限を設定できることがわかっています。文字列に有効期限が設定されていて、それをリセットすると、以前の有効期限は無効になるとします。
RedisのsetKey
ソースコードは次のとおりです。
void setKey(redisDb *db,robj *key,robj *val) {
if(lookupKeyWrite(db,key)==NULL) {
dbAdd(db,key,val);
}else{
dbOverwrite(db,key,val);
}
incrRefCount(val);
removeExpire(db,key); //去掉过期时间
signalModifiedKey(db,key);
}
复制代码
実際のビジネス開発では、同時に、Redisの容量を合理的に評価して、有効期限のあるキーが無効になるような頻繁なセットカバレッジを回避する必要があります。初心者Xiaobaiはこの間違いを犯しやすいです。
3.4キャッシュ侵入の問題
まず、一般的なキャッシュの使用方法を見てみましょう。読み取り要求が来たら、最初にキャッシュを確認します。キャッシュに値がヒットした場合は直接戻ります。キャッシュが失敗した場合は、データベースを確認してから値を更新します。データベースをキャッシュに追加してから、戻ります。
キャッシュペネトレーション:特定の存在しないデータをクエリすることを指します。キャッシュが欠落しているため、データベースからクエリを実行する必要があり、データが見つからないため、キャッシュに書き込まれません。これにより、キャッシュが失われます。要求が行われるたびにデータベースに移動する既存のデータクエリを実行し、データベースに圧力をかけます。
素人の言葉で言えば、読み取り要求にアクセスすると、キャッシュもデータベースにも特定の値がないため、この値に対する各クエリ要求がデータベースに浸透します。これがキャッシュの浸透です。
キャッシュの侵入は通常、次の状況によって発生します。
- 不合理な事業の設計は、例えば、ほとんどのユーザーは、警備員を持っていませんが、あなたのすべての要求がキャッシュされている、あなたは、特定のユーザーIDが守られているかどうかを照会することができます。
- キャッシュやデータベースデータなどの業務・運用・開発のミスを誤って削除してしまいました。
- ハッカーは、存在しないビジネスデータを読み取るために意図的に多数の違法な要求を作成するなど、違法な要求攻撃を行います。
キャッシュの侵入を回避する方法は?一般的に3つの方法があります。
-
- 不正なリクエストの場合は、APIの入り口でパラメータをチェックして、不正な値を除外します。
-
- クエリデータベースが空の場合、キャッシュにnull値またはデフォルト値を設定できます。ただし、書き込み要求が発生した場合は、キャッシュの一貫性を確保するためにキャッシュを更新すると同時に、最後にキャッシュに適切な有効期限を設定する必要があります。(ビジネスで一般的に使用され、シンプルで効果的)
-
- ブルームフィルターを使用して、データが存在するかどうかをすばやく判断します。つまり、クエリ要求が受信されると、ブルームフィルタを使用して値が存在するかどうかが判断され、クエリが続行されます。
ブルームフィルターの原理:初期値が0のビットマップ配列とN個のハッシュ関数で構成されます。キーに対してN個のハッシュアルゴリズムを実行してN個の値を取得し、ビット配列でハッシュした後、これらのN個の値を1に設定します。次に、これらの特定の位置がすべて1であるかどうかを確認するときに、ブルームフィルタリングがキーが存在します。
3.5キャッシュスノーベンの問題
Xuebenのキャッシュ:有効期限までのキャッシュ内の大量のデータを指し、クエリデータの量は膨大であり、すべてのリクエストがデータベースに直接アクセスするため、データベースに過度の負荷がかかり、ダウンタイムが発生することもあります。
- Xuebenのキャッシングは、通常、同時に大量のデータが期限切れになることによって発生します。このため、有効期限を均等に設定する、つまり有効期限を比較的離散的にすることで解決できます。大きい固定値+小さいランダム値を使用する場合、5時間+ 0〜1800秒のソースパープル。
- Redisの失敗とダウンタイムも、キャッシュが雪にぶつかる原因となる可能性があります。これには、Redisの高可用性クラスターを構築する必要があります。
3.6キャッシュの故障の問題
キャッシュの内訳:特定の時点でホットキーの有効期限が切れ、この時点でこのキーに対する多数の同時リクエストが発生し、多数のリクエストがデータベースにヒットしたことを示します。
キャッシュの内訳は、実際には少し似ています。実際、2つの違いは、キャッシュXuebenは、データベースに過度のプレッシャーがかかっているか、マシンがダウンしていることを意味します。キャッシュの内訳は、DBデータベースへの多数の同時リクエストです。レベル。内訳はキャッシュXuebenのサブセットであると見なすことができます。一部の記事では、2つの違いは、Xuebenには多くのキーがあるのに対し、内訳はホットキーキャッシュに関するものであると考えています。
2つの解決策があります:
- 1.ミューテックスロックスキームを使用します。キャッシュが失敗した場合、dbデータをすぐにロードするのではなく、最初に(Redis setnx)などの正常に戻ったいくつかのアトミック操作コマンドを使用して操作し、次にdbデータベースデータをロードして、成功したときにキャッシュを設定します。それ以外の場合は、キャッシュの取得を再試行してください。
- 2.「無期限」とは、有効期限が設定されていないが、ホットデータが間もなく期限切れになると、非同期スレッドが更新して有効期限を設定することを意味します。
3.7。キャッシュホットキーの問題
Redisでは、アクセス頻度の高いキーをホットスポットキーと呼びます。ホットキーの要求がサーバーホストに送信されると、要求の量が非常に多いため、ホストのリソースが不足したり、ダウンタイムが発生したりして、通常のサービスに影響を与える可能性があります。
そして、ホットキーはどのように生成されますか?主な理由は2つあります。
- ユーザーが消費するデータは、スパイク、ホットニュース、および読み取りが増減するその他のシナリオなど、生成されるデータよりもはるかに大きくなります。
- リクエストの断片化が集中し、単一のRediサーバーのパフォーマンスを超えています。たとえば、固定名キーとハッシュが同じサーバーにある場合、瞬間的なトラフィックが非常に大きくなり、マシンのボトルネックを超えて、ホットキーの問題が発生します。
では、日々の開発において、ホットキーを特定する方法は?
- 経験に基づいてホットキーであるかどうかを判断します。
- クライアント統計レポート;
- サービスエージェント層のレポート
ホットキーの問題を解決する方法は?
- Redisクラスター拡張:シャードコピーを増やして読み取りトラフィックのバランスを取ります。
- キーをkey1、key2 ... keyNとしてバックアップする、同じデータのN個のバックアップ、異なるシャードに分散されたN個のバックアップ、アクセス中にN個のバックアップの1つにランダムアクセスするなどのホットキーをハッシュします。トラフィック;
- 2番目のレベルのキャッシュであるJVMローカルキャッシュを使用して、Redisの読み取り要求を減らします。
4.Redis構成の操作とメンテナンス
4.1短い接続の代わりに長い接続を使用し、クライアントの接続プールを適切に構成します
- 短い接続を使用する場合は、TCPスリーウェイハンドシェイクと4つのウェーブハンドを毎回実行する必要があり、時間がかかります。ただし、接続が長い場合は、接続を1回確立するだけで、redisコマンドをいつでも使用できます。Jiangziを使用すると、redis接続を確立する時間を短縮できます。
- 接続プールは、クライアント上で複数の接続を解放せずに確立できます。接続が必要な場合は、毎回接続を作成する必要がないため、時間を節約できます。ただし、パラメータを適切に設定する必要があり、Redisを長時間操作しない場合は、接続リソースを時間内に解放する必要があります。
4.2db0のみを使用する
Redisスタンドアロンアーキテクチャでは、db0以外の使用が禁止されています。2つの理由があります。
- 接続の場合、Redisはコマンドselect0とselect1を実行して切り替えます。これにより、新しいエネルギーが消費されます。
- Redis Clusterはdb0のみをサポートします。移行する場合は、コストが高くなります
4.3 maxmemory +適切な除去戦略を設定します。
メモリバックログの膨張を防ぐため。たとえば、取引量が増えたり、redisキーが多用されたり、メモリが直接不足したり、運用保守の兄弟もメモリを増やすのを忘れたりすることがあります。redisはこのように電話を切るだけですか?したがって、maxmemory-policy(最大メモリ除去戦略)を選択し、実際のビジネスに応じて有効期限を設定する必要があります。合計8つのメモリ除去戦略があります。
- Volatile-lru:メモリが新しく書き込まれたデータを収容するのに不十分な場合、LRU(最も最近使用されていない)アルゴリズムを使用して、有効期限のあるキーを削除します。
- allkeys-lru:新しく書き込まれたデータを収容するのにメモリが不足している場合、LRU(最も最近使用されていない)アルゴリズムを使用してすべてのキーが削除されます。
- Volatile-lfu:バージョン4.0の新機能。新しく書き込まれたデータを収容するのにメモリが不足している場合、LFUアルゴリズムを使用して、期限切れのキーからキーを削除します。
- allkeys-lfu:バージョン4.0の新機能で、メモリが新しく書き込まれたデータを収容するのに十分でない場合、LFUアルゴリズムを使用してすべてのキーを削除します。
- Volatile-random:メモリが新しく書き込まれたデータを保持するのに十分でない場合、データは有効期限が設定されたキーからランダムに削除されます。
- allkeys-random:メモリが新しく書き込まれたデータを収容するのに不十分な場合、データはすべてのキーからランダムに削除されます。
- Volatile-ttl:新しく書き込まれたデータを収容するのに十分なメモリがない場合、有効期限が設定されたキーの中で、有効期限に応じてキーが削除され、先に期限切れになったキーが最初に削除されます。
- noeviction:デフォルトの戦略では、メモリが新しく書き込まれたデータを収容するのに十分でない場合、新しい書き込み操作はエラーを報告します。
4.4レイジーフリーメカニズムを有効にする
Redis4.0 +バージョンはレイジーフリーメカニズムをサポートしています。RedisにまだbigKeyがある場合は、レイジーフリーを有効にすることをお勧めします。オンにすると、Redisがビッグキーを削除すると、メモリを解放するという時間のかかる操作がバックグラウンドスレッドで実行され、メインスレッドへのブロッキングの影響が軽減されます。
著者:カタツムリの少年のピッキング
リンク:https://juejin.cn/post/6942643266613411854
出典:ナゲッツ