- 后端
- 即时通讯
【免费下载链接】laravel-websockets
Websockets for Laravel. Done right.
本文聚焦 laravel-websockets 包中的websockets:restart命令,讲解如何像queue:restart一样,在 Supervisor 托管环境下优雅地重启 WebSocket 服务器。读完本文,你将掌握该命令的用法、10 秒内完成重启的底层原理、优雅关停(软关停)的完整流程,以及搭配 Supervisor 实现零中断运维的实战配置。
适用场景:为什么 WebSocket 服务器需要"优雅重启"
WebSocket 服务器是一个长期驻留的守护进程。官方建议在生产环境使用 Supervisor 这类进程管理器来保证它始终存活(详见 部署文档)。当你修改了配置、更新了代码、或注册了新的自定义路由(参见 自定义 WebSocket 处理器)之后,往往需要重启服务器才能让改动生效。
此时如果直接粗暴地 kill 进程,正在连接中的客户端会立即断线,且可能丢失状态。laravel-websockets 为此提供了与 Laravel 队列系统queue:restart类似的解决方案:向正在运行的服务器广播一个重启信号,让它优雅地关闭现有连接,再交由 Supervisor 重新拉起进程。
快速上手:一条命令触发重启
根据 官方文档 的定义,使用方式极其简单,与php artisan queue:restart的体验完全一致:
php artisan websockets:restart执行后终端会输出如下提示:
Broadcasted the restart signal to the WebSocket server!该命令本身没有参数选项,其全部职责就是"广播重启信号"。自命令下达起,最多 10 秒内,正在运行的 WebSocket 服务器会完成软关停;随后由 Supervisor(配置了autorestart=true)自动把服务重新拉起。
底层原理一:如何"广播"重启信号
websockets:restart的实现非常精简,完整源码位于 src/Console/Commands/RestartServer.php,核心逻辑只有两步:
public function handle() { Cache::forever( 'beyondcode:websockets:restart', $this->currentTime() ); $this->info( 'Broadcasted the restart signal to the WebSocket server!' ); }- 通过 Laravel 的
Cache门面,以Cache::forever将当前时间戳写入固定键名beyondcode:websockets:restart(forever表示该值永不过期,直到下一次命令再次覆盖它); - 在控制台输出成功提示。
也就是说,命令本身并不直接操作服务器进程,它只是向共享的缓存写入一个"信号值"(时间戳)。服务器进程通过周期性地读取这个缓存键,感知到时间戳变化后自行触发关停流程。
一个关键前提:Cache 驱动必须是共享且持久的
从上述机制可以推断出一个重要约束:命令进程与服务器进程必须能够读到同一个缓存值。
- 生产环境应使用
file、redis、memcached或database等持久化且跨进程共享的 Cache 驱动; - 绝不能使用
array驱动,否则命令写入的信号只存在于当前 CLI 进程的内存中,服务器进程根本读不到,重启信号会"静默丢失"。
这一点虽然未在文档中显式强调,但从 RestartServer.php 与 StartServer.php 均通过Illuminate\Support\Facades\Cache读写同一键名的代码结构可以直接确认。
底层原理二:服务器端如何感知信号并触发软关停
服务器端由websockets:serve命令(StartServer.php)驱动。该命令在启动时会调用configureRestartTimer()注册一个每 10 秒触发一次的周期性定时器:
public function configureRestartTimer() { $this->lastRestart = $this->getLastRestart(); $this->loop->addPeriodicTimer(10, function () { if ($this->getLastRestart() !== $this->lastRestart) { $this->triggerSoftShutdown(); } }); } protected function getLastRestart() { return Cache::get( 'beyondcode:websockets:restart', 0 ); }工作流程如下:
- 服务器启动时,将缓存中的重启时间戳读入内存变量
$this->lastRestart作为基线; - 事件循环每 10 秒执行一次回调,调用
getLastRestart()重新读取缓存中的时间戳; - 若两次读取的值不一致,说明有人执行过
websockets:restart,随即调用triggerSoftShutdown()进入优雅关停。
这正是文档中"最多 10 秒内服务器会被重启"的由来——10 秒是轮询周期上限,而不是延迟固定为 10 秒。实际等待时间取决于轮询定时器恰好落在哪个相位上,最短可能仅需数毫秒。
优雅关停(Soft Shutdown)完整流程
triggerSoftShutdown()是整套机制的落点,其实现(见 StartServer.php)展示了一个教科书式的优雅关停流程:
protected function triggerSoftShutdown() { $channelManager = $this->laravel->make(ChannelManager::class); // 1. 关闭新连接入口 $channelManager->declineNewConnections(); // 2. 收集本地全部连接,逐个触发 onClose 并关闭 $channelManager->getLocalConnections() ->then(function ($connections) { return all(collect($connections)->map(function ($connection) { return app('websockets.handler') ->onClose($connection) ->then(function () use ($connection) { $connection->close(); }); })->toArray()); }) ->then(function () { // 3. 全部连接处理完毕后,停止事件循环 $this->loop->stop(); }); }三个阶段环环相扣:
- 拒绝新连接:调用
ChannelManager::declineNewConnections(),将实例内部标记acceptsNewConnections置为false(对应 LocalChannelManager.php),此后新的 WebSocket 握手请求将不再被接受; - 逐个关闭现有连接:通过
getLocalConnections()获取当前实例上的所有连接,逐个交给websockets.handler的onClose()完成反订阅清理(连接会自动从所有频道中注销),随后调用$connection->close()真正断开。这些操作以 React Promise 链式串联,全部完成前不会进入下一步; - 停止事件循环:所有连接优雅关闭后,
$this->loop->stop()让进程自然退出。
进程退出后,Supervisor 检测到子进程结束(配置了autorestart=true),会自动重新启动websockets:serve,新进程从缓存读取到的是更新后的重启时间戳基线,从而继续正常运行。
与 SIGTERM/SIGINT 软关停的异同
值得一提的是,同样的triggerSoftShutdown()也被用于处理系统信号。在 StartServer.php 中,当 PCNTL 扩展可用时,服务器会注册 SIGTERM 与 SIGINT 的监听器,收到信号后打印Closing existing connections...并执行同样的软关停流程:
$this->loop->addSignal(SIGTERM, function () { $this->line('Closing existing connections...'); $this->triggerSoftShutdown(); }); $this->loop->addSignal(SIGINT, function () { $this->line('Closing existing connections...'); $this->triggerSoftShutdown(); });两者的区别在于触发源:
websockets:restart:由外部命令驱动,通过缓存信号触发,适合在部署脚本、CI 流程或运维操作中调用;- SIGTERM/SIGINT:由操作系统驱动,例如手动
Ctrl+C或执行kill时触发。
殊途同归:两者最终都走declineNewConnections()→ 逐个onClose()→loop->stop()这条优雅路径,确保连接不丢失、频道状态被正确清理。相关行为在 StartServerTest.php 中有对应测试(test_pcntl_sigint_signal与test_pcntl_sigterm_signal均会先建立活跃连接再发送信号)。
结合 Supervisor 的完整运维方案
既然"优雅重启"依赖 Supervisor 自动拉起进程,这里给出完整的配套配置。首先安装 Supervisor(Debian/Ubuntu:apt install supervisor,Red Hat/CentOS:yum install supervisor并systemctl enable supervisord),然后在/etc/supervisor/conf.d(Debian/Ubuntu)或/etc/supervisord.d(Red Hat/CentOS)下创建websockets.conf:
[program:websockets] command=/usr/bin/php /home/laravel-echo/laravel-websockets/artisan websockets:serve numprocs=1 autostart=true autorestart=true user=laravel-echo加载配置并启动:
supervisorctl update supervisorctl start websockets supervisorctl status这样一套组合的日常运维节奏是:
- 发布新代码、修改配置或注册新路由后,执行
php artisan websockets:restart; - 服务器在 10 秒内优雅关闭所有连接并退出;
- Supervisor 检测到进程退出,立即自动重启
websockets:serve,服务无缝衔接。
完整的 Supervisor 部署说明(包括minfds打开文件数上限调整)可参考 部署文档;服务启动参数(如--host、--port)详见 启动文档。
多实例 / 多服务器场景下的行为
从机制上看,该重启信号是广播式的:所有运行中的 WebSocket 服务器实例都会周期性地读取同一个缓存键,因此一次websockets:restart会让集群中的每个实例都在各自的事件循环内感知到信号变化,并各自执行软关停。在多节点横向扩展部署时(参见 横向扩展文档),这意味着你只需要在任意一台机器上执行一次命令,即可对整个集群发出重启请求,无需逐个节点操作。
测试与验证
仓库为这套机制提供了自动化测试保障:
- tests/Commands/RestartServerTest.php 中的
test_it_can_broadcast_restart_signal验证了执行websockets:restart后,缓存键beyondcode:websockets:restart的值会更新为当前时间(assertGreaterThanOrEqual断言新值不小于执行前的时间); - tests/Commands/StartServerTest.php 则验证了服务器在 SIGINT / SIGTERM 信号下能够正常完成软关停,间接佐证了
triggerSoftShutdown()流程的可用性。
你也可以在自己的环境中手动验证:先以php artisan websockets:serve启动服务器并保持终端在前台运行,另开一个终端执行php artisan websockets:restart,观察服务器终端——它会在 10 秒内安静地完成关停流程并退出(若由 Supervisor 托管,则会立刻看到进程被重新拉起)。
常见问题与注意事项
- 重启命令没有生效:优先检查 Laravel 的 Cache 驱动配置。
array驱动下信号无法跨进程传递;同时确认服务器进程与命令运行在同一个应用实例/同一个.env配置下,二者读取的缓存后端必须一致。 - 10 秒是从哪里来的:来自 StartServer.php 中
addPeriodicTimer(10, ...)的轮询周期,这是"最迟 10 秒"而非固定 10 秒。 - 为什么是优雅关闭而非强杀:软关停会先拒绝新连接、再逐个清理频道订阅关系、最后才停止事件循环,避免客户端数据与频道成员状态不一致;强杀进程则可能留下脏状态。
- 务必配合进程管理器使用:
websockets:restart只是"发出信号",它不会自己拉起新进程。没有 Supervisor 守护时,进程退出后将不再提供服务,此时需要手动重新执行php artisan websockets:serve。
- 后端
- 即时通讯
【免费下载链接】laravel-websockets
Websockets for Laravel. Done right.
相关推荐
零停机重启革命:Goagain优雅重启原理解析
零停机重启革命:Goagain优雅重启原理解析 引言:服务重启的"痛点"与解决方案 你是否曾因服务重启导致用户请求失败而头疼?在高可用系统架构中,"零停机部署"
后端gorilla/mux优雅关闭:实现零停机部署的平滑重启机制
gorilla/mux优雅关闭:实现零停机部署的平滑重启机制 痛点:为什么需要优雅关闭? 在现代微服务架构中,服务部署和更新是家常便饭。传统的粗暴关闭方式会导致
后端Laravel Octane 命令详解:启动、停止、重载等所有操作指南
Laravel Octane 命令详解:启动、停止、重载等所有操作指南 Laravel Octane 是专为 Laravel 应用程序设计的性能加速工具,通过利
后端开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考