微服务架构设计

对于 Java 后端开发,微服务架构设计最核心的不是把一个 Spring Boot 项目拆成多个服务,而是根据业务领域划分服务边界,让每个服务拥有明确的业务职责、独立的数据管理能力,并通过合理的接口协作。

  1. 什么是服务拆分?
    假设原来有一个环保监测平台,所有功能都放在一个单体项目中:
  • 用户管理
  • 设备管理
  • 监测数据采集
  • 数据分析
  • 报表管理
  • 告警管理
    随着业务增长,系统可能面临代码耦合、发布困难、扩容不灵活等问题。

可以逐步拆成:
API Gateway / 网关

统一入口、认证、路由、限流

用户服务

用户、角色、权限

设备服务

设备档案、设备状态

数据采集服务

数据接入、清洗、存储

统计分析服务

统计、聚合、报表

告警服务

规则判断、告警记录

配置服务

业务配置、规则配置

示意架构:服务数量和边界应根据真实业务复杂度确定,不代表每个模块都必须独立部署。

拆分的目标是让每个服务能够独立开发、测试、部署、扩容和演进,而不是单纯增加项目数量。
2. 服务拆分有哪些原则?
原则 具体含义 实际例子
高内聚 同一服务内的功能围绕同一业务职责 设备档案、设备维护归设备领域
低耦合 服务间尽量通过明确的接口协作 告警服务调用设备服务查询设备信息
业务边界清晰 一个服务对某项业务规则负责 告警服务负责判断是否触发告警
独立演进 一个服务变更尽量不要求其他服务同步发布 统计服务单独增加新报表
按需扩容 对高负载服务独立增加实例 数据采集服务独立扩容
适度拆分 避免拆得太细导致调用链复杂 不必把每个简单 CRUD 模块都拆成服务
这里最重要的是:优先按业务职责拆分,而不是按照技术层次拆分。

例如,以下拆法通常不合理:

  • Controller 服务
  • Service 服务
  • Mapper 服务
  • 数据库访问服务
    因为一个业务请求需要跨多个服务才能完成,反而增加网络调用和故障点。
  1. 如何判断某个模块是否应该独立成服务?
    可以从五个方面判断。

第一,业务职责是否独立。

设备管理和告警管理虽然有关联,但设备档案维护和告警规则判断属于不同的业务职责。

第二,是否有独立的业务规则。

如果一个模块有自己的状态机、校验规则、业务流程,通常更适合形成独立领域模块。

第三,是否存在不同的扩容需求。

例如,数据采集每秒处理大量设备数据,而用户管理的请求量相对较低,可以考虑分开部署。

第四,是否需要独立发布。

某些业务变化频繁,独立部署可以减少对其他业务的影响。

第五,拆分后的分布式成本是否值得。

如果两个模块每次操作都需要强事务、频繁互相调用,且规模不大,暂时放在同一个服务中可能更合理。

注意:一个服务可以有多个实例;多个服务也可以暂时使用同一个数据库实例。服务拆分、部署隔离、数据库隔离是相关但不同的设计决策。
二、领域边界:每个服务究竟应该负责什么?
领域边界是服务拆分中最难的部分。它解决的不是“代码放在哪里”,而是“谁对哪一类业务规则和业务数据负责”。

  1. 什么是领域驱动设计 DDD?
    DDD,即领域驱动设计(Domain-Driven Design),是一种以业务领域为核心进行软件建模的方法。

它帮助我们识别:

  • 业务领域:系统解决什么业务问题。
  • 子域:领域中有哪些相对独立的业务部分。
  • 限界上下文:某一套领域模型、术语和业务规则适用的明确范围。
  • 聚合:需要在一致性边界内维护业务不变量的一组领域对象。
    这些概念并不是一一对应的,但可以共同帮助我们确定微服务的职责边界。
  1. 用环保平台理解领域边界
    假设有一张设备表 device_info,一个告警表 alarm_record,以及一张监测数据表 monitor_data。

可以识别出三个领域:
设备领域

负责什么:

  • 设备注册、档案维护
  • 设备启停及设备状态
  • 设备编码、设备类型等业务规则
    核心数据: device_info

不负责: 监测指标的统计计算、告警规则执行。

监测数据领域

负责什么:

  • 监测数据接入和校验
  • 数据去重、补传、持久化
  • 数据查询及时间范围内的聚合
    核心数据: monitor_data

不负责: 设备档案的修改、告警规则的维护。

告警领域

负责什么:

  • 告警阈值和规则
  • 告警触发、恢复、确认
  • 告警状态流转和告警记录
    核心数据: alarm_rule、alarm_record

不负责: 设备档案的完整生命周期。

  1. 限界上下文为什么重要?
    不同业务即使使用同一个词,也可能代表不同的含义。

例如,“设备状态”:

  • 在设备领域中,可能表示在线、离线、停用。
  • 在告警领域中,可能表示是否处于告警状态。
  • 在监测领域中,可能表示数据是否有效、是否超标。
    如果把这些状态混在一个统一的模型里,随着业务发展,字段和规则就会相互影响。

因此,应当让不同上下文维护自己的业务模型,通过接口或事件交换必要的信息。
4. 跨服务如何关联数据?
例如,告警记录需要保存设备编码、设备名称。

不建议告警服务直接跨库关联设备服务的数据库。可以选择以下方案:

  • 调用接口: 告警服务通过设备服务接口查询设备名称,适合需要获取最新信息的场景。
  • 本地冗余: 告警记录保存设备编码、触发时的设备名称,适合审计和历史追溯。
  • 事件同步: 设备信息变更时发布事件,其他服务维护自己的只读副本,适合高频查询。
  • 组合查询: 由查询聚合层调用多个服务,或者查询专用的数据模型。
    要注意,冗余数据不一定是错误设计。关键是明确它是历史快照还是需要持续同步的当前状态。

