Web の高速化: PHP セッションによる実行速度の低下を回避します
1. 内容 - 同時アクセス、実行のブロック
1.1 セッションを使用しないでください
File Index.php:
<script type="text/javascript" src="http://code.jquery.com/jquery-2.0.3.min.js"></script><script type="text/javascript">$(document).ready(function(){$.ajax({url:"/ajax.php"});$.ajax({url:"/ajax2.php"});$.ajax({url:"/ajax3.php"});});</script>
ファイル ajax.php ajax2.php と ajax3.php の内容はすべて
<?phpsleep(1);echo "php script run";
各リクエストは 1 秒スリープします。 jq の ajax リクエストは非同期です。つまり、これら 3 つのリクエストは基本的に同時に発行されます。理論的には、ブラウザが 1 秒待って 3 つのインターフェイスすべてが戻るのが最良の状況です。
http://localhost にアクセスし、Chrome でテスト結果を表示します。
図 1.1 セッションなしのテスト結果
テスト結果は基本的に理論と一致しています
1.2 セッションを使用します
次に、ファイルを ajax に置きます.php 、 ajax2.php 、 ajax3.php はすべて
<?phpsession_start();sleep(1);echo "php script run";
これをやると何か違いはありますか?テスト結果を直接見てみましょう:
図 1.2 セッションを使用したテスト結果
各リクエストは 1 秒間しか実行されませんか? ajax2.php は 2 秒を消費し、ajax3.php は 3 秒を消費するのはなぜですか?
2. WHO -- セッション ロック
セッション ロックの正式な定義:
セッション データは通常、session_write_close() を呼び出す必要なくスクリプト終了後に保存されますが、同時書き込みを防ぐためにセッション データは 1 つのスクリプトのみにロックされます。
一般的な意味は、session_start() が呼び出されてから session_write_close() が呼び出されるか、スクリプトが終了するまで、セッション データはロックされ、同じ SESSIONID ユーザーからのリクエストです。ブロックされます。
図 1.2 のテスト結果から、ajax2.php と ajax3.php がそれぞれ 1 秒と 2 秒実行されたことがわかります。 session_start() 操作がブロッキングを引き起こしたことは明らかです。
index.phpがajax.php、ajax2.php、ajax3.phpに同時にアクセスすると、ajax.phpが先に実行されます。このとき、ajax2.phpとajax3.phpはajax.phpの解放を待っています。セッション ロック、両方とも 1 を消費します。
ajax.php が実行され、セッション ロックが解放されると、ajax2.php と ajax3.php が再びセッション ロックを競合します。同様に、ajax3.php はさらに 1 秒待機します。結果は次のようになります:
ajax.php は 1 秒を消費します
ajax2.php は 2 秒を消費します
ajax3.php は 3 秒を消費します
3、いつ - セッション ロックはいつトリガーされますか?
第 2 章のセッション ロックの定義セッション ロックは session_start() の開始時にトリガーされるため、ほとんどの既存の PHP フレームワーク (ネイティブ PHP セッションを使用する場合) では、セッション ロックが原因でユーザー リクエストがブロックされるという問題があります。 2 つのリクエストがあるとします。リクエスト A は結果を返すのに 3 秒かかり、リクエスト B は 10 ミリ秒しかかかりません。バックグラウンドが最初にリクエストを処理すると、リクエスト B は結果を返します。結果を返すまでにさらに 10 ミリ秒かかります。ただし、最適な状況は、リクエストが同時に開始され、リクエスト B が受信されて 10 ミリ秒後に返され、リクエスト A が受信されて 3 秒後に返されることです。
4. なぜ -- セッションの内部実行メカニズム
デフォルトでは、php セッションはサーバー側のストレージメディアとしてファイルを使用します。 PHP セッション モジュールのソース コードには、より重要な構造があります。
図 4.1 PHP セッション モジュールの構造 ps_module_struct
この構造内のいくつかの関数ポインターは、セッション操作のオープンとオープンにそれぞれ対応します。クローズ、読み取り、書き込み、破棄、GC リサイクルおよびその他の機能。これら 6 つの関数を見て、php の SessionHandlerInterface インターフェイス (バージョン >= php5.4) を思い浮かべますか?
図 4.2 php SessionHandlerInterface インターフェイス
このインターフェイスを session_set_save_handler とともに使用して、セッションのこれらの主要な操作を書き換えます。したがって、次のコードを使用して、リクエストのライフサイクルにおける php セッションの実行順序を調べることができます:
図 4.3 セッション実行順序のテスト コード
テスト結果:
テスト結果から、次のことができます。一般的なセッション実行を知る 連続して、 session_start() が呼び出されると、php は open 操作と read 操作を呼び出し、スクリプトの実行が完了した後 (php script run が出力されます)、 write 操作と close 操作を再度呼び出します。
ここで、PHP はセッションのオープンまたは読み取り操作中にセッション ロックをオンにし、書き込みまたはクローズ後にセッション ロックを解放するという大胆な推測を行ってもよいでしょう。この推測は、第 1 章のテスト結果とも一致します。
为了验证我们的猜想,就需要去php的源码探个究竟了。在php源码文件/ ext / session / mod_files.c中可以看到默认session的6个重要操作的部分实现。在156行(点击打开链接),看到open操作有一个打开文件操作:flock(data->fd, LOCK_EX);该操作以互斥锁定的方式打开文件。在110行关掉文件close(data->fd);。
看到这里,我们应该得到的结论是:
在默认情况下,所谓的php session锁其实就是文件锁
所以,当我们使用session_set_save_handler来自定session操作,改用memcache或其他介质时,只要我们在SessionHandlerInterface的接口中没有锁的逻辑,那么session锁自然也不会存在。作者私下也做了这样的实验,实践证明也的确如此。
五、HOW -- 如何避免Session锁带来的阻塞现象
首先,session锁不一定是坏事情,在一种情况下就非常好用,例如某接口对与同一个用户的请求默认同一时刻只能执行一次。这种时候,就可以用seesion_start()和session_write_close()把要阻塞的代码括起来。非常简单暴力实用。
但是大部分时候我们还是要避免这种锁的存在,解决方案:
1、在用完session的时候就马上session_write_close()掉,释放session锁
2、采用没有锁的session操作,如章节4中所说的用session_set_save_handler来自定义一个没有锁的session操作。
3、再使用默认php session时,个人比较中意的一个方案:大部分情况下,我们对session的操作基本上都是读操作,写操作一般都比较少。这种时候,我们可以自己写一个session类。
构造函数:将session读入cache,关闭session锁
写操作:打开session锁,写入值,关闭session锁
读操作:直接读cache
部分代码如下:
//将session读入全局变量$_SEESIONstatic private function init(){if(self::$not_init){ session_start(); session_write_close(); self::$not_init = false;}}
//读sessionstatic public function get($name){self::init();return $_SESSION[$name];}
//写sessionstatic public function set($name, $val){session_start();$_SESSION[$name] = $val;session_write_close();}
注意:如果是写操作频繁的操作,就不适合使用该方法。