尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

【基于 Swoole+Hyperf 的微服务实战】第一周·周二:正式踏入 HTTP 世界,并亲手揭开 Swoole 协程调度的面纱

【基于 Swoole+Hyperf 的微服务实战】第一周·周二:正式踏入 HTTP 世界,并亲手揭开 Swoole 协程调度的面纱 这是第一周周二课程的详细展开我们将在周一 TCP 服务器的基础上正式踏入 HTTP 世界并亲手揭开 Swoole 协程调度的面纱。今日目标在 Swoole 中创建高性能 HTTP 服务器理解其处理流程。掌握 Request/Response 对象能够处理 GET/POST 参数并返回 JSON 数据。实现静态资源如图片的托管理解浏览器加载原理。深刻理解go()协程的创建与调度在请求中使用协程异步写日志并肉眼观察并发奇迹。使用ab压测工具用数据证明协程并发能力。一、环境准备与工具安装 (约 30 分钟)今日的所有代码依然在swoole-course目录下编写在容器内运行。1. 进入容器cdswoole-coursedocker-composeexecswoolebash2. 安装ab(Apache Bench) 压测工具 (宿主机)容器内专注于运行 PHP 服务压测我们直接在宿主机发起结果更清晰。MacOS自带ab无需安装。Linux (Debian/Ubuntu)sudoapt-getupdatesudoapt-getinstallapache2-utilsWindows可以下载 Apache 发行包或者使用 Git Bash 自带的ab或使用 WSL。验证安装ab-V# 应显示 ApacheBench 版本信息3. 创建今日工作目录mkdir-pday2cdday2二、知识核心HTTP 服务器与协程调度 (约 1.5 小时)1. Swoole HTTP 服务器原理Swoole 的 HTTP 服务器不是传统的 PHP-FPM它是常驻内存的。它内部是一个 TCP 服务器但专门解析 HTTP 协议。流程图客户端发起 HTTP 请求。服务器onRequest事件触发。回调函数接收$request(请求信息) 和$response(响应对象)。开发者处理逻辑调用$response-end()发送数据并结束此次请求。服务器继续监听下一个请求。常驻内存的优势框架初始化、数据库连接池等只需做一次后续请求直接复用因此速度极快。静态资源托管Swoole 可以直接读取文件系统把图片、CSS、JS 返回给浏览器无需 Nginx。2. 协程调度初探周一我们使用了Swoole\Coroutine\System::sleep()今天要理解更主动的协程创建go()。go(callable)创建一个新的协程立即返回不阻塞当前代码。协程是用户态的轻量级线程由 Swoole 的调度器管理。调度时机当协程遇到 I/O 操作网络请求、文件读写、System::sleep()时会自动让出 CPU 给其他协程。这就是协作式调度。核心实验我们将在主请求处理流程中用go()启动一个协程去写日志。这会发生什么主流程会继续执行立刻返回响应给浏览器。写日志的协程在后台执行即使耗时较长比如我们模拟 sleep也完全不影响用户感受到的响应速度。这就是异步非阻塞的灵魂。三、实战HTTP 服务、静态资源与协程日志 (约 2.5 小时)1. 基础 HTTP 服务返回 JSON创建文件http_server.php?php$httpnewSwoole\Http\Server(0.0.0.0,9501);$http-on(Request,function($request,$response){// 打印请求方法和路径echo收到请求:{$request-server[request_method]}{$request-server[request_uri]}\n;// 设置响应头为 JSON$response-header(Content-Type,application/json);// 构建一个数据数组$data[code200,messageHello Swoole HTTP,method$request-server[request_method],timedate(Y-m-d H:i:s),];// 将数组转为 JSON 并发送end 结束响应$response-end(json_encode($data,JSON_UNESCAPED_UNICODE));});$http-start();测试在容器内启动服务器php http_server.php用浏览器访问http://localhost:9501或使用curlcurlhttp://localhost:9501你会看到 JSON 返回。尝试加参数curl http://localhost:9501/user?id1但在代码中我们先不用$request-get。关键点$response-end()是必须调用的否则请求会一直挂起。2. 处理 GET/POST 参数修改http_server.php增加参数处理$http-on(Request,function($request,$response){$uri$request-server[request_uri];// 路由简易分发if($uri/json){$response-header(Content-Type,application/json);$name$request-get[name]??World;$response-end(json_encode([greetingHello,{$name}!]));}elseif($uri/form$request-server[request_method]POST){$response-header(Content-Type,application/json);$username$request-post[username]??Guest;$response-end(json_encode([received$username]));}else{$response-status(404);$response-end(Not Found);}});测试curlhttp://localhost:9501/json?nameSwoolecurl-XPOST-dusernameHyperfhttp://localhost:9501/form3. 静态资源托管假设我们有一张图片放在static/logo.png。创建目录并放一张图你可以随便找一个图片文件放进去。mkdir-pstatic# 用你自己的图片复制进来比如 logo.png修改http_server.php在onRequest开头添加静态文件处理$http-on(Request,function($request,$response){$uri$request-server[request_uri];// --- 静态资源托管 ---// 定义静态资源扩展名与 MIME 类型$staticExt[png,jpg,jpeg,gif,css,js,html];$extpathinfo($uri,PATHINFO_EXTENSION);if(in_array($ext,$staticExt)){$filePath__DIR__./static.$uri;if(file_exists($filePath)){// 自动设置 Content-Type$response-header(Content-Type,mime_content_type($filePath));$response-sendfile($filePath);return;// 注意要结束不再往下执行}else{$response-status(404);$response-end(File Not Found);return;}}// ... 后续动态路由 ...});测试浏览器访问http://localhost:9501/logo.png应显示图片。注意图片路径。4. 重磅协程并发——异步写日志这是今天的精华。我们将创建一个协程去执行一个耗时的“写日志”任务用文件写入模拟并故意 sleep 2 秒同时主请求立即返回。创建文件http_server_async.php完整代码如下?php$httpnewSwoole\Http\Server(0.0.0.0,9501);$http-on(Request,function($request,$response){$uri$request-server[request_uri];// 路由触发异步日志if($uri/async-log){// 获取客户端 IP$ip$request-server[remote_addr];$timedate(Y-m-d H:i:s);// 使用 go() 创建一个新协程执行耗时日志记录go(function()use($ip,$time){// 模拟耗时操作写日志之前先 sleep 2 秒echo[协程] 开始记录日志休眠2秒...\n;Swoole\Coroutine\System::sleep(2);// 写入日志文件追加模式$logMsg[{$time}] 用户{$ip}访问了 /async-log\n;file_put_contents(/var/www/day2/access.log,$logMsg,FILE_APPEND);echo[协程] 日志写入完成。\n;});// 主请求立即返回不等待协程完成$response-header(Content-Type,application/json);$response-end(json_encode([code200,msg请求已接收日志正在后台记录,time$time]));}else{$response-status(404);$response-end(Not Found);}});$http-start();启动并观察php http_server_async.php打开两个终端终端 A使用curl连续快速发送请求curlhttp://localhost:9501/async-logcurlhttp://localhost:9501/async-log终端 B实时查看日志文件变化tail-f/var/www/day2/access.log预期现象与原理分析每次curl都立刻返回 JSON 响应不会等待 2 秒。约 2 秒后access.log文件中会出现两条日志记录且时间戳是发起请求的时间。观察服务器控制台输出[协程] 开始记录日志休眠2秒... [协程] 开始记录日志休眠2秒... 约2秒后 [协程] 日志写入完成。 [协程] 日志写入完成。这说明多个协程并发执行了sleep而不是顺序各等2秒。总耗时约2秒而非4秒证明协程是并发的。这就是go()的神奇之处它把同步的阻塞操作变成了并发非阻塞。在实际项目中你可以用同样的方式异步发送邮件、推送消息、写入数据库日志。四、成果测试与性能初探 (约 1 小时)1. 使用ab压测观察协程威力我们依然使用http_server_async.php并为其增加一个不开启协程的对比路由。修改代码增加// 同步阻塞版本危险仅用于对比elseif($uri/sync-log){$ip$request-server[remote_addr];$timedate(Y-m-d H:i:s);// 直接阻塞 sleepecho同步处理开始阻塞2秒...\n;sleep(2);file_put_contents(/var/www/day2/access.log,[{$time}] 同步用户{$ip}\n,FILE_APPEND);$response-end(json_encode([msgsync done]));}压测异步接口# 并发10总请求100ab-n100-c10http://localhost:9501/async-log查看报告中的Time per request平均响应时间和Failed requests。由于异步立即返回响应时间应该非常短且没有失败。压测同步接口谨慎可能会超时# 并发10总请求20 就好因为每个请求阻塞2秒会很慢ab-n20-c10http://localhost:9501/sync-log你会发现平均响应时间非常长而且很多请求可能因为超过 ab 的默认超时时间而失败。思考为什么同步接口在并发下如此糟糕因为 Worker 进程被sleep(2)阻塞事件循环无法处理新的请求导致请求排队总耗时成倍增加。2. 进阶自测任务任务一改造代码使用go()同时发起 3 个不同的“耗时任务”比如记录不同日志观察它们是否并发执行总耗时是多少。任务二使用Swoole\Coroutine\WaitGroup让主请求等待所有日志协程完成后再返回响应。提示$wg new WaitGroup(); $wg-add(); go() ... $wg-done(); $wg-wait();任务三给静态资源托管增加缓存头Cache-Control体验常驻内存下自定义响应头的便利。3. 测试检验清单检验项方法通过标准HTTP 服务返回 JSONcurl访问检查 Content-Type返回application/json且结构正确GET/POST 参数解析curl -X POST -d kv正确接收并响应静态图片访问浏览器访问/logo.png图片显示状态码 200异步日志并发写入两次快速curl查看access.log及控制台输出请求立即返回2秒后日志出现且时间戳正确ab 压测异步接口ab -n 100 -c 10失败请求为0平均响应时间 50msab 压测同步接口相同参数响应时间极大或出现失败今日作业与学习产出提交代码将http_server.php、http_server_async.php和static目录提交至 Git。压测截图将异步接口和同步接口的ab压测结果截图并附上你的分析结论。协程笔记画出go()协程的并发执行流程图标注主请求和协程之间的关系以及何时让出 CPU。挑战实现WaitGroup版本使得请求在等待所有日志协程完成后才返回对比与立即返回的场景差异。通过今天的实战你已经掌握了 Swoole HTTP 服务的核心并亲眼见证了协程如何用单进程实现高并发。明天我们将利用 Hyperf 框架再次实现这一切感受框架的强大封装。
返回列表