三、数据自治:为什么每个微服务要管理自己的数据?
这是微服务架构设计中非常重要的知识点,也是面试中经常深入追问的内容。

  1. 什么是数据自治?
    数据自治(Database per Service)的核心是:

每个微服务对自己负责的业务数据拥有写入、修改、查询规则和数据模型的控制权,其他服务不能绕过它直接修改这些数据。

例如:
设备服务

设备业务规则与数据访问
拥有设备数据

设备 API / 业务事件

监测数据服务

采集、校验、存储监测数据
拥有监测数据

告警事件 / 规则接口

告警服务

告警规则、状态流转、告警记录
拥有告警数据

每个服务负责自己的数据,跨服务访问通过接口、消息或专门的查询模型实现,而不是随意跨库读写。
2. 数据自治是不是要求每个服务使用独立数据库?
不完全是。需要区分三个层次:
隔离层次 具体方案 隔离程度
逻辑隔离 同一个数据库实例,不同表,约定数据归属 较弱
Schema 隔离 同一实例,不同数据库或 Schema,独立账号授权 中等,取决于权限和实例共享程度
独立实例 每个服务使用独立数据库实例 更强,但运维成本更高
例如,设备服务和告警服务可以暂时都部署在同一 MySQL 实例上,但各自管理不同的表,并通过独立数据库账号限制访问权限。

不过,仅仅把表分开,并不等于真正实现数据自治。如果告警服务仍然直接修改设备服务的表,两个服务就存在数据层面的耦合。

实际项目可以循序渐进:先明确表的归属、访问权限和写入边界,再根据规模、稳定性和扩展需求决定是否拆分数据库实例。
3. 为什么不建议微服务直接跨库操作?
假设告警服务执行下面的 SQL:
SELECT device_name
FROM device_db.device_info
WHERE device_code = 'A001';

如果这只是通过有权限的只读账号获取数据,技术上可以实现,但会造成一定的架构耦合。

如果告警服务进一步执行:
UPDATE device_db.device_info
SET device_status = 'OFFLINE'
WHERE device_code = 'A001';

问题就更明显了:

  1. 告警服务绕过设备服务的业务校验。
  2. 设备服务无法保证所有修改都经过自己的业务规则。
  3. 设备表结构变更可能影响告警服务。
  4. 跨服务事务、权限控制和故障排查更复杂。
  5. 无法独立演进两个服务的数据模型。
    更合理的做法是调用设备服务的业务接口,例如:
    POST /devices/A001/offline

由设备服务完成状态校验、更新数据库并发布相应事件。

当然,如果离线判断属于告警领域的独立规则,就不应让告警服务随意修改设备状态;应当先明确哪个领域才是该业务状态的权威所有者。
4. 数据自治如何影响数据库事务?
这是一个关键问题。

假设业务要求:

  1. 创建一条告警记录。
  2. 更新设备的告警状态。
  3. 发送告警通知。
    如果告警记录和设备状态分别由两个微服务管理,就不能简单地依赖一个普通的本地数据库事务保证整个流程原子提交。

常见方案如下:
方案 适用场景 主要代价
本地事务 单个服务内部的数据修改 不能原子覆盖其他服务
可靠消息 + 最终一致性 业务允许短暂的数据不一致 需要重试、幂等和补偿
Saga 多服务业务流程,允许分步提交和补偿 需要设计补偿动作和异常状态
Seata AT 等分布式事务方案 有跨服务事务要求,且技术条件适配 增加协调、锁和运维复杂度
TCC 对业务资源和事务控制要求较高的场景 需要实现 Try、Confirm、Cancel
例如,使用 Kafka 实现可靠的告警事件传递时,可以采用:

  1. 告警服务在本地事务中保存告警记录和待发送事件(Outbox)。
  2. 后台任务将事件可靠地投递到 Kafka。
  3. 消费者收到事件后,更新自己负责的数据。
  4. 消费者通过业务唯一键实现幂等,重复消息不会重复产生业务效果。
  5. 失败消息通过重试、死信或人工补偿处理。
    这里必须注意:Kafka 的消息投递、数据库事务和消费者的业务事务并不会自动成为一个全局原子事务。可靠消息、幂等和补偿机制需要配合设计。

四、三个知识点如何结合起来完成架构设计?
以环保监测平台为例,假设你要设计一套基于 Spring Cloud Alibaba 的微服务架构,可以按照下面的顺序推进。

第一步:梳理业务领域

识别用户、设备、监测数据、告警、统计分析等业务领域,明确每个领域的业务目标。

第二步:确定服务边界

根据业务内聚性、业务规则、数据归属、调用关系和扩容需求确定服务,不盲目拆分。

第三步:确定数据归属

明确每张核心业务表由哪个服务负责写入、校验和维护,设置数据库权限,避免跨服务直接修改数据。

第四步:设计服务协作

同步查询使用 HTTP / OpenFeign;异步通知使用 Kafka;需要跨服务业务一致性时,设计可靠消息、幂等、重试和补偿。

第五步:设计治理与容错

使用 Nacos 进行服务发现和配置管理,结合 Gateway、Sentinel、Micrometer Tracing 等实现路由、限流、熔断、监控与链路追踪。

posted @ 2026-10-20 16:19  程序小小猿  阅读(6)  评论(0)    收藏  举报