系列导航:本文是《Spring框架第一性原理》系列的第5篇
- 第1篇:为什么我们需要Spring框架?
- 第2篇:IoC容器:从手动new到自动装配的演进
- 第3篇:AOP:从代码重复到面向切面编程
- 第4篇:Spring Boot:约定优于配置的威力
- 第5篇:Spring Cloud:从单体到微服务的架构演进(本文)
引子:单体应用的困境
场景重现:一个电商系统的演进之路
让我们从一个真实的电商系统的成长历程说起。
第一阶段:创业初期(2015年)
团队规模:5人(2个后端、1个前端、1个产品、1个UI)
技术选型:单体架构 + Spring Boot
系统架构:
┌─────────────────────────────────────┐
│ 电商单体应用 │
│ (monolithic-ecommerce-app) │
├─────────────────────────────────────┤
│ 用户模块 (UserModule) │
│ 商品模块 (ProductModule) │
│ 订单模块 (OrderModule) │
│ 库存模块 (InventoryModule) │
│ 支付模块 (PaymentModule) │
│ 物流模块 (LogisticsModule) │
│ 营销模块 (MarketingModule) │
├─────────────────────────────────────┤
│ Spring Boot + MyBatis + MySQL │
└─────────────────────────────────────┘
↓
MySQL数据库
代码结构:
ecommerce-app/
├── src/main/java/com/example/
│ ├── user/ # 用户模块
│ │ ├── UserController.java
│ │ ├── UserService.java
│ │ └── UserRepository.java
│ ├── product/ # 商品模块
│ │ ├── ProductController.java
│ │ ├── ProductService.java
│ │ └── ProductRepository.java
│ ├── order/ # 订单模块
│ │ ├── OrderController.java
│ │ ├── OrderService.java
│ │ └── OrderRepository.java
│ ├── inventory/ # 库存模块
│ ├── payment/ # 支付模块
│ ├── logistics/ # 物流模块
│ └── marketing/ # 营销模块
└── pom.xml
典型业务流程(创建订单):
@Service
public class OrderService {
@Autowired
private UserService userService;
@Autowired
private ProductService productService;
@Autowired
private InventoryService inventoryService;
@Autowired
private PaymentService paymentService;
@Autowired
private LogisticsService logisticsService;
@Transactional
public Order createOrder(OrderRequest request) {
// 1. 验证用户
User user = userService.getUser(request.getUserId());
if (user == null) {
throw new BusinessException("用户不存在");
}
// 2. 验证商品
Product product = productService.getProduct(request.getProductId());
if (product == null) {
throw new BusinessException("商品不存在");
}
// 3. 扣减库存
boolean stockSuccess = inventoryService.deduct(
request.getProductId(),
request.getQuantity()
);
if (!stockSuccess) {
throw new BusinessException("库存不足");
}
// 4. 创建订单
Order order = new Order();
order.setUserId(request.getUserId());
order.setProductId(request.getProductId());
order.setQuantity(request.getQuantity());
order.setAmount(product.getPrice() * request.getQuantity());
order.setStatus("CREATED");
orderRepository.save(order);
// 5. 调用支付
boolean paySuccess = paymentService.pay(order.getId(), order.getAmount());
if (paySuccess) {
order.setStatus("PAID");
orderRepository.update(order);
// 6. 创建物流订单
logisticsService.createShipment(order.getId());
}
return order;
}
}
这个阶段的特点:
✅ 优势:
├─ 开发简单:所有代码在一个项目中,开发方便
├─ 部署简单:打包成一个JAR,部署一台服务器
├─ 调试简单:本地启动即可调试所有功能
├─ 事务简单:使用@Transactional保证一致性
└─ 性能不错:本地方法调用,无网络开销
❌ 没有明显问题:
├─ 用户量:日活1000+
├─ 并发量:QPS < 100
├─ 团队规模:5人
└─ 代码量:2万行
第二阶段:快速增长期(2016-2017年)
业务增长:
- 日活用户:1000 → 10万
- 日订单量:100 → 5万
- 团队规模:5人 → 30人(6个开发团队)
单体应用开始出现问题:
问题1:部署困境
场景:营销团队开发了一个秒杀功能
需求:紧急上线(晚上8点)
部署流程(单体应用):
1. 营销团队提交代码
2. 合并到主分支
3. 整个应用重新编译
4. 停止现有服务
5. 部署新版本
6. 启动服务(需要5分钟)
7. 验证所有功能(需要30分钟)
问题:
❌ 秒杀功能上线,但影响了所有模块
❌ 部署期间,整个网站不可用(5分钟)
❌ 任何一个模块的bug,都会导致整个应用回滚
❌ 团队之间互相等待(营销等订单、订单等支付)
数据:
├─ 部署频率:每周1次(不敢频繁部署)
├─ 部署时长:30-60分钟
├─ 部署风险:高(牵一发动全身)
└─ 回滚成本:高(整个应用回滚)
问题2:性能瓶颈
场景:618大促,流量激增
系统负载分析:
┌────────────────────────────────────┐
│ 模块 │ QPS │ 资源占用 │
├────────────────────────────────────┤
│ 商品浏览 │ 10000 │ CPU 60% │ ← 高并发
│ 订单创建 │ 500 │ CPU 20% │
│ 支付处理 │ 300 │ CPU 10% │
│ 物流查询 │ 200 │ CPU 5% │
│ 用户中心 │ 100 │ CPU 5% │
└────────────────────────────────────┘
问题:
❌ 商品浏览是高并发场景,需要大量机器
❌ 但订单、支付是低并发,不需要那么多资源
❌ 单体应用无法独立扩展某个模块
❌ 只能整体扩展(浪费资源)
扩展方案(单体应用):
部署10台服务器 × 4核8G = 40核80G
实际需求:
├─ 商品模块:30核 60G(占75%)
├─ 订单模块:5核 10G
├─ 支付模块:3核 6G
└─ 其他模块:2核 4G
浪费:
├─ 订单、支付、物流等模块资源严重浪费
└─ 无法根据模块单独扩展
问题3:技术栈绑定
场景:营销团队想用Python开发推荐算法服务
问题:
❌ 单体应用是Java技术栈
❌ 无法在单体应用中集成Python代码
❌ 只能用Java重写(效率低、效果差)
❌ 或者放弃(错过商机)
团队诉求:
├─ 营销团队:想用Python做推荐算法
├─ 数据团队:想用Go做数据采集
├─ 搜索团队:想用Elasticsearch
└─ 但单体应用无法支持
问题4:代码管理混乱
场景:30人的团队协作开发
代码冲突:
├─ 6个团队共用一个Git仓库
├─ 每天都有代码冲突(合并地狱)
├─ 一个团队的bug影响其他团队
└─ 测试环境经常被其他团队"搞坏"
编译缓慢:
├─ 代码量:2万行 → 20万行
├─ 编译时间:10秒 → 5分钟
├─ 启动时间:30秒 → 3分钟
└─ 开发效率严重下降
依赖复杂:
├─ 订单模块依赖支付模块
├─ 支付模块依赖订单模块(循环依赖)
├─ 营销模块依赖所有模块
└─ 改一个模块,影响所有模块
问题5:数据库瓶颈
场景:所有模块共用一个MySQL数据库
数据库压力:
┌────────────────────────────────────┐
│ 表 │ 数据量 │ QPS │
├────────────────────────────────────┤
│ products │ 100万 │ 10000 │ ← 高并发
│ orders │ 500万 │ 500 │
│ users │ 50万 │ 300 │
│ payments │ 500万 │ 300 │
│ logistics │ 500万 │ 200 │
└────────────────────────────────────┘
总QPS:11300(单库无法支撑)
问题:
❌ 所有表在一个数据库,无法独立扩展
❌ 商品表的高并发拖垮整个数据库
❌ 订单表数据量大,慢查询影响其他表
❌ 无法做垂直拆分(按业务拆库)
单体应用的本质问题
单体应用的五大本质问题:
1. 耦合度高
└─ 所有模块在一个进程中
└─ 任何模块的bug都会影响整个应用
└─ 任何模块的升级都需要重新部署整个应用
2. 扩展性差
└─ 无法独立扩展某个模块
└─ 只能整体扩展(资源浪费)
└─ 无法根据流量特点灵活扩展
3. 技术栈单一
└─ 整个应用必须使用同一技术栈
└─ 无法针对不同场景选择最优技术
└─ 无法引入新技术(风险高)
4. 团队协作困难
└─ 所有团队共用一个代码库
└─ 代码冲突频繁
└─ 编译缓慢(代码量大)
└─ 测试环境互相干扰
5. 部署风险高
└─ 小改动需要重新部署整个应用
└─ 部署时间长(编译+启动)
└─ 部署风险高(牵一发动全身)
└─ 回滚成本高(整个应用回滚)
微服务架构:问题的解决方案
什么是微服务?
微服务的定义:
将一个单体应用拆分成多个小型、独立的服务
每个服务:
├─ 运行在自己的进程中
├─ 使用轻量级通信机制(通常是HTTP/REST或RPC)
├─ 围绕业务能力构建
├─ 可以独立部署
├─ 可以使用不同的技术栈
└─ 由独立的团队维护
微服务 vs 单体应用:
单体应用 = 1个进程 × 所有功能
微服务 = N个进程 × 单一职责
类比:
单体应用 = 瑞士军刀(集成所有功能,但笨重)
微服务 = 工具箱(每个工具专注一件事,灵活组合)
电商系统的微服务拆分
拆分原则:按业务能力划分
拆分策略(DDD领域驱动设计):
1. 识别业务领域
├─ 用户域:用户注册、登录、个人信息
├─ 商品域:商品管理、分类、搜索
├─ 订单域:订单创建、支付、取消
├─ 库存域:库存管理、扣减、回滚
├─ 支付域:支付处理、退款
├─ 物流域:物流跟踪、配送
└─ 营销域:优惠券、促销活动
2. 定义服务边界
├─ 每个服务独立部署
├─ 每个服务有自己的数据库
├─ 服务间通过API通信
└─ 服务内部高内聚
3. 识别依赖关系
├─ 订单服务依赖:用户、商品、库存、支付
├─ 支付服务依赖:订单
└─ 避免循环依赖
微服务架构图
┌─────────────────────────────────────────────────────────┐
│ API网关 (Gateway) │
│ 统一入口、路由、鉴权 │
└─────────────────────────────────────────────────────────┘
│
┌─────────┼─────────────────────────────────┐
│ │ │
┌───▼───┐ ┌──▼────┐ ┌──────┐ ┌───────┐ ┌──────┐ ┌───────┐
│ 用户 │ │ 商品 │ │ 订单 │ │ 库存 │ │ 支付 │ │ 物流 │
│服务 │ │ 服务 │ │ 服务 │ │ 服务 │ │ 服务 │ │ 服务 │
└───┬───┘ └──┬────┘ └──┬───┘ └───┬───┘ └──┬───┘ └───┬───┘
│ │ │ │ │ │
┌───▼───┐ ┌──▼────┐ ┌──▼───┐ ┌──▼────┐ ┌──▼───┐ ┌──▼────┐
│UserDB │ │ProdDB │ │OrderDB│ │InvDB │ │PayDB │ │LogDB │
└───────┘ └───────┘ └──────┘ └───────┘ └──────┘ └───────┘
┌────────────────────┐
│ 注册中心 (Nacos) │
│ 服务发现、配置管理 │
└────────────────────┘
订单服务的微服务实现
// 订单服务(独立应用)
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
public Order createOrder(@RequestBody OrderRequest request) {
return orderService.createOrder(request);
}
}
@Service
public class OrderService {
@Autowired
private UserServiceClient userServiceClient; // 远程调用用户服务
@Autowired
private ProductServiceClient productServiceClient; // 远程调用商品服务
@Autowired
private InventoryServiceClient inventoryServiceClient; // 远程调用库存服务
@Autowired
private PaymentServiceClient paymentServiceClient; // 远程调用支付服务
public Order createOrder(OrderRequest request) {
// 1. 远程调用用户服务验证用户
User user = userServiceClient.getUser(request.getUserId());
if (user == null) {
throw new BusinessException("用户不存在");
}
// 2. 远程调用商品服务验证商品
Product product = productServiceClient.getProduct(request.getProductId());
if (product == null) {
throw new BusinessException("商品不存在");
}
// 3. 远程调用库存服务扣减库存
boolean stockSuccess = inventoryServiceClient.deduct(
request.getProductId(),
request.getQuantity()
);
if (!stockSuccess) {
throw new BusinessException("库存不足");
}
// 4. 创建订单(本地数据库)
Order order = new Order();
order.setUserId(request.getUserId());
order.setProductId(request.getProductId());
order.setQuantity(request.getQuantity());
order.setAmount(product.getPrice() * request.getQuantity());
order.setStatus("CREATED");
orderRepository.save(order);
// 5. 远程调用支付服务
boolean paySuccess = paymentServiceClient.pay(order.getId(), order.getAmount());
if (paySuccess) {
order.setStatus("PAID");
orderRepository.update(order);
}
return order;
}
}
// 用户服务客户端(Feign声明式HTTP客户端)
@FeignClient(name = "user-service")
public interface UserServiceClient {
@GetMapping("/users/{id}")
User getUser(@PathVariable("id") Long id);
}
// 商品服务客户端
@FeignClient(name = "product-service")
public interface ProductServiceClient {
@GetMapping("/products/{id}")
Product getProduct(@PathVariable("id") Long id);
}
// 库存服务客户端
@FeignClient(name = "inventory-service")
public interface InventoryServiceClient {
@PostMapping("/inventory/deduct")
boolean deduct(@RequestParam("productId") Long productId,
@RequestParam("quantity") int quantity);
}
// 支付服务客户端
@FeignClient(name = "payment-service")
public interface PaymentServiceClient {
@PostMapping("/payments/pay")
boolean pay(@RequestParam("orderId") Long orderId,
@RequestParam("amount") BigDecimal amount);
}
微服务带来的好处
对比:单体应用 vs 微服务
1. 部署独立性
单体:修改营销模块 → 重新部署整个应用 → 影响所有用户
微服务:修改营销服务 → 只部署营销服务 → 只影响营销功能
2. 扩展灵活性
单体:商品模块高并发 → 扩展整个应用 → 资源浪费
微服务:商品服务高并发 → 只扩展商品服务 → 资源优化
3. 技术栈自由
单体:整个应用必须用Java
微服务:商品服务用Java、推荐服务用Python、搜索用Go
4. 团队独立性
单体:6个团队共用一个代码库 → 代码冲突频繁
微服务:6个团队各自维护自己的服务 → 独立开发、独立部署
5. 故障隔离
单体:支付模块bug → 整个应用崩溃 → 所有功能不可用
微服务:支付服务bug → 只影响支付 → 其他功能正常
微服务的五大核心问题
虽然微服务解决了单体应用的问题,但也引入了新的复杂度。
问题1:服务发现(Service Discovery)
问题场景
场景:订单服务需要调用用户服务
单体应用:
orderService.userService.getUser(userId);
// 本地方法调用,直接调用即可
微服务:
userServiceClient.getUser(userId);
// 问题:用户服务的IP地址和端口是什么?
困境:
├─ 用户服务可能部署在多台服务器(负载均衡)
│ ├─ 192.168.1.10:8080
│ ├─ 192.168.1.11:8080
│ └─ 192.168.1.12:8080
├─ 服务器可能动态增减(弹性伸缩)
│ └─ 新增服务器、下线服务器
├─ 服务器可能故障(健康检查)
│ └─ 某台服务器宕机,不能再调用
└─ 如何知道可用的服务器列表?
方案1:硬编码(不推荐)
// 硬编码服务地址
@Service
public class OrderService {
public Order createOrder(OrderRequest request) {
// 问题:硬编码IP地址
String userServiceUrl = "http://192.168.1.10:8080";
User user = restTemplate.getForObject(
userServiceUrl + "/users/" + request.getUserId(),
User.class
);
// ...
}
}
问题:
❌ IP地址硬编码,修改需要重新发布
❌ 无法负载均衡(只能调用一台服务器)
❌ 无法感知服务器上下线
❌ 无法做故障转移
方案2:配置文件(部分解决)
# application.yml
services:
user-service:
urls:
- http://192.168.1.10:8080
- http://192.168.1.11:8080
- http://192.168.1.12:8080
@Service
public class OrderService {
@Value("${services.user-service.urls}")
private List<String> userServiceUrls;
private int currentIndex = 0;
public Order createOrder(OrderRequest request) {
// 简单的轮询负载均衡
String url = userServiceUrls.get(currentIndex % userServiceUrls.size());
currentIndex++;
User user = restTemplate.getForObject(
url + "/users/" + request.getUserId(),
User.class
);
// ...
}
}
改进:
✅ IP地址外部化配置
✅ 支持负载均衡(轮询)
仍存在问题:
❌ 修改服务器需要重启应用
❌ 无法感知服务器健康状态
❌ 无法自动剔除故障服务器
方案3:服务注册中心(完美解决)
核心思想:引入第三方组件(注册中心),统一管理所有服务的地址
服务注册中心(如Nacos、Eureka、Consul):
工作流程:
1. 服务启动时,向注册中心注册自己的地址
用户服务A:192.168.1.10:8080 → Nacos
用户服务B:192.168.1.11:8080 → Nacos
用户服务C:192.168.1.12:8080 → Nacos
2. 服务定期发送心跳(健康检查)
用户服务A → Nacos: "我还活着"(每10秒)
3. 注册中心检测服务健康状态
├─ 收到心跳 → 标记为健康
└─ 超过30秒未收到心跳 → 标记为下线
4. 调用方从注册中心获取服务列表
订单服务 → Nacos: "给我用户服务的地址列表"
Nacos → 订单服务: [192.168.1.10:8080, 192.168.1.11:8080]
5. 调用方选择一个服务调用(负载均衡)
订单服务 → 用户服务A(轮询、随机、加权等策略)
6. 服务下线时,从注册中心注销
用户服务A下线 → Nacos移除192.168.1.10:8080
Spring Cloud + Nacos实现:
<!-- 1. 引入Nacos服务发现依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
# 2. 配置Nacos地址
spring:
application:
name: order-service # 服务名称
cloud:
nacos:
discovery:
server-addr: localhost:8848 # Nacos地址
namespace: dev # 命名空间(环境隔离)
group: DEFAULT_GROUP # 分组
// 3. 启动类添加注解
@SpringBootApplication
@EnableDiscoveryClient // 启用服务发现
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
// 4. 使用Feign客户端(自动服务发现 + 负载均衡)
@FeignClient(name = "user-service") // 服务名称(不是IP地址)
public interface UserServiceClient {
@GetMapping("/users/{id}")
User getUser(@PathVariable("id") Long id);
}
// 5. 业务代码(无需关心服务地址)
@Service
public class OrderService {
@Autowired
private UserServiceClient userServiceClient;
public Order createOrder(OrderRequest request) {
// 自动从Nacos获取user-service的地址列表
// 自动负载均衡选择一个服务调用
User user = userServiceClient.getUser(request.getUserId());
// ...
}
}
工作原理:
启动流程:
1. 订单服务启动
2. 订单服务注册到Nacos(名称:order-service,地址:192.168.1.20:8081)
3. 用户服务启动
4. 用户服务注册到Nacos(名称:user-service,地址:192.168.1.10:8080)
调用流程:
1. 订单服务调用userServiceClient.getUser(123)
2. Feign拦截调用,向Nacos查询user-service的地址列表
3. Nacos返回:[192.168.1.10:8080, 192.168.1.11:8080, 192.168.1.12:8080]
4. 负载均衡器选择一个地址(例如:192.168.1.10:8080)
5. 发起HTTP请求:GET http://192.168.1.10:8080/users/123
6. 用户服务返回结果
7. Feign返回给订单服务
故障转移:
1. 用户服务A(192.168.1.10:8080)宕机
2. Nacos检测到心跳超时(30秒)
3. Nacos从服务列表移除192.168.1.10:8080
4. 订单服务下次调用时,只会获取到[192.168.1.11:8080, 192.168.1.12:8080]
5. 自动故障转移,不会调用到故障服务器
问题2:配置管理(Configuration Management)
问题场景
场景:订单服务需要连接MySQL数据库
单体应用:
application.yml(1个配置文件)
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: 123456
微服务:
订单服务(application.yml)
商品服务(application.yml)
用户服务(application.yml)
支付服务(application.yml)
库存服务(application.yml)
物流服务(application.yml)
... 共10个服务 × 3个环境(开发、测试、生产)= 30个配置文件
困境:
├─ 配置分散:30个配置文件,管理复杂
├─ 配置重复:数据库连接、Redis地址等配置在多个服务重复
├─ 配置不一致:开发环境配置和生产环境不一致
├─ 配置修改困难:修改Redis地址,需要改10个服务的配置文件
└─ 配置变更需要重启:修改配置后需要重启服务(停机)
方案1:配置文件(传统方式)
问题示例:修改Redis地址
步骤:
1. 修改订单服务的application.yml
2. 修改商品服务的application.yml
3. 修改用户服务的application.yml
... 修改10个服务的配置文件
4. 重启所有服务(停机10-30分钟)
问题:
❌ 修改繁琐:需要改10个配置文件
❌ 容易遗漏:某个服务忘记改,导致连接失败
❌ 需要重启:配置修改后必须重启服务
❌ 无版本控制:无法回滚到之前的配置
❌ 无权限控制:任何人都可以修改配置
方案2:配置中心(完美解决)
核心思想:引入配置中心(如Nacos、Apollo、Spring Cloud Config),集中管理所有服务的配置
配置中心(Nacos Config):
工作流程:
1. 配置存储在Nacos
├─ 公共配置(common):所有服务共用
│ ├─ Redis地址:redis://localhost:6379
│ ├─ 日志级别:INFO
│ └─ 监控地址:http://monitor:9090
└─ 服务专属配置(order-service):
├─ 数据库地址:jdbc:mysql://localhost:3306/order_db
└─ 支付超时时间:30秒
2. 服务启动时,从Nacos加载配置
订单服务启动 → Nacos获取配置 → 应用配置 → 启动完成
3. 配置动态刷新(无需重启)
├─ 管理员在Nacos修改Redis地址
├─ Nacos推送配置变更通知
├─ 订单服务收到通知
├─ 订单服务刷新配置
└─ 新的Redis地址生效(无需重启)
4. 配置版本管理
├─ 每次修改配置,Nacos记录版本
├─ 支持回滚到历史版本
└─ 支持灰度发布(部分服务使用新配置)
5. 权限控制
├─ 开发人员:只能读取配置
├─ 运维人员:可以修改配置
└─ 审计日志:记录谁在什么时候修改了什么配置
Spring Cloud + Nacos Config实现:
<!-- 1. 引入Nacos配置管理依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
# 2. bootstrap.yml(优先级高于application.yml)
spring:
application:
name: order-service
cloud:
nacos:
config:
server-addr: localhost:8848 # Nacos地址
namespace: dev # 命名空间
group: DEFAULT_GROUP # 分组
file-extension: yaml # 配置文件格式
shared-configs: # 共享配置
- data-id: common.yaml # 公共配置
refresh: true # 支持动态刷新
// 3. 使用@RefreshScope支持动态刷新
@RestController
@RefreshScope // 配置变更时自动刷新此Bean
public class OrderController {
@Value("${redis.host}")
private String redisHost; // 从Nacos读取配置
@Value("${order.timeout}")
private int orderTimeout;
@GetMapping("/config")
public Map<String, Object> getConfig() {
Map<String, Object> config = new HashMap<>();
config.put("redisHost", redisHost);
config.put("orderTimeout", orderTimeout);
return config;
}
}
# 4. 在Nacos创建配置文件(通过Web UI)
# Data ID: order-service.yaml
# Group: DEFAULT_GROUP
# 内容:
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/order_db
username: root
password: ${DB_PASSWORD} # 支持环境变量
redis:
host: localhost
port: 6379
order:
timeout: 30
# Data ID: common.yaml(公共配置)
# 内容:
logging:
level:
root: INFO
com.example: DEBUG
management:
endpoints:
web:
exposure:
include: "*"
配置变更流程:
场景:修改Redis地址(从localhost改为redis-server)
传统方式:
1. 修改10个服务的application.yml
2. 重启10个服务
3. 总耗时:30分钟
4. 停机时间:30分钟
Nacos Config方式:
1. 在Nacos Web UI修改common.yaml
redis.host: localhost → redis-server
2. 点击"发布"按钮
3. Nacos推送配置变更通知给所有订阅此配置的服务
4. 所有服务自动刷新配置(@RefreshScope)
5. 新的Redis地址生效
6. 总耗时:10秒
7. 停机时间:0秒
对比:
✅ 修改集中:只需在Nacos改1次
✅ 无需重启:配置动态刷新
✅ 生效迅速:10秒内生效
✅ 零停机:不影响服务运行
问题3:负载均衡(Load Balancing)
问题场景
场景:用户服务部署了3台服务器
用户服务实例:
├─ 实例A:192.168.1.10:8080(CPU 80%)
├─ 实例B:192.168.1.11:8080(CPU 20%)
└─ 实例C:192.168.1.12:8080(CPU 30%)
问题:
订单服务调用用户服务时,应该选择哪个实例?
├─ 如果总是调用实例A → 实例A负载过高,可能崩溃
├─ 如果随机调用 → 可能仍然不均衡
└─ 如何做到负载均衡?
负载均衡策略
// Spring Cloud LoadBalancer提供的负载均衡策略
// 1. 轮询策略(Round Robin)- 默认
public class RoundRobinLoadBalancer {
private AtomicInteger position = new AtomicInteger(0);
public ServiceInstance choose(List<ServiceInstance> instances) {
int pos = position.incrementAndGet() % instances.size();
return instances.get(pos);
}
}
// 调用顺序:A → B → C → A → B → C ...
// 优点:简单、公平
// 缺点:不考虑服务器性能差异
// 2. 随机策略(Random)
public class RandomLoadBalancer {
private Random random = new Random();
public ServiceInstance choose(List<ServiceInstance> instances) {
int index = random.nextInt(instances.size());
return instances.get(index);
}
}
// 调用顺序:随机选择
// 优点:简单、分散
// 缺点:可能不均衡
// 3. 加权轮询策略(Weighted Round Robin)
public class WeightedRoundRobinLoadBalancer {
public ServiceInstance choose(List<ServiceInstance> instances) {
// 根据服务器性能设置权重
// 实例A:权重1(性能差)
// 实例B:权重2(性能好)
// 实例C:权重3(性能最好)
// 调用顺序:C → C → C → B → B → A
}
}
// 优点:考虑服务器性能差异
// 缺点:需要手动配置权重
// 4. 最少活跃连接策略(Least Active)
public class LeastActiveLoadBalancer {
public ServiceInstance choose(List<ServiceInstance> instances) {
// 选择当前活跃连接数最少的服务器
// 实例A:10个活跃连接
// 实例B:5个活跃连接 ← 选择B
// 实例C:8个活跃连接
}
}
// 优点:动态感知服务器负载
// 缺点:需要额外维护连接数信息
// 5. 一致性哈希策略(Consistent Hash)
public class ConsistentHashLoadBalancer {
public ServiceInstance choose(List<ServiceInstance> instances, Object key) {
// 根据key(如用户ID)计算哈希值
// 相同key总是路由到同一服务器
// 用户123 → 实例A
// 用户456 → 实例B
}
}
// 优点:相同key总是路由到同一服务器(适合会话保持)
// 缺点:服务器增减时会导致部分key路由变化
Spring Cloud LoadBalancer配置:
# application.yml
spring:
cloud:
loadbalancer:
retry:
enabled: true # 启用重试
max-retries: 3 # 最大重试次数
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule # 轮询策略
// 自定义负载均衡策略
@Configuration
public class LoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
name
);
}
}
问题4:熔断降级(Circuit Breaker & Fallback)
问题场景
场景:订单服务调用支付服务
正常情况:
订单服务 → 支付服务(100ms响应)→ 返回成功
异常情况1:支付服务响应慢
订单服务 → 支付服务(5秒响应)→ 订单服务等待5秒 → 用户体验差
异常情况2:支付服务宕机
订单服务 → 支付服务(连接超时30秒)→ 订单服务等待30秒 → 用户体验极差
雪崩效应:
1. 支付服务响应慢
2. 订单服务线程阻塞(等待支付服务响应)
3. 订单服务线程池耗尽(所有线程都在等待)
4. 订单服务无法响应新请求
5. 订单服务崩溃
6. 依赖订单服务的服务也崩溃
7. 整个系统雪崩
问题本质:
服务之间的依赖关系形成了脆弱的链条
任何一个环节出问题,都可能导致整个链条崩溃
解决方案:熔断降级
熔断器(Circuit Breaker)原理:
熔断器三种状态:
1. 关闭状态(Closed)- 正常状态
├─ 所有请求正常通过
├─ 统计错误率
└─ 错误率超过阈值(如50%)→ 进入打开状态
2. 打开状态(Open)- 熔断状态
├─ 所有请求直接失败(不调用远程服务)
├─ 快速失败(不等待超时)
├─ 返回降级结果
└─ 等待一段时间(如60秒)→ 进入半开状态
3. 半开状态(Half-Open)- 尝试恢复
├─ 允许少量请求通过
├─ 如果请求成功 → 进入关闭状态(恢复正常)
└─ 如果请求失败 → 进入打开状态(继续熔断)
示例:
时间 状态 请求1 请求2 请求3 请求4 请求5
10:00 关闭 ✅ ✅ ✅ ✅ ✅
10:01 关闭 ❌ ❌ ❌ ✅ ✅ 错误率40%(未达阈值)
10:02 关闭 ❌ ❌ ❌ ❌ ❌ 错误率100%(超过阈值)
10:02 打开 ⚡ ⚡ ⚡ ⚡ ⚡ 所有请求直接失败(快速失败)
11:02 半开 ✅ ✅ ❌ - - 允许3个请求通过
11:02 打开 ⚡ ⚡ ⚡ ⚡ ⚡ 仍有失败,继续熔断
12:02 半开 ✅ ✅ ✅ - - 3个请求全部成功
12:02 关闭 ✅ ✅ ✅ ✅ ✅ 恢复正常
降级(Fallback):
降级的本质:当服务不可用时,返回一个默认值,而不是让用户等待或报错
示例:订单服务调用支付服务
正常:
订单服务 → 支付服务 → 返回支付结果(成功/失败)
支付服务不可用(熔断):
订单服务 → 熔断器(直接失败)→ 降级逻辑 → 返回默认值("支付服务暂时不可用,请稍后重试")
降级策略:
1. 返回默认值
├─ 支付服务:返回"支付失败,请稍后重试"
├─ 推荐服务:返回热门商品列表(而不是个性化推荐)
└─ 评论服务:返回空列表(而不是报错)
2. 返回缓存数据
├─ 商品服务:返回缓存的商品信息
└─ 库存服务:返回缓存的库存数量
3. 降级到简化逻辑
├─ 订单服务:跳过积分计算、优惠券验证等非核心逻辑
└─ 用户服务:跳过会员等级计算
Sentinel实现熔断降级:
<!-- 1. 引入Sentinel依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
# 2. 配置Sentinel
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 # Sentinel控制台地址
port: 8719 # 与控制台通信的端口
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: sentinel-rules
groupId: DEFAULT_GROUP
rule-type: flow # 流控规则
// 3. 定义降级逻辑
@Service
public class OrderService {
@Autowired
private PaymentServiceClient paymentServiceClient;
@SentinelResource(
value = "createOrder", // 资源名称
fallback = "createOrderFallback", // 降级方法
blockHandler = "createOrderBlockHandler" // 熔断方法
)
public Order createOrder(OrderRequest request) {
// 正常业务逻辑
// ...
// 调用支付服务
boolean paySuccess = paymentServiceClient.pay(order.getId(), order.getAmount());
// ...
}
// 降级方法(服务异常时调用)
public Order createOrderFallback(OrderRequest request, Throwable ex) {
log.error("订单创建失败,进入降级逻辑", ex);
// 返回默认订单(状态为"待支付")
Order order = new Order();
order.setStatus("PENDING_PAYMENT");
order.setMessage("支付服务暂时不可用,请稍后重试");
return order;
}
// 熔断方法(触发限流/熔断时调用)
public Order createOrderBlockHandler(OrderRequest request, BlockException ex) {
log.warn("订单创建被限流/熔断", ex);
// 返回提示信息
Order order = new Order();
order.setStatus("BLOCKED");
order.setMessage("当前订单量过大,请稍后重试");
return order;
}
}
// 4. Feign集成Sentinel(自动熔断)
@FeignClient(
name = "payment-service",
fallback = PaymentServiceFallback.class // 降级实现类
)
public interface PaymentServiceClient {
@PostMapping("/payments/pay")
boolean pay(@RequestParam("orderId") Long orderId,
@RequestParam("amount") BigDecimal amount);
}
// 降级实现类
@Component
public class PaymentServiceFallback implements PaymentServiceClient {
@Override
public boolean pay(Long orderId, BigDecimal amount) {
log.warn("支付服务不可用,返回降级结果");
// 返回失败(触发重试或人工处理)
return false;
}
}
// 5. 在Sentinel控制台配置熔断规则(通过Web UI或代码)
// 规则示例:
DegradeRule rule = new DegradeRule();
rule.setResource("createOrder"); // 资源名称
rule.setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType()); // 按错误率熔断
rule.setCount(0.5); // 错误率阈值50%
rule.setTimeWindow(60); // 熔断时长60秒
rule.setMinRequestAmount(10); // 最小请求数(请求数少于10不熔断)
rule.setStatIntervalMs(10000); // 统计时长10秒
DegradeRuleManager.loadRules(Collections.singletonList(rule));
熔断降级效果:
场景:支付服务宕机
没有熔断降级:
10:00 - 订单1 → 支付服务(超时30秒)→ 失败
10:00 - 订单2 → 支付服务(超时30秒)→ 失败
10:00 - 订单3 → 支付服务(超时30秒)→ 失败
...
10:05 - 订单服务线程池耗尽 → 订单服务崩溃
结果:
❌ 用户等待时间长(30秒)
❌ 大量线程阻塞
❌ 订单服务崩溃
有熔断降级:
10:00 - 订单1 → 支付服务(超时30秒)→ 失败
10:00 - 订单2 → 支付服务(超时30秒)→ 失败
10:00 - 订单3 → 支付服务(超时30秒)→ 失败
10:00 - 错误率达到50%,触发熔断
10:00 - 订单4 → 熔断器(快速失败100ms)→ 降级逻辑 → 返回"支付服务暂时不可用"
10:00 - 订单5 → 熔断器(快速失败100ms)→ 降级逻辑 → 返回"支付服务暂时不可用"
...
11:00 - 熔断器进入半开状态,尝试恢复
结果:
✅ 用户等待时间短(100ms)
✅ 没有线程阻塞
✅ 订单服务正常运行
✅ 给用户友好提示
问题5:链路追踪(Distributed Tracing)
问题场景
场景:用户反馈"下单很慢"
单体应用:
├─ 查看日志:[OrderService] createOrder executed in 5000ms
├─ 找到慢的方法:inventoryService.deduct()耗时4800ms
└─ 优化库存扣减逻辑
微服务:
用户请求 → API网关 → 订单服务 → 用户服务 → 商品服务 → 库存服务 → 支付服务
↑ ↑ ↑ ↑
100ms 200ms 300ms 4800ms(慢)
问题:
❌ 如何知道整个调用链路?
❌ 如何知道每个服务的耗时?
❌ 如何定位慢在哪个服务?
❌ 如何关联不同服务的日志?
解决方案:分布式链路追踪
核心概念:
1. Trace(跟踪):一次完整的请求链路
用户请求 → API网关 → 订单服务 → 用户服务 → 商品服务 → 库存服务 → 支付服务
└─ TraceID: abc123(唯一标识这次请求)
2. Span(跨度):链路中的一个节点
├─ Span1: API网关(耗时10ms)
├─ Span2: 订单服务(耗时5200ms)
│ ├─ Span3: 调用用户服务(耗时100ms)
│ ├─ Span4: 调用商品服务(耗时200ms)
│ ├─ Span5: 调用库存服务(耗时4800ms)← 找到瓶颈
│ └─ Span6: 调用支付服务(耗时100ms)
└─ 每个Span包含:
├─ SpanID: 唯一标识
├─ ParentSpanID: 父Span的ID
├─ ServiceName: 服务名称
├─ OperationName: 操作名称
├─ StartTime: 开始时间
├─ Duration: 持续时间
└─ Tags: 标签(HTTP方法、URL、状态码等)
3. 日志关联
订单服务日志:[TraceID=abc123] [SpanID=span2] OrderService.createOrder
用户服务日志:[TraceID=abc123] [SpanID=span3] UserService.getUser
库存服务日志:[TraceID=abc123] [SpanID=span5] InventoryService.deduct
└─ 通过TraceID关联所有服务的日志
SkyWalking实现链路追踪:
<!-- 1. 引入SkyWalking依赖 -->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
<version>8.12.0</version>
</dependency>
# 2. 配置SkyWalking Agent(JVM启动参数)
java -javaagent:/path/to/skywalking-agent.jar \
-Dskywalking.agent.service_name=order-service \
-Dskywalking.collector.backend_service=localhost:11800 \
-jar order-service.jar
// 3. 业务代码(无侵入,自动追踪)
@Service
public class OrderService {
@Autowired
private UserServiceClient userServiceClient;
@Autowired
private ProductServiceClient productServiceClient;
@Autowired
private InventoryServiceClient inventoryServiceClient;
public Order createOrder(OrderRequest request) {
// SkyWalking自动追踪所有方法调用
// 无需修改代码
User user = userServiceClient.getUser(request.getUserId());
Product product = productServiceClient.getProduct(request.getProductId());
boolean stockSuccess = inventoryServiceClient.deduct(request.getProductId(), request.getQuantity());
// ...
}
}
// 4. 自定义追踪(可选)
@Service
public class OrderService {
@Trace // 手动标记需要追踪的方法
@Tags({
@Tag(key = "orderId", value = "arg[0].orderId"),
@Tag(key = "userId", value = "arg[0].userId")
})
public Order createOrder(OrderRequest request) {
// 添加自定义标签
TraceContext.putCorrelation("orderType", request.getType());
// 业务逻辑
// ...
}
}
SkyWalking UI界面:
1. 拓扑图(Topology):
显示所有服务的调用关系
[API网关] → [订单服务] → [用户服务]
↓
[商品服务]
↓
[库存服务]
↓
[支付服务]
2. 追踪详情(Trace):
TraceID: abc123
总耗时: 5210ms
└─ API网关 (10ms)
└─ 订单服务 (5200ms)
├─ 用户服务 (100ms) ✅ 快
├─ 商品服务 (200ms) ✅ 快
├─ 库存服务 (4800ms) ❌ 慢(瓶颈)
└─ 支付服务 (100ms) ✅ 快
3. 日志查询:
输入TraceID: abc123
返回所有相关日志:
├─ [订单服务] OrderService.createOrder started
├─ [用户服务] UserService.getUser userId=123
├─ [商品服务] ProductService.getProduct productId=456
├─ [库存服务] InventoryService.deduct slow query detected
└─ [支付服务] PaymentService.pay orderId=789
4. 性能分析:
├─ 平均响应时间:200ms
├─ P95响应时间:500ms
├─ P99响应时间:2000ms
└─ 错误率:0.1%
5. 慢请求分析:
├─ 最慢的10个请求
├─ 慢在哪个服务
└─ 慢在哪个方法
微服务拆分的第一性原理
康威定律(Conway’s Law)
定律内容:
"组织架构决定系统架构"
"System design is a reflection of organizational structure"
含义:
├─ 如果一个组织有4个团队开发编译器
├─ 那么最终会得到一个4-pass编译器
└─ 因为每个团队负责一个pass
应用到微服务:
├─ 团队结构 → 服务边界
├─ 如果有用户团队、商品团队、订单团队
├─ 那么应该拆分为用户服务、商品服务、订单服务
└─ 每个团队独立维护自己的服务
反康威定律(逆向康威定律):
├─ 如果先设计好微服务架构
├─ 然后按照服务边界组织团队
└─ 可以更好地发挥微服务的优势
实践建议:
✅ 一个服务 = 一个团队(或小组)
✅ 团队规模:3-9人(2个pizza原则)
✅ 团队全栈:前端 + 后端 + 数据库 + 运维
❌ 避免:多个团队维护同一个服务
❌ 避免:一个团队维护多个服务(超过3个)
领域驱动设计(DDD)
DDD核心概念:
1. 限界上下文(Bounded Context)
└─ 一个明确的边界,在边界内部:
├─ 术语有唯一的含义
├─ 规则是一致的
└─ 模型是完整的
示例:电商系统的"商品"
├─ 在商品上下文:商品 = 名称 + 价格 + 库存 + 描述
├─ 在订单上下文:商品 = ID + 名称 + 价格(不关心库存)
├─ 在物流上下文:商品 = ID + 重量 + 体积(不关心价格)
└─ 每个上下文对"商品"的理解不同,这是正常的
2. 聚合根(Aggregate Root)
└─ 一组相关对象的根实体
├─ 订单聚合根:Order(包含OrderItem)
├─ 用户聚合根:User(包含Address)
└─ 商品聚合根:Product(包含SKU)
3. 领域事件(Domain Event)
└─ 领域内发生的重要事件
├─ 订单已创建(OrderCreated)
├─ 订单已支付(OrderPaid)
└─ 订单已发货(OrderShipped)
4. 服务拆分原则
└─ 一个限界上下文 = 一个微服务(或一组微服务)
├─ 用户上下文 → 用户服务
├─ 商品上下文 → 商品服务
├─ 订单上下文 → 订单服务
└─ 库存上下文 → 库存服务
DDD拆分实践:
电商系统领域模型:
核心域(Core Domain):订单域
├─ 订单服务(Order Service)
├─ 支付服务(Payment Service)
└─ 库存服务(Inventory Service)
核心业务逻辑,最复杂,需要最优秀的团队
支撑域(Supporting Domain):用户域、商品域
├─ 用户服务(User Service)
├─ 商品服务(Product Service)
└─ 物流服务(Logistics Service)
支撑核心业务,重要但不复杂
通用域(Generic Domain):营销域、通知域
├─ 营销服务(Marketing Service)
├─ 通知服务(Notification Service)
└─ 搜索服务(Search Service)
通用功能,可以使用第三方服务
拆分依据:
✅ 业务重要性:核心域优先拆分
✅ 变更频率:经常变更的优先拆分
✅ 性能要求:高并发的优先拆分
✅ 团队结构:按团队拆分
❌ 避免:按技术层次拆分(如Controller层服务、Service层服务)
❌ 避免:拆分过细(增加维护成本)
微服务拆分步骤
第一步:识别业务领域
├─ 分析业务流程
├─ 识别核心实体
├─ 定义限界上下文
└─ 绘制上下文地图
第二步:定义服务边界
├─ 一个限界上下文 = 一个微服务
├─ 识别服务依赖关系
├─ 避免循环依赖
└─ 定义服务接口(API)
第三步:拆分数据库
├─ 每个服务独立数据库
├─ 服务间不直接访问数据库
├─ 通过API通信
└─ 处理分布式事务
第四步:迁移策略
├─ 先拆分新功能(增量式)
├─ 再拆分老功能(绞杀者模式)
├─ 逐步迁移流量
└─ 保留回滚能力
第五步:团队组织
├─ 按服务组织团队
├─ 团队全栈(前后端+运维)
├─ 明确团队职责
└─ 建立团队协作机制
微服务的代价与权衡
微服务引入的复杂度
1. 分布式系统复杂度
├─ 网络不可靠:调用可能失败、超时
├─ 网络延迟:远程调用比本地调用慢100倍
├─ 数据一致性:分布式事务难以保证
└─ 调试困难:跨服务调试、日志分散
2. 运维复杂度
├─ 服务数量增加:10个服务 × 3个环境 = 30个实例
├─ 部署复杂:需要CI/CD自动化
├─ 监控复杂:需要统一监控平台
└─ 配置管理:配置文件数量激增
3. 开发复杂度
├─ 服务间通信:需要定义API、序列化/反序列化
├─ 服务依赖:需要管理依赖关系
├─ 本地开发:需要启动多个服务
└─ 集成测试:需要搭建完整环境
4. 组织复杂度
├─ 团队协作:需要跨团队沟通
├─ 接口变更:需要版本管理
├─ 服务治理:需要建立规范
└─ 技能要求:团队需要全栈能力
何时使用微服务?
✅ 适合使用微服务的场景:
1. 团队规模大(30人+)
└─ 团队大,需要独立开发、独立部署
2. 业务复杂度高
└─ 业务模块多,耦合度高,难以维护
3. 并发要求高
└─ 不同模块的并发差异大,需要独立扩展
4. 技术栈多样
└─ 不同业务需要不同技术栈
5. 频繁部署
└─ 需要持续集成、持续部署
❌ 不适合使用微服务的场景:
1. 团队规模小(<10人)
└─ 团队小,单体应用更高效
2. 业务简单
└─ 业务逻辑简单,无需拆分
3. 初创项目
└─ 需求变化快,过早拆分增加成本
4. 并发要求低
└─ QPS < 1000,单体应用足够
5. 运维能力弱
└─ 没有DevOps能力,无法管理大量服务
渐进式演进路径
阶段1:单体应用(0-10人)
├─ 快速开发、快速迭代
├─ 验证产品方向
└─ 积累业务复杂度
阶段2:单体应用 + 少量微服务(10-30人)
├─ 拆分高并发模块(如商品服务)
├─ 拆分频繁变更模块(如营销服务)
└─ 保留单体应用的核心逻辑
阶段3:微服务为主(30-100人)
├─ 核心业务全部微服务化
├─ 建立完整的服务治理体系
└─ 团队按服务组织
阶段4:平台化(100人+)
├─ 服务平台化(提供给第三方)
├─ 中台架构(业务中台、数据中台)
└─ 服务网格(Istio)
建议:
✅ 从单体开始,根据需要逐步演进
✅ 不要过早优化,不要盲目追求微服务
✅ 考虑团队能力、业务复杂度、运维成本
❌ 避免为了微服务而微服务
总结:微服务架构的核心价值
微服务解决的核心问题
单体应用的问题 → 微服务的解决方案:
1. 部署困难 → 独立部署
├─ 单体:修改任何模块都需要重新部署整个应用
└─ 微服务:只需部署修改的服务
2. 扩展困难 → 独立扩展
├─ 单体:只能整体扩展,资源浪费
└─ 微服务:按需扩展高并发服务
3. 技术栈单一 → 技术栈自由
├─ 单体:整个应用必须用同一技术栈
└─ 微服务:每个服务可以用不同技术栈
4. 团队协作困难 → 团队独立
├─ 单体:所有团队共用一个代码库
└─ 微服务:每个团队维护自己的服务
5. 故障影响大 → 故障隔离
├─ 单体:任何模块崩溃都影响整个应用
└─ 微服务:故障只影响单个服务
Spring Cloud组件总结
Spring Cloud生态系统:
1. 服务注册与发现
└─ Nacos、Eureka、Consul
2. 配置管理
└─ Nacos Config、Apollo、Spring Cloud Config
3. 负载均衡
└─ Spring Cloud LoadBalancer、Ribbon
4. 服务调用
└─ OpenFeign、RestTemplate
5. 熔断降级
└─ Sentinel、Hystrix、Resilience4j
6. API网关
└─ Spring Cloud Gateway、Zuul
7. 链路追踪
└─ SkyWalking、Zipkin、Jaeger
8. 服务监控
└─ Spring Boot Actuator、Prometheus
完整技术栈:
├─ 注册中心:Nacos
├─ 配置中心:Nacos Config
├─ 服务调用:OpenFeign + LoadBalancer
├─ 熔断降级:Sentinel
├─ API网关:Spring Cloud Gateway
├─ 链路追踪:SkyWalking
├─ 服务监控:Prometheus + Grafana
└─ 日志收集:ELK Stack
给从业者的建议
L1(初级):理解微服务基本概念
├─ 掌握服务注册与发现(Nacos)
├─ 掌握配置管理(Nacos Config)
├─ 掌握服务调用(OpenFeign)
└─ 能搭建简单的微服务系统
L2(中级):掌握服务治理
├─ 掌握熔断降级(Sentinel)
├─ 掌握API网关(Gateway)
├─ 掌握链路追踪(SkyWalking)
├─ 掌握分布式事务(Seata)
└─ 能设计复杂的微服务系统
L3(高级):微服务架构设计
├─ 掌握DDD领域驱动设计
├─ 掌握微服务拆分原则
├─ 掌握服务网格(Istio)
├─ 掌握云原生架构
└─ 能主导企业级微服务改造
学习路径:
单体应用 → 简单微服务 → 服务治理 → 架构设计
结语
微服务架构通过"分而治之"的思想,将复杂的单体应用拆分成多个小型、独立的服务,从而解决了单体应用在可扩展性、技术栈选择、团队协作等方面的问题。
核心要点回顾:
- 服务注册与发现:Nacos统一管理服务地址,支持动态上下线
- 配置管理:Nacos Config集中管理配置,支持动态刷新
- 负载均衡:多种策略(轮询、随机、加权等),自动故障转移
- 熔断降级:Sentinel保护系统,防止雪崩效应
- 链路追踪:SkyWalking追踪调用链路,快速定位性能瓶颈
关键收获:
- 从第一性原理理解微服务的设计动机
- 掌握微服务的五大核心问题及解决方案
- 理解DDD和康威定律在微服务拆分中的应用
- 认识到微服务的代价,避免过度设计
下一篇预告: 第6篇:Spring源码深度解析
- 如何阅读Spring源码
- IoC容器启动流程源码剖析
- AOP代理创建流程源码剖析
- Spring设计模式精华
系列文章:
- 为什么我们需要Spring框架?
- IoC容器:从手动new到自动装配的演进
- AOP:从代码重复到面向切面编程
- Spring Boot:约定优于配置的威力
- Spring Cloud:从单体到微服务的架构演进(本文)
- Spring源码深度解析(待发布)
🤔 思考题:
- 你的项目适合使用微服务架构吗?为什么?
- 如何判断一个单体应用是否应该拆分为微服务?
- 微服务的"服务边界"应该如何划分?
欢迎在评论区分享你的思考和经验!
📚 推荐阅读:
- Spring Cloud官方文档
- Spring Cloud Alibaba文档
- 《微服务架构设计模式》- Chris Richardson
- 《领域驱动设计》- Eric Evans