Article

なぜ最終的にリアルタイムの動的更新の実装に Server-Sent Events を採用したのか?

AI 要約

著者は個人サイトに「現在の活動状況(コーディングや音楽再生など)」を表示する機能を実装する際、最適なデータ転送手法を比較検討しました。ポーリングはリソースの無駄が多く、WebSocketは単方向の通知にはオーバースペックで運用コストも高いため不採用としました。結果として、サーバーからの単方向通信であるSSE(Server-Sent Events)を採用しました。SSEは標準HTTPプロトコルを使用し、自動再接続機能を備え、軽量で今回の要件に最適でした。記事ではSSEのテキスト専用という仕様や接続数制限、簡易的なエラー処理などの注意点にも触れつつ、単純なステータス配信には最も効率的な選択肢であると結論づけています。

技術公開·更新·言語 中国語 -> 日本語·AI 翻訳
#オリジナル#ネットワーク#バックエンド開発#リアルタイム通信

最初の個人ホームページを設計したとき、私は自ら墓穴を掘りました——今この瞬間、コードを書いているのか、音楽を聴いているのか、それともゲームをしているのかを示す「リアルタイムの動的更新(即时动态)」機能がどうしても必要だと思ったのです。😵‍💫 これは単なる見掛け倒しのように聞こえるかもしれませんが、間違いなく見掛け倒しそのものです。

この執着は、以前 Mix-Space の Shiro にある 我的动态 のために情報レポートソフトウェアを開発したことから生まれました。現在でも私は、動的情報のレポートを実現する AlienFamilyHub/Kizuna: 基于 Tauri 的动态信息上报程序 をメンテナンスし続けています。🫣

ネットサーフィン中に、偶然面白いものを見つけましたH2#

その一H3#

コーディング時間統計プラグインpluginの codetime は、エディタで編集中の情報を API を介してフロントエンドに返し、リアルタイムのコーディングステータス情報を提供します。实际上就是发现了这个然后打开控制台才发现了这个好玩的 api 的 以下のような返される結果は非常に詳細で、ファイルパスや Git リポジトリまで正確にわかります:

json
{
    "id": number,
    "uid": number,
    "eventTime": number,
    "language": "typescript",
    "project": "Kizuna",
    "relativeFile": "src/stores/eventStore.ts",
    "absoluteFile": "c:\\Users\\tianx\\Desktop\\Kizuna\\src\\stores\\eventStore.ts",
    "editor": "VSCode",
    "platform": "Windows 11",
    "gitOrigin": "https://github.com/AlienFamilyHub/Kizuna.git",
    "gitBranch": "master"
}

この API を通じて、現在のユーザーが何を編集しているのか、どのプラットフォームで書いているのか、どのエディタを使用しているのか、具体的なプロジェクト名、さらにはどのファイルを書いているのかまで取得できます。

その二H3#

NetEase Cloud Music(網易雲音楽)の「再生中ステータス」は、私が最も表示したかった要素の一つです。マルチユーザーの同期が可能であれば、必ず再生ステータスを取得できるインターフェースがあるはずだと考えました。

公開された API はありませんが、有志の凄腕エンジニアたちがパケットキャプチャなどのリバースエンジニアリングを行い、関連するコンテンツを開発者が利用できるように公開してくれていました。

XiaoMengXinX/Music163Api-Go: 网易云音乐 API Golang 实现 というプロジェクトで、この機能の詳細な実装を発見し、最終的に単一ファイルで呼び出せる形にカプセル化することができました。こうして ProcessReporterWingo/core/NcmNowPlay/main.go at master · TNXG/ProcessReporterWingo が誕生しました。

そこで、これらをブログの動的更新に組み込めないかと考えました。H2#

答えはもちろん「イエス」ですが、どのような方法でこれらの情報を伝達すればよいでしょうか?

ポーリング(コンコン——、新しいメッセージはありますか?)H3#

最初は最もシンプルな方法である**ポーリングpolling**を使用しました。フロントエンドが数秒ごとに API をリクエストし、サーバーに「そっちに何か新しい更新はある?」と尋ねる方法です。

