再ファックリングに直面





1つのリファクタリングの物語、またはPHPで開発してはならない方法に関する物語...



いつものように、顧客が涙とトランス状態で現れ、自分の声ではなく大声で叫んだ。「保存、助けて、私のプロジェクトは遅くなり、ユーザーは私から逃げ出しました。 1週間あります...」



私たちはサーバーでプロジェクトを開始しました-はい、そうです...その後、会社「Q」(ウクライナの会社が話しいることを知っている人)の作成、涙による笑い声、ヒステリーの分析が1週間ありました。 まず、XDEBUGが採用され、プロファイラーによって、何がどのように(クリック可能)であったかが見られました。





Webgrindは、便利で視覚的に、このようなクールなものを描画します




つまり Core2 Quad CPU Q6600@2.40GHz/16Gb(Qオフィスのヒステリーとヘイトレイ)を搭載したサーバー上で4.6秒で生成された、あいさつ+言語選択+表示用のカテゴリ選択(方法でハードコード化)が生成されたページがあります。



メインページでもっと不気味な写真が待っていた:





Zend Frameworkを適用する前に、...コードの最適化をさらに数週間行いました。



FastTemplateはそのような「高速」です





よく見ると、前の画面で特定の関数parse_bodyが非常に高価であることがわかります。







内部では、私たちを待っています:







コードを見てみましょう:

// $ content-テンプレート

// $ rec-変数

$ content = preg_replace "/ \\ $([AZ] [A-Z0-9 _] +)/" "@ \\ 1 @" $ content ;

if is_array $ rec {

foreach $ rec as $ key => $ value {

$ name = strtoupper $ key ;

$ content = str_replace "@ $ name @" " $ value " " $ content " ;

}

}

$コンテンツを 返し ます




つまり 最初に、テンプレートで$ HTTP_PATHのようなものを探し、それを@ HTTP_PATH @に置き換えてから、すべての既知の変数をブルートフォースに置き換えます。 1つの問題があります-私たちにはたくさんのテンプレートがあり、それらは数百の変数からかなり多くを使用します。 str_replaceをpreg_replace_callbackに単純に置き換えると、数秒の増加(つまり、約10%)が生じました。



コード例



気弱な方はこの段落を飛ばしてください。



インド人と私たちの学生に見られる古典的な悪いPHPコード:

関数 n {

グローバル $ db $ CONSTANTS $ user ...; //一言で言えば多数



echo $ CONSTANTS [ HOMEPAGE_BANNER1_ID ] ; //これは定数だと思いますか?

echo $ CONSTANTS [ HOMEPAGE_BANNER2_ID ] ; //何が入っているか知っていますか?

echo $ CONSTANTS [ HOMEPAGE_BANNER3_ID ] ; // 1、2、3、O_oが宣言されている場所ではまったく変化しない

}




