ChatGPTの使用状況を調査した結果、GPTがストリーミングデータの返却方法を採用していることがわかりました。理論的には、これは全二重通信プロトコルを用いた永続的な接続、あるいはEventStreamベースのイベントストリームを利用することで実現できます。しかし、ChatGPTは後者、つまりこの記事で詳しく説明するSSE(Server-Sent Events)テクノロジーを採用しました。
この選択を理解するには、ChatGPTのユースケースを考慮する必要があります。ChatGPTはディープラーニングに基づく大規模言語モデルであるため、膨大な量の自然言語データを処理する必要があり、当然ながら膨大な計算リソースと時間を必要とします。通常のデータベース読み取り操作と比較すると、応答速度は当然ながらはるかに遅くなります。
ChatGPTは、応答に長時間かかる可能性のある対話シナリオにおいて、巧妙な戦略を採用しています。計算済みのデータをユーザーに「プッシュ」し、SSE技術を用いて計算プロセス中に継続的にデータを返します。これにより、ユーザーが待ち時間の長さのためにページを閉じることを防ぎます。
SSEとは何ですか?
Server-Sent Events(SSE)は、サーバーがクライアントにリアルタイムでデータをプッシュすることを可能にするWebテクノロジーです。従来のポーリングやロングポーリングのメカニズムと比較して、SSEはより効率的でリアルタイムなデータプッシュ方式を提供します。このテクノロジーは主に、リアルタイムメッセージプッシュや株価更新などのリアルタイムアプリケーションの構築に使用されます。
SSEはHTML5仕様における通信関連のAPIです。主に、サーバーとブラウザ間の通信プロトコル(HTTPプロトコルに基づく)と、ブラウザ側のJavaScriptで使用できるEventSourceオブジェクトの2つの部分で構成されています。
SSEはHTTPプロトコル上で動作し、サーバーがイベントストリームの形式でクライアントにデータを送信できるようにします。クライアントは永続的なHTTP接続を確立し、このイベントストリームをリッスンすることで、サーバーからプッシュされたデータをリアルタイムで受信します。
SSE の主な特徴は次のとおりです。
- シンプルで使いやすい: SSE はプレーン テキストや JSON などのテキストベースのデータ形式を使用するため、データの送信と解析が比較的シンプルで簡単になります。
- 一方向通信:SSEはサーバーからクライアントへの一方向通信のみをサポートします。つまり、サーバーはクライアントにデータを能動的にプッシュできますが、クライアントはデータを受動的に受信することしかできません。
- リアルタイムパフォーマンス:SSEは永続的な接続を確立できるため、サーバーはクライアントからの頻繁なリクエストを必要とせずに、リアルタイムでデータをクライアントにプッシュできます。これにより、データ転送の効率とリアルタイムパフォーマンスが大幅に向上します。
SSEとWebSocketの比較
WebSocketはリアルタイムの双方向通信を実現するWeb技術であり、SSE(Server-Sent Events)とはいくつかの点で異なります。以下は両者の比較です。
- データプッシュ方向:SSEは主にサーバーからクライアントへの一方向通信をサポートしており、サーバーはクライアントにデータをプロアクティブにプッシュできます。一方、WebSocketは双方向通信をサポートしており、サーバーとクライアント間でリアルタイムのデータ交換が可能です。
- 接続の確立:SSEはHTTPベースの永続的な接続を利用し、通常のHTTPリクエストとレスポンスを通じて接続を確立することで、リアルタイムのデータプッシュを実現します。一方、WebSocketはカスタムプロトコルを採用し、WebSocket接続を作成することで双方向通信を実現します。
- 互換性:SSEはHTTPプロトコルをベースとしているため、追加のプロトコルアップグレードを必要とせず、ほとんどの最新ブラウザで使用できます。WebSocketもほとんどの最新ブラウザでサポートされていますが、特定のネットワーク環境では問題が発生する可能性があります。
- 適切なシナリオ:SSEは、株価更新やリアルタイムニュースプッシュなど、サーバーからクライアントへリアルタイムでデータをプッシュする必要があるシナリオに適しています。一方、WebSocketは、チャットアプリケーションや複数ユーザーによるオンライン共同編集など、リアルタイムの双方向通信が必要なシナリオに適しています。
SSEとWebSocketのどちらを選ぶかは、主に具体的なビジネスニーズとシナリオによって異なります。サーバーからクライアントへの一方向のデータプッシュのみを実装し、操作性と優れた互換性を維持したい場合は、SSEが理想的な選択肢です。ただし、双方向通信を実装する必要がある場合、またはより高度な機能と制御が必要な場合は、WebSocketの方が適している可能性があります。
SSEの実装原則
SSE (Server-Sent Events) の実装原則は次のとおりです。
- 接続の確立: 通常、クライアント (ブラウザなど) は、サーバーに HTTP GET 要求を送信して SSE 接続を確立するよう要求します。
- サーバー応答: サーバーがリクエストを受信すると、ステータス コード 200 とコンテンツ タイプ "text/event-stream" の HTTP 応答を返します。
- データプッシュ:サーバーは確立された接続を介してクライアントにデータをプッシュできます。プッシュされた各データはイベントと呼ばれます。各イベントは「\n\n」で区切られた1つ以上のデータブロックで構成されます。各データブロックは1行のテキストで、「:」で始まるコメント行、「data:」で始まるデータ行、またはイベントIDとイベントタイプを指定する「id:」と「event:」で始まる行を含むことができます。
- クライアント側の処理: クライアントがサーバーからプッシュされたイベントを受信すると、対応する JavaScript イベント ハンドラーがトリガーされ、これらのイベントが処理されます。
- 再接続:接続が失われた場合、クライアントは自動的に再接続を試みます。サーバーがイベントにIDを指定している場合、クライアントは再接続時に「Last-Event-ID」HTTPヘッダーをサーバーに送信し、クライアントが最後に受信したイベントのIDをサーバーに伝えます。この情報に基づいて、サーバーはどのイベントからデータの再送信を開始するかを決定します。
要約すると、SSE はテキストと HTTP プロトコルに基づくシンプルなメカニズムを使用して、クライアントが頻繁に新しいリクエストを開始することなく、サーバーがクライアントにデータをリアルタイムでプッシュできるようにします。
SSE使用上の注意
リアルタイム データ プッシュに SSE (Server-Sent Events) テクノロジーを使用する際に注意すべき重要なポイントは次のとおりです。
- 非同期処理:SSEは永続的な接続に依存するため、データのプッシュ処理にはかなりの時間がかかる場合があります。サーバースレッドがブロックされるのを防ぐため、SSEリクエストを非同期で処理することをお勧めします。例えば、コントローラーメソッドで@Asyncアノテーションを使用するか、CompletableFutureなどの非同期プログラミング手法を活用することができます。
- タイムアウト処理:SSE接続は、ネットワークの中断、クライアントのシャットダウン、その他の理由によりタイムアウトすることがあります。無効な接続によるサーバーリソースの消費を防ぐため、タイムアウト期間を設定し、タイムアウト状況に対処することをお勧めします。例えば、`SseEmitter`オブジェクトの`setTimeout()`メソッドを使用してタイムアウト期間を設定し、`onTimeout()`メソッドを使用してタイムアウトロジックを処理できます。
- 例外処理:実際のアプリケーションでは、ネットワークエラーやデータプッシュの失敗などの問題が発生する可能性があります。このような場合、`SseEmitter` オブジェクトの `completeWithError()` メソッドを使用して例外情報をクライアントに送信し、クライアントは `eventSource.onerror` イベントを通じて例外を処理できます。
- メモリ管理:SseEmitterを使用する場合、特に多数の同時接続が発生するシナリオでは、メモリ管理に特別な注意を払う必要があります。クライアントが切断された場合、リソースリークやメモリオーバーフローを回避するために、SseEmitterオブジェクトを速やかに解放することが不可欠です。
- 同時実行パフォーマンス:SSEにおける同時接続数はサーバーパフォーマンスに影響を与える可能性があります。多数の同時接続を処理する必要がある場合は、スレッドプールなどの非同期処理方法を使用してサーバーリソースの利用率を最大化することを検討してください。
- クライアントの互換性:最近のブラウザのほとんどはSSEをサポートしていますが、一部の古いブラウザはサポートしていない場合があります。そのため、SSEを使用する場合は、対象クライアントがSSEを適切にサポートしていることを確認するか、代替のリアルタイムデータプッシュメカニズムを提供する必要があります。
上記の注意事項は、特定のアプリケーションのニーズに合わせて調整・最適化できます。実際のアプリケーションでは、サーバーの安定性、セキュリティ、パフォーマンスの確保が不可欠です。さらに、SSE接続を扱う際には、不正利用や悪意のある接続を防ぐために、適切なレート制限とセキュリティ制御を考慮する必要があります。まとめると、SSEテクノロジーを使用するには、効率的で安定した安全なリアルタイムデータプッシュサービスを実現するために、様々な要素を総合的に考慮する必要があります。
SpringBoot と SSE の統合例
リアルタイム株価モニタリングアプリケーションを開発し、株価をリアルタイムでクライアントにプッシュする必要があるとします。以下は、Spring BootとSSEテクノロジーを使用して実装したサンプルコードです。
まず、SSE リクエストを処理し、リアルタイムの株価を送信するコントローラーを定義します。
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.Random;
@RestController
public class StockController {
@GetMapping (value = "/stock-price" , produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter streamStockPrice () {
SseEmitter emitter = new SseEmitter();
// 模拟生成实时股票价格并推送给客户端
Random random = new Random();
new Thread(() -> {
try {
while ( true ) {
// 生成随机的股票价格
double price = 100 + random.nextDouble() * 10 ;
// 构造股票价格的消息
String message = String.format( "%.2f" , price);
// 发送消息给客户端
emitter.send(SseEmitter.event().data(message));
// 休眠1秒钟
Thread.sleep( 1000 );
}
} catch (Exception e) {
emitter.completeWithError(e);
}
}).start();
return emitter;
}
}
上記のコードでは、 streamStockPrice()メソッドが定義されています。このメソッドは、 @GetMappingアノテーションを使用して/stock-priceパスをメソッドにマッピングし、 produces = MediaType.TEXT_EVENT_STREAM_VALUEを指定して、メソッドがSSEイベントストリームを生成することを示しています。
メソッド内では、イベント エミッターとしてSseEmitterオブジェクトが作成され、別のスレッドでランダムな株価が継続的に生成され、文字列形式に変換されてクライアントに送信されます。
emitter.send()メソッドを介して送信されたデータは SSE イベント ストリームとしてカプセル化され、クライアントはこれをリッスンして株価をリアルタイムで受信できます。
フロントエンドで、リアルタイムの株価を表示する簡単な HTML ページを作成します。
<!DOCTYPE html >
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< title >实时股票价格监控</ title >
</ head >
< body >
< h1 >实时股票价格</ h1 >
< div id = "stock-price" > </ div >
< script >
const eventSource = new EventSource( '/stock-price' );
eventSource.onmessage = function ( event ) {
document .getElementById( 'stock-price' ).innerHTML = event.data;
};
</ script >
</ body >
</ html >
上記のコードでは、 new EventSource('/stock-price')を使用してEventSourceオブジェクトを作成し、 /stock-priceパスとの SSE 接続を確立しています。次に、 eventSource.onmessageを使用してコールバック関数を定義し、新しいメッセージを受信してページ上の株価を更新します。
上記のコードは、ブラウザでHTMLページを開きます。サーバーとのSSE接続を確立し、株価をリアルタイムで受信して表示します。これは、SSEを用いてリアルタイムデータプッシュを実装するシンプルな例です。実際には、具体的なビジネスニーズやシナリオに応じて、より複雑で洗練された実装を開発することも可能です。
まとめ
Server-Sent Events (SSE) は、HTTP プロトコルをベースとした軽量なリアルタイム通信技術であり、サーバー側でのプッシュ、切断後の再接続、シンプルさといった利点を備えています。しかし、双方向通信ができない、接続数が限られている、GET リクエストのみをサポートしているといった制限もあります。
Web アプリケーションでは、SSE はオンライン株価データの更新、ログのプッシュ、チャット ルームの参加者のリアルタイム表示など、さまざまなリアルタイム データ プッシュ機能を実装できます。
ただし、SSEはすべてのリアルタイムプッシュシナリオに適しているわけではないことに注意が必要です。高い同時実行性、高スループット、低レイテンシが求められるシナリオでは、WebSocketの方が適している可能性があります。一方、軽量なプッシュソリューションが求められるシナリオでは、SSEの方が適している可能性があります。
したがって、リアルタイム更新ソリューションを選択する際には、具体的なニーズとアプリケーションシナリオに基づいて決定を下す必要があります。そうすることで初めて、選択したテクノロジーがニーズに最適なものとなることを確信できるのです。