关于session锁引起的接口请求响应慢的问题

405次阅读 21人点赞 作者: WuBin 发布时间: 2025-03-05 17:03:06
扫码到手机查看

问题描述

老贴,可以参考:访问

index.html 运行的时候会同时发送(并发)三个都非常耗时的异步sse请求:

index.html
     |- A.php
     |- B.php
     |- C.php

ABC三个接口会在握手后,持续接收数据。其中ABC三个文件共同引用一个common.php,并且ABC三个接口都是使用session做用户登录状态以及信息的管理。如下:

common.php ->A
           ->B
           ->C
A:require('common.php')
B:require('common.php')
C:require('common.php')

common.php中:

session_start();
/** 一系列从数据库 读取用户数据操作 */
$_SESSION['id'] = 'xxx';

在并发的情况下,我发现三个请求都非常的慢!经过排查发现问题原因如下:

当使用PHP的默认session处理机制时,session数据通常存储在服务器的文件系统中。当一个请求开始访问session(通过 session_start() )时,PHP会对该session文件加锁,直到请求结束或显式调用 session_write_close() 。

在你的场景中:

  • 1. A请求开始,调用 session_start() ,获取session文件锁
  • 2. A的SSE连接建立,保持打开状态(长连接)
  • 3. 由于SSE连接未关闭,session文件锁未释放
  • 4. B请求开始,尝试调用 session_start() ,但必须等待session文件锁释放
  • 5. B请求被阻塞,直到A的SSE连接关闭或超时
  • 这就解释了为什么后续请求会很慢或失败 - 它们在等待前一个请求释放session锁。

解决方案

在后端修改session处理方式 :

- 在验证完用户身份后立即调用 session_write_close() 释放锁

- 使用无锁的session存储方式(如Redis、Memcached等)

- 对于SSE连接,可以在建立连接前验证用户身份,然后关闭session

前端修改请求方式 :

- 串行化请求,等待前一个请求完全结束(包括SSE关闭)后再发起下一个请求

- 使用token而非session进行身份验证

前端修改这里就不多废话了,着重讲一下后端的修改session_write_close()

session_write_close

在 PHP 中,session 文件锁是由 PHP 的 session 处理机制自动管理的。当你使用 session_start() 开始一个会话时,PHP 会自动锁定对应的 session 文件,以防止并发访问导致的数据不一致问题。要解除 session 文件锁,你可以使用以下几种方法:

使用 session_write_close() 函数:

记住一定要是在哪个文件中开启的session_start(),就要在哪个文件中执行session_write_close(),对文件锁进行释放!!!不能在common.php中开启session_start(),然后在A.php中执行session_write_close(),这样可能会导致无效果!
<?php
session_start();
// 进行一些 session 操作
$_SESSION['user_id'] = 123;

// 完成操作后,写入并关闭 session,释放文件锁
session_write_close();

// 继续执行其他不需要修改 session 的代码
// ...
?>

使用 session_commit() 函数

(这是 session_write_close() 的别名):

<?php
session_start();
$_SESSION['user_id'] = 123;

// 释放 session 锁
session_commit();

// 继续执行其他代码
?>

在需要再次修改 session 时,可以重新启动 session

<?php
session_start();
$_SESSION['user_id'] = 123;

// 释放锁
session_write_close();

// 执行一些耗时操作
sleep(5);

// 需要再次修改 session 时,重新启动
session_start();
$_SESSION['last_activity'] = time();
session_write_close();
?>

这对于需要执行长时间运行的脚本特别有用,如果使用session进行验证的话,一定要重视这个问题!!并且一定要在耗时的操作开始前关闭session锁!!!可以避免长时间锁定 session 文件,从而提高并发性能。

注意:在使用 AJAX 或并发请求的应用中,及时释放 session 锁尤为重要,否则可能导致请求排队等待,影响用户体验。

close之后session数据依然有效

session_write_close() 执行后, $_SESSION 中的数据依然有效。这个函数只是释放了 session 文件锁,并将当前的 session 数据写入存储,但不会销毁或清除 session 数据。
<?php
session_start();

// 存储用户信息
$_SESSION['user_id'] = 123;
$_SESSION['username'] = 'test';

// 释放文件锁
session_write_close();

// 这里依然可以读取 session 数据
echo $_SESSION['user_id'];  // 输出: 123
echo $_SESSION['username']; // 输出: test

// 如果需要修改 session 数据,需要重新启动 session
session_start();
$_SESSION['new_data'] = 'new value';
session_write_close();
?>

一些重要说明:

1. session_write_close() 后:

   - 可以继续读取$_SESSION 中的数据

   -不能修改 $_SESSION 中的数据,除非重新调用 session_start()

2.使用场景:

   -在需要执行耗时操作前释放 session 锁

   -在并发请求场景下提高性能

   -在异步操作中避免 session 锁冲突

最佳实践:

<?php
session_start();
// 验证用户状态
$_SESSION['user_id'] = 123;
// 写入完成后立即释放锁
session_write_close();

