今天我们正式引入API 网关。在微服务架构中,网关是整个系统的统一入口,负责请求路由、鉴权、限流、日志、聚合等横切关注点。今天我们将使用 Hyperf 从零搭建一个网关服务,实现对后端用户服务、文章服务和订单服务的动态路由转发,让客户端只需与网关交互,而不再直接访问各个微服务。
今日目标
- 理解 API 网关在微服务架构中的核心价值与常见模式。
- 创建独立的网关项目
hyperf-gateway,配置 HTTP 服务器。 - 实现基础的请求转发:网关接收请求,根据路径前缀将请求透明地转发到对应的后端服务。
- 实现动态路由:将路由规则存储在配置中心(Nacos),支持运行时更改,无需重启网关。
- 测试网关转发功能,验证客户端通过网关能正常访问文章、订单等接口,并保持后端服务无感。
一、环境准备:创建网关项目(约 30 分钟)
1. 创建新项目
我们将网关作为独立的微服务项目,与hyperf-app分离。进入容器并创建新项目:
docker-composeexecswoolebashcd/var/wwwcomposercreate-project hyperf/hyperf-skeleton hyperf-gateway等待安装完成,进入项目:
cdhyperf-gateway2. 安装所需组件
网关需要 HTTP 服务器、JSON-RPC 客户端(用于转发?不,网关直接做 HTTP 代理即可,但也可以集成 RPC。今天先实现 HTTP 反向代理)、配置中心客户端。
composerrequire hyperf/http-server hyperf/guzzle hyperf/config-nacoshyperf/guzzle是基于 Swoole 的协程 HTTP 客户端,用于网关转发请求时发起后端调用。
3. 配置基本环境
编辑.env,设置端口为9500(避免与 9501 冲突):
APP_NAME=Gateway APP_ENV=dev APP_PORT=9500确认config/autoload/server.php中 HTTP 服务监听0.0.0.0:9500。
4. 配置 Nacos 连接
创建config/autoload/config_center.php,写入与之前相同的 Nacos 连接信息(参考hyperf-app中的配置):
<?phpreturn['driver'=>Hyperf\ConfigNacos\NacosDriver::class,'client'=>['host'=>'nacos','port'=>8848,'username'=>'nacos','password'=>'nacos',],'config'=>['data_id'=>'gateway-routes','group'=>'DEFAULT_GROUP','namespace_id'=>env('NACOS_NAMESPACE_ID','dev-001'),'type'=>'json',],'listener'=>['enable'=>true,'interval'=>3,],];在 Nacos 控制台的dev命名空间下新建配置gateway-routes(JSON 格式),内容暂时为空对象{},后续会填充路由规则。
二、知识核心:API 网关模式(约 1 小时)
1. 为什么需要 API 网关?
在微服务架构中,客户端直接访问各个微服务会带来诸多问题:
- 多入口:前端需要记住多个域名/IP,增加复杂度。
- 横切逻辑重复:每个服务都要处理鉴权、限流、日志等,代码冗余。
- 请求聚合不便:移动端需要从多个服务拉取数据,产生多次网络往返。
- 安全性:内部服务直接暴露,攻击面增大。
API 网关作为反向代理,统一接收所有客户端请求,然后转发到相应的后端服务,并在此过程中插入公共逻辑。
2. 网关的常见功能
- 路由转发:根据 URL、Header 等将请求映射到具体的后端服务。
- 认证授权:在网关层统一校验 JWT,解析用户信息并传递给下游。
- 限流熔断:保护后端服务不被突发流量冲垮。
- 日志与监控:记录所有请求的元数据,实现链路追踪的起点。
- 协议转换:对外暴露 HTTP/WebSocket,对内可能调用 gRPC 或 JSON-RPC。
- 聚合与裁剪:将多个后端响应合并为一个,减少客户端请求次数。
3. 我们今天的实现方案
- 网关:独立的 Hyperf 项目,监听
9500,内部使用Guzzle协程客户端向后端发起 HTTP 请求。 - 路由配置:存储在 Nacos 中,格式例如:
{"routes":[{"prefix":"/api/user","target":"http://127.0.0.1:9502"},{"prefix":"/api/order","target":"http://127.0.0.1:9501"}]}- 动态感知:网关启动时加载路由规则,并通过 Nacos 监听变更,实时更新本地路由表。
- 请求转发:网关接收到请求后,匹配最长路径前缀,将请求的 URI、方法、Body、Header 透传到目标服务,并将响应原样返回客户端。
三、实战:构建动态路由转发网关(约 2.5 小时)
步骤 1:创建路由管理服务
新建app/Service/RouteService.php,负责加载、缓存和匹配路由:
<?phpnamespaceApp\Service;useHyperf\Contract\ConfigInterface;useHyperf\Di\Annotation\Inject;classRouteService{#[Inject]privateConfigInterface$config;privatearray$routes=[];publicfunctionloadRoutes():void{// 从 Nacos 配置中心读取 routes 节点$this->routes=$this->config->get('routes',[]);}/** * 根据请求URI匹配目标服务地址 */publicfunctionmatch(string$uri):?string{// 按前缀长度降序排序,优先匹配更具体的路径$matched=null;$maxLength=0;foreach($this->routesas$route){$prefix=$route['prefix'];if(str_starts_with($uri,$prefix)&&strlen($prefix)>$maxLength){$maxLength=strlen($prefix);$matched=$route['target'];}}return$matched;}}步骤 2:网关核心中间件:转发请求
创建app/Middleware/GatewayMiddleware.php,实现核心转发逻辑:
<?phpnamespaceApp\Middleware;useApp\Service\RouteService;useHyperf\Di\Annotation\Inject;useHyperf\Guzzle\ClientFactory;usePsr\Http\Message\ResponseInterface;usePsr\Http\Message\ServerRequestInterface;usePsr\Http\Server\MiddlewareInterface;usePsr\Http\Server\RequestHandlerInterface;classGatewayMiddlewareimplementsMiddlewareInterface{#[Inject]privateRouteService$routeService;#[Inject]privateClientFactory$clientFactory;publicfunctionprocess(ServerRequestInterface$request,RequestHandlerInterface$handler):ResponseInterface{$uri=$request->getUri()->getPath();$target=$this->routeService->match($uri);if(!$target){// 未匹配路由,返回 404return\Hyperf\Utils\ApplicationContext::getContainer()->get(\Hyperf\HttpServer\Contract\ResponseInterface::class)->json(['code'=>404,'message'=>'Gateway: route not found'])->withStatus(404);}// 构造目标 URL(保留查询参数)$query=$request->getUri()->getQuery();$targetUrl=$target.$uri.($query?'?'.$query:'');try{// 使用协程 Guzzle 客户端转发请求$client=$this->clientFactory->create();$response=$client->request($request->getMethod(),$targetUrl,['headers'=>$request->getHeaders(),'body'=>(string)$request->getBody(),'timeout'=>5,// 超时时间]);// 将后端响应转换为 PSR-7 响应并返回return\Hyperf\Utils\ApplicationContext::getContainer()->get(\Hyperf\HttpServer\Contract\ResponseInterface::class)->withStatus($response->getStatusCode())->withBody(new\Hyperf\HttpMessage\Stream\SwooleStream($response->getBody()->getContents()))->withHeaders($response->getHeaders());}catch(\Throwable$e){// 转发失败,返回 502 Bad Gatewayreturn\Hyperf\Utils\ApplicationContext::getContainer()->get(\Hyperf\HttpServer\Contract\ResponseInterface::class)->json(['code'=>502,'message'=>'Gateway error: '.$e->getMessage()])->withStatus(502);}}}步骤 3:注册中间件并配置路由
在config/autoload/middlewares.php中添加全局中间件:
<?phpreturn['http'=>[\App\Middleware\GatewayMiddleware::class,],];同时确保config/routes.php中没有任何特定路由,因为所有请求都应该进入网关中间件进行转发。可以删除或保留默认的闭包路由,网关中间件会拦截并处理。
步骤 4:配置 Nacos 路由规则
打开 Nacos 控制台(http://localhost:8848/nacos),在dev命名空间下编辑gateway-routes,写入:
{"routes":[{"prefix":"/api/user","target":"http://hyperf-app:9502"},{"prefix":"/api/product","target":"http://hyperf-app:9504"},{"prefix":"/articles","target":"http://hyperf-app:9501"},{"prefix":"/orders","target":"http://hyperf-app:9501"},{"prefix":"/auth","target":"http://hyperf-app:9501"}]}注意:因为网关和hyperf-app在同一个 Docker 网络中,可以使用容器名hyperf-app(需确认 Docker Compose 中服务名,或者直接用127.0.0.1,但推荐容器名)。如果服务都在宿主机网络,可以用host.docker.internal或127.0.0.1。
步骤 5:启动网关并测试转发
启动网关服务(端口 9500):
php bin/hyperf.php start测试通过网关访问文章列表:
curlhttp://localhost:9500/articles应返回文章列表数据(来自hyperf-app:9501)。
测试订单详情:
curlhttp://localhost:9500/orders/1应返回聚合订单数据,包括用户和商品信息(内部 RPC 调用仍然在hyperf-app内完成)。
测试用户服务直接通过网关:
curl-XPOST http://localhost:9500/api/user-H"Content-Type: application/json"-d'{"jsonrpc":"2.0","method":"user/GetUserById","params":[1],"id":1}'注意:用户服务原本是 JSON-RPC 接口,但网关目前透明转发 HTTP,所以只要路径前缀匹配,网关就会转发请求到9502的 JSON-RPC 处理器,因此能正常工作。
验证动态路由:在 Nacos 中修改gateway-routes,添加一个前缀/test指向http://hyperf-app:9501,发布。稍等几秒,访问curl http://localhost:9500/test,预期能到达hyperf-app的默认路由(可能 404,但说明网关已加载新路由)。无需重启网关。
四、成果测试与验证(约 1 小时)
1. 测试清单
| 检验项 | 方法 | 通过标准 |
|---|---|---|
| 网关启动并监听 | curl http://localhost:9500无路由时返回 404 | 网关响应,状态码 404 由网关生成 |
| 路由匹配成功 | curl http://localhost:9500/articles | 返回文章列表,与直接访问 9501 一致 |
| 路径前缀匹配 | 新增路由/orders指向 9501 | 能正确获取订单 |
| 不存在的路由 | curl http://localhost:9500/nonexist | 返回 404,由网关提供 |
| 动态添加路由 | 在 Nacos 新增路由,等待后访问 | 新路由生效,无需重启 |
| 后端服务故障 | 暂时停止 9501 服务,访问/articles | 返回 502,网关提示错误 |
| 请求方法透传 | curl -X POST http://localhost:9500/auth/login ... | 正常登录,说明 POST 和 Body 正确转发 |
| 查询参数透传 | curl http://localhost:9500/articles?page=1 | 分页参数生效 |
2. 常见问题与调试
- 无法连接后端:检查网关能否解析
hyperf-app容器名,可在网关容器内ping hyperf-app或直接用 IP。 - 路由不生效:检查 Nacos 连接配置,确认
gateway-routes的 Data ID 正确;观察网关日志是否有配置更新。 - Guzzle 超时:后端处理慢可能导致超时,调整
timeout参数,并考虑异步化或增加重试。
3. 性能初探
网关增加了一层网络转发,延迟会略微上升。通过ab对比直连和网关访问的响应时间,可以看到网关引入的延迟在毫秒级,但带来的架构收益巨大。
五、今日作业与学习产出
- 提交代码:将
hyperf-gateway项目提交到 Git,包括GatewayMiddleware、RouteService、配置等。 - 增强网关:
- 为网关添加请求日志中间件,记录每个请求的方法、路径、状态码和耗时。
- 实现请求重试:转发失败时,尝试重试一次(适用于幂等 GET 请求)。
- 学习笔记:
- 画出网关在微服务架构中的位置图,标出数据流。
- 对比 API 网关与反向代理(Nginx)的异同,思考为什么需要应用层网关。
- 挑战任务:
- 研究 Hyperf 的协程 HTTP 客户端,了解
hyperf/guzzle的连接池配置,优化网关并发性能。 - 实现基于请求头的路由(如
X-Group: v2),实现更灵活的灰度分流。
- 研究 Hyperf 的协程 HTTP 客户端,了解
通过今天的学习,你已经成功构建了一个灵活、可动态配置的 API 网关,为整个微服务系统提供了统一入口。明天我们将为网关加上全局 JWT 鉴权,并实现向下游服务透传用户信息。