【入門】Redis Pub/Sub でリアルタイム通信を実現!仕組みと活用法


【入門】Redis Pub/Sub でリアルタイム通信を実現!仕組みと活用法

現代のアプリケーションにおいて、「リアルタイム性」はユーザー体験を向上させる上で非常に重要な要素となっています。チャットアプリケーションでのメッセージの即時表示、ソーシャルメディアでの新しい通知、オンラインゲームでの状況の共有、ライブデータの更新など、様々な場面でリアルタイム通信が求められています。

しかし、これらのリアルタイム通信を実現するには、従来の通信モデルとは異なるアプローチが必要です。そこで本記事では、インメモリデータ構造ストアとして広く利用されている Redis の機能の一つである Pub/Sub(Publish/Subscribe、発行/購読) に焦点を当て、その仕組みとリアルタイム通信への活用法について、初心者の方にも分かりやすく詳細に解説していきます。

Redis Pub/Subは、そのシンプルさと高速性から、様々なシステムでリアルタイムな情報配信基盤として利用されています。本記事を通じて、Pub/Subの基本的な概念から実践的な使い方、そしてシステム設計上の注意点までを理解し、自身のアプリケーションにリアルタイム性を取り入れるための一歩を踏み出しましょう。

1. はじめに:なぜリアルタイム通信が必要なのか?

Webの初期の段階では、クライアント(ブラウザなど)がサーバーにリクエストを送り、サーバーがそれに応答する「リクエスト/レスポンス」モデルが主流でした。例えば、ユーザーがウェブページを閲覧する際に、ブラウザがサーバーにページデータを要求し、サーバーがそのデータを送り返すという流れです。

このモデルは静的な情報や、ユーザーのアクションに対する明確な応答が必要な場合には適していますが、サーバー側で発生した新しい情報をユーザーに即座に通知する、といった用途には不向きです。ユーザーは新しい情報があるかを知るために、定期的にサーバーに問い合わせる(ポーリング)必要がありますが、これは無駄なリソース消費や遅延の原因となります。

今日のアプリケーションでは、以下のような要求が高まっています。

  • 即時性: 新しい情報が発生したら、できるだけ早くユーザーに届けたい。
  • 双方向性: ユーザーだけでなく、サーバーからも能動的に情報を送りたい。
  • 効率性: 不要な通信を減らし、サーバーとクライアント双方の負荷を軽減したい。

このようなニーズに応えるために、リアルタイム通信技術が発展してきました。代表的なものとしては、ブラウザとサーバー間で持続的な接続を確立する WebSocket や、サーバーからクライアントへの一方的なデータ配信を行う Server-Sent Events (SSE) などがあります。

Redis Pub/Subは、これらのリアルタイム通信を実現する技術の一つ、というよりは、リアルタイムな情報配信の「バックエンド」や「ミドルウェア」としてよく利用されます。例えば、バックエンドサーバーで発生したイベントをRedis Pub/Subを通じてWebSocketサーバーに送り、WebSocketサーバーが接続しているクライアントに配信する、といった構成が一般的です。

Redis Pub/Subの最大の特徴は、そのシンプルさ高速性にあります。複雑なメッセージングプロトコルや永続化機構を持たず、純粋にメッセージを「発行」し「購読」しているクライアントに「配信」することに特化しています。この特化が、高いパフォーマンスと低いレイテンシを実現しています。

本記事では、このRedis Pub/Subの基本的な仕組みから、実際にどのようにリアルタイム通信に活用できるのか、そして利用する上での注意点や他の技術との使い分けについて、深掘りしていきます。

2. リアルタイム通信の基礎とPub/Subの位置づけ

リアルタイム通信とは、データの送信側と受信側との間で、データが生成されてから相手に届くまでの遅延を極力小さく抑えた通信方式全般を指します。前述のリクエスト/レスポンスモデルやポーリングとは異なり、サーバー側から能動的にクライアントにデータをプッシュできる点が大きな違いです。

リアルタイム通信を実現するための技術はいくつかありますが、大別すると以下のようになります。

  • クライアントとサーバー間の直接的なリアルタイム接続:

    • WebSocket: ブラウザとサーバー間で単一のTCP接続上で全二重通信(双方向通信)を可能にするプロトコルです。チャットやオンラインゲームなど、低遅延な双方向通信が必要な場合に広く利用されます。
    • Server-Sent Events (SSE): サーバーからクライアントへの一方的なデータストリーミングを可能にする技術です。ニュースフィードや株価表示など、サーバーからの更新をクライアントが表示する用途に適しています。
  • バックエンドシステム内のリアルタイムな情報伝達基盤:

    • メッセージキュー (Message Queue): プロデューサー(メッセージ生成者)がキューにメッセージを送信し、コンシューマー(メッセージ処理者)がキューからメッセージを受信するシステムです。非同期処理や負荷分散、信頼性の高いメッセージ配信などに利用されます。RabbitMQ, Kafka, ActiveMQなどが代表的です。
    • Pub/Subシステム (Publish/Subscribe): 発行者(Publisher)が特定のトピック(チャンネル)にメッセージを発行し、そのトピックを購読している購読者(Subscriber)にメッセージが配信されるシステムです。Pub/Subは、発行者と購読者が互いの存在を知らなくても通信できる「疎結合」が特徴です。Redis Pub/Subはこのカテゴリに属します。

Redis Pub/Subは、後者の「バックエンドシステム内のリアルタイムな情報伝達基盤」として利用されることが多いです。例えば、以下のような構成が考えられます。

[バックエンドサービスA] --(PUBLISH)--> [Redis Pub/Sub] --(SUBSCRIBE)--> [バックエンドサービスB]
|
+--(SUBSCRIBE)--> [WebSocketサーバー] --(WebSocket)--> [ブラウザクライアント]

この例では、バックエンドサービスAで何らかのイベントが発生したら、その情報をRedis Pub/Subの特定のチャンネルに発行します。同じチャンネルを購読しているバックエンドサービスBはその情報を受け取って別の処理を実行したり、WebSocketサーバーはその情報を受け取って接続しているブラウザクライアントにリアルタイムにプッシュしたりします。

