本文目录导读:

在 PHP 中实现无缝切换存储,主要目标是让应用在切换存储系统时不改动业务代码,同时保证数据一致性和平滑过渡,以下是几种常见且有效的方案:
抽象层模式(最推荐)
通过定义一个统一的存储接口,让所有存储实现都遵循该接口。
<?php
// 1. 定义统一接口
interface StorageInterface {
public function get(string $key): mixed;
public function set(string $key, mixed $value, int $ttl = 0): bool;
public function delete(string $key): bool;
public function exists(string $key): bool;
}
// 2. MySQL 实现
class MySqlStorage implements StorageInterface {
private PDO $pdo;
public function get(string $key): mixed {
$stmt = $this->pdo->prepare("SELECT value FROM cache WHERE `key` = ?");
$stmt->execute([$key]);
return $stmt->fetchColumn();
}
public function set(string $key, mixed $value, int $ttl = 0): bool {
$stmt = $this->pdo->prepare(
"INSERT INTO cache (`key`, value, expiry) VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE value = ?, expiry = ?"
);
$expiry = $ttl > 0 ? time() + $ttl : 0;
return $stmt->execute([$key, serialize($value), $expiry, serialize($value), $expiry]);
}
public function delete(string $key): bool {
$stmt = $this->pdo->prepare("DELETE FROM cache WHERE `key` = ?");
return $stmt->execute([$key]);
}
public function exists(string $key): bool {
$stmt = $this->pdo->prepare("SELECT COUNT(*) FROM cache WHERE `key` = ?");
$stmt->execute([$key]);
return (bool)$stmt->fetchColumn();
}
}
// 3. Redis 实现
class RedisStorage implements StorageInterface {
private Redis $redis;
public function __construct(Redis $redis) {
$this->redis = $redis;
}
public function get(string $key): mixed {
$value = $this->redis->get($key);
return $value !== false ? unserialize($value) : null;
}
public function set(string $key, mixed $value, int $ttl = 0): bool {
$serialized = serialize($value);
if ($ttl > 0) {
return $this->redis->setex($key, $ttl, $serialized);
}
return $this->redis->set($key, $serialized);
}
public function delete(string $key): bool {
return $this->redis->del($key) > 0;
}
public function exists(string $key): bool {
return $this->redis->exists($key);
}
}
// 4. 工厂类 - 根据配置创建存储实例
class StorageFactory {
public static function create(string $type, array $config): StorageInterface {
return match ($type) {
'mysql' => new MySqlStorage(
new PDO($config['dsn'], $config['user'], $config['password'])
),
'redis' => new RedisStorage(
(new Redis())->connect($config['host'], $config['port'])
),
default => throw new InvalidArgumentException("Unsupported storage type: {$type}")
};
}
}
// 5. 业务代码中只依赖接口
class CacheService {
public function __construct(private StorageInterface $storage) {}
public function getData(string $key): mixed {
return $this->storage->get($key);
}
public function setData(string $key, mixed $value, int $ttl = 0): void {
$this->storage->set($key, $value, $ttl);
}
}
?>
配置文件切换
<?php
// config/storage.php
return [
'default' => env('STORAGE_DRIVER', 'redis'),
'drivers' => [
'mysql' => [
'type' => 'mysql',
'dsn' => env('MYSQL_DSN', 'mysql:host=localhost;dbname=cache'),
'user' => env('MYSQL_USER', 'root'),
'password' => env('MYSQL_PASS', ''),
],
'redis' => [
'type' => 'redis',
'host' => env('REDIS_HOST', '127.0.0.1'),
'port' => env('REDIS_PORT', 6379),
],
],
];
// bootstrap.php
$config = require 'config/storage.php';
$driver = $config['drivers'][$config['default']];
$storage = StorageFactory::create($driver['type'], $driver);
// 业务代码
$cacheService = new CacheService($storage);
?>
平滑迁移(双写方案)
适用于需要实时切换但又要保证数据不丢失的场景:
<?php
class DualWriteStorage implements StorageInterface {
private StorageInterface $primary;
private StorageInterface $secondary;
private string $mode; // 'write_to_primary', 'write_to_secondary', 'read_from_secondary'
public function __construct(
StorageInterface $primary,
StorageInterface $secondary,
string $mode = 'write_to_primary'
) {
$this->primary = $primary;
$this->secondary = $secondary;
$this->mode = $mode;
}
public function get(string $key): mixed {
// 读取时可以先从 primary 读,如果失败再从 secondary 读
$value = $this->primary->get($key);
if ($value === null) {
$value = $this->secondary->get($key);
}
return $value;
}
public function set(string $key, mixed $value, int $ttl = 0): bool {
// 写入时双写,确保数据一致性
$primarySet = $this->primary->set($key, $value, $ttl);
$secondarySet = $this->secondary->set($key, $value, $ttl);
return $primarySet && $secondarySet;
}
public function delete(string $key): bool {
$primaryDelete = $this->primary->delete($key);
$secondaryDelete = $this->secondary->delete($key);
return $primaryDelete && $secondaryDelete;
}
public function exists(string $key): bool {
return $this->primary->exists($key) || $this->secondary->exists($key);
}
}
?>
使用容器管理(依赖注入)
<?php
// 使用简单的服务容器
class Container {
private array $instances = [];
private array $aliases = [];
public function set(string $alias, callable $factory): void {
$this->aliases[$alias] = $factory;
}
public function get(string $alias): mixed {
if (!isset($this->instances[$alias])) {
$this->instances[$alias] = $this->aliases[$alias]($this);
}
return $this->instances[$alias];
}
}
// 配置容器
$container = new Container();
$container->set('mysql.storage', function($container) use ($config) {
return StorageFactory::create('mysql', $config['drivers']['mysql']);
});
$container->set('redis.storage', function($container) use ($config) {
return StorageFactory::create('redis', $config['drivers']['redis']);
});
// 根据环境变量选择实现
$storageDriver = getenv('STORAGE_DRIVER') ?: 'mysql';
$container->set('cache.storage', function($container) use ($storageDriver) {
return $container->get($storageDriver . '.storage');
});
// 业务代码中获取存储实例
$storage = $container->get('cache.storage');
$cacheService = new CacheService($storage);
?>
最佳实践建议
<?php
// 1. 始终通过接口操作
interface StorageInterface {
// 统一接口
}
// 2. 配置集中管理
class StorageConfig {
public static function getConfig(): array {
return [
'driver' => getenv('STORAGE_DRIVER') ?: 'redis',
'options' => [
'enable_dual_write' => getenv('ENABLE_DUAL_WRITE') ?: false,
'failover_timeout' => 5,
'retry_count' => 3,
]
];
}
}
// 3. 添加监控和日志
class MonitoredStorage implements StorageInterface {
public function get(string $key): mixed {
$start = microtime(true);
$result = $this->storage->get($key);
$this->log("GET {$key}", microtime(true) - $start);
return $result;
}
private function log(string $operation, float $duration): void {
// 记录到日志系统
error_log("{$operation} took {$duration}ms");
}
}
// 4. 失败降级机制
class FallbackStorage implements StorageInterface {
public function get(string $key): mixed {
try {
return $this->primary->get($key);
} catch (Exception $e) {
// 主存储失败,降级到备存储
$this->switchToFallback();
return $this->secondary->get($key);
}
}
}
?>
关键要点
- 接口抽象:所有存储系统实现统一的接口
- 配置驱动:通过配置随时切换,无需修改业务代码
- 平滑过渡:使用双写方案确保数据一致性
- 监控告警:监控存储性能,便于随时发现和解决问题
- 失败降级:主存储失败时自动切换到备存储
- 缓存策略:合理设置 TTL,避免切换时数据丢失
这种设计能让你的应用在执行存储切换时对业务代码完全透明,只需修改配置即可完成切换。