在ThinkPHP8中实现缓存降级,该方案还在探讨中:
1. 基础配置和依赖
安装熔断器组件(推荐)
bash
composer require hyperf/circuit-breaker
# 或者使用更轻量的
composer require workerman/workerman
缓存配置文件 config/cache.php
php
return [
'default' => env('cache.driver', 'redis'),
'stores' => [
'redis' => [
'type' => 'redis',
'host' => env('redis.host', '127.0.0.1'),
'port' => env('redis.port', 6379),
'password' => env('redis.password', ''),
'select' => env('redis.database', 0),
'timeout' => 3, // 3秒超时
],
'file' => [
'type' => 'file',
'path' => app()->getRuntimePath() . 'cache',
'expire' => 3600,
],
],
// 降级配置
'degrade' => [
'enabled' => true,
'switch_key' => 'cache_degrade_switch', // 动态开关的缓存key
'timeout' => 2, // 超时时间(秒)
'fail_count' => 3, // 连续失败次数触发降级
],
];
2. 创建降级服务类
降级开关服务 app/service/DegradeService.php
php
<?php
declare(strict_types=1);
namespace app\service;
use think\facade\Cache;
use think\facade\Log;
class DegradeService
{
// 降级状态常量
const STATUS_NORMAL = 0; // 正常
const STATUS_DEGRADE = 1; // 降级中
const STATUS_RECOVER = 2; // 恢复中
/**
* 检查是否应该降级
*/
public static function shouldDegrade(string $service): bool
{
// 1. 检查手动开关
if (self::isManualDegrade($service)) {
return true;
}
// 2. 检查自动降级状态
if (self::isAutoDegrade($service)) {
return true;
}
return false;
}
/**
* 手动降级开关
*/
public static function setManualDegrade(string $service, bool $status): bool
{
$key = "degrade:manual:{$service}";
return Cache::store('file')->set($key, $status ? 1 : 0, 3600);
}
/**
* 检查手动降级状态
*/
private static function isManualDegrade(string $service): bool
{
$key = "degrade:manual:{$service}";
return (bool) Cache::store('file')->get($key, 0);
}
/**
* 记录失败次数并判断是否触发自动降级
*/
public static function recordFailure(string $service): bool
{
$key = "degrade:fail_count:{$service}";
$failCount = Cache::store('file')->increment($key);
Cache::store('file')->expire($key, 60); // 1分钟窗口
$threshold = config('cache.degrade.fail_count', 3);
if ($failCount >= $threshold) {
// 触发自动降级
$degradeKey = "degrade:auto:{$service}";
Cache::store('file')->set($degradeKey, time(), 300); // 降级5分钟
Cache::store('file')->delete($key); // 清空计数
Log::warning("服务自动降级触发: {$service}, 失败次数: {$failCount}");
return true;
}
return false;
}
/**
* 检查自动降级状态
*/
private static function isAutoDegrade(string $service): bool
{
$key = "degrade:auto:{$service}";
$degradeTime = Cache::store('file')->get($key);
if (!$degradeTime) {
return false;
}
// 检查是否还在降级时间窗口内
if (time() - $degradeTime < 300) { // 5分钟降级窗口
return true;
}
// 降级窗口结束,清除状态
Cache::store('file')->delete($key);
return false;
}
/**
* 记录成功调用(用于恢复)
*/
public static function recordSuccess(string $service): void
{
$key = "degrade:success:{$service}";
$successCount = Cache::store('file')->increment($key);
Cache::store('file')->expire($key, 60);
// 连续成功次数达到阈值,可以尝试恢复
if ($successCount >= 5) {
$degradeKey = "degrade:auto:{$service}";
Cache::store('file')->delete($degradeKey);
Cache::store('file')->delete($key);
Log::info("服务自动恢复: {$service}");
}
}
}
3. 缓存降级装饰器
降级缓存装饰器 app/common/CacheWithDegrade.php
php
<?php
declare(strict_types=1);
namespace app\common;
use think\facade\Cache;
use think\facade\Log;
use app\service\DegradeService;
class CacheWithDegrade
{
private $primaryStore = 'redis'; // 主缓存
private $fallbackStore = 'file'; // 降级缓存
private $serviceName = 'cache'; // 服务名称
public function __construct(string $serviceName = 'cache')
{
$this->serviceName = $serviceName;
}
/**
* 带降级的获取缓存
*/
public function get(string $key, $default = null)
{
// 检查降级状态
if (DegradeService::shouldDegrade($this->serviceName)) {
Log::info("缓存降级中,使用降级存储: {$key}");
return $this->getFromFallback($key, $default);
}
try {
// 设置超时
$value = $this->withTimeout(function() use ($key) {
return Cache::store($this->primaryStore)->get($key);
});
DegradeService::recordSuccess($this->serviceName);
return $value ?? $default;
} catch (\Throwable $e) {
Log::error("主缓存获取失败: {$key}, 错误: " . $e->getMessage());
DegradeService::recordFailure($this->serviceName);
// 降级到备用缓存
return $this->getFromFallback($key, $default);
}
}
/**
* 带降级的设置缓存
*/
public function set(string $key, $value, $ttl = null): bool
{
// 即使降级中,也尝试写入主缓存(用于恢复)
try {
$this->withTimeout(function() use ($key, $value, $ttl) {
Cache::store($this->primaryStore)->set($key, $value, $ttl);
});
DegradeService::recordSuccess($this->serviceName);
} catch (\Throwable $e) {
Log::error("主缓存设置失败: {$key}, 错误: " . $e->getMessage());
DegradeService::recordFailure($this->serviceName);
}
// 同时写入降级缓存(保证数据不丢)
try {
Cache::store($this->fallbackStore)->set($key, $value, $ttl);
return true;
} catch (\Throwable $e) {
Log::error("降级缓存设置失败: {$key}");
return false;
}
}
/**
* 从降级存储获取
*/
private function getFromFallback(string $key, $default = null)
{
try {
return Cache::store($this->fallbackStore)->get($key) ?? $default;
} catch (\Throwable $e) {
Log::error("降级缓存也失败,返回默认值: {$key}");
return $default;
}
}
/**
* 带超时的操作
*/
private function withTimeout(callable $callback, ?int $timeout = null)
{
$timeout = $timeout ?? config('cache.degrade.timeout', 2);
if (function_exists('pcntl_async_signals')) {
// Linux环境使用pcntl超时
pcntl_async_signals(true);
pcntl_signal(SIGALRM, function() {
throw new \RuntimeException('Cache operation timeout');
});
pcntl_alarm($timeout);
try {
$result = $callback();
pcntl_alarm(0);
return $result;
} finally {
pcntl_alarm(0);
}
} else {
// Windows环境简单处理
return $callback();
}
}
/**
* 删除缓存
*/
public function delete(string $key): bool
{
$success = true;
try {
Cache::store($this->primaryStore)->delete($key);
} catch (\Throwable $e) {
$success = false;
}
try {
Cache::store($this->fallbackStore)->delete($key);
} catch (\Throwable $e) {
$success = false;
}
return $success;
}
}
4. 业务层使用示例
用户服务示例 app/service/UserService.php
php
<?php
declare(strict_types=1);
namespace app\service;
use app\common\CacheWithDegrade;
use think\facade\Db;
class UserService
{
private $cache;
public function __construct()
{
$this->cache = new CacheWithDegrade('user_service');
}
/**
* 获取用户信息(带缓存降级)- 修正版本
*/
public function getUserInfo(int $userId): array
{
$cacheKey = "user:info:{$userId}";
// 1. 先检查是否处于降级状态
if (DegradeService::shouldDegrade('user_service')) {
Log::info("用户服务降级中,直接返回默认数据: {$userId}");
return $this->getDefaultUserInfo($userId);
}
// 2. 尝试从缓存获取
$userInfo = $this->cache->get($cacheKey);
if ($userInfo && !empty($userInfo['id'])) {
return $userInfo;
}
// 3. 缓存不存在,查询数据库
try {
$userInfo = Db::name('user')
->where('id', $userId)
->findOrEmpty();
if ($userInfo) {
// 异步写入缓存
$this->setCacheAsync($cacheKey, $userInfo, 3600);
return $userInfo;
} else {
// 用户确实不存在,返回空或特定错误
return $this->getUserNotFoundInfo($userId);
}
} catch (\Throwable $e) {
// 数据库查询异常,触发降级
Log::error("数据库查询失败,触发降级: {$userId}, 错误: " . $e->getMessage());
DegradeService::recordFailure('user_service');
return $this->getDefaultUserInfo($userId);
}
}
/**
* 更完善的降级检查方法
*/
public function getUserInfoWithMultiLevel(int $userId): array
{
$cacheKey = "user:info:{$userId}";
// 第一级:检查全局降级开关
if (DegradeService::shouldDegrade('user_service')) {
// 第二级:尝试本地内存缓存(如果支持)
$localData = $this->getFromLocalCache($userId);
if ($localData) {
return $localData;
}
// 第三级:返回完全降级数据
return $this->getDefaultUserInfo($userId);
}
// 正常流程...
return $this->getNormalUserInfo($userId, $cacheKey);
}
/**
* 正常流程获取用户信息
*/
private function getNormalUserInfo(int $userId, string $cacheKey): array
{
// 带超时的缓存获取
$userInfo = $this->getCacheWithTimeout($cacheKey, 2);
if ($userInfo) {
return $userInfo;
}
// 数据库查询(带超时控制)
return $this->getFromDatabaseWithTimeout($userId, $cacheKey);
}
/**
* 带超时的缓存获取
*/
private function getCacheWithTimeout(string $key, int $timeout): ?array
{
try {
// 使用协程或信号实现超时控制
if (function_exists('pcntl_alarm')) {
pcntl_alarm($timeout);
$result = $this->cache->get($key);
pcntl_alarm(0);
return $result;
}
// 简化版本
return $this->cache->get($key);
} catch (\Throwable $e) {
Log::warning("缓存获取超时: {$key}");
return null;
}
}
/**
* 带超时的数据库查询
*/
private function getFromDatabaseWithTimeout(int $userId, string $cacheKey): array
{
try {
// 实际生产环境可以使用数据库超时配置
$userInfo = Db::name('user')
->where('id', $userId)
->findOrEmpty();
if ($userInfo) {
$this->setCacheAsync($cacheKey, $userInfo, 3600);
return $userInfo;
}
return $this->getUserNotFoundInfo($userId);
} catch (\Throwable $e) {
Log::error("数据库查询异常: {$userId}");
DegradeService::recordFailure('user_service');
// 根据异常类型决定是否降级
if ($this->shouldDegradeOnDbError($e)) {
return $this->getDefaultUserInfo($userId);
}
throw $e; // 重新抛出非降级异常
}
}
/**
* 判断数据库异常是否应该触发降级
*/
private function shouldDegradeOnDbError(\Throwable $e): bool
{
$errorMessage = $e->getMessage();
$degradeErrors = [
'timeout', 'TIMEOUT', '连接超时', 'Connection timed out',
'too many connections', 'max_connections',
'server has gone away'
];
foreach ($degradeErrors as $error) {
if (stripos($errorMessage, $error) !== false) {
return true;
}
}
return false;
}
/**
* 从本地缓存获取(如APCu、共享内存)
*/
private function getFromLocalCache(int $userId): ?array
{
if (function_exists('apcu_fetch')) {
$key = "local_user_{$userId}";
$data = apcu_fetch($key, $success);
if ($success) {
return $data;
}
}
return null;
}
/**
* 用户不存在的处理
*/
private function getUserNotFoundInfo(int $userId): array
{
return [
'id' => $userId,
'exists' => false,
'message' => '用户不存在',
'code' => 404
];
}
/**
* 降级时的默认用户信息
*/
private function getDefaultUserInfo(int $userId): array
{
return [
'id' => $userId,
'name' => '游客',
'avatar' => '/static/images/default_avatar.png',
'level' => 0,
'is_degraded' => true,
'degrade_time' => date('Y-m-d H:i:s'),
'message' => '系统繁忙,正在降级运行'
];
}
/**
* 异步设置缓存
*/
private function setCacheAsync(string $key, $value, int $ttl): void
{
// 使用协程或队列异步处理
go(function() use ($key, $value, $ttl) {
try {
$this->cache->set($key, $value, $ttl);
} catch (\Throwable $e) {
// 异步写入失败可忽略或记录日志
Log::warning("异步缓存写入失败: {$key}");
}
});
}
}
5. 控制器中使用
控制器示例 app/controller/User.php
php
// 在控制器中
public function userInfo(int $id)
{
try {
$userService = new UserService();
// 使用修正后的方法
$userInfo = $userService->getUserInfo($id);
return json([
'code' => 0,
'data' => $userInfo,
'degraded' => $userInfo['is_degraded'] ?? false,
]);
} catch (\Throwable $e) {
// 只有非降级异常才会到达这里
return json([
'code' => 500,
'msg' => '系统错误',
'degraded' => false
]);
}
}
6. 监控和告警
在应用入口文件添加监控
php
// public/index.php 或 app/event.php
app()->event->listen('HttpEnd', function() {
// 记录缓存命中率、降级次数等指标
$metrics = [
'cache_hits' => Cache::get('stats:hits', 0),
'cache_misses' => Cache::get('stats:misses', 0),
'degrade_count' => Cache::get('stats:degrade', 0),
];
// 发送到监控系统
// $this->sendToMonitor($metrics);
});
核心要点总结
- 多级降级:Redis → 文件缓存 → 默认值
- 自动切换:基于失败次数自动触发降级
- 手动控制:支持管理界面手动开关
- 渐进恢复:成功次数达标后自动恢复
- 超时控制:防止长时间阻塞
- 异步写入:不阻塞主流程
这样的实现既保证了系统的高可用性,又具备了生产环境所需的各项稳定性保障措施。