上篇聊了整体架构与技术选型,这篇进入落地层面——怎么拆服务、拆多细、服务之间怎么通信。趣玩搭从 yudao-cloud 单体框架起步,按照 DDD 领域驱动思路,拆分为 15 个微服务,本文记录拆分过程中的思考与踩坑。
一、拆分原则
微服务不是拆得越细越好。30 人的团队维护 50 个服务,光是联调和运维就能把人累死。我们遵循三条底线:
一个服务对应一个限界上下文(Bounded Context),不跨领域聚合
一个服务由一个小组(2–4 人)完整负责,包括开发、测试和值班
能不拆就不拆——如果两个模块 90% 的变更总是一起发生,那它们就是同一个服务
二、服务全景图
基于以上原则,趣玩搭拆分为 15 个微服务,每个服务独立数据库、独立部署、独立伸缩:
三、几个关键拆分决策的推演
3.1 为什么票务和订单分开?
初版设计中,票务和订单放在同一个服务里。但实际跑下来发现两者的变更节奏和性能诉求差异很大:
票务侧重于库存管理,核心操作是 Redis Lua 扣减,对延迟极度敏感(秒杀场景),变更频率低但稳定性要求极高
订单侧重于状态机流转和持久化查询,是写入最密集的服务,需要独立做分库分表
拆开之后,票务服务可以独立扩缩容应对秒杀洪峰,订单服务可以独立做分片优化,互不影响。
3.2 为什么支付独立成服务?
支付涉及资金操作,安全等级最高。独立之后有三个好处:
最小权限原则:只有 funplay-payment 持有微信商户密钥和证书,其他服务通过内部 RPC 调用支付能力,减少密钥扩散风险
独立审计:支付服务的日志、变更、发布都可以单独走更严格的审批流程
独立容灾:支付服务挂了,活动浏览和搜索不受影响,用户体验降级但不至于全站不可用
3.3 消息中心为什么不嵌入各业务服务?
站内信、短信、微信模板消息、邮件——这四个通道如果散落在各业务服务里,会出现大量重复代码和通道管理混乱。统一收口到 funplay-notify 之后:
业务服务只需发送一条 MQ 消息(携带模板 ID + 参数 + 接收人),不关心具体走哪个通道
消息中心统一管理通道优先级、频率限制、失败重试和用户免打扰配置
新增通道(比如接入企业微信)只需在消息中心扩展,业务服务无感知
四、服务间通信规范
通信方式的选择直接决定了系统的耦合程度和可靠性。我们制定了一套简单但有效的规范。
4.1 同步调用:Dubbo Triple
适用场景:需要立即获取结果的调用,如下单时查询票务库存、支付时校验订单状态。
规范约束:
超时统一 3 秒,重试 1 次
熔断阈值:50% 错误率触发熔断,10 秒后半开探测
所有 RPC 接口必须定义在独立的
api模块中(Maven module),供调用方依赖禁止 A → B → C → D 超过三级的同步调用链,超过则改为异步
4.2 异步解耦:RocketMQ
适用场景:跨服务的写操作、非实时通知、最终一致性场景。
核心 Topic 定义:
规范约束:
消息体统一使用 JSON,携带
msgId(UUID)用于幂等消费端必须实现幂等(MySQL 唯一索引 / Redis SETNX)
消费失败走 RocketMQ 自带重试(最多 16 次指数退避),最终失败进入死信队列,告警人工处理
所有跨服务写操作优先采用异步消息 + 最终一致,仅支付核心链路使用 TCC 分布式事务
4.3 一条典型链路串联
以"用户抢票下单支付"为例,看同步和异步如何配合:
用户点击抢票
│
▼
[funplay-gateway] 限流 + 鉴权
│ (HTTP)
▼
[funplay-ticket] Redis Lua 扣减库存
│ (MQ: TICKET_SOLD)
▼
[funplay-order] 创建待支付订单
│ (返回订单号给前端)
│ (MQ: ORDER_CREATED → funplay-notify 发通知)
▼
用户确认支付
│ (HTTP)
▼
[funplay-payment] 调起微信支付
│ (微信回调)
│ (MQ: PAYMENT_SUCCESS)
▼
[funplay-order] 更新订单状态为已支付
[funplay-member] 发放积分
[funplay-notify] 发送出票通知这条链路中,只有「扣减库存」这一步是同步的(Redis Lua 操作本身在毫秒级),其余全部走 MQ 异步,最大限度降低了链路耦合和响应延迟。
五、数据库隔离策略
每个服务独占自己的数据库 Schema(逻辑隔离),禁止跨库 JOIN。需要聚合数据时有两种方式:
RPC 查询:适用于低频、小数据量场景(如支付服务查订单状态)
数据冗余 / ES 宽表:适用于高频查询(如管理后台需要看「某活动下所有订单及支付状态」,通过 Canal 监听 Binlog 同步至 ES 宽表,毫秒级延迟)
分库分表策略(funplay-order):
按
user_id取模分 8 库 16 表(ShardingSphere-JDBC)同一用户的订单落在同一分片,满足「我的订单」高频查询
活动维度的聚合查询走 ES 宽表
六、踩坑总结
不要一开始就拆太细。我们最初把「充值」和「支付」拆成两个服务,后来发现它们共享同一套账务模型,合并后反而更清晰
RPC 接口的 api 模块版本管理很重要。一定要语义化版本号,避免上游改了接口下游编译不过
MQ 的 Topic 命名要规范。我们统一用
{动作}_{过去分词}格式(如 TICKET_SOLD、ORDER_CREATED),语义清晰且不易冲突每个服务第一天就要接入监控。不要等出了问题再加,SkyWalking 的 agent 挂载成本极低
下一篇:趣玩搭关键技术方案:抢票秒杀、门票候补与钱包对账——深入三个最具技术挑战的场景,逐步拆解方案设计。