この方法は最も簡単ですが、その欠点も明らかです(AI はよくトラフィックの無駄遣いだと私に言います。確かにそれがポーリングの主な問題ですが、今の時代に誰が従量課金のデバイスであなたのウェブサイトを見るというのでしょうか(取消線、でも最適化できるならしておきましょう

コンソールの「ネットワーク」タブを汚染し、ネットワークリソースの浪費を引き起こします。しかも、長い間新しいメッセージがないにもかかわらず、5秒ごとにガンガンとドアをノックし続けます。その熱意には感動しますが、サーバーへの配慮や開発者のマウスホイールへの配慮(違います)が全くありません。

即時性も問題です。ステータスが更新されても、すぐにそれを知ることができるとは限りません。「ポーリングした直後に変更されたので、また次回確認します」となってしまうからです。

「ポーリングの時間を短くすればいいのでは?」と言う人もいるでしょう。しかし、ここでまたポーリングの別の問題が生じます。ポーリングの間隔が短すぎると、サーバーへの負荷がさらに大きくなり、リクエストのほとんどが「新しいメッセージはありません」という無効なものになってしまいます。

逆にポーリングの間隔が長すぎると、今度はリアルタイム性が大きく損なわれます。これは永遠に両立できない矛盾のようなものです。

WebSocket(持続的な接続を確立しましょう!)H3#

ポーリングがこんなに面倒なら、なぜ直接**持続的な接続(Long connection**を確立しないのでしょうか? WebSocket はまさにそのために生まれました。

WebSocket は、単一の TCP 接続上で**フルデュプレックスfull duplex(全二重)通信**を行うためのプロトコルです。

これにより、サーバーがクライアントへ能動的にデータをプッシュすることが可能になります。その結果、サーバーに新しいメッセージがある場合、クライアントが絶えず尋ねる必要なく、すぐにクライアントにプッシュすることができます。また、クライアントが能動的にサーバーへ現在のデータや稼働状況をレポートすることも可能です。

WebSocket の利点は誰もが知っています。双方向通信、低遅延、高いリアルタイム性を持ち、IM(インスタントメッセージング)やオンライン共同編集などのシナリオを構築するための第一選択肢です。ポーリングに比べると、まさに「牛刀」と呼ぶにふさわしいものです。

しかし、それが牛刀であるからこそ、私のシンプルな「リアルタイムの動的更新」機能にとっては少しオーバースペックであり、まさに「鶏を割くに焉んぞ牛刀を用いん(鶏を殺すのに牛刀を使う)」と言えます。

双方向通信が不要:私の「リアルタイムの動的更新」は実際には単方向です。バックエンドがフロントエンドに状態を伝えて終わりであり、フロントエンドがバックエンドに「見ましたよ」と返すことはありません。WebSocket は、トランシーバーを買ったのにラジオを聴くためだけに使っているようなものです。

接続管理がより複雑:接続のアクティブ維持、ハートビート、切断後の再接続などのロジックについて、SSE よりも記述するコードが明らかに多くなり、メンテナンスコストが高くなります。

リソース消費がやや高い:ブラウザは持続的な接続を維持する必要があり、サーバー側もずっとソケットsocketを保持し続けなければならないため、セッションが増えると負荷も小さくありません。

正直なところ、私は TypeScript を書いていることを訪問者に伝えたいだけであり、チャットルームを開きたいわけではありません。その上、WebSocket にはもう一つの現実的な問題があります——

多くの CDN サービスプロバイダーは、これに対するサポートがあまり友好的ではありません。WebSocket プロトコルはそのフルデュプレックス通信の特性上、理論的にはプロキシなどの「特殊な用途」の構築に利用される可能性があります。そのため、一部の小規模なクラウドベンダーは、そのサポートを直接無効にすることがあります。

対照的に、SSE は純粋な HTTP プロトコルに基づいており、データの流れが明確(サーバーからクライアントのみ)であるため、悪用される可能性が大幅に低くなります。企業のファイアウォールやセキュリティデバイスは通常、WebSocket トラフィックを詳細に検査しますが、SSE は標準の HTTP ストリームとして、通常のウェブトラフィックとして処理されることが多いです。これも、一部の特殊なネットワーク環境下で WebSocket 接続が中断されやすい一方で、SSE は安定して動作する理由です。

Server-Sent Events(ねえ、新しいメッセージがあるよ!)H3#

ついに主役の SSE の登場です! それはまるで単方向の放送局のようなもので、サーバーはいつでもクライアントにメッセージをプッシュでき、クライアントはただ大人しく聞いていればよいのです。

SSE の動作の仕組みは非常に簡単です:

  1. クライアントが通常の HTTP リクエストを発行する
  2. サーバーは接続を切断せずに維持し、Content-Type: text/event-stream を設定する
  3. 新しいメッセージがある場合、サーバーはこの接続にデータを書き込む
javascript
// 前端代码简单到令人发指
const eventSource = new EventSource('/api/status');
eventSource.onmessage = (event) => {
    console.log('收到新动态啦!', event.data);
};

ブラウザにラジオを取り付けたようなもので、チャンネルを合わせるとサーバーの「放送」を聴き続けます。しかもこの「ラジオ」は非常に気が利いており、信号が途切れた場合(ネットワークの揺らぎなど)、自動的に再接続してくれるため、全く手間がかかりません。ただし、ネットワーク機器にアイドル状態と誤認されて接続が閉じられるのを防ぐため(閉じられても自動で再接続されますが)、サーバーから定期的にコロン付きのコメントメッセージを送信させ、接続をアクティブに保つようにしています。🎵

WebSocket の「大砲で蚊を撃つ」ような状態に比べると、SSE はまさに私のような単方向のプッシュシナリオのために作られたかのようです。特別なプロトコルのサポートを必要とせず、複雑な接続状態を処理する必要もなく、切断後の再接続さえもブラウザが自動的にやってくれます。

さらに素晴らしいのは、非常に手間がかからないことです:

  • 切断後の再接続処理が不要(ブラウザに組み込まれている)
  • ファイアウォールの問題を心配する必要がない(標準の HTTP プロトコル)
  • コードの量が WebSocket よりもはるかに少ない(怠け者の福音)

数少ない「欠点」は、それが単方向であることくらいですが——それこそがまさに私の求めていたものです! 訪問者とチャットしたいわけではなく、ただ「おっと、今 VS Code でバグを書いちゃったよ!🐛」と伝えたいだけなのです。

SSE は出しゃばらず、無駄話もせず、ただ静かにバックエンドの「話したいこと」を待ち、それをフロントエンドに届けます。私のような「現在の状態」を表示したいだけのプロジェクトにとっては、まさにぴったりで、十分な機能です。

したがって、最終的に私はリアルタイムの動的機能を実現するために SSE を選びました。それは、おしゃべりではない親友のようなもので、私が何をしているのかを知りたいときに一番に教えてくれますが、「見た?」と尋ねることは絶対にありません。——そもそも尋ねることができないからです。😆

もしあなたも SSE に興味があり、この技術を深く理解したり実践したりしたい場合は、以下の資料を読むことをお勧めします:

これらのチュートリアルは、より詳細な技術的実装の詳細とベストプラクティスを提供しており、SSE をよりよく理解して適用するのに役立つと信じています。

もちろん、注意すべき点もいくつかあります!H2#

単方向データフローの特性H3#

これは SSE の欠点とも言えますし、利点とも言えます。

そのデータの単方向性は、軽量であるという主な特徴を保証しますが、同時に複雑な状況に対処できないという事態も招きます。

データ型の制限H3#

SSE はプレーンテキストデータのみをサポートし、バイナリデータを転送することはできません。

画像、音声、ビデオストリームなどのマルチメディアコンテンツのシナリオには、SSE はあまり適していません。

SSE を通じてリアルタイムのビデオストリームやオーディオを直接送信することはできません。しかし、画像データをエンコードしてテキスト形式(Base64 エンコードなど)に変換し、SSE で送信するといった巧妙な方法で、この制限を回避することは実際に可能です。

ただし、この手法で画像は転送できるものの、決して効率的な方法ではありません

時には、画像を繋ぎ合わせて見ると動画のように見えますが、転送速度ははるかに遅くなります。🎥(日本人プログラマー.jpg(笑

ブラウザの同時接続制限H3#

HTTP/2 経由で使用しない場合、SSE は最大接続数の制限を受けます。これは、複数のタブを開く際に特に厄介です。なぜなら、この制限は各ブラウザに対して適用され、6 という非常に低い数字に設定されているからです。

この問題は、ChromeFirefox において「修正しない(Won't fix)」とマークされています。

ただし、この制限はブラウザ+ドメインごとであるため、すべてのタブをまたいで www.example1.com に 6 つの SSE 接続を開き、同時に www.example2.com に 6 つの SSE 接続を開くことができることを意味します。(Stackoverflow より)。

幸いなことに、現代のブラウザは一般的に HTTP/2 プロトコルをサポートしています。HTTP/2 では**マルチプレックス(多重化)**をサポートしているため、接続数の制限が大幅に改善されています。

HTTP/1.x では、1 つの TCP 接続で一度に 1 つのリクエストしか処理できません。つまり、リクエストを送信するたびに新しい接続を確立しなければならず、リソースの浪費やページの読み込み遅延に繋がります。これは、スーパーに買い物に行くのに、毎回順番に並んで一人ずつドアを入らなければならないようなもので、非常に非効率的です。

一方 HTTP/2 では、マルチプレックスによってこの問題を解決し、同じ TCP 接続上で複数のリクエストとレスポンスを同時に処理することが可能になりました。これは、スーパーに入った後、直接複数の棚に行き、いつでも品物を取ることができ、並ぶ必要がないようなものです。🍏🥖

簡単に言えば、HTTP/2 により、より少ない TCP 接続でより多くの SSE ストリームを処理できるようになり、接続数制限による煩わしさを回避できるということです。😎

ただし注意が必要なのは、HTTP/2 が万能というわけではない点です。アプリケーションが依然として高頻度や低遅延の双方向通信(オンラインチャット、マルチプレイヤーゲームなど)を必要とする場合、より複雑な要件を満たすために WebSocket などの他のプロトコルを検討する必要があるかもしれません。

エラー処理と再接続メカニズムの限界H3#

SSE には自動再接続メカニズムが備わっており、これは確かに一部のネットワーク切断問題への対処に役立ちますが、そのエラー処理と接続復旧メカニズムは比較的基礎的なものです。

ネットワークが不安定な環境に遭遇した場合、SSE はやや不安定な挙動を示す可能性があり、データ損失、リトライロジック、切断後の再接続のタイミングなど、一部の高度なエラーシナリオを手動で処理する必要があります。

別の言い方をすれば、ネットワーク異常時、SSE は非常に「怠惰」であり、単に再接続するだけで、どのような状況で直ちに接続を回復すべきかを「賢く」判断することはありません。

パフォーマンスとサーバーの負荷H3#

SSE は HTTP プロトコルを介してクライアントにデータをプッシュし続けます。これは、クライアントが正常に接続するたびに、サーバーは接続を維持し、継続的にデータをプッシュし続けなければならないことを意味します。

クライアントの接続数が多い場合、またはデータ量が大きい場合、サーバーへの負荷はかなり大きくなり、パフォーマンスが低下する可能性があります。特に頻繁なデータ更新が必要なシナリオでは、SSE は最適な選択肢とは言えないかもしれません。

さらに、SSE のデータ転送速度は比較的遅いため、リアルタイム性への要求が非常に高いアプリケーションについては、効率を高めるために WebSocket などの他のプロトコルを検討する必要があります。

メッセージの信頼性の問題H3#

SSE の 単方向データフロー という特性上、クライアントが受信するデータは 信頼性の低いストリーミングデータ となります。つまり、メッセージ転送の過程で データの損失や順序の狂い が発生する可能性があります。

例えば、プッシュメッセージが転送中に失われた場合、クライアントが自発的に再送を要求することはなくそのままスキップされます。これは、一部の極端な状況下で、あなたの動的情報の一部が失われる可能性があることを意味します。

これは、ある程度の重要性が高いデータ転送シナリオ(金銭や状態の同期に関わるシステムなど)にはあまり適していません。個人ホームページのような「低リスク」のアプリケーションであれば、小さな更新情報がいくつか失われても問題ありませんが、高頻度取引やリアルタイムの共同作業システムなどでは、信頼性が特に重要になってきます。

おわりにH2#

お読みいただく中で内容の誤りや説明が不明確な箇所を見つけた場合は、ぜひご指摘ください!拜托了

この記事を書いた当初の目的は、私の個人ホームページの「ブログの動的更新」に関するデータ転送の技術的な詳細を明確に説明したいというものでした。皆様からの交流やディスカッションも歓迎します。また、実際の開発で直面した問題や見解なども、ぜひコメント欄にお寄せください!🤓

もう一つ、この記事のスラグslugには以前の命名規則を採用しておらず、新しいスタートといったところです! 以上です。

Copyright & License
© 2025 天翔TNXG
なぜ最終的にリアルタイムの動的更新の実装に Server-Sent Events を採用したのか?
CCクリエイティブ・コモンズ・ライセンス
BY表示:原作者のクレジット表示が必要です
NC非営利:営利目的での利用は禁止です
SA継承:同じライセンスで共有する必要があります
ライセンス:表示-非営利-継承