この投稿は、オリンピックではなくウェブプログラマーにとって興味深いものになります。
いくつかの統計、nginxの設定、便利なトリック、および経験のある人にはよく知られているはずのレーキの多くですが、とにかく多くの場合は攻撃します...
背景
数日前、スポーツプログラミングの主要イベントの1つが開催されました。このイベントはすでにハブで記述されています
これらの競技の愛好家の狭い輪(これらは世界中に数千人いる)には伝統があります:5時間の決勝戦中、モニターのスクリーンの後ろに集まり、結果の「ライブ」テーブルに従う。 興味は、普通の人がワールドカップの放送を見るのと似ています:)(残念ながら、ロシアはそうではなく、ヒットしませんでした)。
残念ながら、切望されているタブレットの公式ページは毎年Habr、トップコーダー、その他の効果に該当し、特別な読みやすさの違いはありません。今年は、そこで簡単なリアルタイムチャットを行いました。 その結果、今年の私の放送はロシアでの公式放送よりも人気が高まりました。
簡単な統計
5時間強で:
-6000の一意のIP(10分間隔のピーク時-2000)
-静的ファイルの1Mリクエスト
-50万件のロングポーリングクエリ
ピーク時のサーバーの負荷(モスクワの午前9時-競技の最後の1時間の始まり、テーブルの「凍結」の時間)-nginxへの1.5K接続、それによって消費されるメモリ50M、CPU負荷の1%未満、送信トラフィック7 Mbps つまり、VDSは最小限の構成に近く、ひざの上にラップトップさえあれば十分です。主なことは、チャネルが許可することです。
cactiのいくつかのグラフ(2日間のデータ。「平和」時間でサーバーの負荷と比較できます):
構成
過去数年間、「顔」全体はシンプルなページであり、15〜30秒ごとにajaxを使用して主要部分(テーブル)に更新を引き出していました(期間はユーザーが構成しました)。 最小限のキャッシュを備えた平凡なapache + phpは、すべての負荷を強打で処理しました-公式テーブルは10秒で1回しかダウンロードされず、jsonで解析され、その後、要求されたすべての人に配られました。
今年は、ロングポーリングや彗星などについての記事を読んで、新しいことを試したかったので、家で何かをすることにしました。 はい、そして長い間チャットを止めたいと思いました...
ロングポーリングの本質は、サーバーに新しいデータが到着するまでクライアント要求を「一時停止」することであるため、クライアントはこのデータが表示されるとすぐに回答を受け取ります。 この関数を実装するために、 nginx_http_push_moduleが使用されました。
なぜ彼は、 リアプレクサーでも 、彗星でも、自作の悪魔でもないのですか? 彼が望んでいたことを正確に知っているからです、すなわち:
-データ形式の完全な制御-データを更新するためにサーバー上で簡単なPOSTリクエストが行われ、その内容全体がクライアントを期待どおりに正確に提供されます
-追加のライブラリを使用する必要はありません。同じajaxリクエストがクライアントで行われ、長時間実行されるだけです:)
-最初の段落のおかげで、gzip_staticアナログのサポート(クライアントへの問題の時点ではなく、予備的な圧縮)
安全性を確保するために、クライアントは通常のタイマーajaxリクエストを使用して古いモードで動作することができ、新しいモードはユーザーが設定で「リアルタイム」を選択することでオンになりました。
nginx_http_push_moduleの柔軟性のおかげで、モードの変更はポーリングとタイマーのURLを変更することのみで構成されていました-データはまったく同じでした。
システムコンポーネント:
-メインページ-すべてのクライアントコードを含むシェル、理想的には一度ロードされる
-2つのファイルの形式の動的データ-standings.js(テーブル)とchat.js(最後の30のチャットメッセージ)-クライアントはタイマーまたは長いポーリング要求のいずれかでプルします
-Apacheに関係なく常に実行されているPHPのデーモン。3秒ごとに公式テーブルをダウンロードして蒸し、standings.jsに保存します。
-チャットする(またajaxも)メッセージを受け入れ、chat.jsを再構築するphpスクリプト
-nginx_http_push_module。以前の2つのスクリプトも更新(gzipで圧縮された対応するファイルの内容)をスローし、「リアルタイム」モードを使用するクライアントが接続しました。
上記のロジックを実装するNginxの構成:
場所〜^ / fairy / post /([a-z0-9 ._-] +)$ { access_log /var/log/nginx-fairy.log main; push_publisher set push_channel_id $ 1; #チャネルがファイル名で命名されるように push_message_timeout 2h; #より長い保管時間 push_min_message_recipients 0; push_message_buffer_length 1; #メッセージキューを使用した高度なロジックは必要ありません。 } 場所〜^ / fairy /([a-z0-9 ._-] +)$ { access_log /var/log/nginx-fairy.log main; push_subscriber; push_authorized_channels_only on; #存在しないチャンネルからの読み取りを禁止 push_subscriber_concurrencyブロードキャスト; #チャンネルはパーソナライズされていませんが、共有されています set push_channel_id $ 1; default_type text / plain; add_header Content-Encoding gzip; #gzip_staticエミュレーション エポックが期限切れになります。 #キャッシングについては以下を参照 }
(なぜ/妖精?はい、名前の彗星が迷惑だからです:))
「チャネル」は通常のjsファイルをエミュレートし、/ fairy / standings.jsで/standings.js(静的ファイル)と同じものを返しますが、更新する前に「フリーズ」し、更新はPOST /fairy/post/standings.js、このようなシンプルで理解しやすいURLスキーム。
善と悪のトピックに関するいくつかの考え
1. gzipは良い
公式の表(50KのHTML、30秒ごとにメタリフレッシュで更新)にはgzipがまったくなかったため、チャンネルは死んでいました。
(主催者の功績により、後に彼らは彼女を鏡にし、gzipが登場しました)
2. gzip_static-良い
更新されたファイルの隣にgzファイルを作成し、クライアントがサポートしている場合はnginxにそれを返すことができる場合、これらのメガバイトの発信トラフィックをすべてオンザフライで圧縮し、CPUを費やすのはなぜですか?
3.上書きしてファイルを更新する(近くに+名前を変更する代わりに)-悪
この時点でそれが汲み上げられた場合(そして、それは汲み上げられます)-トラブルが始まります。
公式テーブルが更新されたため、ユーザーが「破損」テーブルを見たことがあり、私のスクリプトがこのように引っ張った場合...すべては問題ありませんが、テーブルの新しいバージョンと以前のバージョンの違いを考慮し、新しい「プラス」に関する情報をスローしましたチャットする。 その結果、チャットは定期的に誤検知の波で一杯になり、準備ができていない視聴者にショックを与えました:)
(私はどうにかして追加のチェックを追加することで外出先で修正することができました)
4.ロード時間の最適化-ようこそ
すべてのクライアントコードの重量はgzipで15k未満です-このため、お気に入りのjqueryや他のフレームワークは使用しませんでした-フレームワークの重量が他のすべての2倍になったときは好きではありません:)
また、最初の訪問で「読み込み中...」という碑文を避けるために、データファイルが<script src = "...">によってページに追加され、onload / documentreadyを待たずに読み込まれました。
結果-既製の組み立て済みテーブル+私からの最後のチャットメッセージは、サーバーへのping(firebugによる)を含め、0.2-0.3秒でゼロからロードされ、メインのGoogleページよりも速く表示されました:)
5.クライアントキャッシュが正しく構成されている-ようこそ
今まで、リクエストに&random = 74925のようなものが追加された松葉杖がよく見られます...
一方、過去の時制でExpiresヘッダーを設定して、ブラウザが常にリクエストを行い(nginxではこれは1行で終了-エポックを期限切れにする)、クライアントスクリプトに応答304を処理するように教えるだけで十分です。
また、ajaxリクエスト中に正しいIf-Modified-Sinceが送信されることを確認する必要があり、同じnginx_http_push_moduleがそれを積極的に使用します。
同じURLに受け取った最後の応答のLast-Modifiedヘッダーのコンテンツを置き換えました(一般的に、ブラウザー自体はそうしますが、ユーザーがキャッシュをオフにしている場合はそうではありません)。
6. openidの欠如は悪
チャットの参加者は、ニックネームをTopCoderにサブスクライブする機会があり(適切な色で名前を強調するため)、特別な登録なしで(「一晩」スクリプトでは不可能である)真正性を検証する能力が本当にありませんでした。 しかし、ほとんどの参加者は驚くほど文化的な振る舞いをしましたが、最も不誠実な個人はIP禁止で扱われなければなりませんでした:)
残念なことに、彼らの可用性のために、私は最後に少し眠ることに決めたとき、私は最後にチャットをオフにすることを余儀なくされました-私はただ節度なしですべてを残すことを敢えてしませんでした。
7. IE6のサポートの拒否-ようこそ
開始直前にチャットがすべての「お気に入り」ブラウザで機能しないことを発見したため(詳細にわずかに互換性のないXMLHTTPRequestがあるようです)、私は明確な良心を持って、「ごめん、IE6では動作しないかもしれません」の精神でサインを掛けました、更新ついに」と他のユーザーにとってより有用なものを取り上げました。
私はこれが行われるべきだと信じています 主な機能-結果テーブルの表示-はそこでも機能し続けました。
文字が多すぎたようです。 しかし、私の経験が誰かに役立つと思います。
はい、PPNH :)