猿问

如何优化死循环程序占用资源过大的问题

问题描述

死循环或者非常大的循环程序运行一段时间后发现占用CPU和内存巨大,两个程序占用8核16G阿里ECS机器中的60%内存和CPU。

程序作用

一个程序是从阿里redis中取zset数据然后消费删除。
一个程序是订阅redis中的一个消息,也是简单的消费。

主要代码

        $redisQueue = new WechatFollowServices();
        //3600 * 26小时
        $time1 = 93600;
        //3600 * 24小时
        $time2 = 86400;
        if (config('app.push_debug') == true) {
            $time1 = 70;
            $time2 = 60;
        }

        while (true) {
            $now = time();
            $queues = $redisQueue->getQueues(0, 100, true);

            foreach ($queues as $member => $time) {

                $queue = \GuzzleHttp\json_decode($member, true);

                /**
                 * 计算时间差,>= 26小时的数据删除
                 */
                $timeDiff = $now - $time;
                if ($timeDiff >= $time1) {
                    $redisQueue->deleteQueue($member);
                }

                /**
                 * >= 24小时的推送
                 */
                if ($timeDiff >= $time2) {

                    $content = WechatPushServices::getMilPlanTemplate($queue['nickname']);
                    $res = app('GrpcServices')->customByOpenID([$queue['openId']], \GuzzleHttp\json_encode($content), 'news');
                    if ($res) {
                        $msg = '推送成功';
                    } else {
                        $msg = '推送失败';
                    }
                    info($this->description . ': ' . $queue['nickname'] . $msg);

                    //不考虑推送结果,直接删除
                    $redisQueue->deleteQueue($member);

                    unset($content, $res, $msg);
                }

                unset($member, $time, $queue, $timeDiff);
                continue;
            }

            unset($now, $queues);
            //测试模式下推送一个就退出
            if (config('app.push_debug') == true) {
                break;
            }
        }

初步分析

初步考虑是不是程序变量的问题导致占用资源特别多。所以加上了 unset()。那么又有问题了,是unset掉呢还是将变量附空值呢?

对占用资源较大的程序您有什么优化的实践和建议?

补充修改

第二版代码

根据一楼楼主的意见修改如下:

        $redisQueue = new WechatFollowServices();

        // 休眠时间
        $sleepTime = 0;
        // 延迟推送的时间长度 3600 * 24小时
        $delayTime = 86400;
        // 未处理的过期时间长度 3600 * 26小时
        $expireTime = 93600;
        if (config('app.push_debug') == true) {
            $delayTime = 60;
            $expireTime = 70;
        }

        while (true) {
            $now = time();
            $queues = $redisQueue->getQueues(0, 100, true);
            if (empty($queues)) {
                // 休眠两分钟
                $sleepTime = 120;
            }

            /**
             * @var int $time 关注时的具体时间
             */
            foreach ($queues as $member => $time) {

                /**
                 * 已关注的时间长度
                 */
                $followedTime = $now - $time;

                /**
                 * 小于 24小时
                 */
                if ($followedTime < $delayTime) {

                    $sleepTime = $delayTime - $followedTime;
                    unset($member, $time, $followedTime);
                    break;
                }

                /**
                 * >= 26小时的数据删除
                 */
                if ($followedTime >= $expireTime) {
                    $redisQueue->deleteQueue($member);
                    unset($member, $time, $followedTime);
                    continue;
                }

                /**
                 * >= 24小时且 < 26小时的推送
                 */
                $queue = \GuzzleHttp\json_decode($member, true);
                $content = WechatPushServices::getMilPlanTemplate($queue['nickname']);
                $res = app('GrpcServices')->customByOpenID([$queue['openId']], \GuzzleHttp\json_encode($content), 'news');
                if ($res) {
                    $msg = '推送成功';
                } else {
                    $msg = '推送失败';
                }
                info($this->description . ': ' . $queue['nickname'] . $msg);

                //不考虑推送结果,直接删除
                $redisQueue->deleteQueue($member);

                unset($member, $time, $followedTime, $queue, $content, $res, $msg);

                //测试模式下推送一个就退出
                if (config('app.push_debug') == true) {
                    break 2;
                }
            }

            unset($now, $queues);

            /**
             * 开始休眠
             */
            if ($sleepTime) {
                sleep($sleepTime);
            }
        }
蛊毒传说
浏览 672回答 3
3回答

慕容3067478

应该发现题主的问题出现在: 队列里面的记录需要延迟24小时, 在这个延迟时间内, 循环会一直重复不停的检查队列内容, 判断每一个用户的时间, 但是这里就是一直重复工作, 重复的检查同一批用户, 知道用户时间达到后才会进行发送并清除队列. 假定队列的用户延迟时间是有序的, 只要取出部分前面的用户, 判断用户的额外等待时间, 然后挂起进程等待对应的时间. 下面是改进了一下处理逻辑, 希望可以有帮助. while (true) { $now = time(); // 这个是默认最长睡眠间隔时间, 10分钟后再检查 $minSleep = 600; // 假定这里的队列时间是有序的, 队列最前面的时间最早, 否则最好这里能够取出所有队列记录 $queues = $redisQueue->getQueues(0, 100, true); foreach ($queues as $member => $time) { $timeDiff = $now - $time; if ($timeDiff >= $time1) { // 假如有达到条件的用户, 我们这次循环后不再挂起进程 $minSleep = 0; // 删除队列 } elseif ($timeDiff >= $time2) { $minSleep = 0; // 这里才需要用到的数据, 就这里才解析吧, 省点CPU $queue = \GuzzleHttp\json_decode($member, true); // 原来做什么工作, 就做什么工作 } else { // 判断当前要达到24小时延迟, 需要等待多久 $timeDiff = $time2 - $timeDiff; if ($timeDiff < $minSleep) { $minSleep = $timeDiff; } } } // 加入一个睡眠, 让进程挂起来, 直到下一个用户的延迟时间或者达到最大睡眠时间 if ($minSleep) { sleep($minSleep); } }

30秒到达战场

使用工具分析一下吧!尝试一下死循环一天停止清理在启动脚本试试呢?一般来说一天的方式问题不会太大

UYOU

如果资源释放的够快,死循环也没有关系。
随时随地看视频慕课网APP
我要回答