跳至正文
来两杯美式
返回

Dubbo 服务拆分与架构设计:子系统划分的四大原则

By 来两杯美式
发布于

上一篇 《Dubbo 入门:什么是 RPC 与服务治理?》 讲了 Dubbo 是什么。这一篇进入实战最头疼的问题:分布式改造时,系统该怎么拆?

子系统划分:为什么拆不对就全盘皆输

把单体拆成多个服务,第一刀如果切错了,后面所有的 Dubbo 配置、集群容错、链路追踪都是在错误的地基上盖楼。当年踩坑总结出的四大原则,每一条都是血泪教训。

原则一:不要出现跨库 SQL 查询

不要出现 A 服务的 SQL 需要连接查询 B 服务中的表。

拆分的依据是数据而不是代码。每个服务拥有自己独立的数据库,服务 A 只能通过 RPC 调用服务 B 的接口拿数据,绝不能直接连 B 的库做 JOIN。

❌ 错误:A 服务的 SQL 直接 join B 服务的表
   A服务(库A) SELECT * FROM a_tbl JOIN b_tbl ...

✅ 正确:A 服务通过 RPC 调 B 服务的接口
   A服务 --dubbo调用--> B服务.getXxx() --> 查库B

为什么这条最重要?因为垂直拆库(把单库拆成多个独立库)是服务化的必然结果。如果服务间的数据耦合剪不断,将来做垂直拆库时所有跨库 JOIN 全部报错,到时候再回头拆服务,成本翻十倍。

判断标准:两个业务模块的表之间是否存在强关联查询?如果有,要么合并成一个服务,要么通过接口把数据”取过来再组装”。

原则二:避免环状依赖调用

子系统间避免环状依赖调用(A 服务调用 B,B 调用 C,C 调用 A)。

❌ 环状依赖(无法初始化,也无法升级)
   A ---调用---> B
   ^              |
   |              v
   +---调用------- C

✅ 分层依赖(单向依赖,边界清晰)
   A ---> B ---> C

环状依赖的危害:

解决思路:把环打断,让依赖变成单向分层。如果确实存在双向业务需求,通过引入中间层(如消息队列、公共服务)解耦。

原则三:服务间关系链不要过长

服务间关系链不要过长(A 调用 B,B 调用 C,C 调用 D…)。

❌ 调用链过长:一次请求穿 5 个服务
   A -> B -> C -> D -> E

✅ 调用链尽量短:聚合服务把结果组装好再返回
   A -> B(B 内部聚合 C、D)

链路过长带来的问题:

解决思路:优先保证关键链路短。把多个服务合并成”聚合服务”,或者让中间服务并行调用多个下游再组装结果。

原则四:尽量避免分布式事务

尽量避免分布式事务。

单体时代一个事务搞定的事,拆分后变成了跨服务的数据一致性难题。分布式事务方案(两阶段提交、TCC、最终一致性)要么性能差,要么实现复杂,都是当年踩过的坑。

❌ 跨服务强一致:A 服务扣库存 + B 服务扣余额,要求同时成功
✅ 最终一致:A 先成功,通过消息队列通知 B 异步处理,失败补偿

解决思路

接口设计规范

子系统拆完之后,Dubbo 接口的设计直接影响后续的维护成本。当年总结的经验:

接口要面向业务设计

参数对象化

RPC 接口的参数尽量用对象而不是散落的多个基本类型:

// ❌ 参数散乱,新增字段就要改接口
User getUser(String name, Integer age, String phone, String email);

// ✅ 参数对象化,新增字段不影响已有调用方
User getUser(QueryUserRequest request);

参数对象要实现 Serializable

public class QueryUserRequest implements Serializable {
    private static final long serialVersionUID = 1L;  // 显式声明,防止序列化兼容问题
    private String name;
    // getter/setter ...
}

Dubbo 的远程调用基于序列化传输,所有跨服务的参数对象和返回对象必须实现 Serializable,否则运行时抛 NotSerializableException

接口版本管理

接口一旦发布,就永远不要修改它——升级必须通过版本号。

<!-- 提供方声明版本 -->
<dubbo:service interface="com.example.UserService" ref="userServiceImpl" version="1.0.0"/>

<!-- 消费方引用指定版本 -->
<dubbo:reference id="userService" interface="com.example.UserService" version="1.0.0"/>

服务升级时,新旧版本可以同时存在(新版本发布、旧版本下线前给调用方迁移时间),这就是 Dubbo 接口版本管理最大的价值。

超时与重试配置

服务拆分后,网络调用有不确定性,超时重试必须显式配置(当年不配超时导致请求卡死的问题太常见):

<!-- 推荐在服务端配置超时 -->
<dubbo:provider timeout="3000" retries="2"/>

<!-- 消费方可覆盖 -->
<dubbo:reference interface="com.example.UserService" timeout="3000" retries="2"/>

配置建议:

小结

下一篇 《Dubbo 注册中心与直连调试:只订阅、只注册、多注册中心》 讲开发调试阶段怎么连服务。


分享这篇文章:
通过邮件分享这篇文章✓ 链接已复制
所属专题
Dubbo
第 5 / 5 篇
查看系列全部文章
  1. 01.Dubbo 入门:什么是 RPC 与服务治理?
  2. 02.Dubbo 注册中心与直连调试:只订阅、只注册、多注册中心
  3. 03.Dubbo 负载均衡:四种策略与源码分析
  4. 04.Dubbo 集群容错:架构、五种模式与服务暴露
  5. 05.Dubbo 服务拆分与架构设计:子系统划分的四大原则

上一篇
Filter、Interceptor 与 Aspect:三个请求拦截层次详解
下一篇
Dubbo 集群容错:架构、五种模式与服务暴露