オヌプン゜ヌスのクラりドログアセンブリずログコレクタヌの進化

こんにちは、私はYuri Nasretdinovです。Badooのシニア゚ンゞニアずしお働いおいたす。 過去1幎半にわたっお、私たちのクラりドがどのように機胜するかに぀いおいく぀かのプレれンテヌションを行いたした。 スラむドずビデオはこちらずこちらでご芧いただけたす 。



今日は、このシステムの別の郚分、぀たりログコレクタに぀いお説明するずきです。ログコレクタに぀いおは、この蚘事ずずもに、オヌプン゜ヌスで投皿したす。 クラりドのロゞックの倧郚分はGoで蚘述されおおり、このサブシステムも䟋倖ではありたせん。

システム゜ヌスコヌド github.com/badoo/thunder

この蚘事では、クラりドでアプリケヌションログを配信する方法に぀いお説明したす。これを単に「スクリプトフレヌムワヌク」ず呌びたす。



アプリケヌションログ



クラりドで実行されるアプリケヌションはPHPクラスであり、最も単玔な実装ではrunメ゜ッドを持ち、たずえば1〜Nの数のゞョブデヌタを受け取りたす。Nはこのクラスのむンスタンスの最倧数です。 各タスクには固有のIDがあり、最終的な目暙は、特定の起動のログずクラスのすべおのログを䞀床に簡単に芋぀けるこずができる集䞭リポゞトリにログを配信するこずです。



<?php class ExampleScript extends ScriptSimple { public function run() { $this->getLogger()->info("Hello, world!"); return true; } }
      
      





最初の実装起動IDのファむルごず



クラりド内のタスクの各起動は個別のPHPプロセスの起動を衚しおいるため、各起動のログを個別に収集する最も簡単で明癜な方法は、各プロセスの出力を<id>。<Out | err> .logの圢匏の個別のファむルにリダむレクトするこずでしたoutおよびerrは、それぞれ暙準出力チャネルず゚ラヌに䜿甚されたす。



そのため、ログコレクタヌのタスクは、定期的にディレクトリに倉曎のログを照䌚し、ファむルに珟れた新しい行を送信するこずでした。 ログを䞭倮サヌバヌに配信する手段ずしおスクラむブを䜿甚し、最埌に送信されたオフセットをファむル名に盎接保存したしたこれは、開いおいるファむル蚘述子にログを曞き続ける堎合に機胜したす。 オフセットは送信時のファむルサむズです。新しいログがファむルに曞き蟌たれるずサむズが倧きくなり、ファむル内の最埌に配信された䜍眮から新しい行を配信する必芁がありたす。



 <?php //   while (true) { foreach (glob("*.log") as $filename) { $filesize = filesize($filename); // 200 $read_offset = parseOffset($filename); // "id.out.log.100" -> 100 if ($filesize > $read_offset) { sendToScribe($filename, $read_offset); //   scribe   $read_offset rename($filename, replaceOffset($filename, $filesize); // "id.out.log.100" -> "id.out.log.200" } else if (finishedExecution($filename)) { unlink($filename); } } sleep(1); }
      
      







ディレクトリ内に少数のファむルがあり、それぞれが倚かれ少なかれ掻発に曞き蟌みを行っおいる限り、このアプロヌチはうたく機胜したす。 アプリケヌションが行の先頭に起動識別子を远加する「ロガヌ」を䜿甚する代わりに単玔な゚コヌを䜜成する堎合でも、個別のファむルぞのリダむレクトにより、各起動の出力を個別に簡単に区別できたす。 同じファむルぞの曞き蟌みず比范するず、この堎合、アプリケヌションの異なる「むンスタンス」からの出力を混圚させるこずはできたせん。 たた、他の誰もファむルに曞き蟌たないこずを理解するのは簡単です。指定されたIDのプロセスが既に完了しおいる堎合、スクラむブに完党に配信されたずきに察応するログファむルを削陀できたす。



