线上定时脚本执行慢,分析过程

问题描述:
有一个脚本是用来确认收货的。
像淘宝一样,我们买的商品,快递送完货以后,可以手动点击已收货,或者是过几天自动改状态为已收货。
就做这个功能的脚本执行时间很长,是一开始挺快的,越来越慢。

解决过程:
开始的时候感觉性能有问题,把一次性取出所有数据改成一次取出100条
下手太快,感觉没找到问题原因。

用什么工具解决这个问题呢?
常用的xhprof,我本地是装了这个扩展的,
问题是本地环境没有这么多数据,我把线上数据导入本地环境(线上数据大,费时费力),执行了下,没发现这个问题。

我想到线上环境和本地环境不一样,还是要到线上环境调试。
问题是线上没有权限修改代码,再说了修改代码容易出错,用户访问时候会报错。

线上环境没有xhprof,用什么工具好呢?
经常听人说使用用strace调查一些问题,我也试试看

strace -p pid
看输出看不懂

我把问题和我们的架构师说了一遍,给他看strace的输出,问他看懂吗?有什么办法解决这个问题。
他很果断,说是看代码,不用其他工具。

我从头看代码,调用的来来回回,晕晕乎乎。

后来想起来把线上代码拷到我的账号的家目录,可以调试,接下来顺利多了
我线上发现了有一个方法,执行一段时间需要2s甚至更长时间
strace打印的日志中看到了:

sendto(6, "*4\r\n$7\r\nHINCRBY\r\n$23\r\norder_stat"..., 77, MSG_DONTWAIT, NULL, 0) = 77
sendto(6, "*1\r\n$4\r\nEXEC\r\n", 14, MSG_DONTWAIT, NULL, 0) = 14
sendto(6, "*2\r\n$6\r\nEXISTS\r\n$23\r\norder_stati"..., 46, MSG_DONTWAIT, NULL, 0) = 46

关键字HINCRBY EXEC EXISTS,像是redis操作

主要是程序一直没有卡住过,到了后来一直就是这些操作
我通过下面命令查看了
lsof -d 6
结果是:

COMMAND   PID  USER   FD   TYPE     DEVICE SIZE/OFF       NODE NAME
php     25106          6u  IPv4 3612956560      0t0        TCP *.*.*.*:25571->*.*.*.*:6379 (ESTABLISHED)

6379端口是我熟悉的redis端口

问了同事,什么地方有redis加一操作,他告诉了我代码的位置,我一看知道了就是这儿的问题

为什么一开始没有那么多的自增加一操作,运行到后来却有了呢?

我看到代码中有用到事件模型,猜测是这儿的原因,果不其然:
循环中调用了下面代码:

            Helper::statistics($this, 'orderFinish', [
                'user_name' => !empty($order_info['member_name']) ? $order_info['member_name'] : '',
                'user_id' => !empty($order_info['member_id']) ? $order_info['member_id'] : 0
            ]);

该方法定义是这样:

        $statistic = new OrderStatistics();
        $obj->on($action, [$statistic, $action], $data);
        $obj->trigger($action); 

可以看到监听的事件越来越多,所以就是我们看到的现象,执行到后来越来越慢

问题总结:
事件模型使用不当,循环中,给一个对象不断添加事件。而我们想要的效果是循环一次,事件要定义一次,触发一次,销毁一次。

解决办法:
我的解决方案,代码改成这样:

        $statistic = new OrderStatistics();
        $event = new Event();
        $event->data = $data;
        $statistic->{$action}($event);

我感觉在这个地方应用事件模型,有点牵强。

另外一个解决方案,

        $statistic = new OrderStatistics();
        $obj->on($action, [$statistic, $action], $data);
        $obj->trigger($action);
        $obj->off($action, [$statistic, $action]);//加了这一行,调用一次,立马把事件取消掉

大家喜欢哪种解决方案?

参考资料:
https://www.jianshu.com/p/8a247cae629a

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!