// 执行一些耗时操作
sleep(5);

// 仍然可以读取数据
$user_id = $_SESSION['user_id'];

// 如果需要修改 session
session_start();
$_SESSION['new_data'] = 'value';
session_write_close();
?>

AI疑问解答

问题1

现在有个问题:

如果A与C都 引用了同一个文件common.php,common.php中:session_start() ,A与C如下:

// A.php
require_once 'common.php';
$_SESSION['id'] = 10;
// 在开始耗时操作前释放锁
session_write_close();
// 执行耗时的 SSE 请求...

// C.php
require_once 'common.php';
$id = $_SESSION['id'];
session_write_close();

回答:不会释放同一个 session 文件锁。

C 中执行的 session_write_close() 确实会有效,但它只会释放 C 这个脚本对 session 文件的锁定。

- PHP 的 session 锁是基于文件的,每个用户的 session 对应一个文件

- 当一个脚本调用 session_start() 时,会锁定该用户的 session 文件

- 不同的 PHP 脚本实例是独立的进程,各自管理自己的 session 锁

所以只要session是同一个用户,就会锁定。C中的 session_write_close() 只会释放该脚本对 session 文件的锁定,不会影响到 A 中的 session 锁。 A 会继续保持对 session 文件的锁定,直到它自己调用 session_write_close() 或脚本执行完毕。所以就是不要考虑在C中去解除A的锁,就算锁住的是同一个用户的,也不要在C中操作。

如果你希望在 A 中执行耗时操作前释放 session 锁,应该在A中的耗时操作前调用 session_write_close() :

// dify.php
session_start();
$_SESSION['id'] = 10;
// 在开始耗时操作前释放session锁
session_write_close();

// 然后执行耗时的SSE请求
// ...

如此,其他脚本(如 C )就可以在 dify.php 执行耗时操作期间访问和修改 session 数据,而不会被阻塞。

问题2

我发现当A执行 一直处于“待处理”的时候,然后我刷新页面,发现C也是会一直处于“待处理” 无法获取数据,必须要等一会(服务器设置一个请求最长等待3分钟)之后,突然C开始接收数据了。这是什么原因?

这种情况是因为 PHP 的 session 机制导致的。让我解释一下具体原因:

Session 文件锁机制:

- PHP 使用文件来存储 session 数据

- 当一个脚本访问 session 时,PHP 会锁定整个 session 文件

- 其他想要访问同一个 session 的请求必须等待锁释放

遇到的情况实际是:

A                                C
---------                         --------------
session_start()                   等待获取session锁...
执行耗时的SSE请求...              等待获取session锁...
(请求未完成,锁未释放)            等待获取session锁...
3分钟后请求超时                   终于获得session锁
                                        开始执行...

解决方案:在 dify.php 中,应该在开始 SSE 请求之前就释放 session 锁:

// dify.php
session_start();
// 读取需要的 session 数据
$user_id = $_SESSION['id'];

// 重要:在开始 SSE 请求前释放 session 锁
session_write_close();

// 然后再执行耗时的 SSE 请求
// ... SSE 请求代码 ...

- 释放锁后,其他脚本可以立即访问 session

- SSE 请求期间不需要修改 session,所以可以安全释放

- 避免阻塞其他需要访问 session 的请求

如果 SSE 请求过程中需要修改 session,可以临时重新开启:

session_start();
$_SESSION['new_data'] = 'value';
session_write_close();

确保在读取完所需的 session 数据后再释放锁。

问题3

你上一个回答告诉我说 :PHP 的 session 锁是基于文件的,每个用户的 session 对应一个文件,不同的 PHP 脚本实例是独立的进程,各自管理自己的 session 锁。现在我有点不懂了。 假设现在只有一个用户,不要考虑多个用户。他同时发送多个请求,A和B和C,如果A耗时长被锁住session,那么C就会受影响是吗?然后我如果在C中执行session_write_close()会解开session锁吗?

Session 锁的工作机制:

- 每个用户的 session 数据存储在一个文件中

- 同一个用户 的所有请求都会访问同一个 session 文件

- 当一个请求获得 session 锁时,其他请求必须等待锁释放

在你的场景中:

用户发起请求1A                   用户发起请求2C
session_start() ✓获得锁                  session_start() ❌等待锁
执行耗时的 SSE 请求...                   继续等待...
继续执行...                                继续等待...

所以:- 是的,如果 dify.php 持有 session 锁且耗时长,check_login.php 会被阻塞

- 在 C 中执行 session_write_close() 没有意义,因为它根本获取不到锁

- 正确的做法是在A中执行耗时操作前释放锁:

// dify.php
session_start();
$user_id = $_SESSION['id']; // 先读取需要的数据
session_write_close();      // 释放锁
// 执行耗时的 SSE 请求...

// check_login.php
session_start();           // 现在可以立即获得锁
$id = $_SESSION['id'];
session_write_close();

