Laravel PostgreSQL主从连接sticky选项不生效问题
Laravel PostgreSQL主从读写分离sticky配置失效修复与验证
现有配置参考
已搭建PostgreSQL主从架构,在config/database.php中配置pgsql连接如下:
'pgsql' => [ 'driver' => 'pgsql', 'read' => [ 'host' => [ env('DB_HOST_READ_1'), env('DB_HOST_READ_2') ] ], 'write' => [ 'host' => env('DB_HOST_WRITE') ], 'sticky' => true, 'port' => env('DB_PORT', '5432'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'prefix' => '', 'prefix_indexes' => true, 'schema' => 'public', 'sslmode' => env('DB_SSLMODE', 'prefer'), 'options' => array( PDO::ATTR_EMULATE_PREPARES => true ), 'binary_parameters' => 'yes', ],
配置中开启sticky => true原本用于规避主从复制延迟导致的写入后立即读不到数据的问题,但实际故障表现为:表单POST请求写入数据后,立即重定向到新数据的详情路由时返回404,手动刷新页面才能正常加载。
问题1:修复方案
首先明确sticky失效的核心原因:Laravel的sticky特性仅在单次请求生命周期内生效。POST请求处理完写入、返回重定向响应后,当前请求的上下文就会被销毁,sticky绑定的写连接状态不会保留。重定向触发的GET请求是全新的独立请求,默认会轮询读节点,此时如果主从同步还没完成,读节点查不到刚写入的数据就会报404,这不是配置错误,是sticky本身的设计作用域就不跨请求。
可根据业务场景选以下修复方案:
- 单场景强制走主库
对写入后立即跳转读取的这类场景,在查询时调用onWriteConnection()方法强制走写节点,不需要修改全局配置,侵入性最低:// POST写入逻辑 $record = Order::create($request->all()); return redirect()->route('order.detail', ['id' => $record->id]); // 详情页查询逻辑 强制走主库查询刚写入的数据 $record = Order::onWriteConnection()->findOrFail($id); - 开启PG同步复制
不想改业务代码的话,可以调整PostgreSQL主库配置,开启同步复制,让主库等待至少一个从节点同步完成后再返回事务提交成功的响应,从根源上降低复制延迟的影响。修改主库postgresql.conf:
以上配置表示主库提交事务时,至少等待列表中1个从节点写入WAL日志才返回成功,事务返回后至少有一个从节点已经有最新数据。注意这个方案会增加写请求的响应耗时,适合对一致性要求高的业务。synchronous_commit = on synchronous_standby_names = 'ANY 1 (read1, read2)' - 会话级写后读窗口
自定义全局中间件,当用户会话中触发了写操作,就在session里打一个1~2秒有效期的标记,标记有效期内该用户的所有查询都强制走写节点,窗口过期后自动切回读节点。这种方式可以覆盖所有跨请求的写后读场景,注意控制窗口时长,避免给主库造成额外读压力。
问题2:sticky功能验证方法
sticky只在单次请求内生效,不要用跨重定向的场景测试,按以下步骤验证即可:
- 临时写一个测试路由,在同一个请求内先执行写入,再执行查询,不要加跳转逻辑:
Route::get('/test-sticky', function () { // 写入操作 固定走写节点 $data = TestModel::create(['title' => 'test_'.time()]); // 写入后马上查询 $queryRes = TestModel::where('id', $data->id)->first(); // 打印当前查询实际使用的数据库host dd(DB::connection('pgsql')->getConfig('host')); }); - 切换sticky配置状态分别测试:
sticky => true时,访问测试路由打印的应该是写节点地址,说明写入后的同请求查询自动路由到了写节点,sticky工作正常sticky => false时,访问测试路由打印的应该是读节点地址,说明查询默认走了读节点
- 补充验证:写一个只做查询不做任何写入的路由,无论sticky开启还是关闭,查询都应该走读节点,符合预期就说明配置逻辑没有问题。
注意:如果开启sticky后同请求内写入后的查询依然走读节点,先检查Laravel版本,6.x之前的版本对多读节点的数组格式配置存在兼容bug,升级到8.x及以上版本即可修复。
内容的提问来源于stack exchange,提问作者Mikaele
相关产品推荐
相关产品推荐

