Redisでのマルチチャネルバルクメーリング

入門



こんにちは、Habr!私の名前はボリスです。この作品では、教師(以下、エイダとも呼びます)による包括的な学生通知システムの一部として大量メールサービスを設計および実装した経験を皆さんと共有します。







地獄



次に、次の理由により、教育プロセスの中断の数無効にする必要があります。



  1. 教師は個人の連絡先の詳細を共有したくありません。
  2. 学生も本当にそうです-彼らはただ多くの選択肢を持っていません。
  3. 私の母校の詳細により、多くの教師はインターネットにアクセスせずにモバイルデバイスを使用することを余儀なくされたり、好んだりしています。
  4. グループのリーダーを介してメッセージを送信すると、「破損した電話」の影響と、「ああ、忘れました:(」という要素が作用します。


それはそう走っます:



  1. 教師は、利用可能な通信チャネルの1つ(SMS、テレグラム、SPAアプリケーション)を介して、メッセージのテキストと宛先のリストをAdaに送信します。
  2. Adaは、受信したメッセージをさまざまな通信チャネルを通じて、関心のあるすべての*学生にブロードキャストします。


*サービスへのアクセスは任意のアプリケーションベースで提供されます。



これは、想定されます



  1. ユーザーの総数は1万人を超えません。
  2. 学生-教師/内務局のメンバー(学部長、ヘルスセンター、軍事登録デスクなど)の比率は、10:1のレベルに保たれます。
  3. : « », « ))0» ..




  1. ;
  2. , , ;
  3. ;
  4. : - - , , .


この作品は、入門、準備、概念、主題、最終の5つの部分で構成されています。



Pub / SubパターンのRedis解釈、イベントのメカニズム、LUAスクリプト、廃止されたキーの処理に精通している場合は、準備部分を安全にスキップできます。さらに、ソフトウェアのマイクロサービスアーキテクチャについて少なくともある程度の知識を持っていることが非常に望ましいです。



主題部分では、Pythonのコードをレビューしますが、このようなものを何にでも書くことができるように、十分な情報があると思います。



準備



非常にラフで非常に抽象的な〜5分
Redis — [BSD 3-clause] , «-» ().



, .



, -, .



( ).



, , , LUA 5.1.



詳細と直接〜15分
  1. Pub/Sub — Redis. , fire&forget , , PUBLISH, SUBSCRIBE -;
  2. Redis Keyspace Notifications. ;
  3. EXPIRE — Redis. «How Redis expires keys»;
  4. Redis 6.0 Default Configuration File. . 939:948 (The default effort of the expire cycle…);
  5. EVAL — Redis. EVAL EVALSHA, «Atomicity of scripts», «Global variables protection» «Available libraries», cjson;
  6. Redis Lua Scripts Debugger. , . — ;
  7. . , .


概念



素朴なアプローチ



最も明白なあなたが考えることができる解決策:(複数の配信方法send_vksend_telegramなど)と、必要な引数でそれらを呼び出します1つのハンドラ。



拡張性の問題



新しい配信方法を追加する場合は、既存のコードを変更する必要があります。これは、ソフトウェアプラットフォームの制限です。



安定性の問題



メソッドの1つが壊れています=サービス全体が壊れています。



応用問題



異なる通信チャネルのAPIは、相互作用の点で互いに大きく異なります。たとえば、VKontakteは大量メール送信をサポートしていますが、1回の通話で数百人を超えるユーザーはサポートしていません。テレグラムは存在しませんが、1秒あたりの呼び出し数を増やすことができます。



VKAPIはHTTP経由でのみ機能します。TelegramにはHTTPゲートウェイがありますが、MTProtoよりも安定性が低く、十分に文書化されていません。



そのような違いはたくさんあります:最大メッセージ長random_id、解釈とエラー処理など。



これにどう対処するか?



メッセージをキューに入れるプロセスと送信プロセス(以下、クーリエと呼びます)を組織レベルで分離することを決定しました。これにより、前者は後者の存在を疑うことすらなく、その逆も同様であり、Redisはそれらの間の接続リンクとして機能します。



不明ですか?食事を注文してください!



その間、あなたは待っています-この高貴な行動の私の解釈を紹介しましょう。デザインから始まり、宅配便の後ろのドアが閉まっています。







  1. 大きな黄色の「注文」ボタンをクリックします。
  2. Yandex.Foodは宅配便業者を見つけ、選択したアイテムについてレストランに通知し、期待の不確実性を薄めるために注文番号を返します。
  3. 調理が完了すると、レストランは注文ステータスを更新し、宅配便業者に食べ物を渡します。
  4. 次に、宅配便業者があなたに食べ物を渡し、注文に完了のマークを付けます。


ボンアペティ!



デザインに戻る



前の段落で与えられたモデルが現実に完全に対応していない可能性がありますが、開発されたソリューションの基礎を形成したのは彼女でした。



注文番号に関連付けられたデータは履歴と呼ばれ、いつでも次の質問答えることができます



  1. 誰が送ったか。
  2. 彼が送ったもの;
  3. どこから;
  4. 誰に;
  5. 誰がどのように入手したか。


履歴は、サフィックスを介してリンクされた2つの個別のRedisキーとして順序とともに作成されます。



suffix={ }:{UNIX-  }
=history:{suffix}
=delivery:{suffix}


注文は、発送が完了した後に「誰がどのようにそれを受け取ったか」という質問への回答を変更するために、宅配便業者がいつ履歴を一度見るかを決定します。



宅配便業者の「ビジョン」はDEL、フォームのイベントキーへのサブスクリプションを通じて機能しますdelivery:*



配達の瞬間が来ると、Redisは注文キーを削除し、その後、宅配便業者が注文キーの処理を開始します。



宅配便業者が複数あるため、歴史が変わる段階で競争する可能性が高い。







対応する操作をアトミックに定義することで、これを回避できます。Redisでは、これはLUAスクリプトを介して行われます。



実装の詳細については、次の章で詳しく説明します。ここで、ソリューション全体を明確に把握することが重要です。これは、次の図で役立ちます。







ステータスの追跡


クライアントは、メッセージがキューに入れられるに開発中のサービスの別のAPIメソッドによって生成される履歴キーを介して配信ステータスを追跡できます(注文番号が最初にYandex.Foodによって生成されるのと同じように)。



キーが生成された後、タイムアウトのあるトラッカーが(オプションで、また別の方法で)ハングし、クーリエ(SETイベント)による履歴の変更数を監視します。現在、メッセージはキューに入れられています。







宅配便業者が自分のドメイン(通信チャネル)で受信者の連絡先を見つけられない場合、SETコマンドを介して人為的なイベントをトリガーしますPUBLISH。これにより、宅配便業者は「大丈夫」であり、もう待つ必要がないことを示します。



RabbitMQとCeleryがあるのに、なぜRedisのイベントを台無しにするのか



これには少なくとも5つの客観的な理由があります。



  1. Redis , RabbitMQ/Celery — ;
  2. Redis , , , IPC;
  3. Redis’a SQL- ;
  4. . , API-, ;
  5. Celery asyncio, asyncio .




通知システム(包括的)は、一連のマイクロサービスの形式で実装されます。便宜上、インターフェイス、データレイヤーを初期化するためのメソッド、エラーテキスト、および反復ロジックの一部のブロックがライブラリcore移動されました。ライブラリはgino(asyncioラッパーSQLAlchemy)、aioredisおよびに依存していaiohttpます。



たとえばUserContactまたはAllegianceなどコード内のさまざまなエンティティを確認できますそれらの間の接続は下の図に示されています、簡単な説明はスポイラーの下にあります。





エンティティについて〜3分
— .



: , , . ., .



, : , Telegram, . .



[allegiance].



[supergroup].



[ownership] .



履歴キーの生成



配信/ハンドラー/ history_key / get-GitHub



キュー



配信/ハンドラー/キュー/プット-GitHub



注:



  1. コメント171:174;
  2. Redis [164:179]を使用したすべての操作がトランザクションにラップされていること。


宅配便業者の光景[94:117]



コア/配信-GitHub



宅配便業者による履歴の更新



core / redis_lua --GitHub CPythonを含むほとんどのプログラミング言語はLUAとは異なる方法で解釈するため、



命令[48:60]は空のリストを辞書([] -> {})に変換しません



ISS:適切な空のオブジェクトのシリアル化のために配列とオブジェクトの区別を許可する-GitHub



トラッカー



配信/ハンドラー/追跡/投稿-GitHub-実装。

接続/テレグラム/ハンドラー/選択-GitHub [101:134]-ユーザーインターフェイスでの使用例。



宅配便



task_stream(@Sight Couriers) からの配信は、別の非同期コルーチンで処理されます。



APIのタイミング制約に対処するための一般的な戦略は、次のとおりです。RPS(1秒あたりの要求数)はカウントしませんが、タイプごとに正しく/反応/応答しhttp.TooManyRequestsます。



インターフェイスがグローバル(アプリケーション用)に加えてカスタム時間制限も実装している場合、それらはキューの順序で処理されます。最初に私達は私達ができるすべての人に送ります、そしてそれから私達はそれほど長くはないにしても待ち始めます。



電報



courier / telegram-GitHub

前述のように、TelegramのMTProtoインターフェイスは、安定性とドキュメントサイズの点でHTTPの対応するインターフェイスよりも優れています。それと対話するために、既製のソリューション、つまりLonamiWebs / Telethonを使用します。



と接触して



courier / vk-GitHub

VKontakte APIは、識別子のリストをmessages.sendメソッド(100以下)に渡すことで大量メール送信をサポートしmessages.send、1回の実行最大25を「接着」できるため、1回の呼び出しで2500のメッセージが得られます。



不思議な事実
API, execute , .



最終



この作業では、マルチチャネル質量警告システムを編成する方法を提案します。結果として得られるソリューションは、ほとんどの利害関係者の要求(@メールサービスの主要な要件)を満たし、拡張の可能性も想定しています。



主な欠点は、ファイア&フォーゲットパブ/サブエフェクトです。宅配便業者の病気のときに注文キーの削除が必要な場合、対応するドメインでは誰も何も受け取りませんが、履歴に反映されます。



All Articles