解决服务器缓存失效后可能导致的雪崩问题(thinkphp为例)

此篇文章为学生时代作品,大佬们看到这里就别往下看了。现在再回来看,感觉当时太水了,这篇文章尤其是没有参与实际工作的孩子,能引起你们的思考就好,但不要认为真实工业场景中真的是通过文件锁解决的。当然许多分布式锁系统,比如google的(忘记名字了),确实是通过一系列的小文件来解决的。redis、Zookeeper等都是很好的方式。


1、什么是雪崩问题?


         为了加快网站访问速度,也为了减少服务器资源消耗和压力,将页面在一段时间内静态化、设置缓存是我们常用的手段,这样在缓存失效前,就不用再去访问数据库。但是当缓存失效的一瞬间,服务器需要访问数据库,重新生成缓存文件。假如一个网站的并发性很高,这一瞬间的访问量可能也很高,而数据库是非常脆弱的,对数据库过高的访问请求数可能会导致数据库直接挂掉,造成服务器瘫痪,这就是缓存失效后可能造成的学崩问题。


2、如何解决雪崩问题?

         先说明解决雪崩问题的思路,我们可以在服务器中设置:当服务器缓存失效后,只允许访问的第一个人直接访问数据库,其他访问者则阻塞在此,等待第一个人访问成功后生成缓存文件,此时其他用户可以继续访问生成的缓存文件。这样数据库只需要访问一次,而其他用户访问的仍然是缓存文件,很好的减少了服务器的压力。

        以thinkphp框架为例,thinkphp中访问请求时,一般先判读访问的页面是否拥有缓存文件,如果有缓存文件并且缓存文件在有效期内,则直接给浏览器返回缓存文件;如果没有则继续执行相应的控制器、方法。该部分代码位于TP框架的

ThinkPHP\Library\Behavior\ReadHtmlCacheBehavior.class.php之中,打开这个文件,我们可以看到以下代码:


 
  
namespace Behavior;
use Think\Storage;
/**
 * 系统行为扩展:静态缓存读取
 */
class ReadHtmlCacheBehavior
{
// 行为扩展的执行入口必须是run
    public function run(&$params)
    {
// 判读是否开启静态缓存
        if (IS_GET && C('HTML_CACHE_ON')) {
            $cacheTime = $this->requireHtmlCache();
            if (false !== $cacheTime && $this->checkHTMLCache(HTML_FILE_NAME, $cacheTime)) { //静态页面是否有效
// 读取静态页面输出
                echo Storage::read(HTML_FILE_NAME, 'html');
                exit();
            }
        }
    }
}

        我们浏览run方法,可以发现,TP框架在处理访问请求时,先判断该访问页面是否开启了缓存,开启缓存后判断缓存是否有效,如果有效,则直接将缓存的静态页面输出,我们可以在这个方法中增加一段代码

 
   
namespace Behavior;
use Think\Storage;
$fp = null;
/**
 * 系统行为扩展:静态缓存读取
 */
class ReadHtmlCacheBehavior
{
// 行为扩展的执行入口必须是run
    public function run(&$params)
    {
// 开启静态缓存
        if (IS_GET && C('HTML_CACHE_ON')) {
            $cacheTime = $this->requireHtmlCache();
// 判断静态页是否存在
            if (false !== $cacheTime && $this->checkHTMLCache(HTML_FILE_NAME, $cacheTime)) { //静态页面有效
// 读取静态页面输出
                echo Storage::read(HTML_FILE_NAME, 'html');
                exit();
            }
// 如果静态页不存在或者已经过期就向后执行 --》 只让一个客户端通过这里生成静态页,其他客户端阻塞在这等待第一个客户端生成静态页
            global $fp;
            $fp = fopen('./read_html_cache.lock', 'r');
            flock($fp, LOCK_EX); // 只有一个客户端可以通过其他的阻塞在这
            if (false !== $cacheTime && $this->checkHTMLCache(HTML_FILE_NAME, $cacheTime)) { //静态页面有效
// 读取静态页面输出
                echo Storage::read(HTML_FILE_NAME, 'html');
                exit();
            }
        }
    }
}

        在这段代码中我们新增了一个全局的变量$fp,在判断缓存失效的一瞬间,用户在访问页面时,第一个用户被允许打开文件,之后给这个文件加上了排他锁,之后的用户不允许再访问这个文件,则一直阻塞等待打开这个文件,之后的代码不能再执行。当第一个用户访问成功后关闭对浏览器的请求,此时已经生成了新的缓存文件。而之前被锁的文件由于第一个用户已经访问完成,文件锁失效,将允许打开,用户也可以执行之后的代码,再次判断是否拥有缓存文件,之后的用户将直接访问缓存文件而不需要再次操纵数据库。需要注意的是,这里的文件描述符$fp必须为全局变量,否则文件锁只在第一个用户自己的局部代码中有效,在第一个用户执行完这段局部代码后,文件锁将失效,对其他用户无效,其他用户仍然可以打开文件而不是阻塞状态,此时第一个用户可能仍然在访问数据库,并未生成缓存文件,并没有解决雪崩问题所以切记将$fp声明为全局变量。


猜你喜欢

转载自blog.csdn.net/key_next/article/details/51525023