このように、Redis Pub/Subはアプリケーションの異なるコンポーネント間や、バックエンドとリアルタイム配信サーバーの間での「イベントバス」や「メッセージブローカー」のような役割を担うことができます。WebSocketやSSEは主にクライアントとの「最後の1マイル」のリアルタイム通信を担当するのに対し、Pub/Subはシステム内部でのリアルタイムな情報伝達を効率的に行うための仕組みと言えます。

Redis Pub/Subの強みは、そのシンプルさにあります。複雑なキューの管理やACK(確認応答)、メッセージの永続化といった機能は持たず、ひたすらメッセージを高速に配信することに特化しています。このシンプルさが、高いパフォーマンスと低い運用コストを実現しています。一方で、メッセージの信頼性(確実に届いたか、オフラインだったSubscriberに後から届けるかなど)については、Pub/Sub単体では保証されないため、アプリケーション側で考慮が必要になります。

次のセクションでは、このRedis Pub/Subの具体的な仕組みについて、より深く掘り下げていきます。

3. Redis Pub/Subの仕組み

Redis Pub/Subは、その名の通り「Publish」(発行)と「Subscribe」(購読)という2つの主要な操作と、その中心となる「Channel」(チャンネル)という概念に基づいています。

仕組みは非常にシンプルです。

  1. Publisher (発行者): メッセージを特定のチャンネルに送信するクライアントです。Publisherはメッセージを発行するだけで、そのメッセージが誰に届くかは関知しません。
  2. Subscriber (購読者): 特定のチャンネルに関心を持ち、そのチャンネルに発行されるメッセージを受信するクライアントです。Subscriberは受信したいチャンネルをRedisに「購読」を要求します。
  3. Channel (チャンネル): メッセージが流れる「通り道」のようなものです。Publisherは特定のチャンネルにメッセージを発行し、そのチャンネルを購読しているすべてのSubscriberがそのメッセージを受け取ります。チャンネルは文字列で識別されます。

Redisサーバーは、Publisherから受け取ったメッセージを、そのメッセージが発行されたチャンネルを購読しているすべてのSubscriberに配信する役割を担います。

Pub/Subの基本的なデータフロー

  1. PublisherがRedisサーバーに対して、PUBLISH channel_name message というコマンドを実行します。
  2. Redisサーバーは channel_name というチャンネルにメッセージ message が発行されたことを認識します。
  3. Redisサーバーは、現在 channel_name を購読しているすべてのSubscriberクライアントを探し出します。
  4. Redisサーバーは、見つけ出したすべてのSubscriberクライアントに対して、message を送信します。
  5. SubscriberクライアントはRedisサーバーから送られてきたメッセージを受信し、アプリケーション側で処理します。

重要なのは、発行者と購読者の間に直接的な依存関係がないという点です。発行者は誰が購読しているかを知りませんし、購読者も誰が発行したかを知りません。両者はRedisサーバーという共通のエンティティを通じて間接的に結びついています。これは「疎結合」と呼ばれ、システムコンポーネント間の依存関係を減らし、柔軟性や拡張性を高める上で有利に働きます。

Pub/Sub関連のコマンド

Redis Pub/Subは、主に以下のコマンドを使って操作します。

  • PUBLISH channel message: 指定した channelmessage を発行します。戻り値は、そのメッセージが配信されたSubscriberの数です(ただし、この数は目安であり、ネットワークの問題などで実際にSubscriberに届いたかどうかは保証されません)。
  • SUBSCRIBE channel [channel ...]: 指定した1つまたは複数のチャンネルを購読します。このコマンドを実行したクライアント接続は、Pub/Subモードに入り、他のコマンド(PUBLISH 以外の通常のRedisコマンドなど)を受け付けなくなります。Redisサーバーからメッセージが届くと、クライアントは受信したメッセージの種類(メッセージの種類、チャンネル名、メッセージ本文)を受け取ります。
  • UNSUBSCRIBE [channel [channel ...]]: 現在購読している指定したチャンネルの購読を解除します。チャンネルを指定しない場合は、現在購読しているすべてのチャンネルの購読を解除します。購読しているチャンネルが一つもなくなると、Pub/Subモードから抜けて通常のコマンドを受け付けられるようになります。

ワイルドカードによる購読 (Pattern Matching)

Redis Pub/Subには、特定のパターンに一致する複数のチャンネルを一度に購読できる「ワイルドカード購読(Pattern Matching Subscription)」という機能があります。これは、関連する複数のチャンネルのメッセージをまとめて処理したい場合に便利です。

  • PSUBSCRIBE pattern [pattern ...]: 指定した1つまたは複数のパターンに一致するチャンネルを購読します。* は任意の文字列(0文字以上)にマッチし、? は任意の一文字にマッチします。例えば、news.*news.sports, news.politics, news.weather といったチャンネルにマッチします。このコマンドを実行したクライアントもPub/Subモードに入ります。
  • PUNSUBSCRIBE [pattern [pattern ...]]: 現在ワイルドカード購読している指定したパターンの購読を解除します。パターンを指定しない場合は、現在ワイルドカード購読しているすべてのパターンの購読を解除します。

ワイルドカード購読時のメッセージ受信形式:
通常の SUBSCRIBE でメッセージを受信した場合、クライアントは以下の形式でメッセージを受け取ります。
[ "message", channel_name, message_content ]

PSUBSCRIBE でメッセージを受信した場合、クライアントは以下の形式でメッセージを受け取ります。
[ "pmessage", pattern_subscribed, actual_channel_name, message_content ]
pattern_subscribed は購読したパターン(例: news.*)、actual_channel_name は実際にメッセージが発行されたチャンネル名(例: news.sports)を示します。

Pub/Subモードの注意点

