//获取缓存
$tmp = unserialize(file_get_contents('tmp.txt'));
//得到以下缓存数组
$tmp = [
'a' => 'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq',
'b' => 'wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww',
'c' => 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
'd' => 'rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr',
'e' => 'tttttttttttttttttttttttttttttttttttttttttt',
'f' => 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy',
'g' => 'uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu',
'h' => 'iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii',
...
];
//更新缓存
$tmp['b'] = 'ooooooooooooooooooooooooooooooooooooooooo';
file_put_contents('tmp.txt', serialize($tmp));
//那么问题来了,多个线程在读取这个缓存的时候
//当线程 A 在更新缓存时,调用以下方法
$tmp['a'] = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
file_put_contents('tmp.txt', serialize($tmp));
//而在线程 A 更新缓存的同时,线程 B 在读取 tmp 这个缓存
$tmp = unserialize(file_get_contents('tmp.txt'));
//假设 A 线程执行更新缓存时,文件内容刚写入一部分,那么线程 B 读取出来的
//缓存数据是
$tmp = false;
//那么线程 B 就会查数据库后得到数据后写入缓存
$tmp['b'] = 'ssssssssssssssssssssssssssssssssssssssss';
file_put_contents('tmp.txt', serialize($tmp));
//然后最终的问题来了
//如果设置这个缓存数据有效期 1 小时,
//当几十上百个线程频繁访问这个缓存数据,如果不存在其中的 a 或 b 或其他键的值,就
$tmp['x'] = '......................................'; //x 为 abcdefg..的之中一个键
file_put_contents('tmp.txt', serialize($tmp));
//如果更新其中一个键的值时,刚写入一部分到 tmp.txt,那么其他线程读取
//缓存时得到的值是 false,那么又各自更新写入自己的缓存
//这么一来,只要有一个进程更新写入到 tmp.txt 文件而还没全部写完时,另一线程就
//读取,就会造成清空了所有缓存的情况,最后造成这个 tmp 缓存被不停清空又不停写入
//这就缓存的使用目的了
//这种情况该怎么解决
1
chaodada 2021-01-19 17:06:51 +08:00 1
加锁啊
|
2
Makoto 2021-01-19 17:07:05 +08:00 1
缓存读的时候无限制,写之前检查 /加锁,每次只允许一个线程写
|
3
setsunakute 2021-01-19 17:08:21 +08:00 1
加锁 可以用 flock 函数
https://www.php.net/manual/zh/function.flock.php |
4
sagaxu 2021-01-19 17:11:55 +08:00 via Android 1
写临时文件再 rename 过去,rename 是原子操作
|
5
lovecy 2021-01-19 17:14:12 +08:00
多进程条件下保持唯一性累不累啊。。
1. 用一个定时进程去刷新缓存文件 2. 用一个常驻进程去处理所有的缓存读取操作(对,就是让你上 Redis ) |
6
2kCS5c0b0ITXE5k2 2021-01-19 17:14:27 +08:00 1
缓存雪崩 加锁把.
|
7
frozenway OP |
8
frozenway OP TP5.1 的
``` /** * 读取缓存 * @access public * @param string $name 缓存变量名 * @param mixed $default 默认值 * @return mixed */ public function get($name, $default = false) { $this->readTimes++; $filename = $this->getCacheKey($name); if (!is_file($filename)) { return $default; } $content = file_get_contents($filename); $this->expire = null; if (false !== $content) { $expire = (int) substr($content, 8, 12); if (0 != $expire && time() > filemtime($filename) + $expire) { //缓存过期删除缓存文件 $this->unlink($filename); return $default; } $this->expire = $expire; $content = substr($content, 32); if ($this->options['data_compress'] && function_exists('gzcompress')) { //启用数据压缩 $content = gzuncompress($content); } return $this->unserialize($content); } else { return $default; } } /** * 写入缓存 * @access public * @param string $name 缓存变量名 * @param mixed $value 存储数据 * @param int|\DateTime $expire 有效时间 0 为永久 * @return boolean */ public function set($name, $value, $expire = null) { $this->writeTimes++; if (is_null($expire)) { $expire = $this->options['expire']; } $expire = $this->getExpireTime($expire); $filename = $this->getCacheKey($name, true); if ($this->tag && !is_file($filename)) { $first = true; } $data = $this->serialize($value); if ($this->options['data_compress'] && function_exists('gzcompress')) { //数据压缩 $data = gzcompress($data, 3); } $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data; $result = file_put_contents($filename, $data); if ($result) { isset($first) && $this->setTagItem($filename); clearstatcache(); return true; } else { return false; } } ``` 也没加锁,会不会也有问题? |
9
zzhpeng 2021-01-19 17:51:25 +08:00
一下线程,一下进程,搞懵了,fpm 模式不就是多进程单线程吗?
|
10
wangritian 2021-01-19 17:57:32 +08:00 1
最佳解决方案:redis
|
11
zzhpeng 2021-01-19 18:12:42 +08:00 1
还有,为什么这样设计呢?数组序列化存储,反序列化更新。string 存储不就好了,独立开来。案例来看,你的数据独立性挺强。
|
12
sujin190 2021-01-19 18:25:05 +08:00 1
一个 key 一个文件不好么。。
|
13
yavana 2021-03-18 11:35:55 +08:00
@wangritian +1
|