【基于 Swoole+Hyperf 的微服务实战】第十二周·周一:自研一个生产级 API 网关
【基于 SwooleHyperf 的微服务实战】第十二周·周一自研一个生产级 API 网关今天我们进入第十二周周一正式开启第三个综合项目——高性能 API 网关与流量治理平台。在第一个项目中我们构建了电商核心系统第二个项目打造了异步日志分析平台这一次我们将自研一个生产级 API 网关它不再是简单的反向代理而是一个具备动态路由、插件链、多维度限流、熔断降级、安全防护和可视化后台的流量治理平台。今天作为项目奠基日我们将完成网关的架构设计与路由引擎核心。今日目标设计自研网关的整体架构明确核心模块路由管理器、插件链、限流熔断、后台管理。创建hyperf-gateway-plus项目骨架集成 Nacos、Redis、Consul 等基础组件。实现动态路由管理器从 Nacos 实时加载路由规则支持前缀匹配、正则匹配等多种策略。编写网关核心中间件实现对不同路由规则的精确匹配与请求转发。通过 Nacos 动态修改路由配置验证网关无需重启即可应用新规则。一、网关架构设计约 45 分钟1. 整体架构图[客户端] -- [自研网关] -- [路由匹配] -- [插件链处理] -- [负载均衡] -- [后端服务] | | | | | Nacos动态路由 限流/鉴权/日志 Consul服务发现 | [管理后台] -- [路由/插件/限流配置 API] -- [Nacos/Redis]核心模块路由引擎从 Nacos 加载路由表根据请求的 Host/Path/Header 匹配到目标服务集群。插件链可插拔的中间件机制顺序执行鉴权、限流、日志、熔断等插件。负载均衡集成 Consul 服务发现对后端实例进行轮询/加权/一致性哈希路由。管理后台可视化界面配置路由、插件参数、限流规则实时生效。监控Prometheus 指标暴露Grafana 仪表盘展示 QPS、延迟、错误率。2. 今日聚焦路由引擎路由是网关的灵魂。设计目标动态配置路由规则存储在 Nacos支持实时变更推送。多种匹配支持前缀匹配/api/user/*、正则匹配、Header 匹配X-Version: v2。优先级路由规则具有优先级高优先级先匹配。结构化存储JSON 格式易于管理界面编辑。路由规则示例{routes:[{id:route-user,priority:100,predicates:{path:/api/user/**,method:GET|POST},target:{service:user-service,loadBalancer:round-robin},plugins:[rate-limiter,jwt-auth]}]}二、环境准备约 30 分钟1. 项目创建docker-composeexecswoolebashcd/var/wwwcomposercreate-project hyperf/hyperf-skeleton gateway-pluscdgateway-pluscomposerrequire hyperf/http-server hyperf/config-nacos hyperf/redis hyperf/guzzle hyperf/service-governance-consul端口设为9700避免与现有服务冲突。2. 配置 Nacos创建config/autoload/config_center.php?phpreturn[driverHyperf\ConfigNacos\NacosDriver::class,client[hostnacos,port8848,usernamenacos,passwordnacos,],config[data_idgateway-routes,groupDEFAULT_GROUP,namespace_idenv(NACOS_NAMESPACE_ID,dev-001),typejson,],listener[enabletrue,interval3],];3. 初始化 Nacos 路由配置在 Nacos 控制台创建gateway-routes配置内容为空 routes 数组{routes:[]}三、知识核心路由匹配算法与动态加载约 1 小时1. 路由匹配流程对于每个进入网关的请求提取path、method、headers。遍历路由列表按优先级降序。检查每个路由的predicates所有条件必须同时满足AND 关系。匹配成功后执行该路由配置的插件链最终将请求转发到目标服务。2. 匹配器实现Path 匹配支持 Ant 风格路径表达式/api/**、/user/{id}。我们可借用symfony/routing组件或自行实现简单通配。Method 匹配检查请求方法是否在允许列表中。Header 匹配检查特定 Header 的值是否符合预期如X-Group: vip。3. 动态加载机制利用hyperf/config-nacos的长轮询监听当gateway-routes发生变更时config(routes)会自动更新。我们只需在中间件中始终读取最新的配置即可实现热更新无需重启。四、实战构建动态路由引擎约 2.5 小时步骤 1创建路由管理器新建app/Gateway/RouteManager.php?phpnamespaceApp\Gateway;useHyperf\Contract\ConfigInterface;useHyperf\Di\Annotation\Inject;classRouteManager{#[Inject]privateConfigInterface$config;/** * 获取按优先级排序的激活路由 */publicfunctiongetRoutes():array{$routes$this-config-get(routes,[]);// 按优先级降序usort($routes,fn($a,$b)($b[priority]??0)($a[priority]??0));return$routes;}/** * 匹配请求返回最佳路由及提取的路径参数 */publicfunctionmatch(string$method,string$path,array$headers):?array{foreach($this-getRoutes()as$route){if($this-matchPredicates($route[predicates]??[],$method,$path,$headers)){return$route;}}returnnull;}privatefunctionmatchPredicates(array$predicates,string$method,string$path,array$headers):bool{// Method 匹配if(isset($predicates[method])){$allowedMethodsexplode(|,$predicates[method]);if(!in_array($method,$allowedMethods)){returnfalse;}}// Path 匹配支持 /api/** 和 /user/{id} 两种模式if(isset($predicates[path])){if(!$this-matchPath($predicates[path],$path)){returnfalse;}}// Header 匹配if(isset($predicates[headers])){foreach($predicates[headers]as$header$expected){$actual$headers[$header]??;if($actual!$expected){returnfalse;}}}returntrue;}privatefunctionmatchPath(string$pattern,string$path):bool{// 简单实现支持 ** 通配符$regex/^.str_replace([**,*],[.*,[^/]],$pattern).$/;return(bool)preg_match($regex,$path);}}步骤 2编写网关核心中间件创建app/Middleware/GatewayCoreMiddleware.php?phpnamespaceApp\Middleware;useApp\Gateway\RouteManager;useHyperf\Di\Annotation\Inject;useHyperf\Guzzle\ClientFactory;usePsr\Http\Message\ResponseInterface;usePsr\Http\Message\ServerRequestInterface;usePsr\Http\Server\MiddlewareInterface;usePsr\Http\Server\RequestHandlerInterface;classGatewayCoreMiddlewareimplementsMiddlewareInterface{#[Inject]privateRouteManager$routeManager;#[Inject]privateClientFactory$clientFactory;publicfunctionprocess(ServerRequestInterface$request,RequestHandlerInterface$handler):ResponseInterface{$method$request-getMethod();$path$request-getUri()-getPath();$headers$request-getHeaders();$route$this-routeManager-match($method,$path,$headers);if(!$route){return$this-errorResponse(404,No route matched);}// 构造目标 URL简化直接使用 target 中的 base_url 或通过 Consul 发现$target$route[target];$serviceUrl$target[base_url]??http://localhost:9501;// 可从 Consul 动态获取$targetPath$path;// 可配置 stripPrefix$url$serviceUrl.$targetPath;try{$client$this-clientFactory-create();$response$client-request($method,$url,[headers$headers,body(string)$request-getBody(),timeout5,]);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){return$this-errorResponse(502,Gateway error: .$e-getMessage());}}privatefunctionerrorResponse(int$code,string$msg):ResponseInterface{return\Hyperf\Utils\ApplicationContext::getContainer()-get(\Hyperf\HttpServer\Contract\ResponseInterface::class)-json([code$code,message$msg])-withStatus($code);}}步骤 3注册中间件在config/autoload/middlewares.php中添加全局中间件http[\App\Middleware\GatewayCoreMiddleware::class,],步骤 4配置 Nacos 测试路由在 Nacos 控制台更新gateway-routes内容{routes:[{id:test,priority:100,predicates:{path:/test/**,method:GET},target:{base_url:http://hyperf-app:9501}}]}步骤 5启动并验证启动gateway-plusphp bin/hyperf.php start访问http://localhost:9700/test/hello如果能代理到hyperf-app:9501并返回内容即使404也是来自后端说明路由匹配成功。动态修改测试在 Nacos 中修改path为/demo/**发布。稍等几秒再次访问/demo/xxx会被路由而旧的/test/xxx返回 404 (No route matched)证明动态热加载生效。五、成果测试与验证约 1 小时测试清单检验项方法通过标准路由配置加载启动日志 / 访问匹配的路径请求成功转发到后端前缀匹配/test/abc命中/other未命中符合路由规则Method 限制配置只允许 GETPOST 返回 404符合预期动态变更路由修改 Nacos 路径后旧路径失效新路径生效无需重启404 无路由未配置的路径网关返回 404 JSON后端不可用停止后端服务访问匹配路由返回 502 JSON优先级匹配配置多个重叠路由高优先级先匹配匹配到预期路由性能考量当前核心中间件每次请求都从Config读取路由并做正则匹配。对于高并发可以将路由表缓存到路由管理器的私有属性监听 Nacos 变更事件重新加载避免每次读取配置。优化正则匹配将静态前缀路由优先匹配减少正则运算。六、今日作业与学习产出提交代码gateway-plus项目骨架、RouteManager、GatewayCoreMiddleware、Nacos 配置。增强路由匹配实现stripPrefix功能将匹配到的路径前缀去除后再转发。增加host匹配根据请求域名路由。学习笔记画出网关路由匹配流程图。对比自研网关与 Spring Cloud Gateway、Kong 的设计异同。挑战任务集成Consul 服务发现使target可以从注册中心动态获取服务实例列表而不是写死base_url。实现路由优先级动态排序新增路由无需重启即可参与匹配。通过今天的学习你的自研网关拥有了动态路由的灵魂明天我们将实现插件链机制让网关的功能可以通过插件灵活扩展彻底告别硬编码。