如何在Laravel中配置Redis主从复制并实现读写分离?
Laravel Redis主从复制(读写分离+Sticky)配置方案
一、修正主从配置(解决读操作全走主节点问题)
你当前的配置存在两个核心问题:
options中的cluster设为redis是针对Redis原生分片集群的,不适用于主从复制场景;replicas的结构不符合Predis主从模式的识别要求。
替换成以下配置:
// config/database.php 'redis' => [ 'client' => 'predis', // 主节点配置 'default' => [ 'host' => '127.0.0.1', 'password' => env('REDIS_PASSWORD', null), 'port' => 6379, 'database' => 0, ], // 从节点集群(自动负载均衡) 'replicas' => [ 'cluster' => true, 'connections' => [ [ 'host' => '127.0.0.1', 'password' => env('REDIS_PASSWORD', null), 'port' => 6380, 'database' => 0, ], [ 'host' => '127.0.0.1', 'password' => env('REDIS_PASSWORD', null), 'port' => 6381, 'database' => 0, ], ], ], 'options' => [ 'replication' => true, 'cluster' => 'predis', // 使用Predis的集群模式处理主从路由 ], ],
配置说明:
replication => true开启Predis的主从复制路由功能,自动将SET/HSET/INCR等写命令发往主节点,GET/HGET等读命令发往从节点;replicas下的connections定义从节点列表,Predis会自动在从节点间做轮询负载均衡;cluster => 'predis'指定用Predis内置的集群逻辑,而非Redis原生集群。
二、验证读写分离是否生效
可以通过打印当前连接的端口,或者查看Redis节点日志来验证:
// 写操作(强制走主节点6379) Redis::connection('default')->set('test_key', 'hello'); // 读操作(自动路由到6380/6381) $value = Redis::connection('replicas')->get('test_key'); // 打印当前读连接的端口,确认是否为从节点端口 $connection = Redis::connection('replicas'); echo $connection->getConnection()->getParameters()->port;
如果不想每次手动指定连接,也可以自定义一个Facade,自动路由读写命令:
// app/Facades/RedisDB.php namespace App\Facades; use Illuminate\Support\Facades\Facade; class RedisDB extends Facade { protected static function getFacadeAccessor() { return 'redis.db'; } } // app/Providers/AppServiceProvider.php public function register() { $this->app->bind('redis.db', function ($app) { return new class { public function __call($method, $arguments) { // 判断是否为写命令 $writeCommands = ['set', 'hset', 'incr', 'decr', 'del', 'flushdb']; if (in_array(strtolower($method), $writeCommands)) { return app('redis')->connection('default')->$method(...$arguments); } return app('redis')->connection('replicas')->$method(...$arguments); } }; }); }
使用时直接调用RedisDB::get('key')或RedisDB::set('key', 'value')即可自动路由。
三、实现Sticky功能(请求内写后立即读)
Laravel Redis原生没有sticky选项,但可以通过请求生命周期内的标记实现:
- 创建中间件
RedisStickyMiddleware:
// app/Http/Middleware/RedisStickyMiddleware.php namespace App\Http\Middleware; use Closure; use Illuminate\Support\Facades\Redis; class RedisStickyMiddleware { public function handle($request, Closure $next) { // 初始化标记:当前请求是否执行过写操作 $request->attributes->set('redis_has_write', false); // 监听所有Redis写命令,触发时标记为已写 $originalSet = Redis::getFacadeRoot()->set; Redis::macro('set', function ($key, $value, $options = null) use ($originalSet, $request) { $request->attributes->set('redis_has_write', true); return $originalSet($key, $value, $options); }); // 同理,覆盖其他写命令(hset/incr/del等) $originalHset = Redis::getFacadeRoot()->hset; Redis::macro('hset', function ($key, $field, $value) use ($originalHset, $request) { $request->attributes->set('redis_has_write', true); return $originalHset($key, $field, $value); }); // 重写读命令,根据标记决定走主还是从 $originalGet = Redis::getFacadeRoot()->get; Redis::macro('get', function ($key) use ($originalGet, $request) { if ($request->attributes->get('redis_has_write')) { return Redis::connection('default')->get($key); } return $originalGet($key); }); $response = $next($request); // 请求结束后清理宏定义 Redis::forgetMacro('set'); Redis::forgetMacro('hset'); Redis::forgetMacro('get'); return $response; } }
- 在
app/Http/Kernel.php中注册中间件:
protected $middlewareGroups = [ 'web' => [ // ...其他中间件 \App\Http\Middleware\RedisStickyMiddleware::class, ], ];
这样,在同一个请求内执行写操作后,后续所有读操作都会强制走主节点,避免主从延迟导致的读不到最新数据问题。
四、常见问题排查
- 确保从节点已正确配置主从复制(执行
SLAVEOF 127.0.0.1 6379并确认同步正常); - 清除Laravel配置缓存:执行
php artisan config:clear; - 检查Predis版本,建议使用1.x版本(Laravel 8及以下兼容更好)。
内容的提问来源于stack exchange,提问作者LF-DevJourney
相关产品推荐
相关产品推荐

