news 2026/6/23 4:39:16

微服务实战:从单体到分布式架构的演进之路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
微服务实战:从单体到分布式架构的演进之路

上一篇文章梳理了 Spring Cloud 各个组件的作用和学习路径。这次想聊聊更实际的问题:当你真正要把一个系统拆成微服务时,具体该怎么做?

我参考了github上面众多老师的 Spring Cloud 实战仓库(https://github.com/yinjihuan/spring-cloud),结合其中的完整项目案例,整理了一套从服务拆分、数据库设计到分布式事务的实战思路。


一、服务拆分:怎么拆才合理

1.1 拆分的核心原则

微服务拆分没有标准答案,但有几个基本原则可以参考:

按业务边界拆分

每个服务对应一个明确的业务领域,比如用户服务、订单服务、商品服务。这样服务之间的职责清晰,团队可以独立开发和部署。

高内聚、低耦合

服务内部的功能紧密相关,服务之间的依赖尽量少。如果两个服务频繁互相调用,可能说明拆分得不够合理。

数据独立性

每个服务应该有自己的数据库,避免多个服务直接操作同一张表。这是微服务和传统 SOA 的重要区别。

1.2 实战项目的服务拆分案例

参考仓库中的房产项目(fangjia-fsh),它的拆分方式如下:

服务职责对应模块
API 网关统一入口、路由转发、鉴权fangjia-fsh-api
用户服务用户注册、登录、个人信息fangjia-fsh-user-service
房产服务房源信息、搜索、展示fangjia-fsh-house-service
替换服务房源替换逻辑fangjia-fsh-substitution-service
认证服务服务间调用鉴权、Token 颁发fangjia-auth-service
注册中心服务发现与注册fangjia-eureka
监控服务服务健康状态监控fangjia-boot-admin

拆分要点

  • 用户服务和房产服务是核心业务,独立拆分

  • 认证逻辑抽离成独立服务,避免每个服务重复实现

  • API 网关作为统一入口,对外暴露 REST 接口,对内路由到具体服务


二、数据库设计:每个服务一个数据库

2.1 数据库拆分的必要性

微服务架构中,每个服务应该拥有独立的数据库。这样做的好处:

  • 服务之间解耦,一个服务的数据库变更不会影响其他服务

  • 可以根据服务特点选择不同的数据库类型(MySQL、MongoDB、Redis 等)

  • 便于独立扩展,热点服务可以单独做读写分离或分库分表

2.2 读写分离实战

当读请求远多于写请求时,读写分离是常用的优化手段。仓库中的fangjia-sjdbc-read-write模块展示了基于 ShardingJDBC 的实现:

spring: shardingsphere: datasource: names: master, slave master: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/master_db username: root password: 123456 slave: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/slave_db username: root password: 123456 masterslave: name: ms master-data-source-name: master slave-data-source-names: slave

关键点

  • 写操作路由到主库(master)

  • 读操作路由到从库(slave)

  • 主从同步由 MySQL 自身机制保证

2.3 分库分表实战

当单表数据量过大(通常超过千万级)时,需要考虑分库分表。仓库提供了两个示例:

分表(sharding-table):同一数据库内,按某个字段将数据分散到多张表

spring: shardingsphere: sharding: tables: t_order: actual-data-nodes: ds0.t_order_${0..9} table-strategy: inline: sharding-column: order_id algorithm-expression: t_order_${order_id % 10}

分库分表(sharding-db-table):数据分散到多个数据库的多张表

spring: shardingsphere: sharding: default-database-strategy: inline: sharding-column: user_id algorithm-expression: ds${user_id % 2} tables: t_order: actual-data-nodes: ds${0..1}.t_order_${0..9}

分片策略选择

策略适用场景优点缺点
取模分片数据分布均匀的场景负载均衡扩容时需要迁移数据
范围分片按时间、ID 范围查询的场景扩容方便可能存在热点
哈希分片需要均匀分布的场景避免热点范围查询效率低

三、分布式事务:怎么保证数据一致性

3.1 为什么需要分布式事务

微服务架构下,一个业务操作可能涉及多个服务。比如下单操作:

  1. 订单服务创建订单

  2. 库存服务扣减库存

  3. 用户服务扣减余额

这三个操作必须同时成功或同时失败,否则会出现数据不一致。

3.2 分布式事务解决方案对比

方案原理优点缺点适用场景
2PC两阶段提交,协调者统一管理强一致性性能差、协调者单点故障对一致性要求极高的金融场景
TCCTry-Confirm-Cancel,业务层实现补偿性能较好业务侵入性强,开发成本高电商、支付等高并发场景
** Saga**长事务拆分,失败时执行补偿操作性能好最终一致性,补偿逻辑复杂业务流程长、需要异步处理的场景
可靠消息通过消息队列保证最终一致性解耦、性能好实现复杂,需要处理幂等大多数异步场景

3.3 实战:基于可靠消息的分布式事务

仓库中的transaction-mq-service模块实现了一套可靠消息服务,核心思路:

┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ 业务服务 │────▶│ 消息服务 │────▶│ 消息队列 │ │ (发起方) │ │ (可靠消息) │ │ (RocketMQ) │ └─────────────┘ └─────────────┘ └──────┬──────┘ │ ▼ ┌─────────────┐ │ 消费服务 │ │ (接收方) │ └─────────────┘

核心流程

  1. 业务服务执行业务操作,同时向消息服务发送预消息

  2. 消息服务将消息状态设为"待确认",返回给业务服务

  3. 业务服务执行业务逻辑,成功后调用消息服务确认消息

  4. 消息服务将消息投递到 MQ

  5. 消费服务消费消息,执行业务操作

消息表设计

CREATE TABLE transaction_message ( id BIGINT PRIMARY KEY AUTO_INCREMENT, message_id VARCHAR(64) NOT NULL UNIQUE, message_body TEXT NOT NULL, destination VARCHAR(255) NOT NULL, status TINYINT NOT NULL DEFAULT 0, -- 0:待发送 1:已发送 2:消费成功 3:消费失败 send_times INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_status_time (status, create_time) );

定时补偿机制

@Component public class MessageRetryTask { @Scheduled(fixedRate = 60000) // 每分钟执行 public void retry() { // 查询待发送和发送失败的消息 List<TransactionMessage> messages = messageService .findByStatusAndTime(MessageStatus.PENDING, 5); for (TransactionMessage msg : messages) { if (msg.getSendTimes() > 3) { // 超过重试次数,人工介入 msg.setStatus(MessageStatus.FAILED); messageService.update(msg); continue; } // 重新发送 rocketMQTemplate.asyncSend(msg.getDestination(), msg.getMessageBody(), new SendCallback() { @Override public void onSuccess(SendResult sendResult) { msg.setStatus(MessageStatus.SENT); messageService.update(msg); } @Override public void onException(Throwable e) { msg.setSendTimes(msg.getSendTimes() + 1); messageService.update(msg); } }); } } }

关键点

  • 消息先落库再发送,保证消息不丢失

  • 定时任务补偿,处理发送失败的消息

  • 消费端幂等处理,避免重复消费

  • 超过重试次数的消息转入死信队列,人工处理


四、服务间调用安全:怎么防止接口被乱调

4.1 问题背景

微服务内部互相调用时,如果不对调用方进行校验,任何一个服务被攻破,都可能直接调用其他服务的内部接口。

4.2 解决方案:内部认证服务

仓库中的fangjia-auth-service实现了服务间调用的认证机制:

@FeignClient(name = "auth-service") public interface AuthClient { @PostMapping("/auth/token") AuthResponse getToken(@RequestBody AuthRequest request); @PostMapping("/auth/verify") boolean verifyToken(@RequestHeader("Authorization") String token); }

调用流程

  1. 服务 A 调用服务 B 前,先向认证服务申请 Token

  2. 服务 A 在请求头中携带 Token 调用服务 B

  3. 服务 B 通过拦截器验证 Token 的有效性

  4. 验证通过才执行业务逻辑

@Component public class AuthInterceptor implements HandlerInterceptor { @Autowired private AuthClient authClient; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getHeader("Authorization"); if (StringUtils.isEmpty(token)) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } boolean valid = authClient.verifyToken(token); if (!valid) { response.setStatus(HttpStatus.FORBIDDEN.value()); return false; } return true; } }

