猫のしっぽはなぜ:中程度の視界条件でのリアルタイム統計

私の名前はイゴールです。1年半前にMail.ruレーティングプロジェクトで働き始め、1年後、このプロジェクトの一環としてリアルタイム統計を始めました。 今日は、統計の配置方法についてもう少し詳しく説明します。 突然わからない場合、Mail.ru評価カウンターは、すべてのRunetサイトの約20%に設定されます。 訪問のダイナミクスに関する統計、ユーザーに関する統計(人口統計:性別、年齢)、およびページの読み込み時間など、他の多くのレポートを処理して表示します。 リアルタイム統計は、サイト上の現在のユーザー数を示すより動的な統計であり、サイトのどのページが現在人気があり、約1秒の遅延があります。



すべてのリアルタイムレポートをまとめたダッシュボードは次のようになります。







彼は示しています:



もちろん、個別のレポートも利用できます。 私はトートロジーに従事したくありません。各ブログの詳細については、当社のブログをご覧ください。



私は最近、このプロジェクトの作業を終了しました。興味深いと思うパターンをいくつか共有したいと思います。



ここでは発明されていない対 誰も島ではない

私が使用した最も重要なパターンは、「ここでは発明されていない」パターンの逆であるように思えます。 10年前、私はバウマン(CF)で勉強しました。コースワークの1つとして、単語の頻度を数える5つのフォームで小さなアプリケーションを作成しました(そしてもちろん、シェアウェアとして販売しようとしました)。 archive.orgで引き続き利用できます。 私はアンチパターン「Not Inveted Here」の謝罪者であり、他の人のライブラリを使用することを信じていませんでした。 このプログラムは「C with classes」で記述され、Win32 APIをラップする独自のOOライブラリを備えていました。







1万行のコード、4か月の作業、出口でのジルチ。 機能の豊富さは、CDイジェクターほど複雑ではありません。 宗教的に狂信的な「ここでは発明されていない」は、途方もない開発時間とサポートされていないコードにつながります。



今、私は慎重に設計されたライブラリを専門家によって書かれたエレガントなAPIで私の貯金箱にドラッグすることを約束しています。 これは、たとえば、BOOST、Intel Threading Building Blocks、Google Protobufです。 アンチパターン「Not Inveted Here」の対抗者が何と呼ばれているかはよくわかりませんが、個人的には「No man is a island」です。 現在のプロジェクトには、「CD Ejector」テーマのバリエーションよりもはるかに多くの機能があります。 このすべての機能は2千行のコードに分割され、同じ4か月で開発されました。 コンポーネントのうち、次のものを使用します。





私は建築を完全に描くことはしません-それを開発することは大きな喜びであり、同様の問題を解決する私たちの喜びを台無しにしたくありません。 しかし、私は便利だと感じたいくつかの技術的解決策を共有したいと思います。



簿記

どこかに何かが追加された場合、どこかに何かが減少するはずです。 すべてのユーザーとセッションはどこかに保存する必要があります(そして、それらはカウンターによって保存されます)。 しばらくすると、セッションは期限切れになり、レコードを削除する必要があります。



簡単な解決策の1つは、期限切れのレコードを繰り返し削除することです。 より洗練されたソリューションがあります。レコードが追加または更新されると、削除の必要性に関するレコードがconcurrent_bounded_queueに追加されます。 また、別のスレッドでは、ブロックポップが使用され、読み取り対象がある場合にレコードが返されます。 タイムアウトは一定であるため、後続の各エントリは、返されるエントリよりも後に関連します。 また、削除時間がまだ到着していない場合は、関連する瞬間までsleep()を実行すれば十分です。 その後、有効期限とレコードが最後に更新された時間を比較するだけで十分であり、実際に有効期限が切れた場合(現在の削除レコードが追加されてから更新されていない場合)、削除します。 安くて陽気な。



非同期ロギング