SUBSCRIBE または PSUBSCRIBE コマンドを実行したクライアント接続は、Pub/Sub専用のモードに入ります。このモードでは、PUBLISH, SUBSCRIBE, PSUBSCRIBE, UNSUBSCRIBE, PUNSUBSCRIBE, QUIT コマンド以外の通常のRedisコマンドを実行しようとするとエラーになります。

これは、Pub/Subがプッシュ型の通信モデルであるためです。Redisサーバーはメッセージが到着するとすぐにSubscriberに送信しようとしますが、もしSubscriberが別のコマンド(例えば GETSET など)を実行中でブロックされていた場合、メッセージの配信が滞ってしまいます。これを避けるため、Pub/SubクライアントはPub/Sub関連のコマンド以外を実行できないように制限されています。

したがって、アプリケーションでRedis Pub/Subを利用する場合、Pub/Sub専用の接続と、通常のRedisコマンド(データ操作など)用の接続を分ける必要があります。

メッセージの配信メカニズム

Redisサーバーは、Publishされたメッセージを受け取ると、そのチャンネルまたはパターンを購読しているすべてのクライアント接続に対して、直ちにメッセージをキューに入れます。そして、非同期的に各Subscriberにメッセージを送信します。

重要なのは、メッセージの永続性が保証されないことです。RedisはPublishされたメッセージを、それを購読しているSubscriberに配信したら、そのメッセージをメモリから破棄します。メッセージを永続的にディスクに保存したり、Subscriberがオフラインだった場合にキューに保持しておいて後から配信したりする機能は、標準のPub/Subにはありません。

また、Redisは単一スレッドで動作するため、Pub/Subのメッセージ配信もこの単一スレッドで行われます。大量のメッセージが高速にPublishされたり、非常に多くのSubscriberがいたりする場合、Redisサーバーはこのメッセージ配信処理にリソースを消費します。ただし、Redisは非常に効率的に設計されているため、一般的な負荷であれば問題なく処理できます。

4. Redis Pub/Subの利点と欠点

Redis Pub/Subをリアルタイム通信に利用する際には、その特性をよく理解しておくことが重要です。

4.1 利点

  • シンプルさと使いやすさ: コマンドが直感的で少なく、簡単に導入できます。Pub/Subモデル自体も理解しやすい概念です。
  • 高速性・低レイテンシ: Redisはインメモリで動作するため、ディスクI/Oによる遅延がありません。また、Pub/Subの処理は非常に軽量であるため、メッセージの配信が高速に行われます。これは、ミリ秒以下の遅延が求められるリアルタイムアプリケーションにとって大きなメリットです。
  • スケーラビリティ: Redis自体がスケーラビリティの高いデータストアです。Pub/Subに関しても、Publish操作は高速で、Subscribe操作も接続数が増えてもサーバー側の負荷は比較的抑えられます(ただし、メッセージ数が増えるとその分配信負荷は増えます)。Redisクラスタ環境でも利用可能ですが、一部注意が必要です(後述)。
  • 軽量性: プロトコルがシンプルで、メッセージの管理(永続化、ACKなど)に関するオーバーヘッドがないため、サーバー側の負荷が比較的小さく済みます。
  • 疎結合: PublisherとSubscriberが互いの存在を知る必要がないため、システムコンポーネントの独立性が高まります。これにより、コンポーネントの追加や変更が容易になります。
  • 既存のRedisインフラの活用: すでにRedisをキャッシュやセッションストアとして利用している場合、追加のミドルウェアを導入することなくPub/Sub機能を利用できます。

4.2 欠点

  • メッセージの永続性がない: これがPub/Subの最大の欠点です。メッセージは配信されたら破棄されるため、以下の問題が発生します。
    • Subscriberがオフラインだった場合: SubscriberがPub/Sub接続を確立していない間にPublishされたメッセージは、Subscriberが後からオンラインになっても受信できません。メッセージは完全に失われます。
    • Redisサーバーが再起動した場合: Redisサーバーが再起動すると、その時点でメモリ上にあった購読情報や未配信のメッセージはすべて失われます。
  • メッセージの順序保証: 基本的には、同じチャンネルに対する複数のPublish操作の順序は、RedisサーバーがそれらのPublishコマンドを受け付けた順序に従って処理され、各Subscriberに同じ順序で配信されます。しかし、ネットワークの状況やSubscriber側の処理遅延によっては、メッセージが到着する順序がPublishedされた順序と異なる可能性もゼロではありません。また、Redisクラスタ環境においては、異なるノードで処理されたPublish操作の順序は保証されません。
  • ACK (確認応答) がない: メッセージがSubscriberに正常に配信され、アプリケーションによって処理されたことをPublisherが確認する仕組みがありません。メッセージが配信されたかどうかはRedisサーバーは知っていますが、Subscriberが実際にメッセージを受け取ったか、そしてそれを処理できたかまでは追跡しません。
  • メッセージの再送機能がない: 一度配信に失敗したメッセージや、Subscriberが取りこぼしたメッセージをRedis側で再送する機能はありません。
  • Subscriberごとのキューがない: 特定のチャンネルにPublishされたメッセージは、そのチャンネルを購読しているすべてのSubscriberに配信されます。Subscriberごとに独立したキューを持つわけではないため、特定のSubscriberがメッセージ処理に時間がかかっても、他のSubscriberには影響しませんが、メッセージの流量が多い場合はSubscriber側の処理能力がボトルネックになる可能性があります。

これらの欠点を理解した上で、Pub/Subが適しているユースケースと、そうでないユースケースを見極める必要があります。一般的に、メッセージが多少失われても問題ない用途や、最新の情報だけが必要な用途(例:リアルタイムの株価やスポーツスコア配信 – 古い情報は不要で、常に最新情報だけを知りたい)には適しています。一方、すべてのメッセージを確実に処理する必要がある用途(例:銀行取引の通知、重要なシステムイベントの配信)には、Pub/Sub単体ではなく、永続性や信頼性の高いメッセージキューシステム(Kafka, RabbitMQなど)や、後述するRedis Streamsのような機能と組み合わせて利用するか、それらを単独で利用することを検討すべきです。

