最近使用基于 Swoole 开发的 imi 框架开发项目,碰到一个需求,就是想要做项目初始化处理。当初始化处理完成前,不想让 Swoole 处理请求。因为可能有一些值没有加载进来,处理请求极有可能出现问题。
下面给出了思考过程及解决问题的demo代码。
首先分析了一下,Swoole 是多进程模式运行的,分为 Master、Manager、Worker 进程。
Master 进程就是我们启动服务的 cli 命令文件所在进程,在这里面初始化有一个问题,这里所有加载的类、全局变量,其它 Worker 进程里都可以使用,无法热重启生效。
Manager 进程的情况基本和上面差不多。
那么只有在 Worker 进程做处理了,但如果写在 WorkerStart 事件里,每个 Worker 进程都会去执行。
WorkerStart 事件定义:
1 | function onWorkerStart(swoole_server $server , int $worker_id ); |
$worker_id是一个从0-$worker_num之间的数字,表示这个Worker进程的ID
那这个就好办了,直接判断workerid为0的去触发项目初始化事件。剩下还有一个问题就是,如何在初始化执行完成前,让所有 Worker 进程暂时都不处理请求。
思考并尝试了一下,这个问题可以通过协程挂起来解决,demo 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | <?php use Swoole\Coroutine; $http = new swoole_http_server( '127.0.0.1' , 8080); $http ->on( 'WorkerStart' , function (swoole_http_server $server , $workerId ){ $initFlagFile = __DIR__ . '/init.flag' ; if (0 === $server ->worker_id && (! is_file ( $initFlagFile ) || file_get_contents ( $initFlagFile ) != $server ->manager_pid)) { // 处理项目初始化事件 initApp(); // 写入文件,保证不再重复触发项目初始化事件 file_put_contents ( $initFlagFile , $server ->manager_pid); // 当前worker进程恢复协程 resumeCos(); // 通知其它worker进程 for ( $i = 1; $i < $server ->setting[ 'worker_num' ]; ++ $i ) { $server ->sendMessage( 'init' , $i ); } } }); $http ->on( 'PipeMessage' , function (swoole_http_server $server , $srcWorkerId , $data ) { if (0 === $srcWorkerId && 'init' === $data && !defined( 'APP_INITED' )) { // 其它worker进程恢复协程 resumeCos(); } }); $http ->on( 'request' , function (swoole_http_request $request , swoole_http_response $response ) { // 判断未初始化完毕,则挂起协程 if (!defined( 'APP_INITED' )) { $GLOBALS [ 'WORKER_START_END_RESUME_COIDS' ][] = Coroutine::getuid(); Coroutine::suspend(); } $response ->header( 'content-type' , 'text/html;charset=utf-8' ); $response -> end ( 'IMI 是一款基于 Swoole 开发的协程 PHP 开发框架,拥有常驻内存、协程异步非阻塞IO等优点。官方网站:<a href="https://imiphp.com" target="_blank">https://imiphp.com</a>' ); }); $http ->start(); /** * 处理项目初始化事件,比如这里延时5秒,模拟初始化处理 * * @return void */ function initApp() { $count = 5; for ( $i = 0; $i < $count ; ++ $i ) { echo 'initing ' , ( $i + 1), '/' , $count , PHP_EOL; sleep(1); } } /** * 恢复协程 * * @return void */ function resumeCos() { define( 'APP_INITED' , true); $coids = $GLOBALS [ 'WORKER_START_END_RESUME_COIDS' ] ?? []; fwrite(STDOUT, 'suspend co count: ' . count ( $coids ) . PHP_EOL); foreach ( $coids as $id ) { Coroutine::resume( $id ); } } |
通过在 request 事件中判断是否初始化完毕,如果没有初始化完成,则挂起当前协程,将协程ID加入全局变量。
当第0个 worker 进程执行完初始化后,通过向其他 worker 进程发送消息,唤醒曾经挂起的协程们,在初始化期间进来的请求,这时候会被执行。
友情提示:垃圾评论一律封号...