简单来说:

1. 同一个用户的所有请求共享同一个 session 文件和锁

2. 必须在持有锁的脚本中释放锁,其他脚本释放无效

3. 应该在执行耗时操作前释放锁,这样其他请求就不会被阻塞

问题4:exit会终止程序并自动释放session锁吗

比如有如下代码:

session_start()

if($one) {
    if($two) {
        ...
        session_write_close();
    } else {
        ...
        exit;
    }
} else {
    ...
    exit;
}

问题:我只有在$two这里释放了文件锁,没有在其他的else中 释放锁,然后在其他问题的时候 是直接执行了exit 请问执行了exit之后 PHP会自动释放锁吗?

回答:不会!

关于您的问题,我来解释一下:

PHP中的session锁和exit的关系:当PHP脚本执行到 exit 或 die 语句时,PHP会自动执行清理工作,包括关闭所有打开的资源,但不会自动调用 session_write_close() 这意味着如果您在 exit 之前没有显式调用 session_write_close() ,那么session锁可能会一直保持到session垃圾回收机制触发(通常是session.gc_maxlifetime设置的时间)

在您的代码中,我注意到只在$two有效的情况下调用了 session_write_close() ,而在其他分支中没有。这可能会导致session锁问题,特别是在高并发环境下。

建议:

if($one) {
    if($two) {
        ...
        session_write_close();
    } else {
        session_write_close();
        ...
        exit;
    }
} else {
    session_write_close();
    ...
    exit;
}

无论执行哪个分支,都会确保在返回响应前释放session锁,避免其他请求被阻塞。这对于提高并发性能和避免请求超时非常重要。

点赞 支持一下 觉得不错?客官您就稍微鼓励一下吧!
关键词:session文件锁
推荐阅读
  • uniapp实现被浏览器唤起的功能

    当用户打开h5链接时候,点击打开app若用户在已经安装过app的情况下直接打开app,若未安装过跳到应用市场下载安装这个功能在实现上主要分为两种场景,从普通浏览器唤醒以及从微信唤醒。

    10556次阅读 698人点赞 发布时间: 2022-12-14 16:34:53 立即查看
  • Vue

    盘点Vue2和Vue3的10种组件通信方式

    Vue中组件通信方式有很多,其中Vue2和Vue3实现起来也会有很多差异;本文将通过选项式API组合式API以及setup三种不同实现方式全面介绍Vue2和Vue3的组件通信方式。

    5035次阅读 378人点赞 发布时间: 2022-08-19 09:40:16 立即查看
  • JS

    几个高级前端常用的API

    推荐4个前端开发中常用的高端API,分别是MutationObserver、IntersectionObserver、getComputedstyle、getBoundingClientRect、requ...

    15089次阅读 994人点赞 发布时间: 2021-11-11 09:39:54 立即查看
  • PHP

    【正则】一些常用的正则表达式总结

    在日常开发中,正则表达式是非常有用的,正则表达式在每个语言中都是可以使用的,他就跟JSON一样,是通用的。了解一些常用的正则表达式,能大大提高你的工作效率。

    14596次阅读 575人点赞 发布时间: 2021-10-09 15:58:58 立即查看
  • 【中文】免费可商用字体下载与考证

    65款免费、可商用、无任何限制中文字体打包下载,这些字体都是经过长期验证,经得住市场考验的,让您规避被无良厂商起诉的风险。

    13310次阅读 1076人点赞 发布时间: 2021-07-05 15:28:45 立即查看
  • Vue

    Vue3开发一个v-loading的自定义指令

    在vue3中实现一个自定义的指令,有助于我们简化开发,简化复用,通过一个指令的调用即可实现一些可高度复用的交互。

    17435次阅读 1404人点赞 发布时间: 2021-07-02 15:58:35 立即查看
  • JS

    关于手机上滚动穿透问题的解决

    当页面出现浮层的时候,滑动浮层的内容,正常情况下预期应该是浮层下边的内容不会滚动;然而事实并非如此。在PC上使用css即可解决,但是在手机端,情况就变的比较复杂,就需要禁止触摸事件才可以。

    15845次阅读 1282人点赞 发布时间: 2021-05-31 09:25:50 立即查看
  • Vue

    Vue+html2canvas截图空白的问题

    在使用vue做信网单页专题时,有海报生成的功能,这里推荐2个插件:一个是html2canvas,构造好DOM然后转canvas进行截图;另外使用vue-canvas-poster(这个截止到2021年3月...

    31213次阅读 2459人点赞 发布时间: 2021-03-02 09:04:51 立即查看
  • Vue

    vue-router4过度动画无效解决方案

    在初次使用vue3+vue-router4时候,先后遇到了过度动画transition进入和退出分别无效的情况,搜遍百度没没找到合适解决方法,包括vue-route4有一些API都进行了变化,以前的一些操...

    27145次阅读 2089人点赞 发布时间: 2021-02-23 13:37:20 立即查看
交流 收藏 目录