从线上宕机引起的问题谈开去 作者: nbboy 时间: 2020-06-18 分类: 默认分类,PHP 评论 ### 背景 公司的外网线上项目连续宕机将近2周时间,后来通过查找日志,发现是某度蜘蛛大量爬取造成。因为公司花钱进行了SEO推广,所以也不能直接禁掉蜘蛛,所以一度造成网站打不开的情况。期间增加带宽,扩容cpu,增强搜索引擎集群等方案,都得不到好的效果。 ### 分析 开始优化代码,记录慢日志,在云平台看慢查询,渐渐优化慢查询,但是问题依旧。慢慢通过分析慢日志注意到如下细节:  这里我摘取了两个日志片段,其实类似日志片段还有非常多。可以看到将近相同的时间不同的许多进程都进行了GC,而这里的所谓GC是什么? 在StartSession.php上看到了如下代码: ```php /** * Remove the garbage from the session if necessary. * * @param \Illuminate\Session\SessionInterface $session * @return void */ protected function collectGarbage(SessionInterface $session) { $config = $this->manager->getSessionConfig(); // Here we will see if this request hits the garbage collection lottery by hitting // the odds needed to perform garbage collection on any given request. If we do // hit it, we'll call this handler to let it delete all the expired sessions. if ($this->configHitsLottery($config)) { $session->getHandler()->gc($this->getSessionLifetimeInSeconds()); } } ``` 这里注释的意思是有一定概率进行GC,并不是每次都进行GC,先来看下GC做了什么事情。 因为配置的是文件session,所以我们找到文件Session的实现文件FileSessionHandler.php,摘取GC函数代码如下: ```php /** * {@inheritdoc} */ public function gc($lifetime) { $files = Finder::create() ->in($this->path) ->files() ->ignoreDotFiles(true) ->date('<= now - '.$lifetime.' seconds'); foreach ($files as $file) { $this->files->delete($file->getRealPath()); } } ``` 代码的意思很简单,就是通过搜索文件,获取到过期的session文件然后进行删除,这里的过期时间在session.php的配置里定义: ```php /* |-------------------------------------------------------------------------- | Session Lifetime |-------------------------------------------------------------------------- | | Here you may specify the number of minutes that you wish the session | to be allowed to remain idle before it expires. If you want them | to immediately expire on the browser closing, set that option. | */ 'lifetime' => 120, 'expire_on_close' => true, ``` 可以看到默认是2个小时,下面一个参数的意义是是否要在浏览器关闭的时候立即过期!刚才说到了,有一定概率执行这个GC,那这个概率多大呢? ```php /** * Determine if the configuration odds hit the lottery. * * @param array $config * @return bool */ protected function configHitsLottery(array $config) { return random_int(1, 100) <= 2; } ``` 上面代码是我把默认配置替换后的结果,也就是会有2%的可能会触发一次GC!而一次GC我们要回收两个小时之前的过期session文件。如果在并发量稍微有点大的情况下,要删除的文件其实是非常可观的,删除这个IO操作就是直接导致了cpu负荷直接超载的罪魁祸首。 ### 解决方案 方法很简单,就是换driver,比如集中式的redis session方案。 ### 总结 虽然这次项目的主要负责人不是我,不过通过这次抓取日志,跟踪代码,分析代码学到了很多优化的一手资料。Laravel其实是一个相当灵活的框架,文件session方式只能在测试环境进行应用,而在线上环境必须上集中式session方式,不然流量起来的时候,就会吃苦头。Laravel如不经过优化的情况下,他的性能是不高的,好在他也提供了很多可以优化的扩展接口,算的上是目前PHP里相当成熟的框架了。