问题描述
死循环或者非常大的循环程序运行一段时间后发现占用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 回答
![?](http://img1.sycdn.imooc.com/533e564d0001308602000200-100-100.jpg)
慕容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);
}
}
- 3 回答
- 0 关注
- 665 浏览
添加回答
举报
0/150
提交
取消