5. Redis Pub/Subの基本的な使い方(実践)

ここでは、実際にRedis Pub/Subを使ってみる方法を見ていきましょう。まずは redis-cli を使った簡単な操作から、そしてPythonを使ったコード例を示します。

5.1 redis-cli を使った操作

Redisサーバーが起動していることを前提とします。

ステップ1: Subscriberになる

ターミナルを2つ開きます。一方のターミナルでSubscriberとしてRedisに接続し、特定のチャンネルを購読します。

“`bash

ターミナル1 (Subscriber)

redis-cli
SUBSCRIBE my_channel
“`

SUBSCRIBE my_channel コマンドを実行すると、クライアントはPub/Subモードに入ります。このターミナルは、my_channel にメッセージがPublishされるのを待ち受ける状態になります。コマンドを実行すると、Redisからの応答として以下のような表示が出るはずです。

Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "my_channel"
3) (integer) 1

これは、my_channel を購読したこと、そして現在1つのチャンネルを購読していることを示しています。

ステップ2: Publisherになる

もう一方のターミナルでPublisherとしてRedisに接続し、同じチャンネルにメッセージを発行します。

“`bash

ターミナル2 (Publisher)

redis-cli
PUBLISH my_channel “Hello, Pub/Sub!”
“`

PUBLISH my_channel "Hello, Pub/Sub!" コマンドを実行すると、メッセージ "Hello, Pub/Sub!"my_channel に発行されます。Redisサーバーは、このメッセージを my_channel を購読しているすべてのSubscriber(この例ではターミナル1のクライアント)に配信します。Publisher側の redis-cli には、メッセージが配信されたSubscriberの数が表示されます。

(integer) 1

ステップ3: Subscriberでのメッセージ受信

ターミナル1のSubscriber側に戻ると、発行されたメッセージを受信しているはずです。

Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "my_channel"
3) (integer) 1
1) "message"
2) "my_channel"
3) "Hello, Pub/Sub!"

"message" というタイプと共に、メッセージが発行されたチャンネル名 ("my_channel") とメッセージ本文 ("Hello, Pub/Sub!") が表示されています。

ステップ4: 購読解除

Subscriber側のターミナル1で、UNSUBSCRIBE コマンドを実行すると、指定したチャンネルの購読を解除できます。

“`bash

ターミナル1 (Subscriber)

UNSUBSCRIBE my_channel
“`

解除されると、以下のような応答が返ってきます。

1) "unsubscribe"
2) "my_channel"
3) (integer) 0

これは、my_channel の購読を解除し、現在購読しているチャンネル数が0になったことを示しています。購読しているチャンネルがなくなったため、このクライアント接続はPub/Subモードから抜けて、通常のRedisコマンドを受け付けられる状態に戻ります。

もし SUBSCRIBE my_channel another_channel のように複数のチャンネルを購読していた場合、UNSUBSCRIBE my_channelmy_channel だけを解除し、another_channel の購読は継続します。UNSUBSCRIBE と引数なしで実行すると、購読しているすべてのチャンネルを解除します。

ワイルドカード購読 (PSUBSCRIBE) の例:

Subscriber側で以下のように実行すると、news. で始まるすべてのチャンネルを購読できます。

“`bash

ターミナル1 (Subscriber)

PSUBSCRIBE news.*
“`

Publisher側で以下のように発行すると、Subscriberはメッセージを受け取ります。

“`bash

ターミナル2 (Publisher)

PUBLISH news.sports “Latest sports update!”
PUBLISH news.politics “Political news highlights”
“`

Subscriber側では、以下のように受信されます("pmessage" タイプである点に注意)。

Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "news.*"
3) (integer) 1
1) "pmessage"
2) "news.*" # 購読したパターン
3) "news.sports" # 実際に発行されたチャンネル名
4) "Latest sports update!"
1) "pmessage"
2) "news.*" # 購読したパターン
3) "news.politics"# 実際に発行されたチャンネル名
4) "Political news highlights"

PUNSUBSCRIBE [pattern] コマンドでワイルドカード購読を解除できます。

5.2 Pythonを使ったコード例

ここでは、Pythonのredis-pyライブラリを使ったPub/Subの簡単なコード例を示します。redis-pyはRedisサーバーとのやり取りをPythonから行うためのライブラリです。

インストール:
bash
pip install redis

Publisher側のコード (publisher.py)

“`python
import redis
import time

Redisサーバーに接続

デフォルト設定 (ホスト: localhost, ポート: 6379, DB: 0)

r = redis.Redis(decode_responses=True) # decode_responses=True でバイト列ではなく文字列として扱う

channel_name = “my_chat_channel”

print(f”Publisher: メッセージをチャンネル ‘{channel_name}’ に発行します。”)

message_count = 0
try:
while True:
message = f”Hello from Publisher! Message number {message_count}”
# PUBLISH コマンドを実行
# publish() メソッドは、メッセージが配信された Subscriber の数を返す
subscribers_count = r.publish(channel_name, message)

    print(f"Published: '{message}' to '{channel_name}'. Received by {subscribers_count} subscribers.")

    message_count += 1
    time.sleep(1) # 1秒待つ

except KeyboardInterrupt:
print(“\nPublisher terminated.”)
“`

Subscriber側のコード (subscriber.py)

