PHP 多主多从怎么连

wen PHP项目 3

本文目录导读:

PHP 多主多从怎么连

  1. 使用 PDO + 自定义连接管理(基础方案)
  2. 使用 ThinkPHP 框架(内置支持)
  3. 使用 Laravel 框架(内置读写分离)
  4. 使用 Hyberf/ThinkSwoole(协程方案)
  5. 使用重工具库(如 Medoo,支持主从分离)
  6. 推荐方案

在 PHP 中实现多主多从数据库连接,核心在于读写分离负载均衡,以下是几种常用的实现方案:

使用 PDO + 自定义连接管理(基础方案)

<?php
class DatabaseManager {
    private static $instance = null;
    private $writeConnections = [];
    private $readConnections = [];
    // 主库配置(可多个)
    private $writeConfigs = [
        [
            'host' => '192.168.1.10',
            'dbname' => 'test',
            'user' => 'admin',
            'pass' => 'password',
            'port' => 3306
        ],
        [
            'host' => '192.168.1.11',
            'dbname' => 'test',
            'user' => 'admin',
            'pass' => 'password',
            'port' => 3306
        ]
    ];
    // 从库配置(可多个)
    private $readConfigs = [
        [
            'host' => '192.168.2.10',
            'dbname' => 'test',
            'user' => 'readonly',
            'pass' => 'read12138',
            'port' => 3306
        ],
        [
            'host' => '192.168.2.11',
            'dbname' => 'test',
            'user' => 'readonly',
            'pass' => 'read12138',
            'port' => 3306
        ]
    ];
    // 当前连接选择
    private $currentConnection = null;
    private function __construct() {}
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    // 获取写连接(轮询策略)
    private function getWriteConnection() {
        if (empty($this->writeConnections)) {
            foreach ($this->writeConfigs as $config) {
                $this->writeConnections[] = $this->createConnection($config);
            }
        }
        // 轮询选择主库
        static $writeIndex = 0;
        $connection = $this->writeConnections[$writeIndex % count($this->writeConnections)];
        $writeIndex++;
        return $connection;
    }
    // 获取读连接(负载均衡策略)
    private function getReadConnection() {
        if (empty($this->readConnections)) {
            foreach ($this->readConfigs as $config) {
                $this->readConnections[] = $this->createConnection($config);
            }
        }
        // 随机选择从库(也可以使用加权轮询)
        $connection = $this->readConnections[array_rand($this->readConnections)];
        return $connection;
    }
    // 创建PDO连接
    private function createConnection($config) {
        $dsn = "mysql:host={$config['host']};dbname={$config['dbname']};port={$config['port']}";
        return new PDO($dsn, $config['user'], $config['pass'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_TIMEOUT => 5
        ]);
    }
    // 执行写入操作
    public function write($sql, $params = []) {
        $conn = $this->getWriteConnection();
        $stmt = $conn->prepare($sql);
        $stmt->execute($params);
        return $stmt->rowCount();
    }
    // 执行读取操作
    public function read($sql, $params = []) {
        $conn = $this->getReadConnection();
        $stmt = $conn->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll();
    }
    // 获取最后插入ID(从写连接)
    public function getInsertId() {
        $conn = $this->getWriteConnection();
        return $conn->lastInsertId();
    }
    // 事务处理(必须在主库)
    public function transaction($callback) {
        $conn = $this->getWriteConnection();
        try {
            $conn->beginTransaction();
            $result = $callback($conn);
            $conn->commit();
            return $result;
        } catch (Exception $e) {
            $conn->rollBack();
            throw $e;
        }
    }
}
// 使用示例
$db = DatabaseManager::getInstance();
// 读操作
$users = $db->read("SELECT * FROM users WHERE status = ?", [1]);
// 写操作
$db->write("INSERT INTO users (name, email) VALUES (?, ?)", ['张三', 'zhangsan@example.com']);
$id = $db->getInsertId();
// 事务操作
$db->transaction(function ($conn) {
    $conn->exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
    $conn->exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
});
?>

使用 ThinkPHP 框架(内置支持)

