从线上宕机引起的问题谈开去 作者: 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里相当成熟的框架了。
Laravel程序 最佳实践1 作者: nbboy 时间: 2020-03-14 分类: PHP 评论 # 层的定义 Laravel程序在初始化后,其实相比PHP的其他框架,分层还算比较明确的,正如它的作者所说,是为艺术家写的框架。刚开始的时候,会把所有的代码写在Model里,当代码比较多的时候,往往一个Model代码文件就会膨胀开来。不但Model文件代码非常多,Controller文件代码也非常多,这又会导致复用率非常低。 所以后来在思考一种更好的分层,相信很多从静态语言走过来的读者,比如Java或者C#在开发WEB程序的时候,也是会经典的三层架构MVC。他们更习惯把逻辑代码写在Service层,这样既解决了复用的问题,也解决了把业务逻辑写在Controller层的尴尬。 这时很多人感觉有点疑问,为什么把代码写在Controller层不好?因为根据标准的MVC模型的定义,C这层存在的目的则是确保M和V的同步,一旦M改变,V应该同步更新。也就是说,如果把大段的业务逻辑,放在了控制层,不但影响到了复用度,而且导致各层的职责混乱! 说道MVC,简单复习下MVC的一些概念。其实MVC最早不是产生于WEB程序,而开始是存在于桌面程序中的,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。比如一批统计数据可以分别用柱状图、饼图来表示。也就是按照标准的定义,我们很多人用的都不是MVC,而是变态版本的MVSC,中间加入一个Service层。这里也顺便说下,其实按照标准,M和V的数据变动是双向的,但是目前我们的WEB做不到这点。 再说回Laravel的情况,Laravel的模型层真的是非常舒服,借助于Eloquent ORM,写起查询来真的是一种享受。借用Laravel官方文档说:"Eloquent ORM 提供了一个漂亮、简洁的 ActiveRecord 实现来和数据库交互"。写过的肯定对他的链式写法映象深刻,这种做法是他的优点之一。有了一个足够强大的模型层,事情真的解决了一大半。**按照DDD的想法,模型应该反映领域驱动的模型,引入Repository层来解决模型的加载和存储问题,引入Service层来解决跨模型的事物处理问题。**对于一般的中小型项目,这种分层已经够用了。分层其实本身很简单,难得是大家都要去遵守这种约定和随着项目不停得去对代码和模块层级,类层级进行审视和重构。 # 框架的破坏 这个是一种糟糕的写法,其实第一次我看到这样写的时候,内心真的10万头草泥马在奔腾。在团队里的很多小伙伴,在Controller层写类似这种代码: AuthController.php中 ```php $token=User::createToken($oldUser); ``` User.php中 ```php protected function createToken($user){ ... } ``` Model类中的保护成员竟然在Controller层进行直接调用,说好的保护呢???不过回过头一想,业务程序员是缺乏基础的认识,但是框架怎么能放行这种愚蠢的写法呢!!! 看看框架做了什么?  哇,原来是这么一回事,果然是框架在作怪。不过仔细想想,作者的想法是不是就是这样?我猜作者的想法只是为了给使用者方便,不用再去"NEW"一个对象,但是的确在这里给使用者放行了这种行为。我想作者可能已经想到了这个问题,只是让使用者自己去做规范吧。 # 总结 其实这篇文字真的没写多少,一方面博客刚恢复访问,总得写点什么。另外一方面后期有很多文章正在构思,小弟文笔不怎么样,只求在这里做一个备忘吧,如果能帮到读者,那我很开心了。其实,Laravel总体来说真的是PHP中的好框架了,但是使用中有很多细节需要考虑和决策,甚至当项目大了之后,也有必要做一定的改造。