//获取缓存 $tmp = unserialize(file_get_contents('tmp.txt')); //得到以下缓存数组 $tmp = [ 'a' => 'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq', 'b' => 'wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww', '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 ![]() 加锁啊 |
2 Makoto 2021-01-19 17:07:05 +08:00 ![]() 缓存读的时候无限制,写之前检查 /加锁,每次只允许一个线程写 |
![]() | 3 setsunakute 2021-01-19 17:08:21 +08:00 ![]() 加锁 可以用 flock 函数 https://www.php.net/manual/zh/function.flock.php |
![]() | 4 sagaxu 2021-01-19 17:11:55 +08:00 via Android ![]() 写临时文件再 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 ![]() 缓存雪崩 加锁把. |
![]() | 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 ![]() 最佳解决方案:redis |
![]() | 11 zzhpeng 2021-01-19 18:12:42 +08:00 ![]() 还有,为什么这样设计呢?数组序列化存储,反序列化更新。string 存储不就好了,独立开来。案例来看,你的数据独立性挺强。 |
![]() | 12 sujin190 2021-01-19 18:25:05 +08:00 ![]() 一个 key 一个文件不好么。。 |
![]() | 13 yavana 2021-03-18 11:35:55 +08:00 @wangritian +1 |