PHP项目Laravel读写分离怎样实现

wen PHP项目 3

本文目录导读:

PHP项目Laravel读写分离怎样实现

  1. 配置文件方式(最简单)
  2. 使用 DB Facade 手动指定
  3. 使用 Eloquent 模型方式
  4. 高级配置:动态切换
  5. 中间件方式控制
  6. 事务处理注意事项
  7. 负载均衡策略
  8. 监控和日志
  9. 最佳实践建议:

在 Laravel 中实现读写分离有多种方式,我来详细介绍几种常用方法:

配置文件方式(最简单)

第一步:配置数据库连接

.env 文件中配置:

# 主库(写)
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=main_db
DB_USERNAME=root
DB_PASSWORD=password
# 从库(读)- 可配置多个
DB_READ_HOST1=127.0.0.1
DB_READ_HOST2=192.168.1.100
DB_READ_DATABASE=replica_db

第二步:修改 database.php 配置

config/database.php 中配置读写分离:

'mysql' => [
    'driver' => 'mysql',
    // 主库配置(写)
    'read' => [
        // 从库配置(读)- 可配置多个
        [
            'host' => env('DB_READ_HOST1', '127.0.0.1'),
            'database' => env('DB_READ_DATABASE', 'main_db'),
            'username' => env('DB_USERNAME', 'root'),
            'password' => env('DB_PASSWORD', ''),
        ],
        [
            'host' => env('DB_READ_HOST2', '127.0.0.1'),
            'database' => env('DB_READ_DATABASE', 'main_db'),
            'username' => env('DB_USERNAME', 'root'),
            'password' => env('DB_PASSWORD', ''),
        ],
    ],
    // 写库配置
    'write' => [
        [
            'host' => env('DB_HOST', '127.0.0.1'),
            'database' => env('DB_DATABASE', 'main_db'),
            'username' => env('DB_USERNAME', 'root'),
            'password' => env('DB_PASSWORD', ''),
        ],
    ],
    'sticky'    => true, // 是否启用粘性连接
    'driver'    => 'mysql',
    'port'      => env('DB_PORT', 3306),
    'charset'   => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',
    'prefix'    => '',
],

使用 DB Facade 手动指定

use Illuminate\Support\Facades\DB;
// 使用默认连接(会根据配置自动选择读写)
$users = DB::table('users')->get(); // 自动使用读库
// 强制使用主库(写)
$users = DB::connection('mysql')->table('users')->get();
// 手动指定使用读库
$users = DB::connection('mysql::read')->table('users')->get();

使用 Eloquent 模型方式

// User.php 模型
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
    // 默认使用配置好的读写分离连接
    protected $connection = 'mysql';
    // 或者为模型指定特定的读写连接
    protected $readConnection = 'mysql';
    protected $writeConnection = 'mysql';
    // 自定义查询方法,强制使用主库
    public function scopeFromMaster($query)
    {
        $query->useWritePdo();
        return $query;
    }
}
// 使用方式
$users = User::fromMaster()->find(1); // 强制使用主库
$users = User::find(1); // 自动使用读库

高级配置:动态切换

// 创建自定义连接类
namespace App\Support;
use Illuminate\Database\Connection;
use PDO;
class ReadWriteConnection extends Connection
{
    public function select($query, $bindings = [], $useReadPdo = true)
    {
        // 判断是否为写操作
        if ($this->isWriteQuery($query)) {
            $useReadPdo = false;
        }
        return parent::select($query, $bindings, $useReadPdo);
    }
    protected function isWriteQuery($query)
    {
        $query = trim(preg_replace('/\s+/', ' ', strtolower($query)));
        return in_array(substr($query, 0, 6), ['insert', 'update', 'delete', 'create', 'alter', 'drop']);
    }
}

中间件方式控制

// 创建读写分离中间件
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\DB;
class ReadWriteMiddleware
{
    public function handle($request, Closure $next)
    {
        // 判断请求类型
        if ($request->isMethod('POST') || $request->isMethod('PUT') || $request->isMethod('DELETE')) {
            // 写操作强制使用主库
            DB::connection('mysql')->statement('USE WRITE');
        }
        return $next($request);
    }
}

事务处理注意事项

use Illuminate\Support\Facades\DB;
// 事务操作必须使用主库
DB::transaction(function () {
    // 事务内的所有操作都在主库执行
    $user = User::find(1);
    $user->name = 'New Name';
    $user->save();
    // 即使读操作也会在主库执行
    $otherUser = User::find(2);
});
// 或者手动指定使用主库
DB::beginTransaction();
try {
    $user = User::useWritePdo()->find(1); // 强制使用主库
    $user->save();
    DB::commit();
} catch (\Exception $e) {
    DB::rollBack();
}

负载均衡策略

// 在 config/database.php 中可以配置负载均衡策略
'options' => [
    'fetch' => PDO::FETCH_OBJ,
    'emulate_prepares' => false,
    'driver' => 'mysql',
    // 自定义负载均衡算法
],

监控和日志

// 添加事件监听器
namespace App\Providers;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;
class DatabaseServiceProvider extends ServiceProvider
{
    public function boot()
    {
        // 监听每次查询
        DB::listen(function ($query) {
            if (app()->environment('local')) {
                \Log::info($query->sql, $query->bindings);
            }
        });
        // 可以统计读写分离的使用情况
        DB::connection('mysql')->enableQueryLog();
    }
}

最佳实践建议:

  1. 粘性连接(sticky):建议开启,避免同一请求内读写不一致
  2. 事务处理:事务操作必须在主库执行
  3. 查询优化:缓存频繁读取但不常变化的数据
  4. 监控告警:监控主从延迟,设置阈值告警
  5. 故障转移:配置从库故障时自动使用主库
  6. 测试环境:确保开发环境有完整的测试用例
// .env 配置示例
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=main_db
DB_USERNAME=root
DB_PASSWORD=password
DB_STICKY=true
DB_READ_HOST1=192.168.1.101
DB_READ_HOST2=192.168.1.102
DB_READ_DATABASE=replica_db
DB_READ_USERNAME=reader
DB_READ_PASSWORD=readpassword

通过以上配置,Laravel 会自动将所有读操作路由到从库,写操作到主库,从而实现读写分离。

抱歉,评论功能暂时关闭!