五、项目结构组织:怎么放代码才清晰

5.1 多模块项目结构

参考仓库的组织方式,一个完整的微服务项目可以这样分层:

spring-cloud-project/ ├── api-gateway/ # API 网关 ├── service-common/ # 公共模块(工具类、常量、异常定义) ├── service-api/ # Feign 客户端定义(API SDK) ├── service-auth/ # 认证服务 ├── service-user/ # 用户服务 ├── service-order/ # 订单服务 ├── service-message/ # 消息服务(可靠消息) ├── eureka-server/ # 注册中心 ├── config-server/ # 配置中心 └── pom.xml # 父 POM,统一管理依赖版本

5.2 单个服务的内部结构

service-user/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/example/user/ │ │ │ ├── UserApplication.java │ │ │ ├── controller/ # 对外接口层 │ │ │ ├── service/ # 业务逻辑层 │ │ │ ├── mapper/ # 数据访问层 │ │ │ ├── entity/ # 实体类 │ │ │ ├── dto/ # 数据传输对象 │ │ │ ├── config/ # 配置类 │ │ │ └── feign/ # Feign 客户端(调用其他服务) │ │ └── resources/ │ │ ├── application.yml │ │ ├── application-dev.yml │ │ └── mapper/ # MyBatis XML │ └── test/ └── pom.xml