“`python
import redis
import time

Redisサーバーに接続

r = redis.Redis(decode_responses=True)

channel_name = “my_chat_channel”

Pub/Subオブジェクトを作成

このオブジェクトが Redis Pub/Sub モードを管理する

pubsub = r.pubsub()

チャンネルを購読

subscribe() メソッドはすぐに戻る (Pub/Subモードにはまだ入っていない)

pubsub.subscribe(channel_name)

print(f”Subscriber: チャンネル ‘{channel_name}’ を購読します。メッセージを待機中…”)

try:
# listen() メソッドはメッセージが到着するまでブロックするイテレータを返す
# forループ内でメッセージを待ち受け、受信するたびに処理を実行する
for message in pubsub.listen():
# listen() は SUBSCRIBE/UNSUBSCRIBE の応答や、実際にPublishされたメッセージなど、様々な種類のメッセージを返す
# ‘type’ キーでメッセージの種類を判別する
if message[‘type’] == ‘message’:
print(f”Received Message: {message[‘data’]} on channel {message[‘channel’]}”)
elif message[‘type’] == ‘subscribe’:
print(f”Successfully subscribed to channel: {message[‘channel’]}”)
# 他のタイプ (‘unsubscribe’, ‘psubscribe’, ‘pmessage’ など) もここで処理できる

except KeyboardInterrupt:
print(“\nSubscriber terminated.”)
finally:
# 購読を解除し、Pub/Subモードを終了する
# unsubscribe() も listen() 中に実行されると listen() を中断させるシグナルとなる
pubsub.unsubscribe()
print(“Unsubscribed and Pub/Sub connection closed.”)
“`

実行方法:

  1. まず、subscriber.py を実行します。SubscriberはPub/Subモードに入り、メッセージを待ち受けます。
    bash
    python subscriber.py

    Subscriber: チャンネル 'my_chat_channel' を購読します。メッセージを待機中... のような出力の後、Successfully subscribed to channel: my_chat_channel というメッセージ受信ログが表示されるはずです。
  2. 次に、別のターミナルで publisher.py を実行します。Publisherは1秒ごとにメッセージを発行します。
    bash
    python publisher.py

    Publisher側では Published: ... というログが出力され、Subscriber側では Received Message: ... というログがリアルタイムに表示されるはずです。
  3. どちらかのスクリプトを終了するには、Ctrl+C を押します。

この例から分かるように、redis-py を使う場合、Publisherは通常のRedis接続オブジェクトを使いますが、Subscriberは r.pubsub() で取得した PubSub オブジェクトを使います。PubSub オブジェクトの listen() メソッドがメッセージ受信ループを処理し、受信したメッセージを辞書形式で返します。メッセージの種類を判別して適切な処理を行う必要があります。

この簡単な例は、Redis Pub/SubがどのようにメッセージをPublishし、Subscriberがどのようにそれを受け取るかを示しています。実際のアプリケーションでは、メッセージの形式をJSONにするなど、より構造化されたデータを使うことが一般的です。

6. Pub/Subを活用したリアルタイム通信システムの設計

Redis Pub/Subの仕組みと基本的な使い方が分かったところで、具体的なリアルタイム通信システムでどのように活用できるかを考えてみましょう。

6.1 シンプルなチャットシステム

Redis Pub/Subの最も分かりやすい活用例の一つが、シンプルなチャットシステムです。

  • 設計:

    • 各チャットルームをRedisのチャンネルとして扱います。
    • ユーザーが特定のチャットルームに入室する際、そのルームに対応するチャンネルを購読します。
    • ユーザーがメッセージを送信する際、そのメッセージを該当するチャンネルにPublishします。
    • ルームのチャンネルを購読している他のすべてのユーザーは、Publishされたメッセージをリアルタイムに受信します。
  • データフロー:

    1. ユーザーAが「General」ルームに入室。バックエンドサーバーはRedisに接続し、チャンネル chat:GeneralSUBSCRIBE するためのPub/Sub接続を確立します。
    2. ユーザーBも「General」ルームに入室。同様に chat:General チャンネルを購読します。
    3. ユーザーAが「Hello everyone!」というメッセージを送信。バックエンドサーバーはRedisに接続し、PUBLISH chat:General "Hello everyone!" コマンドを実行します。
    4. Redisは chat:General チャンネルを購読しているすべてのクライアント(ユーザーBを含む)にメッセージを配信します。
    5. ユーザーBのバックエンドサーバーはメッセージを受信し、ユーザーBのブラウザにWebSocketなどでプッシュして表示します。
  • 考慮事項:

    • メッセージ履歴: Pub/Subはメッセージを永続化しないため、ユーザーがルームに入室した際に過去のメッセージ履歴を表示するには、別途データベースなどにメッセージを保存しておく必要があります。入室時にDBから履歴を取得し、それ以降のリアルタイムメッセージはPub/Subで受信するという形になります。
    • ユーザープレゼンス: 現在ルームに誰が入室しているか(誰がチャンネルを購読しているか)をRedis Pub/Sub単体で知ることはできません。これは別の仕組み(例: RedisのSet型でユーザーIDを管理する、ログイン/ログアウトリクエストをフックするなど)で管理する必要があります。
    • メッセージの信頼性: Pub/SubはACKや再送機能を持たないため、ネットワークの問題などでメッセージが取りこぼされる可能性があります。厳密なメッセージ保証が必要なチャット(例: ビジネスチャット)にはPub/Sub単体は向かない場合がありますが、一般的なカジュアルチャットであれば許容されることが多いです。
    • スケーラビリティ: 大量のルームやユーザーを扱う場合、Redisの接続数が増加します。Subscriberごとに1つの接続が必要になるため、接続数が非常に多くなる場合はRedisサーバーのリソースを圧迫する可能性があります。

6.2 システム通知/イベント配信

