PHP 做数据缓存时遇到一个不停写入缓存的问题,该怎么解决?

2021-01-19 17:02:50 +08:00
 frozenway
//获取缓存
        $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 缓存被不停清空又不停写入
        //这就缓存的使用目的了
        //这种情况该怎么解决
2140 次点击
所在节点    PHP
13 条回复
chaodada
2021-01-19 17:06:51 +08:00
加锁啊
Makoto
2021-01-19 17:07:05 +08:00
缓存读的时候无限制,写之前检查 /加锁,每次只允许一个线程写
setsunakute
2021-01-19 17:08:21 +08:00
sagaxu
2021-01-19 17:11:55 +08:00
写临时文件再 rename 过去,rename 是原子操作
lovecy
2021-01-19 17:14:12 +08:00
多进程条件下保持唯一性累不累啊。。
1. 用一个定时进程去刷新缓存文件
2. 用一个常驻进程去处理所有的缓存读取操作(对,就是让你上 Redis )
emeab
2021-01-19 17:14:27 +08:00
缓存雪崩 加锁把.
frozenway
2021-01-19 17:29:38 +08:00
@Makoto @setsunakute @sagaxu @lovecy 我现在用的是 thinkphp5.1 的 cache 的 file 模式去写缓存,看了源代码,也存在这种情况,好无助
frozenway
2021-01-19 17:34:05 +08:00
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;
}
}
```
也没加锁,会不会也有问题?
zzhpeng
2021-01-19 17:51:25 +08:00
一下线程,一下进程,搞懵了,fpm 模式不就是多进程单线程吗?
wangritian
2021-01-19 17:57:32 +08:00
最佳解决方案:redis
zzhpeng
2021-01-19 18:12:42 +08:00
还有,为什么这样设计呢?数组序列化存储,反序列化更新。string 存储不就好了,独立开来。案例来看,你的数据独立性挺强。
sujin190
2021-01-19 18:25:05 +08:00
一个 key 一个文件不好么。。
yavana
2021-03-18 11:35:55 +08:00

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/746366

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX