本文目录导读:

我来详细介绍如何在PHP项目中实现灰度数据库(也称为蓝绿部署或金丝雀发布)的多种方案。
基础概念
灰度数据库是指让部分用户使用新数据库,部分用户使用旧数据库,逐步验证新数据库的稳定性和性能。
实现方案
基于配置文件的简单实现
<?php
class DatabaseManager {
private static $instance = null;
private $grayUsers = [];
private $config;
private function __construct() {
$this->config = require 'database_config.php';
// 加载灰度用户列表
$this->grayUsers = $this->loadGrayUsers();
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* 获取数据库连接
*/
public function getConnection($userId = null) {
$isGray = $this->isGrayUser($userId);
if ($isGray) {
return $this->connect($this->config['gray']);
}
return $this->connect($this->config['stable']);
}
/**
* 判断用户是否在灰度名单中
*/
private function isGrayUser($userId) {
if ($userId === null) {
return false;
}
return in_array($userId, $this->grayUsers);
}
/**
* 加载灰度用户列表
*/
private function loadGrayUsers() {
$users = [];
$file = __DIR__ . '/gray_users.txt';
if (file_exists($file)) {
$users = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
}
return $users;
}
/**
* 数据库连接
*/
private function connect($dbConfig) {
try {
$dsn = "mysql:host={$dbConfig['host']};dbname={$dbConfig['database']};charset=utf8mb4";
$pdo = new PDO($dsn, $dbConfig['username'], $dbConfig['password'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
return $pdo;
} catch (PDOException $e) {
throw new Exception("Database connection failed: " . $e->getMessage());
}
}
}
// 使用示例
$userId = 10086; // 当前用户ID
$db = DatabaseManager::getInstance();
$pdo = $db->getConnection($userId);
基于用户ID取模的百分比灰度
<?php
class GrayDatabase {
private $config;
private $grayPercentage = 10; // 10%的用户进入灰度
public function __construct() {
$this->config = [
'stable' => [
'host' => 'stable-db.example.com',
'database' => 'main_db',
'username' => 'user',
'password' => 'pass'
],
'gray' => [
'host' => 'gray-db.example.com',
'database' => 'new_db',
'username' => 'user',
'password' => 'pass'
]
];
}
/**
* 根据用户ID判断是否使用灰度数据库
*/
public function getConnection($userId) {
$hash = crc32((string)$userId);
$mod = abs($hash) % 100;
if ($mod < $this->grayPercentage) {
// 属于灰度范围
return $this->connect($this->config['gray']);
}
return $this->connect($this->config['stable']);
}
private function connect($config) {
// 连接数据库
}
}
基于Redis的动态灰度控制
<?php
class RedisGrayDatabase {
private $redis;
private $pdo;
public function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
}
/**
* 动态检查用户是否在灰度中
*/
public function getConnection($userId) {
$grayKey = "gray:users";
$globalGrayKey = "gray:percentage";
// 检查特定用户是否在灰度列表
if ($this->redis->sIsMember($grayKey, $userId)) {
return $this->getGrayConnection();
}
// 检查全局灰度比例
$percentage = $this->redis->get($globalGrayKey) ?: 0;
if ($percentage > 0) {
$hash = crc32((string)$userId);
if (abs($hash) % 100 < $percentage) {
return $this->getGrayConnection();
}
}
return $this->getStableConnection();
}
/**
* 添加用户到灰度列表
*/
public function addGrayUser($userId) {
$this->redis->sAdd("gray:users", $userId);
}
/**
* 设置灰度比例
*/
public function setGrayPercentage($percentage) {
$this->redis->set("gray:percentage", min(100, max(0, $percentage)));
}
private function getGrayConnection() {
// 返回灰度数据库连接
}
private function getStableConnection() {
// 返回稳定数据库连接
}
}
使用中间件模式
<?php
interface DatabaseMiddleware {
public function handle($userId, callable $next);
}
class GrayDatabaseMiddleware implements DatabaseMiddleware {
private $grayService;
public function __construct(GrayService $grayService) {
$this->grayService = $grayService;
}
public function handle($userId, callable $next) {
if ($this->grayService->shouldUseGray($userId)) {
// 使用灰度数据库
$connection = $this->getGrayConnection();
} else {
// 使用稳定数据库
$connection = $this->getStableConnection();
}
return $next($connection);
}
private function getGrayConnection() {
static $grayConnection = null;
if ($grayConnection === null) {
$grayConnection = new PDO(
'mysql:host=gray-db;dbname=gray_db',
'user', 'pass'
);
}
return $grayConnection;
}
private function getStableConnection() {
static $stableConnection = null;
if ($stableConnection === null) {
$stableConnection = new PDO(
'mysql:host=stable-db;dbname=main_db',
'user', 'pass'
);
}
return $stableConnection;
}
}
// 灰度规则服务
class GrayService {
private $rules = [];
public function shouldUseGray($userId) {
foreach ($this->rules as $rule) {
if ($rule->evaluate($userId)) {
return true;
}
}
return false;
}
public function addRule($rule) {
$this->rules[] = $rule;
}
}
// 使用规则链
class UserIdRule {
private $userIds;
private $percentage;
public function __construct(array $userIds = [], $percentage = 0) {
$this->userIds = $userIds;
$this->percentage = $percentage;
}
public function evaluate($userId) {
// 特定用户
if (in_array($userId, $this->userIds)) {
return true;
}
// 按比例
if ($this->percentage > 0) {
return abs(crc32((string)$userId)) % 100 < $this->percentage;
}
return false;
}
}
// 使用示例
$grayService = new GrayService();
$grayService->addRule(new UserIdRule([10086, 10087], 10));
$middleware = new GrayDatabaseMiddleware($grayService);
$result = $middleware->handle($currentUserId, function($connection) {
// 执行业务逻辑
$stmt = $connection->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$currentUserId]);
return $stmt->fetchAll();
});
数据同步方案
<?php
class DatabaseSync {
private $sourceDb;
private $targetDb;
/**
* 双向同步(从稳定库到灰度库)
*/
public function syncToGray($table, $lastSyncTime) {
$stmt = $this->sourceDb->prepare(
"SELECT * FROM {$table} WHERE updated_at > ?"
);
$stmt->execute([$lastSyncTime]);
while ($row = $stmt->fetch()) {
$this->upsertToTarget($this->targetDb, $table, $row);
}
}
/**
* 写入到目标数据库
*/
private function upsertToTarget($db, $table, $data) {
$columns = implode(', ', array_keys($data));
$placeholders = ':' . implode(', :', array_keys($data));
$sql = "INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})
ON DUPLICATE KEY UPDATE ";
$updates = [];
foreach ($data as $key => $value) {
$updates[] = "{$key} = :{$key}";
}
$sql .= implode(', ', $updates);
$stmt = $db->prepare($sql);
return $stmt->execute($data);
}
}
监控与回滚
<?php
class GrayMonitor {
private $redis;
public function __construct() {
$this->redis = new Redis();
}
/**
* 记录查询性能
*/
public function logQuery($userId, $isGray, $queryTime, $success) {
$key = "gray:monitor:" . date('YmdH');
$this->redis->hIncrBy($key, "gray:queries:{$isGray}", 1);
$this->redis->hIncrBy($key, "gray:errors:{$isGray}", $success ? 0 : 1);
$this->redis->hIncrByFloat($key, "gray:total_time:{$isGray}", $queryTime);
}
/**
* 自动回滚检测
*/
public function checkAndRollback($threshold = 0.05) {
$errorRate = $this->getErrorRate();
if ($errorRate > $threshold) {
$this->rollback();
return ['rolled_back' => true, 'error_rate' => $errorRate];
}
return ['rolled_back' => false, 'error_rate' => $errorRate];
}
private function getErrorRate() {
// 计算错误率
}
private function rollback() {
// 执行回滚操作
}
}
最佳实践建议
- 逐步放量:从1%的用户开始,逐步增加到5%、10%、50%、100%
- 自动回滚:设置错误率阈值,超过自动回滚
- 数据一致性:确保灰度库和稳定库的数据同步
- 监控告警:实时监控数据库性能指标
- 用户白名单:支持特定用户(如内部测试人员)直接使用灰度库
选择哪种方案取决于你的具体需求、团队规模和系统复杂度,生产环境建议使用基于Redis的动态方案,便于实时调整灰度策略。