本文目录导读:

在PHP项目中进行数据库分片扩容时,平滑增加分片节点是一个重要挑战,以下是几种主流方案:
一致性哈希方案(推荐)
实现原理
class ConsistentHash {
private $nodes = [];
private $positions = [];
private $virtualNodeCount = 64; // 每台物理服务器的虚拟节点数
public function addNode($node) {
for ($i = 0; $i < $this->virtualNodeCount; $i++) {
$position = crc32($node . '_' . $i);
$this->positions[$position] = $node;
}
ksort($this->positions);
$this->nodes[$node] = true;
}
public function getNode($key) {
if (empty($this->positions)) return null;
$hash = crc32($key);
$positions = $this->positions;
foreach ($positions as $position => $node) {
if ($hash <= $position) {
return $node;
}
}
// 如果超出范围,返回第一个节点
return reset($this->positions);
}
}
优势
- 最小数据迁移:只迁移需要重新分配的数据
- 平滑扩容:无需停机,逐步增加节点
双写方案(避免停机)
实现步骤
class ShardManager {
private $oldShards = [];
private $newShards = [];
private $migrationMode = 'write_both'; // read_old/write_both/read_new
public function writeData($key, $data) {
switch ($this->migrationMode) {
case 'write_both':
// 同时写入新旧分片
$this->writeToShard($this->oldShards, $key, $data);
$this->writeToShard($this->newShards, $key, $data);
break;
case 'read_new':
// 只写新分片
$this->writeToShard($this->newShards, $key, $data);
break;
default:
$this->writeToShard($this->oldShards, $key, $data);
}
}
public function readData($key) {
switch ($this->migrationMode) {
case 'read_old':
return $this->readFromShard($this->oldShards, $key);
case 'write_both':
// 先读新分片,没有则读旧分片
$data = $this->readFromShard($this->newShards, $key);
if ($data === null) {
$data = $this->readFromShard($this->oldShards, $key);
}
return $data;
case 'read_new':
return $this->readFromShard($this->newShards, $key);
}
}
}
数据迁移策略
渐进式迁移
class DataMigrator {
private $sourceShard;
private $targetShard;
private $batchSize = 1000;
public function migrateData($dateRange) {
$startTime = microtime(true);
$migrated = 0;
// 按主键范围分批迁移
while ($items = $this->getNextBatch($batchSize)) {
$this->migrateBatch($items);
$migrated += count($items);
// 控制迁移速度,避免影响线上服务
if (microtime(true) - $startTime > 5) {
sleep(1); // 短暂暂停
$startTime = microtime(true);
}
}
}
private function migrateBatch($items) {
$this->beginTransaction($this->sourceShard, $this->targetShard);
foreach ($items as $item) {
// 先写入目标分片
$this->writeToTarget($item);
// 标记源数据为已迁移
$this->markMigrated($item['id']);
}
$this->commit();
}
}
完整扩容流程
准备期
// 1. 添加新分片服务器
$shardManager->addNewShard('shard3', $config);
// 2. 启动双写
$shardManager->setMigrationMode('write_both');
// 3. 修改路由规则
$router->addNewShard('shard3', '2024-01-01');
数据迁移
// 1. 分批迁移数据
$migrator = new DataMigrator();
$migrator->startMigration();
// 2. 监控迁移状态
while (!$migrator->isComplete()) {
$progress = $migrator->getProgress();
echo "Migration progress: {$progress}%\n";
sleep(60); // 每分钟检查一次
}
切换期
// 1. 验证数据一致性
$validator = new DataValidator();
$validator->compareData($oldShard, $newShard);
// 2. 切换到只读新分片
$shardManager->setMigrationMode('read_new');
// 3. 等待确认无误后
$shardManager->setMigrationMode('normal'); // 正常模式
$shardManager->removeOldShardConfig();
最佳实践建议
分片键设计
// 使用复合分片键
$shardKey = $userId . '_' . ($date->format('Ymd'));
$shardId = crc32($shardKey) % $totalShards;
监控与告警
class ShardMonitor {
public function monitor() {
$metrics = [
'read_latency' => $this->measureReadLatency(),
'write_latency' => $this->measureWriteLatency(),
'migration_progress' => $this->getMigrationProgress(),
'error_rate' => $this->calculateErrorRate()
];
if ($metrics['error_rate'] > 0.01) { // 1%的错误率
$this->alert('High error rate detected');
$this->rollback();
}
}
}
回滚机制
class RollbackHandler {
public function rollback() {
// 1. 停止所有写操作
$this->pauseWrites();
// 2. 切换回旧分片
$this->setActiveShard('old');
// 3. 清理新分片上的数据
$this->cleanNewShard();
// 4. 恢复写操作
$this->resumeWrites();
}
}
注意事项
- 数据一致性:使用事务或2PC保证数据一致性
- 性能影响:双写期间会有额外开销(约20-30%)
- 监控报警:实时监控延迟、错误率等指标
- 渐进扩容:一次增加太多节点可能影响性能
- 测试验证:先在测试环境模拟完整扩容流程
通过以上方案,可以实现PHP项目分片数据库的平滑扩容,最小化对线上服务的影响。