バックエンドシステム内で発生した様々なイベント(例: 新規ユーザー登録、注文完了、処理の失敗など)を、関連する他のサービスやフロントエンドにリアルタイムに通知する用途にも利用できます。

  • 設計:

    • イベントの種類ごとにチャンネルを分けたり、ワイルドカード購読を利用したりします(例: event:user:registered, event:order:completed, event:process:failed)。
    • イベントを発生させたサービスが、該当するチャンネルにイベント情報をPublishします。
    • これらのイベントに関心のある他のサービスや、リアルタイム通知をユーザーに届けたいWebSocketサーバーなどが、該当チャンネルを購読します。
  • データフロー:

    1. ユーザー登録サービスが新しいユーザー登録を完了。PUBLISH event:user:registered '{ "user_id": "123", ... }' コマンドを実行。
    2. メール送信サービスが event:user:registered チャンネルを購読しており、メッセージを受信。「新規登録ありがとうございます」メールを送信する処理を開始。
    3. 管理画面をリアルタイム更新するWebSocketサーバーが event:user:* パターンを購読しており、メッセージを受信。管理画面に接続しているクライアントに「新規ユーザー登録がありました」という通知をプッシュ。
  • 考慮事項:

    • イベントの重要度: 重要なイベントで、メッセージが失われると業務に支障が出る場合は、Pub/Sub単体ではなく、信頼性の高いメッセージキューシステム(Kafka, RabbitMQなど)や、後述するRedis Streamsのような永続性のある機能の利用を検討すべきです。
    • メッセージ形式: イベントの種類によってペイロードが異なるため、JSONなどの構造化された形式でイベント情報をPublishすることが望ましいです。
      処理の冪等性: 同じイベントメッセージが複数回配信される可能性は低いですが(基本的には1回配信)、Pub/SubはACKがないため、Subscriber側で処理が成功したか発行元には通知されません。Subscriber側で処理が中断された場合のリカバリや、イベント処理の冪等性(複数回処理されても問題ないようにする)を考慮する必要があるケースもあります。

6.3 リアルタイムデータ更新配信

株価、スポーツの試合経過、IoTセンサーデータなど、頻繁に更新されるデータを複数のクライアントにリアルタイムに配信する用途にも適しています。

  • 設計:

    • 配信したいデータの種類や対象ごとにチャンネルを分けます(例: stock:AAPL, game:soccer:123, sensor:temp:roomA)。
    • データを生成/更新するシステムが、新しいデータを該当チャンネルにPublishします。
    • データを表示したいアプリケーション(ウェブサイト、モバイルアプリなど)に接続しているクライアントは、バックエンド/WebSocketサーバーなどを経由して該当チャンネルを購読します。
  • データフロー:

    1. 株価情報フィードプロセッサーがAAPL株の新しい価格を取得。PUBLISH stock:AAPL '{ "price": 175.50, "timestamp": ... }' コマンドを実行。
    2. AAPL株価を表示しているウェブページに接続しているWebSocketサーバーが stock:AAPL チャンネルを購読しており、メッセージを受信。接続しているブラウザクライアントに新しい株価データをプッシュして表示を更新。
  • 考慮事項:

    • メッセージ消失の許容度: 株価やスポーツスコアのように、常に最新情報が重要で、古い情報が多少失われても問題ない用途に非常に適しています。Subscriberが一時的にオフラインだったとしても、再度接続した際に最新の情報を受け取れば問題ないため、Pub/Subのメッセージ永続性の欠点が大きな問題になりにくいです。
    • 高頻度なPublish: 更新頻度が高いデータの場合、Pub/Subのメッセージ配信能力が重要になります。Redisは高速ですが、サーバーのリソース(CPU、ネットワーク帯域)は有限です。メッセージサイズやPublish頻度によっては負荷が高くなる可能性があるため、監視が必要です。
    • Subscriber数の影響: 大量のクライアントにデータを配信する場合、Pub/Sub接続数がRedisサーバーの負荷要因になり得ます。WebSocketサーバーのような、Redis Pub/SubのSubscriberになり、複数のクライアントへの配信を担う仲介役を置く構成が一般的です。

Pub/Subの欠点を補うアーキテクチャパターン

Pub/Subの欠点、特にメッセージの永続性がない点を補いたい場合、以下のようなパターンを検討できます。

  • 別途DBとの併用:

    • PublisherはメッセージをPublishすると同時に、そのメッセージをデータベース(RDB, NoSQL, Redisなど)にも保存します。
    • Subscriberがオフラインから復帰した場合や、アプリケーション起動時に、まずDBから未読または最新のメッセージを取得します。
    • DBからの取得が完了したら、Pub/Sub接続を確立し、リアルタイムメッセージを受信するようにします。DBから取得したメッセージとPub/Subで受信したメッセージの重複や順序のずれをハンドリングするロジックが必要になります。
    • チャットシステムのメッセージ履歴管理などでよく使われるパターンです。
  • Redis Streamsとの組み合わせ:

    • Redis 5.0から導入された Streams は、Pub/Subとメッセージキューの特性を併せ持つ強力な機能です。メッセージが永続化され、コンシューマーグループによる分散処理、ACK、メッセージの再送などが可能です。
    • PublisherはメッセージをPub/SubチャンネルにPublishする代わりに、Streamに追加します。
    • リアルタイム性が必要なコンシューマーは、Streamを尾行するように読み取ります(XREAD コマンドの BLOCK オプションや、新しいエントリを待つ)。
    • 一方、メッセージの永続性や信頼性が必要なコンシューマーは、コンシューマーグループを使ってStreamを処理します。
    • StreamsはPub/Subの欠点を多くカバーするため、Pub/SubよりもStreamsの方が適しているユースケースも増えています。これについては後述します。

このように、Redis Pub/Subは万能ではありませんが、そのシンプルさと高速性を活かせるユースケースは多く存在します。欠点を理解し、必要に応じて他の技術やデータストアと組み合わせることで、堅牢なリアルタイム通信システムを構築することが可能です。

7. Pub/Subの注意点と運用