六、写在最后

微服务架构的落地不是一蹴而就的。从单体到微服务,需要考虑:

  1. 服务拆分是否合理— 拆得太细会增加调用复杂度,拆得太粗失去微服务的意义

  2. 数据库设计— 独立数据库是原则,但数据一致性成为新的挑战

  3. 分布式事务— 没有银弹,根据业务特点选择合适的方案

  4. 服务安全— 内部调用也需要认证,不能假设内网就是安全的

  5. 监控和治理— 服务多了之后,链路追踪、熔断降级、限流等能力必不可少

微服务不是目的,而是手段。不要为了拆分而拆分,根据团队规模和业务复杂度选择合适的架构方案,才是正确的做法。


参考资源

  • 配套源码:https://github.com/yinjihuan/spring-cloud

  • 作者博客:http://cxytiandi.com/blogs/yinjihuan

  • 第一版书籍:《Spring Cloud微服务-全栈技术与案例解析》

  • 第二版书籍:《Spring Cloud微服务 入门 实战与进阶》

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/8 22:00:29

SAP(ERP) 独立需求PIR 从预测到MRP输入业务解析

SAP系统中从“预测”到“MRP输入”的全路径操作的逐步解释&#xff0c;主要是聚焦于计划独立需求&#xff08;PIR&#xff09;交易代码MD61和MD62的应用。我将以结构清晰、逐步展开&#xff0c;完整地梳理和理解整个过程。注意&#xff1a;在实际操作中&#xff0c;请确保需有适…

作者头像 李华
网站建设 2026/6/11 14:36:01

华硕笔记本终极控制神器:G-Helper完整使用指南与配置教程

华硕笔记本终极控制神器&#xff1a;G-Helper完整使用指南与配置教程 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook, Zenbook,…

作者头像 李华
网站建设 2026/6/8 22:00:20

如何快速掌握Umi-OCR:Windows和Linux用户的终极离线文字识别指南

如何快速掌握Umi-OCR&#xff1a;Windows和Linux用户的终极离线文字识别指南 【免费下载链接】Umi-OCR OCR software, free and offline. 开源、免费的离线OCR软件。支持截屏/批量导入图片&#xff0c;PDF文档识别&#xff0c;排除水印/页眉页脚&#xff0c;扫描/生成二维码。内…

作者头像 李华
网站建设 2026/6/8 21:58:54

生鲜小贩年赚300万的秘密:靠代收物业费锁定客户

同一个小区&#xff0c;同样卖菜。别人熬了几年还在保本&#xff0c;他第一年就挣到 300 万。差别不在菜有多新鲜&#xff0c;在于他多干了一件别人懒得干的事——帮物业代收物业费。第一笔钱&#xff1a;57 万净利&#xff0c;一分商品成本都没有物业费难收是老大难。张老板找…

作者头像 李华
网站建设 2026/6/8 21:58:52

PHP变量命名最佳实践

PHP变量命名最佳实践好的命名让代码自文档化。PHP的变量命名有一些约定俗成的规则。今天说说变量命名的常用实践。变量的命名风格。php// camelCase&#xff08;变量名、方法名、函数名&#xff09; $userName 张三; $orderTotal 299.99;function getUserById(int $id): ?ar…

作者头像 李华
网站建设 2026/6/8 21:52:03

网盘限速太折磨?试试这个神奇的网盘直链提取工具

网盘限速太折磨&#xff1f;试试这个神奇的网盘直链提取工具 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云盘 / 天翼云盘 …

作者头像 李华