バージョン管理システムの代わりにファイルシステムを使用する:

 tpl
 |-index.tpl
 |-index2.tpl
 |-index3.tpl
 |-インデックス__。tpl
 `-index.44.tpl




データベースには、categories_old、items_1テーブルなどの同じ番号があります。



少年が仕事が大好きなら

本に指を突く、

これについてここに書いてください:

彼はいい子です。




ご存知のように、これは「男の子」に関するものではなく、9,000件の通知がメインページにあります。 そして、弱者のみがmanual'amiを使用します。

//マニュアルを読みません

while $ row = mysql_fetch_array $ res {

if $ row {

foreach $ row AS $ key => $ field {

if ereg "^ [0-9] +" $ key {

設定解除 $ row [ $ key ] ;

}

}

}

$ rows [ ] = $ row ;

}

//少し終わったら

//些細なことですが、ゲインは約0.1秒です。 23162呼び出しが行われます

while $ row = mysql_fetch_array $ res MYSQL_ASSOC )) {

$ rows [ ] = $ row ;

}




さらに、素晴らしいソリューションであり、私はこの創造の秘密の意味をマスターしませんでした:

$ sqls [ ] = $ sql ;

if is_array $ sqls {

foreach $ sqls AS $ ssql {

if $ ssql {

$ res = mysql_db_query $ db [ 'name' ] $ ssql $ db [ 'id' ] ;

if $ res {

0を 返し ます

}

}

}

} else {

0を 返し ます

}




より簡単な検索結果のカウント:

//クエリを実行する前に、結果の数をカウントできます

// db_count関数を使用(96の異なる場所で呼び出されます)

関数 db_count $ sql {

グローバル $ db ;

$ res = mysql_db_query $ db [ 'name' ] $ sql $ db [ 'id' ] ;

$ result = mysql_num_rows $ res ;

return $ result ;

}




ページナビゲーション。これにより、次のこともできます。

//データベースにリクエストを送信します

$ res = mysql_db_query $ db [ 'name' ] $ sql $ db [ 'id' ] ;

//合計を計算します(db_countは既に呼び出されていますが)

$ row_count = mysql_num_rows $ res ;

//取得するページ数を計算します

$ page_count = floor $ row_count / $ pager [ 'per_page' ] + 1 ;



//これはオフセットです

$ bi = $ pos - 1 * $ pager [ 'per_page' ] ;



//必要なレコードのみを選択するようになりました

for $ i = $ bi ; $ i < $ bi + $ pager [ "per_page" ] ; $ i ++ {

if $ i > = $ row_count {

休憩 ;

}



if mysql_data_seek $ res $ i )) {

休憩 ;

}



if $ row = mysql_fetch_assoc $ res )) {

休憩 ;

}

//結果を保存します

$ new_rows [ ] = $ row ;

}




行の繰り返し-簡単な方法(str_repeat)を探していません:

// Categories.sql.phpファイル内

// HTMLの選択を構築する関数

$ offset_string = '' ;

for $ i = 1 ; $ i < $ rec [ 'level' ] ; $ i ++ {

$ offset_string 。= '&nbsp;&nbsp;&nbsp;&nbsp;' ;

}




文字列を100文字にトリムする必要があり、単語をカットしない場合、解決策は次のとおりです。

$ data [ 'row' ] [ 'description' ] = substr $ description 0、100 ;



$ i = 100 ;

while $ description [ $ i ] == "" || $ description [ $ i ] == "_" && $ i < strlen $ description

$ data [ 'row' ] [ 'description' ] 。= $ description [ $ i ] ;

$ i ++;

終わり ;



if $ i < strlen $ description {

$ data [ 'row' ] [ 'description' ] 。= "..." ;

}




私たちには非常に多くのグローバル変数があり、もちろん$ dbがあり、データベースで動作するすべての関数に渡されます:

関数 db_query $ db $ sql { }

関数 db_sql_query $ db $ sql { }

関数 db_count $ db $ sql { }

//など

//しかし、理由は、データベースが1つしかないためです

関数 db_query $ sql {

グローバル $ db ;

}




海を転がしますが、回避策は常に保存します

$ sql = "SELECT id、name、name = ' $ _POST [username] 'のユーザーからのパスウェイ" ;




SQLの集約については知りません。

// $ rows-データベースのレコード

foreach $行 $値 として {

$ total + = $ value [ "価格" ] ;

}




SEO URLが必要ですか? 問題ありません:

スイッチ $ params [ 1 ]

ケース 「使用契約」

$ page_id = 13 ;

休憩 ;

ケース 「privacypolicy」

$ page_id = 14 ;

休憩 ;

ケース 「termsandconditions」

$ page_id = 15 ;

休憩 ;

ケース 「アフィリエイト」

$ page_id = 22 ;

休憩 ;

ケース 「aboutus」

$ page_id = 19 ;

休憩 ;

エンドスイッチ ;




PHPでも問題ありませんが、HTMLを学ぶことができます。

<!-id such id->

< div id = "banner" > ... < / div >

< div id = "banner" > ... < / div >

< div id = "banner" > ... < / div >

< div id = "banner" > ... < / div >



<!-クラスはほとんどスタイルです->

< li class = "padding-left:15px;" > ... < / li >



<!-テーブルレイアウト->

<!-10個のネストされたテーブルをペイントしないでください->



<!-マージン、マージンとは何ですか? ->

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;




Zend_Cacheを使用する



Cacheは、すべてのSQLクエリ(幸いなことに、誰かが単一のdb_sql_query関数を書くことを推測した)とすべてのparse_body呼び出しのために、世界を救い、それをねじ込みました。 まず第一に、私たちはファイルをキャッシュし、それが助けたテストサーバー上、ライブサーバー上で試してみました-いいえ。 その理由は、非常に多くの小さなテンプレート(メインのテンプレートでは最大200)があるため、ファイルシステムでの操作がキャッシュの増加を無効にしたためです。



2回目の試行はより成功し、memcacheの使用を決定しました-速度は約180%向上しました。 なんてクールな指標ですが、キャッシュが100%ヒットした場合にのみ当てはまるため、システムの完全なリファクタリングの見通しが迫っていました。



クライアントの最適化



さて、次のルールを.htaccessに追加するだけで、少なくとも一度サイトを訪れたユーザーのナビゲーションが非常に容易になりました。



FileETag MTime Size



AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/x-javascript



<ifModule mod_expires.c>

ExpiresActive On

ExpiresDefault "access plus 1 seconds"

ExpiresByType text/html "access plus 1 seconds"

ExpiresByType image/x-icon "access plus 2592000 seconds"

ExpiresByType image/gif "access plus 2592000 seconds"

ExpiresByType image/jpeg "access plus 2592000 seconds"

ExpiresByType image/png "access plus 2592000 seconds"

ExpiresByType text/css "access plus 604800 seconds"

ExpiresByType text/javascript "access plus 216000 seconds"

ExpiresByType application/x-javascript "access plus 216000 seconds"









そして、順番にすべて:





Zendフレームワーク



そして、プロジェクトがゆっくりとZend Frameworkに移行する方法についてお話します。 それはすべて、index.phpの簡単なチェックから始まります(ああ、そうです、システムには1つのエントリポイントがあります)。



//リファクタリング済みのモジュールのリスト

$モジュール = 配列

「/検索/」

「/について/」

;



$ path = $ _SERVER [ 'REQUEST_URI' ] ;



//このような単純なチェックに満足しました、

//ただし、フォーム/ search /?...のリクエストは処理されなくなります

if in_array $ path $ modules {

// ZFを接続します(生成されたpublic / index.phpの標準コード内)

'loader.php' が必要です ;

exit ;

}



//そしてこれは

foreach $ module as $ module {

if strpos $ path $ module === 0 {

'loader.php' が必要です ;

exit ;

}

}




複数のエントリポイントがある場合、影響を受ける各ファイルで、単純な挿入を行います。

$ _SERVER [ 'REQUEST_URI' ] = str_replace $ _SERVER [ 'PHP_SELF' ] '/ admin /' $ _SERVER [ 'REQUEST_URI' ] ;

'loader.php' が必要です ;

exit ;




「グローバルな」恐怖を忘れるために、重要な変数はZend_Registryにスローされました(そして後でそれらが属するapplication.ini設定ファイルにスローされました)。



また、Zend_Translateには、変換されたテーブルが渡されました(配列アダプターを参照)



結果



結果を判断することは困難であり、プロジェクトは開発中ですが、ここにいくつかの比較測定があります:



生成時間 生成時間(再) 生成時間(ZF) ページボリューム ページボリューム(ZF)
メインページ 4,663ms 2 759ms - 699.5Kb 288.0Kb
静的ページ 3 115ms 2,008ms 295ms 263.3Kb 166.2Kb
アイテムページ 3,082ms 1,745ms 180ms 589.1Kb 260.8Kb




日付ごとの平均/最大ページ生成時間の別の明確なスクリーンショット: http : //screencast.com/t/NDY1NGE5



更新 :記事の口調で申し訳ありませんが、どういうわけか沸騰しています。 この記事の目的が、誰が白くふわふわしているかを示すことであり、他の人がそうでないことを示すことであると思われる場合、主なアイデアは経験を共有し、「悪い」コードの例を示すことであり、誰がそれを書いたかは関係なく、誰もが自分で抽出することですレッスン、そしてこのコードは小さくなっていた...



All Articles