
【基于 SwooleHyperf 的微服务实战】第九周·周一第一个大型综合项目——微服务电商核心系统今天我们进入第九周周一第一个大型综合项目——微服务电商核心系统——正式启动。经过前八周的学习我们已经掌握了协程、框架、微服务通信、服务治理、可观测性、消息队列、分布式事务与锁等各项核心技能。接下来的两周我们将把这些能力全部整合构建一个完整的电商后端系统用户、商品、库存、订单、支付、网关等核心服务并通过 Consul、Nacos、Jaeger 实现服务治理与监控。今天作为项目的奠基日我们不会写太多业务代码而是完成项目最关键的顶层设计服务边界规划、Proto 契约定义、数据库 Schema 设计以及一键式开发环境的搭建确保所有服务能在一个docker-compose up后全部启动并可互通。今日目标设计电商系统的微服务架构图明确各服务的职责、通信方式和数据库。使用Protocol Buffers定义各服务的 RPC 接口契约实现“契约优先”开发。设计每个服务的数据库表结构MySQL确保数据隔离。编写docker-compose.yml集成所有基础设施MySQL、Redis、Consul、Nacos、RabbitMQ、Jaeger、Kibana 等和我们的多个服务容器。启动环境验证各服务能正确注册到 Consul并能通过网关访问健康检查接口。一、项目需求与架构设计约 1 小时1. 业务需求描述我们要实现一个简化的电商核心流程用户注册/登录用户服务商品浏览商品服务下单订单服务 库存服务支付支付服务模拟秒杀限时抢购涉及缓存库存与分布式锁网关统一入口JWT 认证限流熔断2. 服务划分与职责服务职责数据库用户服务 (user)注册、登录、用户信息查询user_db商品服务 (product)商品 CRUD、分类、标签product_db库存服务 (inventory)库存查询、冻结、解冻、扣减inventory_db订单服务 (order)订单创建、查询、状态流转order_db支付服务 (payment)支付处理、退款模拟payment_db网关 (gateway)路由转发、JWT 认证、限流熔断、聚合无转发服务间通信同步 RPChyperf/json-rpc通过 Consul 注册发现。异步消息RabbitMQ用于 Saga 事务、库存解冻、日志等。网关对外暴露 RESTful HTTP内部通过 JSON-RPC 调用聚合。3. 基础设施清单Consul服务注册发现Nacos配置中心动态限流、灰度等RabbitMQ消息队列MySQL各服务独立数据库同一实例多个库Redis缓存、分布式锁、SessionJaeger链路追踪ELK (Elasticsearch Kibana Filebeat)日志收集Prometheus Grafana监控与告警Docker Compose编排所有容器二、知识核心Proto 契约设计与多服务架构原则约 1 小时1. 契约优先的价值在微服务开发中服务间接口就是合同一旦定义就不应轻易改变。使用 Protocol Buffers 定义接口可以自动生成服务端骨架和客户端代码减少手写错误并保证跨语言兼容性虽然我们都用 PHP但要养成良好的习惯。2. 如何划分 Proto 文件每个服务一个.proto文件放在统一的protos/目录下。文件内定义该服务暴露的 RPC 方法及请求/响应消息。3. 数据库设计原则每个服务有自己的数据库Database per Service绝不跨库查询。表结构尽量精简避免外键逻辑外键关联通过 ID 由服务层实现。每个表包含created_at和updated_at字段用于审计。三、实战编写 Proto 契约与 SQL Schema约 2.5 小时我们首先创建项目根目录结构在宿主机swoole-course下swoole-course/ ├── docker-compose.yml ├── protos/ │ ├── user.proto │ ├── product.proto │ ├── inventory.proto │ ├── order.proto │ └── payment.proto ├── services/ │ ├── gateway/ (基于 hyperf-gateway) │ ├── user/ (基于 hyperf-skeleton) │ ├── product/ │ ├── inventory/ │ ├── order/ │ └── payment/ ├── databases/ │ ├── user_db.sql │ ├── product_db.sql │ └── ... └── config/ ├── prometheus.yml └── ...步骤 1编写用户服务 Protoprotos/user.protosyntax proto3; package user; service UserService { rpc Register (RegisterReq) returns (UserResp); rpc Login (LoginReq) returns (LoginResp); rpc GetUserById (GetUserByIdReq) returns (UserResp); rpc BatchGetUsers (BatchGetUsersReq) returns (BatchGetUsersResp); } message RegisterReq { string username 1; string password 2; string email 3; } message LoginReq { string username 1; string password 2; } message LoginResp { string token 1; UserResp user 2; } message GetUserByIdReq { int32 id 1; } message BatchGetUsersReq { repeated int32 ids 1; } message BatchGetUsersResp { repeated UserResp users 1; } message UserResp { int32 id 1; string username 2; string email 3; string avatar 4; string created_at 5; }步骤 2商品服务 Protoprotos/product.protosyntax proto3; package product; service ProductService { rpc GetProductById (GetProductByIdReq) returns (ProductResp); rpc ListProducts (ListProductsReq) returns (ListProductsResp); rpc CreateProduct (CreateProductReq) returns (ProductResp); } message GetProductByIdReq { int32 id 1; } message ListProductsReq { int32 page 1; int32 page_size 2; } message ListProductsResp { repeated ProductResp products 1; int32 total 2; } message CreateProductReq { string name 1; float price 2; int32 stock 3; } message ProductResp { int32 id 1; string name 2; float price 3; int32 stock 4; }步骤 3库存服务 Protoprotos/inventory.protosyntax proto3; package inventory; service InventoryService { rpc Freeze (FreezeReq) returns (FreezeResp); rpc Unfreeze (UnfreezeReq) returns (UnfreezeResp); rpc Deduct (DeductReq) returns (DeductResp); rpc GetStock (GetStockReq) returns (GetStockResp); } message FreezeReq { int32 product_id 1; int32 quantity 2; } message FreezeResp { bool success 1; } message UnfreezeReq { int32 product_id 1; int32 quantity 2; } message UnfreezeResp { bool success 1; } message DeductReq { int32 product_id 1; int32 quantity 2; } message DeductResp { bool success 1; } message GetStockReq { int32 product_id 1; } message GetStockResp { int32 available 1; int32 frozen 2; }步骤 4订单服务 Protoprotos/order.protosyntax proto3; package order; service OrderService { rpc CreateOrder (CreateOrderReq) returns (OrderResp); rpc GetOrder (GetOrderReq) returns (OrderResp); rpc CancelOrder (CancelOrderReq) returns (OrderResp); } message CreateOrderReq { int32 user_id 1; int32 product_id 2; float amount 3; } message OrderResp { string order_id 1; int32 user_id 2; int32 product_id 3; float amount 4; string status 5; } message GetOrderReq { string order_id 1; } message CancelOrderReq { string order_id 1; }步骤 5支付服务 Protoprotos/payment.protosyntax proto3; package payment; service PaymentService { rpc Debit (DebitReq) returns (DebitResp); rpc Refund (RefundReq) returns (RefundResp); } message DebitReq { string order_id 1; float amount 2; } message DebitResp { bool success 1; } message RefundReq { string order_id 1; } message RefundResp { bool success 1; }步骤 6设计数据库 Schema为每个服务编写 SQL 文件放在databases/下。user_db.sqlCREATEDATABASEIFNOTEXISTSuser_db;USEuser_db;CREATETABLEusers(idINTUNSIGNEDAUTO_INCREMENTPRIMARYKEY,usernameVARCHAR(50)NOTNULLUNIQUE,passwordVARCHAR(255)NOTNULL,emailVARCHAR(100),avatarVARCHAR(255)DEFAULT,created_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP,updated_atTIMESTAMPDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP);product_db.sqlCREATEDATABASEIFNOTEXISTSproduct_db;USEproduct_db;CREATETABLEproducts(idINTUNSIGNEDAUTO_INCREMENTPRIMARYKEY,nameVARCHAR(255)NOTNULL,priceDECIMAL(10,2)NOTNULL,stockINTNOTNULLDEFAULT0,created_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP,updated_atTIMESTAMPDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP);inventory_db.sqlCREATEDATABASEIFNOTEXISTSinventory_db;USEinventory_db;CREATETABLEstocks(product_idINTUNSIGNEDPRIMARYKEY,totalINTNOTNULLDEFAULT0,frozenINTNOTNULLDEFAULT0,availableINTGENERATED ALWAYSAS(total-frozen)STORED,updated_atTIMESTAMPDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP);-- 同时记录冻结流水用于幂等CREATETABLEfreeze_logs(idBIGINTAUTO_INCREMENTPRIMARYKEY,saga_idVARCHAR(64)NOTNULL,product_idINTNOTNULL,quantityINTNOTNULL,actionENUM(freeze,unfreeze,deduct)NOTNULL,created_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP,UNIQUEKEYuk_saga_action(saga_id,action));order_db.sql和payment_db.sql类似包含订单表和支付流水表并加入saga_id用于幂等。四、环境搭建Docker Compose 编排所有服务约 1 小时1. 编写docker-compose.yml我们将所有基础设施和每个服务容器化。由于每个服务都是一个独立的 Hyperf 项目我们需要为每个服务创建 Dockerfile 或直接使用hyperf/hyperf-skeleton基础镜像并挂载代码。为了快速启动我们可以使用统一的 PHP 镜像通过不同的工作目录和端口区分服务。简化方案适合开发阶段使用一个通用的hyperf镜像在容器内通过不同命令启动多个服务。但这不利于隔离。推荐为每个服务创建一个容器镜像统一使用phpswoole/swoole:5.1-php8.2或hyperf/hyperf并挂载对应代码目录。由于时间关系今天我们着重搭建基础设施和网关业务服务在后续每天逐步添加。但我们需要在docker-compose.yml中预留所有服务的定义。示例docker-compose.yml片段version:3.8services:consul:image:consul:1.15container_name:consulports:[8500:8500]command:agent-dev-client0.0.0.0nacos:image:nacos/nacos-server:v2.2.3container_name:nacosenvironment:[MODEstandalone]ports:[8848:8848,9848:9848]rabbitmq:image:rabbitmq:3-managementcontainer_name:rabbitmqports:[5672:5672,15672:15672]redis:image:redis:7-alpinecontainer_name:redisports:[6379:6379]mysql:image:mysql:8.0container_name:mysqlenvironment:MYSQL_ROOT_PASSWORD:rootports:[3306:3306]volumes:[./databases:/docker-entrypoint-initdb.d]# 自动初始化数据库# 业务服务 (后续几天添加)# user-service:# build: ./services/user# ports: [9502:9502]# depends_on: [consul, nacos, mysql]将databases/目录下的 SQL 文件挂载到 MySQL 的初始化目录容器启动时会自动创建所有数据库和表。2. 初始化网关项目复制hyperf-gateway到services/gateway调整端口等配置确保其能通过 Consul 发现用户服务后续添加。3. 一键启动与验证执行docker-compose up -d确认所有基础设施启动。访问 Consul UI:http://localhost:8500访问 Nacos:http://localhost:8848/nacos访问 RabbitMQ:http://localhost:15672访问 Jaeger:http://localhost:16686访问 Grafana:http://localhost:3000检查 MySQL 中的数据库是否已创建。五、成果测试与产出约 1 小时1. 验收清单项目验收方法通过标准Proto 文件语法正确使用protoc编译检查无语法错误数据库表自动创建连接 MySQL 查看各库表结构符合预期基础设施运行Docker ps 或访问 Web UI所有容器 Up网关启动并与 Consul 连通启动网关查看 Consul 服务列表出现gateway服务服务契约文档查看protos/和databases/目录文件齐全2. 产出物protos/目录下所有.proto文件databases/目录下各服务 SQL完整的docker-compose.yml项目目录结构已推送到 Git3. 学习笔记绘制完整的微服务架构图标注通信方式。总结“契约优先”开发模式的优势。六、今日作业完善 Proto为每个服务补充必要的接口如用户服务增加修改密码。搭建第一个服务尝试创建user-service容器实现GetUserById并注册到 Consul。预习明日将深入实现用户服务的完整注册、登录与 JWT 签发。今天虽然代码量不多但你完成了整个项目的地基接下来的每一天都将填充一块功能模块最终构建出一座微服务大厦。