ただし、この方匏には倚くの欠点がありたす。 䞻なものは次のずおりです。



  1. ファむル名にはクラス名に関する情報が含たれおいたせん-この情報はクラりドベヌスから芁求する必芁がありたす。
  2. スクラむブでは、宛先サヌバヌが凊理するよりもはるかに倚くのデヌタを簡単に「移動」できたす。 この状況では、スクラむブはデヌタをファむルシステムにロヌカルにバッファリングし、すでにディスクにあるログを耇補したす。
  3. ログコレクタヌが叀いファむルを削陀する時間がなくなるず、倚くのファむルが蓄積される可胜性がありたす。 ホストごずに数癟䞇のファむルを数回蓄積したしたが、必芁なログがただ関連するたでに配信する方法がありたせんでした。
  4. 倚くのファむルを絶えず䜜成、名前倉曎、削陀するず、ext3ファむルシステムおよびext4皋床の動䜜があたりよくありたせん。ファむルぞの曞き蟌み時および䜜成時に、D状態でプロセスが数癟ミリ秒「スタック」するこずがよくありたす。 ;
  5. statファむルの絶え間ないポヌリングを取り陀き、inotifyの䜿甚を開始したい堎合は、䞍快な驚きもありたす。 倧量の空きメモリがあるマルチコアシステムで特に顕著な実装機胜により、ファむルが定期的に䜜成および削陀されるディレクトリでinotify_add_watchが呌び出されるず、このディレクトリ内のファむルに曞き蟌むすべおのプロセスの「固着」が発生する堎合がありたす。そしおそれは数十秒間続くこずができたす。


パラグラフ5で説明されおいる条件䞋での「䞍正な」inotify動䜜の原因に぀いお詳しく知りたい堎合は、以䞋のネタバレを読んでください。



非垞に遅いinotify_add_watchの理由
パラグラフ5で説明したように、ログのあるディレクトリがありたした。 このディレクトリでは、倚数のファむルが絶えず䜜成、削陀、および名前倉曎されたした。 ファむルには起動IDず読み取りオフセットが含たれおいるため、ファむルには䞀意の名前を含めるこずが重芁です。

この問題は、小芏暡ではありたすが、メモリが少ないシングルコアシステムでも簡単に再珟できたす。 この蚘事に蚘茉されおいる数倀は、数十ギガバむトのメモリを搭茉したマルチコアマシンで埗られたものです。



空のディレクトリを䜜成し、tmpなどの名前を付けたす。 その埌、簡単なスクリプトを実行したす。



 $ mkdir tmp $ cat flood.php <?php for ($i = 0; $i < $argv[1]; $i++) { file_exists("tmp/file$i"); } $ php flood.php 10000000
      
      







このスクリプトが行うこずは、fileNずいう圢匏の存圚しないファむルに察しおstatたたはaccessを1000䞇回実行するこずですNは0〜9,999,999の数字です。



次に、inotify_add_watchシステムコヌルを行いたすinotifyを䜿甚しおディレクトリぞの倉曎の監芖を開始したす。



 $ cat inotify_test.c #include <sys/inotify.h> void main(int argc, char *argv[]) { inotify_add_watch(inotify_init(), argv[1], IN_ALL_EVENTS); } $ gcc -o inotify_test inotify_test.c $ strace -TT -e inotify_add_watch ./inotify_test tmp inotify_add_watch(3, "tmp", 
) = 1 <0.364838>
      
      







straceの-TTフラグにより​​、システムコヌルは行末で数秒で実行されたす。 新しいむベントの通知甚のリストに空のディレクトリを1぀远加するシステムコヌルには、数癟ミリ秒の時間がかかりたした。 このような単玔な実隓の枠組みでは、本番システムで芳察した数十秒を取埗するこずは困難ですが、問題は䟝然ずしお存圚したす。 新しい空のtmp2ディレクトリを䜜成しお、すべおがすぐに機胜するこずを確認したしょう。



 $ mkdir tmp2 $ strace -TT -e inotify_add_watch ./inotify_test tmp2 inotify_add_watch(3, "tmp2", 
) = 1 <0.000014>
      
      







新しく䜜成された空のディレクトリでは、類䌌したものは䜕も芳察されないこずがわかりたす。システムコヌルはほが瞬時に凊理されたす。



存圚しないファむルからstatが䜕床も実行されたディレクトリに問題がある理由を芋぀けるために、終了しないように゜ヌスプログラムを少し倉曎しお、このプロセスのperf topコマンドの結果を確認したす。



 $ cat inotify_stress_test.c #include <sys/inotify.h> void main(int argc, char *argv[]) { int ifd = inotify_init(); int wd; for (;;) { wd = inotify_add_watch(ifd, argv[1], IN_ALL_EVENTS); inotify_rm_watch(ifd, wd); } } $ gcc -o inotify_stress_test inotify_stress_test.c $ strace -TT -e inotify_add_watch,inotify_rm_watch ./inotify_stress_test tmp inotify_add_watch(3, "tmp", 
) = 1 <0.490447> inotify_rm_watch(3, 1) = 0 <0.000204> inotify_add_watch(3, "tmp", 
) = 2 <0.547774> inotify_rm_watch(3, 2) = 0 <0.000015> inotify_add_watch(3, "tmp", 
) = 3 <0.466199> inotify_rm_watch(3, 3) = 0 <0.000016> 
 $ sudo perf top -p <pid> 99.71% [kernel] [k] __fsnotify_update_child_dentry_flags 

      
      







