Symfony3.4 FirewallListener阻塞问题:请求串行列队求助
根据你描述的情况——FirewallListener成为阻塞点,且已排除数据库层面的问题——核心大概率是会话(Session)锁导致的并发请求串行执行,再结合Firewall对会话的依赖,最终引发了阻塞。下面是针对Symfony3.4+php-fpm7.2+nginx环境的具体解决方案:
1. 替换Session存储引擎,消除文件锁瓶颈
php-fpm默认使用文件存储Session,同一用户的多个并发请求会因为文件锁而串行执行:当大型页面请求持有Session锁时,后续的普通页面请求必须等待锁释放才能继续。
在Symfony3.4中修改config.yml,将Session存储切换为Redis或Memcached这类支持并发访问的存储:
# app/config/config.yml framework: session: handler_id: session.handler.redis # 或者使用memcached:session.handler.memcached services: session.handler.redis: class: Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler arguments: - '@snc_redis.session' # 假设你用了SncRedisBundle,也可以直接传Redis实例
如果未使用RedisBundle,也可以直接配置原生Redis连接,确保php已安装redis扩展。
2. 对无需会话的路由设置stateless: true
如果某些普通页面不需要用户身份验证(比如静态内容、公开页面),可以在Firewall配置中标记这些路由为无状态,这样请求不会经过Session相关的Firewall逻辑,也就不会被锁阻塞:
# app/config/security.yml security: firewalls: public_area: pattern: ^/public/ # 匹配你的公开路由前缀 stateless: true security: false
注意:此方案仅适用于不需要用户会话的页面,若普通页面需要身份验证则不适用。
3. 优化FirewallListener的执行逻辑
FirewallListener在请求初期会加载用户身份、验证权限,若这部分逻辑有冗余或耗时操作,可以通过以下方式优化:
- 缓存用户身份信息:利用Symfony的
UserCacheInterface实现用户身份的缓存,避免每次请求都重新查询用户数据(即使你DB查询快,频繁的序列化/反序列化也会消耗时间)。 - 简化Firewall规则:检查
security.yml中的access_control规则,移除不必要的匹配条件,减少Firewall的判断开销。
4. 异步处理大型页面的非核心逻辑
如果大型页面的耗时并非来自DB,而是某些业务逻辑(比如复杂计算、第三方API调用),可以将这些非核心逻辑异步化,让请求快速完成并释放Session锁:
- 在Symfony3.4中可以使用
OldSoundRabbitMqBundle实现消息队列,将耗时任务放入队列,由消费者进程异步处理。 - 大型页面请求仅返回基础内容,非核心数据通过前端异步请求获取(比如页面加载后通过AJAX拉取详情)。
5. 验证php-fpm进程配置(辅助排查)
虽然核心问题大概率在Session,但可以检查php-fpm的www.conf配置,确保有足够的进程处理并发请求:
pm.max_children = 50 # 根据服务器配置调整,确保能处理同时的请求 pm.start_servers = 10 pm.min_spare_servers = 5 pm.max_spare_servers = 15
内容的提问来源于stack exchange,提问作者develth

