You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel中NativeSession::startSession耗时过高问题及优化诉求

Laravel会话阻塞问题解决方案

问题根源分析

虽然用Redis做会话存储,但PHP原生session_start()底层处理时,会对Redis中的会话数据加读写锁——当一个请求开启会话并持有锁时,同用户的其他请求会等待锁释放,这就是偶尔请求慢到7秒的核心原因,哪怕是只读请求也会被长耗时请求阻塞。


具体解决方法

方案1:给只读请求单独做无锁会话处理

针对不需要修改会话的查询、展示类请求,跳过原生会话启动逻辑,直接无锁读取Redis会话数据,避免锁竞争。

  1. 给只读路由加标记中间件
// routes/web.php 或 routes/api.php
Route::get('/articles', [ArticleController::class, 'list'])->middleware('session.readonly');
Route::get('/user/profile', [UserController::class, 'show'])->middleware('session.readonly');
  1. 创建session.readonly中间件
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class SessionReadonly
{
    public function handle(Request $request, Closure $next)
    {
        // 若会话未启动,直接从Redis读取会话数据(无锁)
        if (session_status() === PHP_SESSION_NONE) {
            $sessionId = $request->cookie(config('session.cookie'));
            if ($sessionId) {
                // 拼接Redis中会话的键名,默认前缀是laravel:
                $sessionKey = 'laravel:' . $sessionId;
                $sessionData = app('redis')->get($sessionKey);
                
                if ($sessionData) {
                    $_SESSION = unserialize($sessionData);
                }
                // 标记会话为已启动,避免后续触发session_start()
                session_status(PHP_SESSION_ACTIVE);
            }
        }

        return $next($request);
    }
}
  1. 注册中间件
    在app/Http/Kernel.php的$routeMiddleware数组中添加:
protected $routeMiddleware = [
    // ...其他中间件
    'session.readonly' => \App\Http\Middleware\SessionReadonly::class,
];

方案2:缩短会话锁超时时间

如果不想改代码,可以先调整会话锁的超时配置,避免长耗时请求长时间占用锁。

修改config/session.php中的lock_timeout参数:

'lock_timeout' => 10, // 默认120秒,改为10秒,超时后自动释放锁

方案3:自定义无锁Redis会话驱动

对Laravel默认的Redis会话驱动做改造,让只读请求跳过锁逻辑:

  1. 创建自定义会话驱动类
<?php

namespace App\Session;

use Illuminate\Session\RedisSessionHandler;

class ReadonlyRedisSessionHandler extends RedisSessionHandler
{
    protected $readonly = false;

    public function setReadonly(bool $readonly)
    {
        $this->readonly = $readonly;
    }

    // 只读请求跳过锁相关操作
    public function lock($sessionId)
    {
        return $this->readonly ? true : parent::lock($sessionId);
    }

    public function unlock($sessionId)
    {
        return $this->readonly ? true : parent::unlock($sessionId);
    }

    // 只读请求禁止写入会话
    public function write($sessionId, $data)
    {
        return $this->readonly ? true : parent::write($sessionId, $data);
    }
}
  1. 在服务提供者中替换驱动
    在App\Providers\AppServiceProvider的boot方法中添加:
public function boot()
{
    $this->app->extend('session.handler', function ($handler, $app) {
        // 判断当前请求是否使用只读中间件
        if ($app->request->route() && $app->request->route()->hasMiddleware('session.readonly')) {
            $readonlyHandler = new \App\Session\ReadonlyRedisSessionHandler(
                $app['redis'],
                config('session.lottery'),
                config('session.expire_on_close'),
                config('session.redis')
            );
            $readonlyHandler->setReadonly(true);
            return $readonlyHandler;
        }
        return $handler;
    });
}

验证效果

实施后用New Relic监控NativeSession::startSession的耗时占比,同时测试并发场景:

  • 发起一个长耗时的写请求(比如表单提交后处理10秒)
  • 同时发送多个只读请求,观察是否不再阻塞,响应时间回到800ms以内

内容的提问来源于stack exchange,提问作者SteDS

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 17:55:35