Redisの速度が低下しましたか?それを試してみて、うまくいかない場合は、私を倒してください

一部のネットワークサービスシステムでは、MySQLなどのハードディスクデータベースのパフォーマンスよりもRedisのパフォーマンスの方が重要なトピックになる場合があります。たとえば、Weibo、ホットなWeibo [1]、最新のユーザー関係はRedisに保存され、多数のクエリがMySQLではなくRedisにヒットします。

では、Redisサービスの場合、どのようなパフォーマンス最適化を実行できますか?言い換えれば、どのようなパフォーマンスの無駄を避ける必要がありますか?

Redisパフォーマンスの基礎

最適化について説明する前に、Redisサービス自体にシングルスレッド操作などのいくつかの機能があることを知っておく必要があります。Redisのソースコードが変更されていない限り、これらの機能はパフォーマンスの最適化について考えるための基本です。

では、検討する必要があるRedisの基本的な機能は何ですか?Redisのプロジェクト紹介では、その特徴を要約しています。

Redisは、ディスク上に存続するインメモリデータベースです。データモデルはKey-Valueですが、さまざまな種類の値がサポートされています。

まず、Redisはオペレーティングシステムが提供する仮想メモリを使用してデータを格納します。さらに、このオペレーティングシステムは一般的にUnixを指します。RedisはWindowsでも実行できますが、特別な処理が必要です。オペレーティングシステムがスワップ領域を使用している場合、Redisデータは実際にはハードディスクに保存されている可能性があります。

次に、Redisは永続性をサポートし、データをハードディスクに保存できます。多くの場合、バックアップ、データリカバリ、その他の要件を達成するために永続性を実行する必要があります。ただし、永続化は孤立して発生するわけではなく、リソースも消費します。

3番目に、Redisはキーと値のメソッドを使用して読み取りと書き込みを行い、値にはさまざまなタイプのデータを含めることができます。さらに、データタイプの最下層は異なる構造に格納されます。さまざまなストレージ構造によって、データの追加、削除、変更、クエリの複雑さとパフォーマンスのオーバーヘッドが決まります。

最後に、上記の説明で言及されていないのは、ほとんどの場合、Redis シングルスレッド[2] a(シングルスレッド)です。つまり、同時にCPUのみを占有し、1つの操作命令のみを実行し、並列の読み取りと書き込みを行います。存在しません。多くの操作によって引き起こされる遅延への答えはここにあります。

