web-dev-qa-db-ja.com

Redis経由でメッセージキューを実装する方法

キューイングにRedisを使用する理由

Redisがキューイングシステムを実装するための良い候補になり得るという印象を受けています。ここまでは、MySQLデータベースとポーリング、またはRabbitMQを使用してきました。 RabbitMQには多くの問題がありました。クライアントライブラリは非常に貧弱でバグが多く、それらを修正するために多くの開発時間を費やしたり、サーバー管理コンソールでいくつかの問題を発生させたりしたくありません。少なくとも、システムがキューをインテリジェントにサポートするアーキテクチャを備えている限り、私たちはおそらくミリ秒を把握していないか、パフォーマンスを大幅に押し上げているわけではありません。

それが背景です。基本的に、私は非常に古典的で単純なキューモデルを持っています。仕事を生み出す複数のプロデューサーと仕事を消費する複数のコンシューマーがあり、プロデューサーとコンシューマーの両方がインテリジェントにスケーリングできる必要があります。素朴なPUBSUBが機能しないことがわかりました。私はallサブスクライバーに作業を消費させたくないので、-oneサブスクライバーに作業。最初は、BRPOPLPUSHはインテリジェントなデザインのように見えます。

BRPOPLPUSHを使用できますか?

BRPOPLPUSHの基本的な設計は、1つのワークキューと1つのプログレスキューがあることです。コンシューマーは作業を受け取ると、アイテムをプログレスキューにアトミックにプッシュし、作業が完了するとLREMです。これにより、クライアントが死亡した場合の作業のブラックホール化が防止され、監視がかなり楽になります。たとえば、大量のタスクがあるかどうかを通知するだけでなく、コンシューマーがタスクの実行に長い時間を要する問題があるかどうかを通知できます。

それは保証します

  • 作業は正確に1人の消費者に配信されます
  • 作業は進捗キューで終了するため、消費者が

欠点

  • 私が見つけた最高のデザインが実際にPUBSUBを使用していないのは不思議に思えます。これは、Redisのキューイングに関するほとんどのブログ投稿が焦点を当てているものだからです。だから私は明らかな何かを見逃しているように感じます。タスクを2回消費することなくPUBSUBを使用する唯一の方法は、作業が到着したという通知を単にPushすることです。これにより、コンシューマーはRPOPLPUSHをブロックすることができなくなります。
  • 一度に複数のワークアイテムを要求することは不可能であり、これはパフォーマンスの問題のようです。私たちの状況ではそれほど大きなものではありませんが、この操作が高スループットまたはこの状況用に設計されていなかったことは明らかです
  • 要するに:私は愚かな何かを逃していますか?

Node.jsタグも追加します。これは、私が主に扱っている言語だからです。 Nodeは、シングルスレッドで非ブロッキングの性質を考えると、実装を簡単にする可能性がありますが、さらにnode-redisライブラリを使用しており、ソリューションは次のようにその長所と短所に敏感であるか敏感である必要があります上手。

29
djechlin

Node.jsのメッセージキューにRedisを使用する必要があり、そのためのモジュールを使用してもかまわない場合は、 [〜#〜] rsmq [〜#〜] -Redisノードのシンプルなメッセージキュー。この質問が尋ねられた時点では利用できませんでしたが、今日は実行可能なオプションです。

あなたが質問で述べたように実際にキューを自分で実装したい場合は、RSMQのソースを読むことができますそれはまさにあなたが求めていることを行います。

見る:

5
rsp

これまでにいくつかの困難に遭遇しましたが、ここで文書化したいと思います。

再接続ロジックをどのように処理しますか?

これは難しい問題であり、メッセージキューの設計と実装では特に難しい問題です。メッセージは、コンシューマーがオフラインのときにどこかにキューイングできる必要があるため、単純なpub-subでは十分ではなく、コンシューマーはリスニング状態で再接続する必要があります。 ブロッキングポップは、べき等でないリスニング状態であるため、維持するのが難しい状態です。リスニングはべき等の操作である必要がありますが、ブロッキングポップに関して切断を処理する場合、切断が操作の成功直後に発生したのか、操作が失敗する直前に発生したのかを非常によく考えることができます。これは乗り越えられないわけではありませんが、望ましくありません。

さらに、リスニング操作はできるだけ単純にする必要があります。理想的には、次のプロパティが必要です。

  • リスニングはべき等です。
  • コンシューマはalwaysリスニングしており、スロットルロジックはリスニングロジックコードの外で処理されます。 RabbitMQはこれをカプセル化し、コンシューマが未確認のメッセージの数を制限できるようにします。
    特に、ブロックポップの再入力が前の操作の成功を条件とする貧弱なデザインを採用しました。

私は今、Redis PUBSUB + RPOPLPUSHソリューションを支持しています。これにより、作業の通知が作業の消費から切り離され、クリーンなリスニングソリューションを除外できます。 PUBSUBは、作業の通知のみを担当します。 RPOPLPUSHのアトミックな性質は、消費、および作業を1人のコンシューマーに委任することを担当します。このソリューションは、最初はブロッキングポップに比べて不必要に複雑に見えましたが、今では複雑さはまったく不要ではありませんでした。それは難しい問題を解決していました。

ただし、このソリューションはそれほど簡単ではありません。

  • 消費者はまた、再接続の作業を確認する必要があります。
  • とにかく、消費者は冗長性のために新しい仕事の投票をしたいと思うかもしれません。ポーリングが実際に成功した場合、警告が発行されます。これは、PUBSUBでの消費とRPOPLPUSHでのポーリングの間でのみ発生するためです。したがって、多くのポーリング成功は、壊れたサブスクリプションシステムを示します。

PUBSUB/RPOPLPUSHデザインにもスケーリングの問題があることに注意してください。Everyコンシューマは、everyメッセージ、これは不要なボトルネックがあることを意味します。チャネルを使用して作業を分割することは可能ではないかと思いますが、これはおそらくうまくいくにはトリッキーな設計です。

22
djechlin

したがって、RedisよりもRabbitMQを使用することを選択する最大の理由は、障害シナリオとクラスタリングです。

この記事では実際にそれを最もよく説明しているので、リンクを提供します。

https://aphyr.com/posts/283-jepsen-redis

Redisセンチネルと最近のredisクラスタリングは、キューにとって悪い選択となった多くの非常に基本的な障害シナリオを処理できません。

RabbitMQには一連の問題がありますが、本番環境では非常に堅固で、優れたメッセージキューです。

これはうさぎの投稿です:

https://aphyr.com/posts/315-jepsen-rabbitmq

CAPの定理(一貫性、可用性、およびパーティション処理)を見ると、3つのうち2つしか選択できません。CP(一貫性とパーティション処理)のRMQをメッセージの負荷に活用していますが、利用できない場合は、世界の終わり。メッセージを失わないようにするために、メッセージを失わないようにするために、パーティション処理にignoreを使用します。ソースがUUIDを管理するため、重複を処理できます。

0
ra9r