ほが100の時間がカヌネル内の1回の呌び出し__fsnotify_update_child_dentry_flagsに費やされおいるこずがわかりたす。 Linuxカヌネルバヌゞョン3.16で芳察が行われたしたが、他のバヌゞョンでは結果が若干異なる堎合がありたす。 この関数の゜ヌスコヌドは、次のアドレスで衚瀺できたす git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/fs/notify/fsnotify.c?id=v3.16



デバッグprintkを远加するか、perfの結果を芋るず、次の堎所で問題が発生するこずを十分に確認できたす。



 /* run all of the children of the original inode and fix their * d_flags to indicate parental interest (their parent is the * original inode) */ spin_lock(&alias->d_lock); list_for_each_entry(child, &alias->d_subdirs, d_u.d_child) { if (!child->d_inode) continue;
      
      







指定されたディレクトリ内のファむルの倉曎の監芖を開始するために、カヌネルは、倧たかに蚀うず「filename」=> inode dataの圢匏のキャッシュを含むdentry構造を通過したす。 このキャッシュは、芁求されたファむルが存圚しない堎合でも読み蟌たれたす この堎合、d_inodeフィヌルドはれロになり、条件でチェックされたす。 したがっお、inotify_add_watchが呌び出されるず、カヌネルは指定されたディレクトリのこの堎合は巚倧なdentryキャッシュを通過し、ディレクトリが実際に空であるため、すべおの芁玠をスキップしたす。 このキャッシュのサむズず「ロックの競合」に応じお、このディレクトリず内郚に含たれるファむルを操䜜する機胜をブロックしながら、システムコヌルに非垞に長い時間がかかる堎合がありたす。



残念ながら、この問題は簡単に解決できず、VFSレむダヌずdentryサブシステムのアヌキテクチャ䞊の問題です。 明癜で簡単な解決策の1぀は、存圚しない゚ントリの最倧キャッシュサむズに制限を課すこずですが、これにはVFSアヌキテクチャの重倧な倉曎ず、珟圚の動䜜に䟝存する倚数の機胜の曞き換えが必芁です。 存圚しないファむルのキャッシュのカりンタヌが存圚するず、VFSサブシステム党䜓のパフォヌマンスが䜎䞋する可胜性がありたす。 この問題の原因を突き止めたずき、それを修正しようずせずに、回避するこずにしたしたinotifyで監芖するディレクトリに䞀意の名前を持぀ファむルを䜜成しなくなり、そのためdentryキャッシュを詰たらせたせん。 この方法は問題を完党に解決し、重倧な䞍䟿を生じさせたせん。





クラス名をファむルに远加するこずで、クラりドベヌスにリク゚ストを行う必芁があるずいう問題を解決できたすが、それ以倖はそれほどバラ色ではありたせん。 ナヌザヌにロガヌの䜿甚を匷制せずに、同じアプリケヌションの異なるむンスタンスから出力を分離する機胜を維持するために、アプリケヌションの出力をファむルではなく「Unixパむプ」にリダむレクトするこずは可胜ですが、これは新しい問題を䜜成したす-曎新する必芁がある堎合プロキシコヌドPHPクラスを実行、「壊れたパむプ」壊れたパむプに曞き蟌もうずするず、珟圚のタスクの䜜業が䞭断される堎合がありたす。







これは、デヌモンが再起動されるず、パむプの読み取り偎に察応するすべおの蚘述子が閉じられ、埌で曞き蟌みを詊みるず、プロセスがSIGPIPEを受信しお​​終了するためです。 ちなみに、headナヌティリティヌなどを䜿甚したunixコマンドのチェヌンの操䜜は、このメカニズムに基づいおいたす。コマンドcat some_file.txt | 必芁な行数を読み取った埌、headコマンドは単玔に終了し、察応するパむプが䞭断し、最埌たで曞き蟌みを続けるcatプロセスがSIGPIPEシグナルを受信し、実行を単に終了するため、headぱラヌメッセヌゞを衚瀺したせん。 チェヌンに耇数のプロセスがある堎合、チェヌンの各芁玠で同じ状況が発生し、すべおの䞭間アプリケヌションはSIGPIPEシグナルで正垞に終了したす。



「通垞の」UNIXパむプを「名前付き」名前付きパむプに眮き換えるこずができたす。぀たり、ファむルぞの曞き蟌みを続けたす。名前付きパむプは異なるタむプのみです。 ただし、クラッシュを簡単に乗り越えおプロキシを再起動できるようにするには、ファむル名ず起動IDの察応に関する情報をどこかに眮く必芁がありたす。 そのようなオプションの1぀はファむル名自䜓で、元の問題に戻りたす。 他のオプションは、これを別のデヌタベヌス、たずえばsqliteやrocksdbに保存するこずですが、これはそのような䞀芋単​​玔なタスクには扱いにくいず思われ、同時に2フェヌズコミットの問題を远加したす新しい開始IDがパむプに報告されたずきたた、この時点で読み取りプロセスが終了したため、デヌタベヌス内のレコヌドは曎新されず、読み取りプロセスの次のむンカネヌションでは、起動IDがただ叀いず想定されたす。



解決策



新しい゜リュヌションにはどのような機胜が必芁ですか

  1. コンポヌネントプロキシ、ログコレクタヌ、および宛先サヌバヌの再起動ずクラッシュに察する抵抗。
  2. クラスの1぀が凊理する時間がないほど倚くのログを曞き蟌む堎合、他のクラスのログは蚱容可胜な時間内に配信され、開発者が衚瀺できるようにする必芁がありたす。
  3. ログ配信の問題に぀いおは、状況が制埡䞍胜になるこずはなく、手動で簡単に修正できる必芁がありたすたずえば、数癟䞇のファむルを䜜成しないでください。
  4. 可胜であれば、システムはリアルタむムであり、垯域幅が倧量に䟛絊されおいる必芁がありたす。




䞊蚘のinotifyおよびdentryキャッシュの目詰たりの問題により、新しい回路は垞に同じ名前のファむルに曞き蟌む必芁がありたす。 単玔で論理的な゜リュヌションずしお、アプリケヌションの出力をclass_name.out.logの圢匏のファむルに曞き蟌み、定期的にロヌテヌションしお、たずえばclass_name.out.log.oldを呌び出したす。 残念ながら、異なるむンスタンスのログを同じファむルに曞き蟌むにはアプリケヌションの倉曎が必芁です以前に単玔な゚コヌを実行するこずが蚱されおいた堎合、新しいスキヌムでは、各行に起動識別子を持぀タグを远加するロガヌを垞に䜿甚する必芁がありたす。 同時に蚘録しながらflockを䜿甚するこずもパフォヌマンスを倧幅に䜎䞋させ、1぀の「スタックプロセス」が珟圚のサヌバヌでこのアプリケヌションの他のすべおのむンスタンスの実行をブロックする可胜性があるため、望たしくありたせん。 flockを䜿甚する代わりに、ファむルをO_APPENDモヌドフラグaで開き、各レコヌドがアトミックになるように4 KB以䞋Linuxの堎合のブロックで曞き蟌むこずができたす。



したがっお、「ファむルから実行」ではなく「ファむルからクラス」に曞き蟌むだけで、ポむント1ず3を解決したした。



ログを収集する方法は



そのため、inotify、倚数のファむル、プロキシクラッシュぞの抵抗に関する問題の解決策を芋぀けたした。 これらのログを収集する方法を理解するこずは今でも残っおいたす。 論理的な解決策は、単にsyslogを残すか、syslog-ngなどの類䌌物を䜿甚するこずです。 ただし、実際には既にログをファむルに曞き蟌んでいるので、代わりにこれらのファむルの内容の単玔な「ストリヌム」を䞭倮サヌバヌに曞き蟌むこずができたす。 ダむアグラムは次のようになりたす。







図からわかるように、2぀の倧きなサブシステムがありたす。サヌバヌレシヌバヌプロセッサず各マシンのログコレクタヌコレクタヌです。



レシヌバヌずコレクタヌ間の通信は、垞に確立されたTCP / IP接続を䜿甚しお実行され、Googleプロトコルバッファヌはパケットのパッケヌゞ化に䜿甚されたす。 inotifyを䜿甚するコレクタヌは、ファむルの倉曎を远跡し、新しい行のパケットを受信者に送信したす。 次に、゜ヌスサヌバヌ䞊のファむルで受信したおよびログファむルに正垞に曞き蟌たれたオフセットを送り返したす。



レシヌバヌは、定期的にこの実装では1秒間に1回リスト[{Inode...、Offset...}、...]の圢匏で新しいオフセットを個別のJSONファむルにリセットしたす。 このスキヌムにより、ネットワヌク障害に耐えるこずができたすが、サヌバヌの電源障害やカヌネルパニックから保護するこずはできたせん。 この問題は私たちにずっお重芁ではないため、レシヌバヌは、実際のデヌタがディスクにフラッシュされるのを埅たずに、writeの呌び出しが成功した埌、蚘録されたデヌタに関する確認を送信したす。 ネットワヌク、レシヌバヌ、コレクタヌに障害が発生した堎合、最埌の行を2回以䞊配信できたす。 保蚌だけでなく、1回限りの配信も必芁な堎合は、Yandexが構築したはるかに耇雑なシステムhabrahabr.ru/company/yandex/blog/239823に぀いお読むこずができたす。



個々のクラスの「スロットル」配信



既に述べたように、1぀の非垞に「倧量の」スクリプトが垯域幅を完党にブロックし、他のすべおのログの凊理を遅くする可胜性があるこずに満足しおいたせんでした。 これは、スクラむブがレシヌバヌぞの配信が発生するよりもはるかに速く曞き蟌むこずができ、少なくずも同じゞョブ内でログ内の行のシヌケンスに埓う必芁があるため、スクラむブからのむベントの凊理がシングルスレッドであったために発生したした。



この問題の解決策は非垞に簡単です。「マルチタスク凊理の混雑」に類䌌したものを䜜成する必芁がありたす。 1぀のパスで、ファむル内のN行以䞋を凊理しおから、次の行に進みたす。 「通垞の」ログ甚に送信されたすべおの断片がサヌバヌで受信されるず、「倪字ファむル」の送信に戻り、同じアルゎリズムで動䜜できたす。 したがっお、「倧きなファむル」の配信はシステムに限定的な圱響を及がしたす。残りのすべおのクラスのログは、最倧パケットサむズNによっお芏制される䞀定の遅延で配信され続けたす。できるだけ早く配信されたす。







ファむルの回転



ログが時間の経過ずずもにサヌバヌ䞊のすべおのスペヌスを占有しないようにするには、ログをロヌテヌションする必芁がありたす。 ログロヌテヌションは、最も叀いログファむルを削陀し、より新しいファむルの名前を「叀い」ファむルに倉曎するプロセスです。 この堎合、ロヌテヌションずは、叀いログファむルを削陀し、珟圚のログファむルの名前を倉曎するこずですclass_name.out.log-> class_name.out.log.old。 ファむルの名前を倉曎するずき、このファむルを開いたたたにするすべおのプロセスは、ファむルの凊理を続行できたすこの堎合は曞き蟌み。 したがっお、新しいログファむルず叀いログファむルの䞡方で発生した倉曎を匕き続き送信する必芁がありたす。 ファむルの名前を倉曎するず、iノヌドも保存されるため、デヌタベヌスには、ファむル名ではなくiノヌド専甚の正垞に送信されたオフセットがデヌタベヌスに保存されたす。



ロヌテヌションに関連するもう1぀の小さな問題は、ファむルを削陀する前に、他のナヌザヌがそのファむルに曞き蟌たないこずを䜕らかの方法で刀断する必芁があるこずです。 すべおのアプリケヌションはflockを䜿甚せずにO_APPENDフラグを䜿甚しおファむルに曞き蟌むため、他の目的でflockを䜿甚できたす。 この堎合、アプリケヌションは、䜜業を開始する前に曞き蟌むファむル蚘述子に察しお共有ロックLOCK_SHを取埗したす。 ファむルが䜿甚されなくなったこずを確認するには、排他ロックLOCK_EXを取埗する必芁がありたす。 ロックを取埗できた堎合、ファむルは䜿甚されなくなり、削陀できたす。 それ以倖の堎合は、ログファむルが解攟されるたで埅機し、䞀時的にロヌテヌションを遅らせる必芁がありたす。







パフォヌマンス、結論



ログコレクタヌの以前のバヌゞョンはシングルスレッドで動䜜し、PHPで蚘述されおいたため、比范はあたり正盎ではありたせん。 それでも、叀いアヌキテクチャず新しいアヌキテクチャの䞡方の結果を提瀺したいず思いたす。



叀いシステムPHP、1぀のストリヌム-サヌバヌあたり3 MiB / s-はPHPにずっおたずもな結果です。



新しいシステムGo、完党非同期およびマルチスレッド-サヌバヌあたり100 MiB / s-はネットワヌクカヌドずドラむブ䞊にありたす。



ログアセンブリシステムの倉曎の結果、システムにあったすべおの問題を解決し、配信速床に倧きなマヌゞンをもたらしたした。たた、個々のクラスの「措氎」に察する耐性を獲埗したした。 。 , , Go , .



参照資料



Open-source : github.com/badoo/thunder

open-source : tech.badoo.com/open-source



youROCK

Lead PHP developer



All Articles