最後の機能に関して、なぜRedisはシングルスレッドであるが、それは優れたパフォーマンスを持っているのか(アムダールの法則によれば、時間のかかるプロセスを最適化する方が理にかなっています。2つの文は次のように要約されます:Redisは複数のチャネルを使用します。 I / O多重化メカニズム[3]、クライアントリクエストを処理するとき、メインスレッドをブロックしません。Redisは、1命令を1マイクロ秒未満で実行(ほとんどの命令)する  ので[4]、シングルコアCPUは1秒かかります。 100万の命令(数十万のリクエストに対応)を処理するため、マルチスレッドを実装する必要はありません(ネットワークがボトルネックです[5])。

ネットワーク遅延を最適化

Redisの公式ブログは、パフォーマンスのボトルネックがネットワークである可能性が高いといくつかの場所で述べています[6]。それでは、ネットワークの遅延をどのように最適化すればよいでしょうか?

まず、スタンドアロン展開を使用する場合(アプリケーションサービスとRedisが同じマシン上にある場合)、Unixプロセス間通信を使用してRedisサービスを要求する方が、ローカルホストLAN(科学名のループバック)よりも高速です。公式文書[7]はそう言っています、考えてみてください。理論的にはこのようになるはずです。

ただし、スタンドアロン展開では多くの企業のビジネススケールをサポートできないため、TCPを使用する必要があります。

Redisクライアントとサーバー間の通信は、通常、TCPロングリンクを使用します。クライアントがRedisが結果を返すのを待ってから、リクエストの送信後に次の命令を送信する必要がある場合、クライアントからの複数のリクエストとRedisは次の関係を構成します。

Redisの速度が低下しましたか? それを試してみて、うまくいかない場合は、私を倒してください

 

(注:送信したいキーが非常に長いというわけではない場合、TCPパケットはRedisコマンドを完全に保持できるため、プッシュパケットのみが描画されます)

これらの2つの要求では、クライアントは一定のネットワーク転送時間を経験する必要があります。

ただし、可能であれば、マルチキーコマンドを使用してリクエストをマージできます。たとえば、2つのGETキーをMGET key1およびkey2とマージできます。これにより、実際の通信ではリクエスト数も減り、当然遅延も改善されます。

SETなどのマルチキーコマンドを使用してマージできない場合、GETはマージできません。実行する方法?

Redisには、複数の命令を1つの要求に組み合わせることができるメソッドが少なくとも2つあります。1つはMULTI / EXECで、もう1つはスクリプトです。前者はもともとRedisトランザクションを構築する方法でしたが、実際には複数の命令を1つのリクエストにマージすることが可能であり、通信プロセスは次のとおりです。スクリプトについては、キャッシュされたスクリプトのsha1ハッシュキーを使用してスクリプトを呼び出し、通信量が少なくなるようにするのが最適です。

Redisの速度が低下しましたか? それを試してみて、うまくいかない場合は、私を倒してください

 

これにより、ネットワークの送信時間が大幅に短縮されます。ただし、この場合、このトランザクション/スクリプトに含まれるキーは同じノード上にある必要があるため、適切に検討してください。

上記の方法を検討しても、複数の要求をマージする方法がまだない場合は、複数の応答をマージすることも検討できます。たとえば、2つの返信メッセージをマージするには:

Redisの速度が低下しましたか? それを試してみて、うまくいかない場合は、私を倒してください

 

このようにして、理論的には、1つの応答に対するネットワーク送信時間を節約できます。これはパイプラインが行うことです。パイプラインを使用したルビークライアントの例を示します。

require 'redis'
@redis = Redis.new()
@redis.pipelined do
    @redis.get 'key1'
    @redis.set 'key2' 'some value'
end# => [1, 2]

node_redisなどの一部の言語クライアントは、デフォルトでパイプラインを使用して遅延の問題を最適化するとも言われています。

さらに、TCPパケットに入れることができる応答メッセージの数は制限されていません。要求が多すぎて応答データが非常に長い場合(たとえば、長い文字列を取得する場合)、TCPは引き続きパケットで送信されますが、パイプラインを使用することは可能です。送信回数を減らしてください。

パイプラインは、アトミックではないという点で上記の他のメソッドとは異なります。したがって、クラスター状態のクラスターでは、これらのアトミックメソッドよりもパイプラインを実装する可能性が高くなります。

要約する:

  1. スタンドアロン展開の場合は、UNIXプロセス間通信を使用します。
  2. 可能であれば、マルチキーコマンドを使用して複数のコマンドを組み合わせ、リクエストの数を減らします。
  3. トランザクションとスクリプトを使用して要求と応答をマージする
  4. パイプラインを使用して応答をマージする

長時間実行される操作に注意する

大量のデータの場合、KEYS *、LRANGE mylist 0 -1、アルゴリズムの複雑度がO(n)のその他の命令など、一部の操作の実行時間は比較的長くなります。Redisはデータクエリに1つのスレッドのみを使用するため、これらの命令に時間がかかる場合、Redisがブロックされ、多くの遅延が発生します。

公式文書にはKEYS *のクエリは非常に高速であると記載されていますが、(通常のノートブックでは)100万個のキーをスキャンするのに40ミリ秒しかかかりません(参照:https://redis.io/commands/keys)が、数十ミリ秒パフォーマンス要件が高いシステムの場合、数億個のキーがあるかどうかは言うまでもありません(1台のマシンに数億個のキーが格納されている場合があります(たとえば、100バイトのキー、1億個のキーは10 GBのみ))。 、長い時間。

したがって、Redisの作者ブログ[8]で言及しているように、本番環境のコードではこれらの低速実行命令を使用しないようにしてくださいさらに、運用と保守の学生は、Redisにクエリを実行するときにこれを使用しないようにする必要があります。さらに、Redis Essentialブックでは、rename-command KEYS ''を使用して、この時間のかかるコマンドの使用を禁止することを推奨しています。

Redisのこれらの時間のかかる命令、トランザクション、およびスクリプトに加えて、複数のコマンドをアトミ​​ック実行プロセスに組み合わせることができるため、Redisには長時間かかる可能性があり、注意が必要です。

本番環境で使用されている「遅い命令」を見つけたい場合は、SLOWLOG GETカウントを使用して、実行時間が長い命令の最新のカウントを表示できます。その長さは、redis.confでslowlog-log-slower-thanを設定することで定義できます。

さらに、多くの場所で言及されていない可能性のある遅いコマンドはDELですがredis.confファイルのコメント[9]言及されています簡単に言えば、DELが大きなオブジェクトである場合、対応するメモリを再利用するのに長い時間(または数秒)がかかる可能性があるため、非同期バージョンのDEL:UNLINKを使用することをお勧めします。後者は、新しいスレッドを開始して、元のスレッドをブロックせずにターゲットキーを削除します。

さらに、キーの有効期限が切れると、Redisは通常、同期的にそれを削除する必要があります。キーを削除する1つの方法は、有効期限を1秒あたり10回に設定してキーをチェックすることです。これらのキーはグローバル構造体に格納され、server.db-> expiresでアクセスできます。確認する方法は次のとおりです。

  1. それからランダムに20個のキーを取り出します
  2. 期限切れのものを削除します。
  3. わずか20個のキーの25%を超える(つまり、5個を超える)有効期限が切れた場合、Redisは有効期限が切れたキーが大量にあると考えているため、終了条件が満たされるまで手順1を繰り返します。いくつかのキーが削除されます過去にそれほど多くのキーはありません。

ここでのパフォーマンスへの影響は、多くのキーが同時に本当に期限切れになると、Redisはそれをループで本当に削除して、メインスレッドを占有することです。

この点に関して、Redisの作成者の提案[10]は、同時にキーが期限切れになる可能性が高いため、EXPIREATコマンドに注意する必要があります。また、キーの有効期限のランダムな変動量を設定するいくつかの提案を見てきました。最後に、redis.confは、キーの有効期限の削除操作を非同期に変更するメソッドも提供します。つまり、redis.confでlazyfree-lazy-expire yesを設定します。

データ構造を最適化し、正しいアルゴリズムを使用する

データ型(文字列、リストなど)の追加、削除、変更、およびチェックの効率は、基礎となるストレージ構造によって決まります。

データ型を使用する場合、基礎となるストレージ構造とアルゴリズムに適切に注意を払い、複雑すぎるメソッドの使用を回避できます。2つの例を挙げます。

  1. ZADDの時間の複雑さはO(log(N))であり、新しい要素を他のデータ型に追加するよりも複雑であるため、慎重に使用してください。
  2. ハッシュタイプの値のフィールド数が制限されている場合、ストレージにziplist構造を使用する可能性が高く、ziplistのクエリ効率は、同じフィールド数のハッシュテーブルほど効率的ではない可能性があります。必要に応じて、Redisストレージ構造を調整できます。

時間パフォーマンスの考慮事項に加えて、ストレージスペースを節約する必要がある場合もあります。たとえば、上記のziplist構造はハッシュテーブル構造よりもストレージスペースを節約します(Redis Essentialsの作成者は500のフィールドをそれぞれハッシュテーブルとziplist構造のハッシュに挿入し、各フィールドと値は約15桁の文字列です。結果としてハッシュテーブル構造で使用されるスペースは、ziplistの4倍です。しかし、スペースを節約するデータ構造の場合、アルゴリズムの複雑さが非常に高くなる可能性があります。したがって、特定の問題に直面した場合、トレードオフを行う必要があります。公式アカウントをフォローすることを歓迎します:Zhu Xiaosiのブログ、返信:1024、redisの排他的な情報を取得できます。

トレードオフを改善するには?私は自分を安心させるために、Redisのストレージ構造を深く掘り下げる必要があると思います。次回はこの内容についてお話します。

上記の3つのポイントはプログラミングレベルでの考慮事項であり、プログラムを作成する際には注意が必要です。以下の点もRedisのパフォーマンスに影響を与えますが、それらを解決するには、コードレベルの調整だけでなく、アーキテクチャと運用とメンテナンスの考慮も必要です。

オペレーティングシステムとハードウェアがパフォーマンスに影響するかどうかを検討する

Redisが実行される外部環境、つまりオペレーティングシステムとハードウェアも、Redisのパフォーマンスに明らかに影響します。公式文書では、いくつかの例が示されています:

  1. CPU:IntelのさまざまなCPUはAMD Opteronシリーズより優れています
  2. 仮想化:物理マシンは仮想マシンよりも優れています。これは、一部の仮想マシンでは、ハードディスクがローカルハードディスクではないため、特にXenが仮想化に使用されている場合、監視ソフトウェアによりfork命令が遅くなる(forkが永続化に使用される)ためです。 。
  3. メモリ管理:Linuxオペレーティングシステムでは、変換ルックアサイドバッファー、またはTLBがより多くのメモリスペースを管理できるようにするため(TLBは限られた数のページしかキャッシュできません)、オペレーティングシステムは2MBや1GBなどの一部のメモリページを大きくします。通常の4096バイトの代わりに、これらの大きなメモリページは巨大ページと呼ばれます。同時に、プログラマがこれらの大きなメモリページを使用しやすくするために、オペレーティングシステムに透過的な巨大ページ(THP)メカニズムが実装され、大きなメモリページを透過的にして通常のメモリページと同じように使用できます。しかし、このメカニズムはデータベースには必要ありません。THPがメモリ空間をコンパクトかつ連続的にするためかもしれません。mongodbドキュメント[11]が明確に述べているように、データベースにはスパースメモリ空間が必要です。 THP機能を無効にします。Redisも例外ではありませんが、Redis公式ブログに記載されている理由は、bgsaveの実行時に大きなメモリページを使用するとフォークの速度が低下することです。これらのメモリページがフォーク後の元のプロセスで変更された場合は、コピーする必要があります。 (つまり、書き込み時にコピー)、そのようなコピーは大量のメモリを消費します(結局、人々は巨大なページであり、コピーをコピーすると大量のコストがかかります)。したがって、オペレーティングシステムで透過的な巨大ページ機能を無効にしてください。
  4. スワップスペース:一部のメモリページがスワップスペースファイルに格納されており、Redisがそれらのデータを要求する必要がある場合、オペレーティングシステムはRedisプロセスをブロックし、必要なページをスワップスペースから取り出してメモリに配置します。これにはプロセス全体のブロックが含まれるため、遅延の問題が発生する可能性があります。1つの解決策は、スワップ領域の使用を禁止することです(Redis Essentialsで提案されているように、メモリ領域が不足している場合は、他の方法を使用してください)。

永続化のコストを検討する

Redisの重要な機能は永続性です。これはデータをハードディスクにコピーすることです。永続性に基づいて、Redisにはデータの回復やその他の機能があります。

ただし、この永続的な機能を維持すると、パフォーマンスのオーバーヘッドも発生します。

まず、RDBは完全に永続的です。

この永続化メソッドは、Redisのすべてのデータをrdbファイルにパックし、それをハードディスクに置きます。ただし、RDB永続化プロセスを実行する元のプロセスは子プロセスをフォークし、forkのシステムコールには時間がかかります。6年前にRedis Labが実施した実験[12]によると、新しいAWS EC2 m1.small ^ 13日、1GBのメモリを占有するRedisプロセスをフォークするのに700+ミリ秒かかりましたが、この間、Redisはリクエストを処理できませんでした。

今日のマシンはそれよりも優れているはずですが、フォークのオーバーヘッドも考慮する必要があります。このため、あまり頻繁にではなく、適切なRDB永続化間隔を使用してください

次に、別の永続化方法であるAOF増分永続化を見てみましょう。

この永続化メソッドは、redisサーバーに送信した指示をテキスト形式で保存します(形式はredisプロトコルに従います)。このプロセス中に、2つのシステムコールが呼び出されます。1つはwrite(2)、もう1つは同期が完了し、もう1つはfsync(2)、非同期で完了します。

これらの両方が遅延の問題の原因である可能性があります。

  1. 出力バッファがいっぱいか、カーネルがバッファ内のデータをハードディスクに同期しているため、書き込みがブロックされている可能性があります。
  2. fsyncの機能は、書き込みによってaofファイルに書き込まれたデータがハードディスクに確実に落ちるようにすることです。7200rpmのハードディスクでは、約20ミリ秒の遅延があり、消費量が非常に大きくなります。さらに重要なことに、fsyncの進行中は書き込みがブロックされる可能性があります。

それらの中で、ファイルにデータを書き込むより良い方法がないため、書き込みブロッキングは許容できるようです。ただし、fsyncの場合、Redisは3つの構成を許可します。どちらを選択するかは、バックアップの適時性とパフォーマンスのバランスによって異なります。

  1. always:appendfsyncをalwaysに設定すると、fsyncはクライアントの指示と同期して実行されるため、遅延の問題が発生する可能性が最も高くなりますが、バックアップの適時性が最適です。
  2. everysec:fsyncは毎秒非同期で実行されます。現時点では、redisのパフォーマンスは向上しますが、fsyncは書き込みをブロックする可能性があります。これは妥協案です。
  3. no:redisはfsyncを積極的に開始しません(決してfsyncしないわけではありませんが、可能性は低いです)が、カーネルはいつfsyncするかを決定します

分散アーキテクチャを使用して、読み取りと書き込みの分離、データの断片化

上記は、すべて最適化のための単一または単一のRedisサービスに基づいています。次に、分散アーキテクチャを使用して、Webサイトの規模が大きくなったときにRedisのパフォーマンスを確保することを検討します。

まず、どのような状況で(または最良の)分散アーキテクチャを使用する必要がありますか。

  1. データ量が非常に多いため、1台のサーバーで1 Tなどのメモリを保持することは不可能です。
  2. 高いサービス可用性の必要性
  3. シングルリクエストのプレッシャーが高すぎる

これらの問題を解決するには、データの断片化またはマスターとスレーブの分離、またはその両方を使用できます(つまり、マスターとスレーブの構造も、フラグメンテーションに使用されるクラスターノードに設定されます)。

このようなアーキテクチャは、パフォーマンス向上のために新しいエントリポイントを追加できます。

  1. 一部のスレーブライブラリに低速の命令を送信して実行する
  2. 使用頻度の低いスレーブライブラリに永続化関数を配置する
  3. いくつかの大きなリストを断片化する

最初の2つはRedisのシングルスレッド機能に基づいており、他のプロセス(またはマシン)を使用してパフォーマンスを補完します。

もちろん、分散アーキテクチャを使用すると、パフォーマンスに影響を与える可能性もあります(たとえば、リクエストを転送したり、データを継続的に複製して分散したりする必要がある)。(確認する)

あとがき

実際、アクティブな再ハッシュ(キーのメインテーブルの再ハッシュ、1秒に10回、オフにすると少しパフォーマンスが向上する)など、Redisのパフォーマンスにも影響を与える多くの要素がありますが、このブログは長い間執筆されてきました。さらに、より重要なことは、他の人から尋ねられた問題を収集してから解決策を暗記することではなく、Redisの基本原則を習得し、一定の方法で新しい問題を解決することです。

おすすめ

転載: blog.csdn.net/AMSRY/article/details/108532595