// 数据库配置文件 `config/database.php`
return [
    // 默认使用主库
    'default' => env('database.driver', 'mysql'),
    // 主库配置(多个)
    'connections' => [
        'mysql' => [
            'type' => 'mysql',
            'host' => '192.168.1.10,192.168.1.11',  // 多个主库
            'port' => '3306',
            'database' => 'test',
            'username' => 'admin',
            'password' => 'password',
            'charset' => 'utf8mb4',
            'prefix' => '',
            'debug' => true,
            'read' => [
                // 从库配置(多个)
                [
                    'host' => '192.168.2.10',
                    'username' => 'readonly',
                    'password' => 'read12138'
                ],
                [
                    'host' => '192.168.2.11',
                    'username' => 'readonly',
                    'password' => 'read12138'
                ]
            ],
            'write' => [
                // 写库配置(可选,默认使用主库)
                [
                    'host' => '192.168.1.10',
                    'username' => 'admin',
                    'password' => 'password'
                ]
            ],
        ],
    ],
];
// 使用示例
$users = Db::name('users')->select();  // 自动使用从库
Db::name('users')->insert(['name' => '张三']);  // 自动使用主库

使用 Laravel 框架(内置读写分离)

// 数据库配置文件 `config/database.php`
'mysql' => [
    'driver' => 'mysql',
    'read' => [
        // 多个从库
        'host' => [
            '192.168.2.10',
            '192.168.2.11',
            '192.168.2.12'
        ],
    ],
    'write' => [
        // 多个主库
        'host' => [
            '192.168.1.10',
            '192.168.1.11'
        ],
    ],
    'sticky' => true,  // 记住会话中已写入的数据库,防止主从同步延迟
    'database' => env('DB_DATABASE', 'test'),
    'username' => env('DB_USERNAME', 'admin'),
    'password' => env('DB_PASSWORD', 'password'),
    'charset' => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',
    'prefix' => '',
    'strict' => true,
    'engine' => null,
],
// 使用示例
$users = DB::table('users')->get();  // 自动使用从库
DB::table('users')->insert(['name' => '张三']);  // 自动使用主库

使用 Hyberf/ThinkSwoole(协程方案)

<?php
declare(strict_types=1);
namespace App\Pool;
use Hyperf\DbConnection\Db;
use Hyperf\Utils\Coroutine;
class DatabaseService {
    // 主库连接池
    private static $masterPools = [
        'db1' => ['host' => '192.168.1.10'],
        'db2' => ['host' => '192.168.1.11']
    ];
    // 从库连接池
    private static $slavePools = [
        'db1' => ['host' => '192.168.2.10'],
        'db2' => ['host' => '192.168.2.11']
    ];
    public static function read(string $sql, array $params = []) {
        // 随机从从库池中选择
        $pool = self::$slavePools[array_rand(self::$slavePools)];
        return Db::connection($pool['host'])->select($sql, $params);
    }
    public static function write(string $sql, array $params = []) {
        // 轮询从主库池中选择
        static $index = 0;
        $pools = array_values(self::$masterPools);
        $pool = $pools[$index % count($pools)];
        $index++;
        $result = Db::connection($pool['host'])->insert($sql, $params);
        return $result;
    }
}

使用重工具库(如 Medoo,支持主从分离)

<?php
use Medoo\Medoo;
$database = new Medoo([
    'type' => 'mysql',
    'master' => [
        ['host' => '192.168.1.10', 'username' => 'admin', 'password' => 'password'],
        ['host' => '192.168.1.11', 'username' => 'admin', 'password' => 'password']
    ],
    'slave' => [
        ['host' => '192.168.2.10', 'username' => 'readonly', 'password' => 'read123'],
        ['host' => '192.168.2.11', 'username' => 'readonly', 'password' => 'read123']
    ],
    'database' => 'test',
    'error' => PDO::ERRMODE_EXCEPTION
]);
// 读操作(自动路由到从库)
$users = $database->select("users", "*");
// 写操作(自动路由到主库)
$database->insert("users", ["name" => "张三"]);
  1. 读写分离:根据 SQL 类型自动路由
  2. 负载均衡:主库轮询写入,从库随机/轮询读取
  3. 故障转移:某个主/从挂掉后可自动切换
  4. 一致性处理:使用 sticky 机制防止主从延迟导致的数据不一致
  5. 监控与统计:记录各节点 SQL 执行时间和状态

推荐方案

  • 简单项目:使用 PDO 自定义连接管理
  • 中型项目:使用 ThinkPHP 或 Laravel 内置支持
  • 分布式大型项目:使用 MyCat/ShardingSphere 做中间件代理
  • 高并发场景:使用 Swoole 协程连接池

选择方案时需要考虑项目的复杂度、团队技术栈和业务需求。

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