背景:最近业务上面有一个多级缓存的需求,也很简单,内存 -> 文件 -> MySQL。开始的时候,我只做了 文件 -> MySQL 的缓存,但是,后面我又加了一个内存作为缓存,并且,我在测试的时候,发现我忘了在读取到下一级的文件缓存数据之后,更新到上一级的内存缓存。于是我就发现了这很容易造成缓存没更新的问题,所以调研了一下,发现责任链设计模式可以很好的解决这个问题。
我们可以先来看一看不用责任链模式,我的代码是如何写的。首先是只有 文件 -> MySQL 的缓存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public function get(string $configKey): array { $filePath = $configKey . DIRECTORY_SEPARATOR . 'cache.json';
$result = $this->readFromCachedFile($filePath); if (! empty($result)) { $this->logger->debug('read config from cached file', ['path' => $filePath]);
return $result; }
$config = Config::query()->where('id', $configKey)->first();
$this->logger->debug('query config from mysql');
$payload = json_decode($config, true);
$result['gslb'] = $payload['gslb']; $result['sdkconfig'] = $payload['sdkconfig'][$role] ?? [];
$this->writeToCachedFile($filePath, json_encode($result)); $this->logger->debug('write config to cached file', ['filePath' => $filePath]);
return $result; }
|
可以看到,代码可读性还是不错的。先查找文件,然后再查找数据库,然后更新文件。一路下来,没有任何难题。
但是,当我变成了 内存 -> 文件 -> MySQL 缓存之后,问题开始凸显出来了。我的第一版代码是这样的:s
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public function get(string $configKey): array { $result = $this->readFromCachedMemory($configKey); if (! empty($result)) { $this->logger->debug('read config from cached memory', ['path' => $configKey]); return $result; }
$filePath = $configKey . DIRECTORY_SEPARATOR . 'cache.json';
$result = $this->readFromCachedFile($filePath); if (! empty($result)) { $this->logger->debug('read config from cached file', ['path' => $filePath]);
return $result; }
$config = Config::query()->where('id', $configKey)->first();
$this->logger->debug('query config from mysql');
$payload = json_decode($config, true);
$result['gslb'] = $payload['gslb']; $result['sdkconfig'] = $payload['sdkconfig'][$role] ?? [];
$this->writeToCachedMemory($configKey, json_encode($result)); $this->logger->debug('write config to cached memory', ['path' => $configKey]); $this->writeToCachedFile($filePath, json_encode($result)); $this->logger->debug('write config to cached file', ['filePath' => $filePath]);
return $result; }
|
咋眼一看,可能还真看不出啥问题。但是编写完足够的单元测试之后,问题就凸显出来了。我发现,这段代码有问题:
1 2 3 4 5 6 7
| $result = $this->readFromCachedFile($filePath); if (! empty($result)) { $this->logger->debug('read config from cached file', ['path' => $filePath]);
return $result; }
|
这里在文件里面找到了数据之后,我忘记去更新数据到内存里面了。当我发现这个问题之后,我意识到了问题的严重性,这简直就是一个维护成本极高的代码。因为我想到,我仅仅是加了一个内存缓存,就出现了忘记保存缓存数据的问题,那以后要是又加了几个缓存,那代码写起来几乎就是灾难了吧。每一次下一级缓存找到之后,我们都要更新所有的上一级缓存,这代码大概会变成这样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| $result = $this->readFromFirstCache($configKey); if (! empty($result)) { return $result; }
$result = $this->readFromSecondCache($configKey); if (! empty($result)) { $this->writeToFirstCache($configKey, $result); return $result; }
$result = $this->readFromThreeCache($configKey); if (! empty($result)) { $this->writeToSecondCache($configKey); $this->writeToFirstCache($configKey, $result); return $result; }
$result = $this->readFromMySQL($configKey); if (! empty($result)) { $this->writeToThreeCache($configKey); $this->writeToSecondCache($configKey); $this->writeToFirstCache($configKey, $result); return $result; }
|
我是觉得这个代码维护起来极其困难了。
然后,责任链设计模式就可以用上了。其实说白了,责任链设计模式就是一个考察你的递归基本功的设计模式。原理很简单,当上一级执行某种操作失败之后,就找下一级,一直递归的执行这个操作,直到找了数据之后,我们开始回溯,并且更新上一级的数据。
我们可以用这份代码进行表达:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| <?php
abstract class CachedChain { public const MEMORY = 1;
public const FILE = 2;
public const MYSQL = 3;
protected $level;
protected $nextCache;
public $data = '';
abstract protected function read(array $param): string;
public function setNextCache(CachedChain $nextCache) { $this->nextCache = $nextCache; }
public function readContent(int $level, array $param): string { $content = "";
if ($this->level >= $level) { $content = $this->read($param); }
if (!empty($content)) { return $content; }
$content = $this->nextCache->readContent($level, $param); $this->data = $content; return $content; } }
class CachedMemory extends CachedChain { protected $level = self::MEMORY;
protected function read(array $param): string { return $this->data; } }
class CachedFile extends CachedChain { protected $level = self::FILE;
protected function read(array $param): string { return $this->data; } }
class CachedDB extends CachedChain { protected $level = self::MYSQL;
protected function read(array $param): string { return $this->data; } }
$memory = new CachedMemory; $file = new CachedFile; $db = new CachedDB; $db->data = 'hello world';
$memory->setNextCache($file); $file->setNextCache($db);
$data = $memory->readContent(CachedChain::MEMORY, []); var_dump($data); $data = $memory->readContent(CachedChain::MEMORY, []); var_dump($data);
|
这个代码运行结果:
1 2
| string(11) "hello world" string(11) "hello world"
|
感兴趣的小伙伴可以调试一下。然后稍加修改,就可以做成一个通用的组件了。