最近、以前に行った最適化手法を見直したところ、いくつかの一般的な最適化手法が依然として再利用できることがわかりました。システムを長期間稼働させると、常に何らかの問題やボトルネックが発生します。問題が発生するのは怖いことではありません。問題を特定することに他なりません。 ;問題の分析 -> 解決策の提案 -> 実践 -> 結果のフィードバック -> 要約して最適化します。
問題の内容: PHP5 + Zend フレームワークを使用してシステムを開発したため、データ規模とアクセス量が増加(数千万件)した後、バックグラウンドの Apache サーバーの負荷が高くなりすぎました。毎日午後 10 時以降(特に金曜日)、マシンの CPU 負荷が 170 以上に急増します。CPU 負荷が高いと、それに応じてリクエストの処理が遅くなるため、この問題を早急に解決する必要があります。
問題の分析: 数日間の観察と分析の結果、CPU 使用率が 100% に達すると、システムの CPU 使用率が大きな割合を占め、ユーザーの CPU 使用率はそれほど高くありません。また、フロントエンドの haproxy とイカのキャッシュもそれほど高くありません。 CPU 負荷は非常に低く、memcached と Squid のヒット率は通常約 60% に達します。
バックエンドのアクセス ログを分析すると、リクエストされたユーザー エージェントの大部分が検索クローラーであることがわかりました。
同時に、xdebug が Apache 上で構成され、アイドル期間中にメイン ページで一連のパフォーマンス データが測定されました。測定されたデータは kcachegrind (xdebug の構成方法。soso で検索できます) を使用して分析され、次のことがわかりました。
パフォーマンス データは十分に安定しておらず、テスト データは同じリクエスト間で大きく異なります
遅いポイントほど点在しています
ほとんどの場合、memcached へのアクセスは比較的遅い (100 ミリ秒以上)
解決策 上記の予備分析を通じて、既存の手順に一連の調整が徐々に加えられました。
最初に考慮すべきことは、フロントエンドの Squid キャッシュのヒット率を高め、それによって Squid を通過してバックエンドの Apache に到達するリクエストの数を減らす方法を見つけることができるかどうかです。
かなりの数のリクエストが Crawler から発信されていることを考慮して、以前は Squid キャッシュは言語 Cookie が設定されたリクエストのみをキャッシュし、Crawler からのリクエストには Cookie 情報がありませんでした。そこで、Crawler からのすべてのリクエストを zh_CN の言語にデフォルト設定してから、haproxy の設定を変更して、User-Agent を使用した共通の Crawler からのすべてのリクエストを Squid キャッシュに転送することを考えました。
PHPコードを変更し、一部のページのキャッシュ時間を長く設定します
上記の 2 つの手順の後、Apache に到着するリクエストの数は確かに減少しましたが、過剰な CPU 負荷の問題はほとんど解決されないため、別の方法を探しました。
次に、xdebug プロファイリングを使用した結果によると、memcached との対話には長い時間がかかるため、memcached がリクエストに速く応答できるようにして、各リクエストをより速く完了して同時実行性を減らす方法を見つけられないかと思います。
コード分析の結果、オンラインの memcached は poll() を使用しており、混雑時でも memcached の接続数は約 1,000 のままであり、memcached の CPU 使用率は約 30% であることがわかりました。明らかに、多数の同時接続を処理する場合、poll() メソッドは非常に非効率的です。そこで、epoll() を使用してリクエストを処理するように memcached を再コンパイルしました。epoll に置き換えた後、memcached の CPU 使用率は約 30% から約 10 倍の約 3% に減少しました。
また、memcached のヒット率はそれほど高くなく、スワップアウトされるアイテムの数も比較的多いため、当初は手動でキャッシュの内容をパーティション分割する予定でしたが、後で判明しました。 PHP の最新の memcache 拡張機能は、キャッシュベースのパーティショニングをサポートしており、キーは自動的にパーティショニングされ、プログラム コードを変更せずに新しい memcached インスタンスを追加できます (構成ファイルを変更する必要があります:-))。そこで、各 Apache の php memcache 拡張機能をアップグレードし、新しい memcached を設定ファイルに追加しました。これで memcached のコンテンツ パーティションが完成しました。変更後の効果はより顕著で、ページの読み込み時間は変更前に比べて大幅に短くなります。
この 2 段階の調整の後、memcached の効率は以前よりも向上しましたが、Apache の負荷は依然として高いため、他の解決策を考えるしかありません。
さらに詳細な分析では、メイン システムの CPU 使用率が非常に高いと述べました。その理由を見つけるには、カーネルを深く掘り下げるしかありません:) ここから、strace の旅が始まります。ナイキの広告スローガンを言い換えると: Just strace it!
Strace は、次の方法を使用してピーク時間中に httpd プロセスで実行されました
strace -p PID -c で概要が得られます
strace -p PID -o Output.log ファイルに書き込んでゆっくり勉強してください
strace -p PID -e trace=file ファイルシステム操作に関連する syscall のみを確認します
strace -p PID -elstat64,stat64,open,getcwd はこれらの syscall のみをトレースします
…
上記の strace 分析から、次の結論が導き出されます:
lstat64、stat64、openなど、たくさんのシステムコールがあります
上記のシステムコールにはかなりの時間がかかります。 6割以上の時間は彼らに奪われますorz
システムコールの大部分は失敗しますが、実際には失敗が繰り返されるケースです
上記のデータにより、これらの無意味なシステム コールがどこから来たのかを突き止めるという問題の方向性がわかりました。
分析後、PHP は特定のクラスをロードしたいときに、include_path で定義された一連のディレクトリ内でそのクラスに対応するファイルを検索し、見つかるまで各ディレクトリを試します。この方法は明らかに非効率的ですが、これを達成するためのより良い方法はあるでしょうか?答えは「はい、あります!」です。そして、方法は複数あります!
require_once() を呼び出すときは、パラメータとして絶対パスを記述します (Zend Framework を作成する人は最初これを理解していませんでした。後で更新されました))
__autoload() を使用してクラスを遅延ロードします。これは、使用されるすべてのクラス ファイルを require_once するのではなく、本当に必要な場合にのみロードされることを意味します。
問題は見つかりましたが、解決すべき問題はまだあります。開発中にコード内での絶対パスの使用に注意してください。改善できる唯一のことは、これを遅延読み込みに変更することです。ただし、Zend Framework の多数の require_once は相対パスを使用しており、これが問題の原因です。この記事で取り上げているのは、過剰な問題の根本的な原因です。
さて、問題が見つかったので、解決しましょう。クラス -> ファイル パスの対応を自動的に生成するスクリプトを作成し、コード内のすべてのクラスと Zend Framework のすべてのクラスの間の対応ファイルを生成します。コードおよび Zend Framework ライブラリ内のすべての require_once をコメント アウトします。その後、本番稼働前に詳細なテストを実施します。結果は驚くべきもので、負荷は 3 以内に減少しました。 !問題が解決しました。
概要:
コードを書く人なら誰でも、問題が発生する可能性のある場所には常に問題が存在することを知っています (たとえそれがまだ見つかっていなくても) 根本から解決することが最善の方法です。どのような問題が解決されるのか、このアイデアとツールの上手な使い方を皆さんに学んでいただければと思います。はい、この件についてはこれで終わりです。