假设我们有一个监控任务,每5秒钟将监控结果上报。
$buffer = []; // 另一个协程在填充buffer go(function() use ($buffer) { while (true) { sleep(5); sendBatch($buffer); } })
那么问题来了,如果我们现在需要关闭服务,如何保证关闭前最后一批数据不丢失?
正常情况下,类unix系统,以及Kubernetes都会先给发送SIGTERM信号提示进程退出。我们的程序如果是个好好先生,就应该听从操作系统的劝告,立刻执行收尾工作并退出。如果程序拒不退出,那么操作系统可以发送强制关闭的信号。SIGINT(control-c)、SIGKILL信号这两个信号都属于“强制关闭”。
在Kuberentes下,总是先发送SIGTERM,如果在一定时间内(默认30s)进程还没有结束,就会SIGKILL。
我们要做的事,就是在收到SIGTERM时,及时把最后一批数据上报并退出。
$exited = false; $buffer = []; $server->on('workerExit', function() use ($exited, $buffer) { $exited = true; }); go(function() use ($exited, $buffer) { while (true) { sleep(5); sendBatch($buffer); if ($exited) { break; } } })
对上述程序这样修改,可以保证总是上报完最后一批时结束协程,然后退出。
然而细心的同学会发现这样还有两个潜在的问题。
- 程序总是要完成最后一次睡眠后才退出,不是很及时。
- 如果睡眠时间大于操作系统或用户容忍的时间,仍然有可能被杀掉进程丢失最后一次上报。
事实上,我们需要的是可以中断的睡眠。当收到SIGTERM时,立刻醒来上报最后一次并退出。
在传统同步编程或Callback编程中,这个不太好实现,但是在CSP编程中,堪称一个经典Pattern。
$exited = new Channel(); $buffer = []; $server->on('workerExit', function() use ($exited, $buffer) { go(function() use ($exited, $buffer){ $exited->close(); }); }); go(function() use ($exited) { while (true) { $exited->pop(5); sendBatch($buffer); if ($exited->errCode === SWOOLE_CHANNEL_CLOSED) { break; } } })
我们完全没有改变编程逻辑,只是巧妙的利用的channel,就实现了可唤醒的睡眠。
这里用了关闭channel来代表睡眠被唤醒,使得多个协程可以复用这个channel来监听结束信号。
在Hyperf 1.1.25后,广泛采用了可以被唤醒的睡眠,使得Hyperf所有组件可以正确响应SIGTERM。
这样在Kubernetes上滚动更新Hyperf更柔滑了。
扫码二维码 获取免费视频学习资料
- 本文固定链接: http://phpxs.com/post/7268/
- 转载请注明:转载必须在正文中标注并保留原文链接
- 扫码: 扫上方二维码获取免费视频资料
查 看2022高级编程视频教程免费获取