一部のロギングライブラリは非同期に動作せず、単一ファイル内の複数のストリームに書き込みます。 さて、ログローテーションのログファイルはSIGHUPによって再検出されることを忘れないでください。 ロギングをよりエレガントにするために30分費やすことができました。



コードのログインは次のようになります。

LOG("All msgs: " << sum);







これはこのメッセージがログでどのように見えるかです:

2014-05-15 18:57:17 INFO void QueueCounter::run(): All msgs: 38288







ログファイルが同じストリームに読み書きされる場所から通常のtbb :: concurrent_bounded_queueにstd :: stringを置くマクロを使用して実装されます。



コンテキスト

考えてみると、concurrent_bounded_queueはメッセージキューにすぎません。 そして、すべてのメッセージキューについて知っているメッセージブローカーが存在することは理にかなっています。 MessageBrokerと呼ばれます。 しかし、その場合、多くのコンポーネント(たとえば、ログや構成)に必要な他のサービスを追加して、コンテキストと呼ぶことはできません。



したがって、LiveObjectが受け入れる唯一のコンストラクターパラメーターは、Contextクラスのオブジェクトへの参照です。 LiveObjectは、runメソッドを持つクラスオブジェクトです。 このメソッドは別のスレッドで実行されます。 通常、このようなオブジェクトの1つが1つの実行スレッドを担当します。



スリープと同期

「訪問ログ」(末尾に類似)は、concurrent_hash_tableに書き込まれます。 1秒のバケットに書き込まれます。 キーはカウンターIDです。 小さな問題:tbb :: concurrent_hash_tableは、非POD構造を使用している場合はバイパスできません。



解決策は非常に簡単です。このテーブルへのアトミックポインターを使用して、1秒に1回置き換えます。 問題はこれです:シリアル化(0.5〜50ミリ秒)後、古いデータ構造が削除されます。 そして、末尾のエントリを「豊かにする」ストリーム(IPによる地理位置情報、OSのマッピング、ブラウザ)がリモートデータ構造に書き込む場合、ボボが発生します。 SEGFAULTとも呼ばれます。



レコード処理時間はマイクロ秒単位で考慮されます。 負荷の軽いサーバーでは、Javaのような「世界を止める」スレッドの魔法の凍結を期待すべきではありません。 シリアル化の前にスリープを追加すると、問題は解決します。 代替手段は、古典的な同期プリミティブの1つかもしれませんが、これはエレガントでも必要でもありません。



カウントトップ

トップ(トップキーワード、トップページなど)を計算するには、わずかに変更された不可逆カウントが使用されます。



元の不可逆カウントは次のように機能します。要素のストリームがあります。 バケットに分割されます(一部の説明では、バケットはウィンドウと呼ばれます)。 要素を持つテーブルに既に要素がある場合、そのカウンターは1増加します。要素がなく、テーブルにスペースがある場合、要素が追加され、カウンターが1に設定されます。バケットの最後で、すべてのカウンターから単位が減算されます。 カウンターがゼロになったアイテムは削除されます。 変更されたアイテムでは、各アイテムのセッション数がカウントされます。 エレメントのセッション数がゼロになると、削除されます。 「ウィンドウの終わり」に達すると、カウンター(floatタイプ)から1は減算されません(1-(ゼロセッションによる削除された要素の数)/ウィンドウサイズ)。



特にトラフィックの少ないサイトでは、トップがよりダイナミックになります。



なぜ猫のしっぽ

なぜ猫のしっぽなのか、私にはわかりませんが、私は推測します。 リアルタイム統計に関しては、すべてがシンプルです-非常に人気があることが判明しました。 そして、最も人気のあるレポートの1つを、サイトにインストールできる情報提供者にすることを決定しました。 訪問したサイトにとって非常に興味深いように見え、サイトで最も人気のあるページとそれらを今見ている人の数を表示します。



登録して人生を楽しんでください。 訪問数が少ないサイトでは、リアルタイム統計はそれほど興味深いものではないことに注意してください。



All Articles