Redis Pub/Subを本番環境で利用する際には、いくつかの注意点があります。

  • スケーラビリティ:

    • 大量のSubscriber: Subscriberが増えると、Redisサーバーはそれらのクライアント接続を維持し、メッセージを配信する必要があります。接続数が数万〜数十万といった規模になると、Redisサーバーのリソース(特にメモリとネットワーク帯域)がボトルネックになる可能性があります。Pub/Sub Subscriber専用のRedisインスタンスを用意したり、WebSocketサーバーなどの仲介役を置くことで、Redisへの直接接続数を減らすなどの対策が考えられます。
    • 大量のメッセージ: メッセージのPublish頻度が高い場合、Redisサーバーはその処理と配信にリソースを使います。メッセージサイズが大きい場合も同様です。パフォーマンスボトルネックになっていないか、Redisのメトリクス(info pubsub コマンドなどで確認できる Pub/Sub 関連の情報や、Redisの監視ツール)を監視することが重要です。
    • Redisクラスタ環境: Redisクラスタでは、データはハッシュスロットに基づいて各ノードに分散されます。Pub/Subのチャンネルは、ハッシュスロットとは関連付けられません。重要な注意点として、Redisクラスタ環境でPub/Subを利用する場合、SubscriberはすべてのマスターノードにSubscribeする必要があります。また、Publisherはメッセージを発行したいチャンネルを購読しているSubscriberが接続しているノードに対してPublishする必要があります。Publisherが間違ったノードにPublishした場合、そのメッセージは購読しているSubscriberに届きません。クライアントライブラリによっては、クラスタ環境でのPub/Subを正しく扱うための機能を提供しているものもありますが、この挙動はスタンドアロン構成とは異なるため注意が必要です。通常、クラスタ環境でのPub/Subはあまり一般的ではなく、代わりにRedis Streamsが推奨される傾向にあります。
  • セキュリティ:

    • デフォルトでは、Redisは認証なしでアクセスできます。Pub/Subも同様に、Redisに接続できれば誰でもチャンネルをPublishまたはSubscribeできてしまいます。これはセキュリティ上の大きなリスクです。Redisにパスワード認証(requirepass 設定)を設定するか、Redis ACL (Access Control List) を利用してPub/Subコマンドの実行権限を制限することが強く推奨されます。ファイアウォールでRedisへのアクセスを制限するのも基本的な対策です。
  • メッセージの消失リスクへの再確認:

    • 前述の通り、Pub/Subはメッセージ永続性を持ちません。Subscriberがオフラインだったり、Redisサーバーが再起動したりするとメッセージは失われます。アプリケーション設計において、メッセージが失われることを許容できるか、あるいは何らかの対策(DBでの永続化、Redis Streamsの使用など)が必要かを十分に検討してください。リアルタイム通信において、メッセージの「最新性」が重要で「確実な配信」が二次的な要件である場合にPub/Subは適しています。
  • モニタリング:

    • RedisサーバーのPub/Sub関連のメトリクスを監視することで、Pub/Sub機能の健全性やパフォーマンスを把握できます。
      • INFO PUBSUB: Pub/Sub関連の統計情報(購読中のパターン数、Pub/Sub接続数など)を取得できます。
      • 接続されているクライアントの状態 (CLIENT LIST コマンドや INFO clients メトリクス) を確認し、Pub/Subモードのクライアント数やアイドル時間などを監視します。
      • Redisサーバー全体のCPU使用率、メモリ使用率、ネットワーク帯域などを監視し、Pub/Subに関連する負荷が高い場合は原因を調査します。
  • デバッグ:

    • Pub/Subは非同期通信であるため、PublishしたメッセージがSubscriberに届かない、あるいは遅延するといった問題が発生した場合のデバッグが難しい場合があります。
    • Redisサーバー側でのエラーログや、クライアントライブラリが出力するログを確認することが重要です。
    • MONITOR コマンドを使うとRedisサーバーが受け付けたすべてのコマンドをリアルタイムに表示できますが、これはパフォーマンスに影響を与える可能性があるため、本番環境での常用は避けるべきです。テスト環境でのデバッグには有効です。
    • Pub/Sub SubscriberクライアントがPub/Subモードから抜けられない、あるいは他のコマンドを実行しようとしてエラーになる場合は、クライアント側の実装を確認してください。

これらの注意点を踏まえ、Pub/Subが自身のアプリケーションの要件に合致するかを慎重に判断し、必要に応じて適切な対策や代替技術の利用を検討することが成功の鍵となります。

8. Pub/Subの代替または組み合わせ技術

Redis Pub/Subが提供する「Publish-Subscribe」モデルは、他の様々な技術でも実現されています。特に、Pub/Subの欠点である「メッセージの永続性」や「信頼性」を補うための機能を持つシステムが多く存在します。ここでは、いくつかの関連技術とRedis Pub/Subとの使い分けについて説明します。

8.1 Redis Streams

前述の通り、Redis StreamsはRedis 5.0で導入された比較的新しい機能です。これは、永続化されたメッセージキューとして機能し、Pub/Subの多くの欠点を克服します。

  • Streamsの特徴:

    • 永続性: 追加されたメッセージはRedisのメモリおよびAOFファイル(設定による)に保存されます。Redisが再起動してもメッセージは失われません。
    • コンシューマーグループ: 複数のコンシューマーをグループ化し、ストリーム内のメッセージを分散して処理させることができます。これにより、コンシューマーのスケーリングが容易になります。
    • ACK: コンシューマーはメッセージを処理した後にACKを送信することで、メッセージが正常に処理されたことを示せます。ACKされなかったメッセージは、後で再配信したり、保留中のメッセージリストを確認したりできます。
    • 履歴参照: Streamに追加されたメッセージには一意のIDが割り当てられ、そのIDを使って過去のメッセージを遡って読み込むことができます。これは、Subscriberがオフラインだった間に発行されたメッセージを後から取得したい場合に非常に有効です。
  • Pub/SubとStreamsの使い分け:

    • Pub/Sub:
      • メッセージの永続性が不要で、最新の情報だけを複数のSubscriberに素早く届けたい場合に適しています(例: 株価、スポーツスコア、カジュアルな通知)。
      • 実装が非常にシンプルです。
      • Subscriberごとに状態管理は不要です。
    • Streams:
      • メッセージの永続性や信頼性が必須で、メッセージが失われては困る場合に適しています(例: システムイベント、タスクキュー、メッセージング)。
      • コンシューマーグループによる分散処理や、メッセージの処理状態管理が必要です。
      • Pub/Subよりも機能が豊富で強力ですが、その分だけ概念やコマンドが複雑になります。

