为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

PHP
蛊毒传说 2019-03-18 18:03:56
问题描述 死循环或者非常大的循环程序运行一段时间后发现占用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); } }
查看完整描述

3 回答

?
慕容3067478

TA贡献1773条经验 获得超3个赞

应该发现题主的问题出现在: 队列里面的记录需要延迟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);
  }
}
查看完整回答
反对 回复 2019-03-18
?
30秒到达战场

TA贡献1828条经验 获得超6个赞

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

查看完整回答
反对 回复 2019-03-18
?
UYOU

TA贡献1878条经验 获得超4个赞

如果资源释放的够快,死循环也没有关系。

查看完整回答
反对 回复 2019-03-18
  • 3 回答
  • 0 关注
  • 665 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信