リアルタイム通信実現のためのPubSubを考える
チャットサービスのようなリアルタイム通信を実現するための技術構成を考える。基本はクライアントとサーバー間でWebsocketもしくはgRPCのstreamを使用することで双方向通信をつないでおき、サーバーの裏側でPubSubを購読するような構成をイメージ。
Cloud PubSub
一番簡単そうだったので採用しようと思ったけど、Cloud PubSubはメッセージを購読するのにSubscriptionが必要。同じSubscriptionを購読しているサーバーがあったときどちらにしかメッセージは配信されない。これはCloud PubSubがat-least-onceな配信ポリシーで作られているから。ただ、設定で1回配信に変更すればある程度は再配信しないよう制御できるっぽい。
ちょっとベストプラクティスがわからないがアプリケーションコードでSubscriptionを作ったり消したりしてメッセージを購読しないとwebsocketのようなクライアントとのコネクションの裏側で購読する場合、メッセージを必ず受信できる保証がない。
コネクションごとにSubscriptionを作成する必要があるよねという話。
事例とか調べたけどマイクロサービスのサービス間通信とかIoTのあらゆるイベントを受信するようなユースケースとかそんなのに使われることが多いっぽい
いろんな分散サービスの中継にあらゆるイベントを受信して割り振ってくみたいな分散して並列に捌きたいみたいな感じだと使えそう
Cloud PubSubがマネージドで簡単に構築できて勝手にスケールして大量のイベント捌けるし再配信ポリシーやデッドレターの設定などある程度細かく設定もできてユースケースにはまればかなり便利
メッセージのスキーマにProtobufが設定できるのもいい
代わりの案
いろいろ調べたけどKafkaかRedisのどっちか。
Kafkaは一から構築するには学習コスト高いと思ってるので採用するならマネージド。
ただ、マネージドを採用してもそれなりに学習コストは高いと思ってるのとかなり大規模なイベント処理に向いてるのかなというのと、思ってるより採用事例というか日本語の情報がないのと、Java以外の言語(例えばGo)でのクライアントライブラリがあんまり使い勝手よさそうな感じもしなかったのでRedisを考える。
リアルタイム通信の事例
一応いろんな会社の事例
独自WebsocketサーバーというかPubSubサーバーみたいなのを使用
Redis ClusterをPubSubで利用した例
Redis Cluster
Redisでリアルタイム通信
ゲームとかライブ配信のチャット、コメントなどのリアルタイム通信でRedisの採用事例がけっこうありそう。あとは独自実装
Redis ClusterをPubSubとして使うのはLIME LIVEの事例がかなり参考になった
なにも考えないでRedisのPubSubを使えば購読してるチャンネル全てにメッセージが配信される。ただ、at-most-onceのポリシーというか配信されたメッセージを購読しているところに送ったらそのメッセージは破棄されて二度とみることはできない。
ので、Cloud PubSubのようなat-least-onceの配信ポリシーで使いたい。
これはRedis Streamsをコンシューマーグループを使って購読することで実現できる。ただ、同じコンシューマーグループに複数のコンシューマーがある場合、どれか一つにしかメッセージは配信されないので全てのコンシューマーでメッセージを受け取りたい場合、全てのコンシューマーが別々のコンシューマーグループに属するようにするといいらしい。Kafkaも同じ感じっぽい
ただ、これCloud PubSubの話と結局同じでアプリケーション側でWebsocketのようなコネクションの裏で購読する時に動的にコンシューマーを作る必要がある。しかし、Cloud PubSubのSubscriptionを動的にアプリケーション側から作るよりは容易に扱えそう。
そもそもat-least-onceは必要なのか?
なんか調べるとat-least-onceの挙動をやっかいに思ってる人が多そう。1回以上メッセージが配信されると重複処理がされる可能性があるので受信側は冪等性を担保しなきゃいけないよねみたいな話っぽい。
これにはみんなメッセージごとの一意なidをredisに入れて、重複処理をしないように冪等性を担保しているっぽい。
絶対メッセージを失わないようにat-least-onceの採用ポリシーを使いたいというよりかは重複処理をしないように冪等性を担保する仕組みが必要だからat-most-onceで1回しか配信しないでほしいという要望の方が多そう。
LINE LIVEはどうしてるんだろうか。
コメントとか最悪失われることを許容しているんだろうか。
それとも再配信されるようにしてるんだろうか。。
Redis Streamsの話
再配信ポリシーは正解がわからないので一旦置いておいてRedisでPubSubを実現するときスケールのことを考えてRedis Clusterが使える。
しかし、Redis ClusterでPubSubを使う場合、クラスター内の全てのシャードにメッセージがブロードキャストされネットワーク帯域を圧迫するという問題があったらしいがRedis7で入ったSharded PubSubで解決したっぽい。
それまではRedis Streamsで解決していたよう。前述した再配信ポリシーの話があるのでどちらを使うかは要件次第みたいな感じだと思う。
memorystore for Redis Cluster
Google CloudのマネージドでRedisは使えるが2023年の11月くらいにRedis ClusterがGAになってるっぽい。ただ高い。
シャード数を最低の3、レプリカセットを0にすると単体のRedisでレプリカセットを使用するStandardな構成と同じくらいの値段だったのでクラスター使いたいならレプリカセット0からがいいかもしれない。
ただそのかわりにレプリカセットがないので可用性は失われる。
でも、シャードが3つあってデータが分散されるのでパフォーマンスは上がる。
多分、そんな感じ。
Redis Streamsのコンシューマーグループについて
コンシューマーグループを作成することでCloud PubSubのようなat-least-onceな配信ポリシーを作成できる。
redis clusterで複数チャネルをsubscribeしようとするとエラーになる。これはkeyが同一スロットにないためらしく、無理やり同一スロットになるようにkeyを修正してあげればいいらしい。
同一スロットに集まるのでデータが偏るということなのでよく考えてやったほうがいい。