どちらの機能を選択するかは、アプリケーションの要件(メッセージ消失の許容度、メッセージ処理の信頼性、コンシューマーのスケーリング要件など)によって判断します。Pub/Subのシンプルさで十分な場合はPub/Subを、より堅牢なメッセージングが必要な場合はStreamsを検討すると良いでしょう。また、Pub/SubとStreamsを組み合わせて利用するケースも考えられます(例: Streamに書き込まれたことをトリガーに、Pub/Subでリアルタイム通知を飛ばすなど)。

8.2 従来のメッセージキューシステム (Kafka, RabbitMQなど)

KafkaやRabbitMQは、Redisとは異なり、メッセージングを主たる目的とした専用のミドルウェアです。これらはRedis Pub/SubやStreamsよりもさらに高機能で、エンタープライズレベルの堅牢性やスケーラビリティを提供します。

  • Kafka: 分散ストリーミングプラットフォーム。非常に高いスループットと永続性、パーティションによる順序保証、コンシューマーグループによる分散処理が特徴です。大量のストリーミングデータを扱う用途や、イベントソーシング基盤として広く利用されています。
  • RabbitMQ: 高機能なメッセージブローカー。AMQPなどの標準プロトコルをサポートし、多様なルーティングオプション、信頼性の高い配信(ACK、再送)、キューの管理機能などを備えています。タスクキューやシステム間の非同期連携、信頼性の高いメッセージングに適しています。

  • Redis Pub/Sub/Streamsとこれらのシステムの使い分け:

    • シンプルさと低遅延: Redis Pub/Subはセットアップと利用が非常に簡単で、低遅延なリアルタイム配信に適しています。Streamsも比較的シンプルに利用できます。
    • 高機能と堅牢性: KafkaやRabbitMQは、メッセージの永続性、順序保証、多様な配信モデル、高可用性、大規模なスケーラビリティなど、より多くの機能と高い信頼性を提供します。これらを運用するには専門知識が必要な場合が多いです。
    • 利用シーン:
      • Redis Pub/Sub: アプリケーション内の軽量なリアルタイム通知、キャッシュ無効化通知、開発/テスト環境での手軽なメッセージングなど。
      • Redis Streams: Pub/Subより信頼性が必要だが、KafkaやRabbitMQほどの複雑な機能は不要な場合。Redisをすでに利用しており、インフラを増やしたくない場合。シンプルな永続化キューやイベントストリームとして。
      • Kafka/RabbitMQ: システム間の重要な非同期連携、大量データのパイプライン処理、ミッションクリティカルなメッセージング、多様なルーティング要件が必要な場合。

自身のプロジェクトの規模、要件(メッセージの重要度、スループット、遅延、運用負荷)、既存インフラなどを考慮して、最適な技術を選択することが重要です。Redis Pub/Subは、他のシステムと比較して機能は限定的ですが、そのシンプルさとRedisエコシステムの一部であるという点で、多くのユースケースで有効な選択肢となり得ます。

8.3 WebSocket/Server-Sent Events (SSE)

これらは主に「クライアント(ブラウザなど)とサーバー間」でのリアルタイム通信を実現する技術です。Pub/Subは「サーバー側のコンポーネント間」または「バックエンドとWebSocket/SSEサーバー間」での情報伝達に使われることが多いです。

  • 組み合わせの例:
    • バックエンドサービスAでイベント発生 -> Redis Pub/SubでイベントをPublish
    • WebSocketサーバーがPub/SubチャンネルをSubscribeしてイベントを受信
    • WebSocketサーバーがイベント情報を接続しているクライアント(ブラウザ)にWebSocketでプッシュ配信
    • クライアントは受信したイベントに基づいてUIを更新

この構成は非常に一般的で、Pub/Subがシステム内部のイベントバスとして機能し、WebSocket/SSEがクライアントへの最後のリアルタイム配信手段を担います。このように、Pub/Subは他のリアルタイム技術と組み合わせて利用することで、より広範なリアルタイム通信のニーズに対応できます。

9. まとめ

本記事では、RedisのPub/Sub機能を利用したリアルタイム通信について、その基本的な仕組みから実践的な使い方、利点・欠点、活用例、注意点、そして関連技術との比較まで、詳細に解説しました。

Redis Pub/Subは、Publisherがチャンネルにメッセージを発行し、そのチャンネルを購読しているSubscriberがメッセージを受信する、という非常にシンプルなモデルに基づいています。このシンプルさが、高速性・低レイテンシというRedis Pub/Sub最大の強みを生み出しています。

Pub/Subは、メッセージの永続性やACKといった機能を持たないため、メッセージが失われる可能性があるという欠点があります。しかし、この特性が逆にシンプルさとパフォーマンスに繋がり、リアルタイムの株価配信や、メッセージが多少失われても問題ないシステム通知など、最新の情報だけを効率的に配信したいユースケースには非常に適しています。

Pub/Subを効果的に活用するためには、その欠点を理解し、必要に応じてメッセージ履歴を別途データベースで管理したり、より堅牢な配信が必要な場合はRedis Streamsや他のメッセージキューシステム(Kafka, RabbitMQなど)の利用を検討したり、WebSocket/SSEと組み合わせてクライアントへのリアルタイム配信を実現したりといったアーキテクチャ的な工夫が必要です。

Pub/Subの基本的なコマンド(PUBLISH, SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE)は少なく、習得は容易です。Pythonの redis-py のようなクライアントライブラリを使えば、簡単にアプリケーションに組み込むことができます。

これからリアルタイム通信を取り入れたいと考えている入門者の方にとって、Redis Pub/Subは手軽に始められる強力なツールとなり得ます。ぜひ実際にRedis Pub/Subを使ってみて、そのシンプルさとスピードを体験してみてください。そして、ご自身のアプリケーションの要件に合わせて、Pub/Subが最適か、それともStreamsや他の技術の方が適しているかを判断し、適切な設計を行っていくことが重要です。

リアルタイムな世界への第一歩として、Redis Pub/Subはきっとあなたの役に立つはずです。


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール