ホームページ >バックエンド開発 >PHPチュートリアル >PHP のセッションに関するいくつかの質問
セッションとは
Webアプリケーション開発では、セッションのことをセッションと呼びます。主に訪問者のデータを保存するために使用されます。
HTTP のステートレスな性質により、サーバーはクライアントを記憶しません。サーバーにとって、すべてのリクエストはまったく新しいものになります。
この場合、サーバーはどの訪問者がリクエストしているかをどのようにして知るのでしょうか?さまざまなデータを適切な訪問者にマッピングするにはどうすればよいでしょうか?答えは、セッション内のデータを取得するための一意の ID を訪問者に与えることです。
例:スーパーマーケットに買い物に行くと、警備員は商品を持ち込むことはできず、スーパーマーケットの保管箱に保管しなければならないと言います。私たちは彼にアイテムを渡しましたが、彼はどのようにしてそれらの所有者が誰であるかを知ったので、私たちに別の鍵を渡しました。アイテムを持ち去りたい場合は、固有のキーを使用して対応するボックスを開けることができます。
上記の例と同様に、Session はデータを保存する「ボックス」として理解できます。もちろん、これらの「ボックス」はすべてサーバー側にあります。サーバーは訪問者にsession_idと呼ばれる一意の「キー」を与えます。訪問者は、サーバー側に保存されている独自の session_id を使用してデータを取得できます。
session_id は、URL または Cookie の 2 つの方法で訪問者 (クライアント) に渡されます。詳細については、セッション ID の送信を参照してください。
セッションと Cookie の関係は何ですか? Cookie も HTTP のステートレスな特性によって生成される技術です。訪問者の識別情報や一部のデータを保存するためにも使用されます。クライアントが HTTP リクエストを開始するたびに、Cookie データが HTTP ヘッダーに追加され、サーバーに送信されます。このようにして、サーバーは Cookie の内容に基づいて訪問者の情報を知ることができます。
Session と Cookie は同様のことを行うと言えますが、Session はサーバー上にデータを保存し、クライアントによって送信された session_id を通じて対応するデータを取得しますが、Cookie はリクエストが行われるたびにクライアント上にデータを保存します。データをサーバーに送信するとき。
上で述べたように、session_id は URL または Cookie を介して渡すことができます。URL メソッドは Cookie メソッドよりも安全性が低く、使用が不便であるため、session_id を渡すには通常 Cookie が使用されます。
サーバーは session_id を生成し、それを HTTP メッセージを通じてクライアント (ブラウザーなど) に送信します。クライアントは、指示に従って session_id を保存する Cookie を作成します。 Cookie はキー/値の形式で保存され、PHPSESSID=e4tqo2ajfbqqia9prm8t83b1f2 のようになります。 PHP では、session_id を保存する Cookie 名はデフォルトで PHPSESSID と呼ばれます。この名前は、php.ini の session.name または関数 session_name() を通じて変更できます。
PHP では、デフォルトのセッション プロセッサはファイルであり、プロセッサはユーザーが実装できます (参照: カスタム セッション マネージャー)。私が知っている成熟したセッション プロセッサはたくさんあります: Redis、Memcached、MongoDB...
PHP 独自のファイル タイプ プロセッサを使用することが推奨されないのはなぜですか? 公式 PHP マニュアルには次のような注記があります:
関数を呼び出すかどうかsession_start() を使用してセッションを手動で開くか、設定項目 session.auto_start を使用してセッションを自動的に開きます (PHP のデフォルトの動作) ファイルベースのセッション データの保存では、セッション データ ファイルはセッションの開始時にロックされるまでロックされます。 PHP スクリプトが実行されるか、session_write_close() が明示的に呼び出されてセッション データが保存されます。 この間、他のスクリプトは同じセッション データ ファイルにアクセスできません。
上記の引用については、「Session の基本的な使用法」を参照してください
この段落を証明するために、2 つのファイルを作成します:
ファイル: session1.php
php<?phpsession_start();sleep(5);var_dump($_SESSION);?>
ファイル: session2.php
php<?phpsession_start();var_dump($_SESSION);?>
同じブラウザで、最初のアクセスhttp://127.0.0.1/session1.php にアクセスし、すぐに現在のブラウザの新しいタブ ページで http://127.0.0.1/session2.php にアクセスします。実験の結果、session1.php は出力するまでに 5 秒待機し、session2.php も出力するまでに 5 秒近く待機したことがわかりました。 session2.php のみにアクセスすると数秒で開きます。 1 つのブラウザで session1.php にアクセスし、すぐに別のブラウザで session2.php にアクセスします。その結果、session1.php は出力を 5 秒間待機しますが、session2.php は数秒以内に開きます。
分析一下造成这个现象的原因:上面例子中,默认使用 Cookie 来传递 session_id,而且 Cookie 的作用域是相同。这样,在同一个浏览器中访问这 2 个地址,提交给服务器的 session_id 就是相同的(这样才能标记访问者,这是我们期望的效果)。当访问 session1.php 时,PHP 根据提交的 session_id,在服务器保存 Session 文件的路径(默认为 /tmp,通过 php.ini 中的 session.save_path 或者函数 session_save_path() 来修改)中找到了对应的 Session 文件,并对其加锁。如果不显式调用 session_write_close(),那么直到当前 PHP 脚本执行完毕才会释放文件锁。如果在脚本中有比较耗时的操作(比如例子中的 sleep(5)),那么另一个持有相同 session_id 的请求由于文件被锁,所以只能被迫等待,于是就发生了请求阻塞的情况。
既然如此,在使用完 Session 后,立刻显示调用 session_write_close() 是不是就解决问题了哩?比如上面例子中,在 sleep(5) 前面调用 session_write_close()。
确实,这样 session2.php 就不会被 session1.php 所阻塞。但是,显示调用了 session_write_close() 就意味着将数据写到文件中并结束当前会话。那么,在后面代码中要使用 Session 时,必须重新调用 session_start()。
例如:
php<?phpsession_start();$_SESSION['name'] = 'Jing';var_dump($_SESSION);session_write_close();sleep(5);session_start();$_SESSION['name'] = 'Mr.Jing';var_dump($_SESSION);?>
官方给出的方案:
对于大量使用 Ajax 或者并发请求的网站而言,这可能是一个严重的问题。 解决这个问题最简单的做法是如果修改了会话中的变量, 那么应该尽快调用 session_write_close() 来保存会话数据并释放文件锁。 还有一种选择就是使用支持并发操作的会话保存管理器来替代文件会话保存管理器。
我推荐的方式是使用 Redis 作为 Session 的处理器。
拓展阅读:
为什么不能用 memcached 存储 Session
如何使用 Redis 作为 PHP Session handler
Session 数据是什么时候被删除的这是一道经常被面试官问起的问题。
先看看官方手册中的说明:
session.gc_maxlifetime 指定过了多少秒之后数据就会被视为"垃圾"并被清除。 垃圾搜集可能会在 session 启动的时候开始( 取决于 session.gc_probability 和 session.gc_divisor)。 session.gc_probability 与 session.gc_divisor 合起来用来管理 gc(garbage collection 垃圾回收)进程启动的概率。此概率用 gc_probability/gc_divisor 计算得来。例如 1/100 意味着在每个请求中有 1% 的概率启动 gc 进程。session.gc_probability 默认为 1,session.gc_divisor 默认为 100。
继续用我上面那个不太恰当的比方吧:如果我们把物品放在超市的储物箱中而不取走,过了很久(比如一个月),那么保安就要清理这些储物箱中的物品了。当然并不是超过期限了保安就一定会来清理,也许他懒,又或者他压根就没有想起来这件事情。
再看看两段手册的引用:
如果使用默认的基于文件的会话处理器,则文件系统必须保持跟踪访问时间(atime)。Windows FAT 文件系统不行,因此如果必须使用 FAT 文件系统或者其他不能跟踪 atime 的文件系统,那就不得不想别的办法来处理会话数据的垃圾回收。自 PHP 4.2.3 起用 mtime(修改时间)来代替了 atime。因此对于不能跟踪 atime 的文件系统也没问题了。
GC 的运行时机并不是精准的,带有一定的或然性,所以这个设置项并不能确保旧的会话数据被删除。某些会话存储处理模块不使用此设置项。
对于这种删除机制,我是存疑的。
比如 gc_probability/gc_divisor 设置得比较大,或者网站的请求量比较大,那么 GC 进程启动就会比较频繁。
还有,GC 进程启动后都需要遍历 Session 文件列表,对比文件的修改时间和服务端的当前时间,判断文件是否过期而决定是否删除文件。
这也是我觉得不应该使用 PHP 自带的 files 型 Session 处理器的原因。而 Redis 或 Memcached 天生就支持 key/value 过期机制的,用于作为会话处理器很合适。或者自己实现一个基于文件的处理器,当根据 session_id 获取对应的单个 Session 文件时判断文件是否过期。
session.cookie_lifetime 以秒数指定了发送到浏览器的 cookie 的生命周期。值为 0 表示"直到关闭浏览器"。默认为 0。
其实,并不是 Session 数据被删除(也有可能是,概率比较小,参见上一节)。只是关闭浏览器时,保存 session_id 的 Cookie 没有了。也就是你弄丢了打开超市储物箱的钥匙(session_id)。
同理,浏览器 Cookie 被手动清除或者其他软件清除也会造成这个结果。
为什么浏览器开着,我很久没有操作就被登出了这个是称为“防呆”,为了保护用户账户安全的。
这个小节放进来,是因为这个功能的实现可能和 Session 的删除机制有关(之所以说是可能,是因为这个功能不一定要借住 Session 实现,用 Cookie 也同样可以实现)。
说简单一点,就是长时间没有操作,服务端的 Session 文件过期被删除了。
在我试验的过程中,发现了小有意思的事情:我把 GC 启动的概率设置为 100%。如果只有一个访问者请求,该访问者即使过了很久(超过了过期时间)后才发起第二次请求,那么 Session 数据也还是存在的('session.save_path' 目录下面的 Session 文件存在)。是的,明明就超过了过期时间,却没有被 GC 删除。这时,我用另外一个浏览器访问时(相对于另一个访问者),这次请求生成了新的 Session 文件,而上一个浏览器请求生成的那个 Session 文件终于没有了(之前那个 Session 文件在 'session.save_path' 目录下面的消失了)。
还有,发现 Session 文件被删除后,再次请求,还是会生成和之前文件名相同的 Session 文件(因为浏览器并没有关闭,再次请求发送的 session_id 是相同的,所以重新生成的 Session 文件的文件名还是一样的)。但是,我不理解的是:这个重新出现的文件的创建时间竟然是第一次的那个创建时间,难道它是从回收站中回来的?(确实,我做这个试验时是在 window 下进行的)
我猜测的原因是这样:当启动会话后,PHP 根据 session_id 找到并打开了对应的 Session 文件,然后才启动 GC 进程。GC 进程就只检查除了当前这个 Session 文件外的其他文件,发现过期的就干掉。所有,即使当前这个 Session 文件已经过期了,GC 也没有删除它。
我认为这个不合理的。
由于发生这种情况影响也不大(毕竟线上请求很多,当前请求的过期文件被其他请求唤起的 GC 干掉的可能性是比较大的) + 我没有信心去看 PHP 源代码 + 我并不在线上使用 PHP 自带的 files 型 Session 处理器。所以,这个问题我就没有深入研究了。请谅解。
php<?php// 过期时间设置为 30 秒ini_set('session.gc_maxlifetime', '30');// GC 启动概率设置为 100%ini_set('session.gc_probability', '100');ini_set('session.gc_divisor', '100');session_start();$_SESSION['name'] = 'Jing';var_dump($_SESSION);?>
拓展阅读:
如何设置一个严格 30 分钟过期的 Session