第 35 章:架构演进
学习目标
- 掌握架构演进的 5 个阶段
- 学会从单体到微服务的演进路径
- 理解云原生与 Service Mesh 趋势
- 避免过早演进、过度技术的坑
一、架构演进 5 阶段
text
阶段 1:单体应用
阶段 2:垂直应用
阶段 3:SOA 面向服务
阶段 4:微服务
阶段 5:云原生 / Service Meshjava
// 阶段 1:单体
com.example.app
├── controller
├── service
└── dao
// 1 个 WAR,1 个数据库
// 阶段 2:垂直(按业务)
com.example.app
├── user-module/
├── order-module/
└── payment-module/
// 多个 WAR,共享数据库
// 阶段 4:微服务
user-service, order-service, payment-service
// 独立部署,独立数据库二、单体到微服务的演进
text
阶段 1:单体
↓ 拆分时机
阶段 2:模块化单体
- 内部模块化,边界清晰
- 公共代码收敛
↓ 业务压力
阶段 3:独立部署
- 把用户、认证独立
- 不引入分布式事务
↓ 团队规模
阶段 4:微服务拆分
- 拆核心业务
- 引入分布式能力
↓ 云原生
阶段 5:Service Mesh
- 服务网格化
- 治理与业务分离⚠️ 坑 1:"上来就拆微服务" → 50% 项目死在该阶段。先模块化单体,再独立部署,最后微服务。
三、模块化单体
java
// 1. 包结构
com.example.ecommerce
├── user/ // 用户模块
│ ├── UserApplicationService
│ ├── UserDomainService
│ └── UserRepository
├── order/ // 订单模块
│ ├── OrderApplicationService
│ └── OrderRepository
└── shared/ // 共享代码
└── common/java
// 2. 模块间调用走接口(便于后续拆服务)
public interface OrderService {
OrderDto getOrder(Long id);
}
@org.springframework.stereotype.Service
public class OrderServiceImpl implements OrderService {
public OrderDto getOrder(Long id) {
return orderRepository.findById(id).orElseThrow();
}
}
// 3. 编排层(后续可换 RPC)
@Service
public class OrderFacade {
@Resource private OrderService orderService;
@Resource private UserService userService;
public OrderDetail getOrderDetail(Long orderId) {
OrderDto order = orderService.getOrder(orderId);
UserDto user = userService.getUser(order.getUserId());
return new OrderDetail(order, user);
}
}java
// 4. 未来拆服务时,接口保留,实现换成 RPC
@org.springframework.stereotype.Service
public class OrderServiceImpl implements OrderService {
@Resource private OrderClient orderClient; // Feign 客户端
public OrderDto getOrder(Long id) {
return orderClient.getOrder(id);
}
}
// 上层调用方零改动四、独立部署
java
// Spring Boot 单 jar 部署
@SpringBootApplication
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
// Dockerfile
FROM eclipse-temurin:17-jre-alpine
COPY target/user-service.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]yaml
# k8s 部署
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: user-service:1.0.0
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi五、服务拆分策略
java
// 1. 优先拆:无状态、无分布式事务
user-service, auth-service, notification-service
// 2. 中期:有一定业务复杂度
product-service, shopping-cart-service
// 3. 最后:分布式事务重的
order-service, payment-service, inventory-servicejava
// 拆分示意
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/api/users/{id}")
UserDto getUser(@PathVariable Long id);
}
@Service
public class OrderService {
@Resource private UserClient userClient;
public OrderDetail getDetail(Long orderId) {
Order order = orderRepository.findById(orderId);
UserDto user = userClient.getUser(order.getBuyerId());
return new OrderDetail(order, user);
}
}六、Service Mesh
text
传统微服务
├── 业务代码(Spring Cloud)
├── 服务发现(Nacos)
├── 熔断(Sentinel)
├── 限流(Sentinel)
└── 监控(自研)
Service Mesh(Istio)
├── 业务代码(只管业务)
├── 服务发现(Envoy)
├── 熔断(Envoy)
├── 限流(Envoy)
└── 监控(Envoy)yaml
# Istio 限流(代替 Sentinel)
apiVersion: networking.istio.io/v1beta1
kind: EnvoyFilter
metadata:
name: rate-limit
spec:
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.local_ratelimit
typed_config:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
type_url: type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
value:
stat_prefix: http_local_rate_limiter
token_bucket:
max_tokens: 100
tokens_per_fill: 100
fill_interval: 60stext
Service Mesh 优势
├── 业务无侵入
├── 多语言支持
├── 统一治理
└── 边车代理
Service Mesh 代价
├── 性能损耗(每跳 1-2ms)
├── 复杂度上升
└── 排障更难七、云原生
text
云原生 = 微服务 + 容器 + DevOps + 服务网格 + 不可变基础设施
CNCF Landscape
├── 容器: Docker / Containerd
├── 编排: Kubernetes
├── 服务网格: Istio / Linkerd
├── 监控: Prometheus / Grafana
├── 日志: Loki / Fluentd
├── Tracing: Jaeger / Tempo
├── CI/CD: Argo / Tekton
├── 配置: Consul / etcd
└── 存储: MinIO / Cephjava
// Kubernetes 核心
// 1. Pod(最小部署单元)
apiVersion: v1
kind: Pod
metadata:
name: order-service
spec:
containers:
- name: order-service
image: order-service:1.0.0
ports:
- containerPort: 8080
// 2. Service(服务发现)
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
ports:
- port: 80
targetPort: 8080
// 3. HPA(自动扩缩容)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: order-service
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: order-service
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70八、Serverless
java
// AWS Lambda
public class OrderPlacedHandler implements RequestHandler<OrderEvent, String> {
@Override
public String handleRequest(OrderEvent event, Context context) {
// 发送通知
emailService.send(event.getUserEmail(), "订单已创建");
return "ok";
}
}
// 阿里云 FC
public class ImageResizeHandler implements RequestHandler<HttpRequest, HttpResponse> {
@Override
public HttpResponse handleRequest(HttpRequest request, Context context) {
String imageUrl = request.getQueryParameters().get("url");
byte[] resized = imageService.resize(imageUrl, 200, 200);
return new HttpResponse(200, new ByteArrayInputStream(resized));
}
}text
Serverless 适用
├── 流量波动大
├── 短任务
├── 事件驱动
└── 边缘计算
Serverless 不适用
├── 长任务(> 15 分钟)
├── 强状态
├── 极致性能
└── 复杂业务⚠️ 坑 2:核心交易链路用 Serverless,冷启动 + 厂商绑定,生产事故频发。Serverless 适合辅助场景(通知、日志)。
九、DDD 与架构演进
text
DDD 战略设计识别边界
├── 限界上下文 = 微服务边界
├── 聚合 = 事务边界
├── 领域事件 = 跨服务通信
└── 统一语言 = 团队沟通java
// 演进过程中,DDD 一直在
// 阶段 1:单体 + DDD 战术
// 阶段 2:模块化单体 + DDD 战略
// 阶段 3:每个限界上下文 = 一个微服务
// 阶段 4:DevOps 自动化部署十、架构选型决策
text
决策框架
├── 业务复杂度
│ ├── 简单 → 单体
│ ├── 中等 → 模块化单体
│ └── 复杂 → 微服务
├── 团队规模
│ ├── < 10 → 单体
│ ├── 10-50 → 模块化 + 部分微服务
│ └── 50+ → 微服务
├── 业务稳定性
│ ├── 探索期 → 简单架构
│ └── 成熟期 → 考虑微服务
└── 运维能力
├── 无 → 单体
├── 基础 → 模块化
└── 成熟 → 微服务 / 云原生java
// 决策表
public class ArchitectureDecision {
public Architecture choose(Requirements req) {
if (req.teamSize < 10 && req.complexity < 5) {
return Architecture.MONOLITH;
}
if (req.teamSize < 50 && req.complexity < 10) {
return Architecture.MODULAR_MONOLITH;
}
if (req.hasDevOps()) {
return Architecture.MICROSERVICE;
}
return Architecture.MODULAR_MONOLITH;
}
}⚠️ 坑 3:"技术越新越好" → 上 K8s + Istio + Knative,3 个月后才上线,业务停滞。架构服务于业务。
十一、演进中的反模式
java
// ❌ 分布式单体:微服务但耦合严重
// 表现:改一个服务,5 个服务要发版
// 原因:服务边界划错、共享数据库
// 解决:重新审视边界,合并过细服务
// ❌ 大泥球:没有清晰架构
// 表现:几千行 Service,代码没注释
// 解决:DDD 重构,引入清晰的领域模型
// ❌ 过度设计:为了 K8s 而 K8s
// 表现:5 个 QPS 的服务,搞容器化 + 服务网格
// 解决:根据业务规模选择技术十二、架构演进总结
text
关键原则
├── 演进式架构,不要 Big Bang
├── 业务驱动,不要技术驱动
├── 先单体后分布
├── 监控先行,问题驱动
└── 持续迭代,小步快跑java
// 架构选型伪代码
public Architecture currentArchitecture() {
if (yearsSinceStart() < 1) return MONOLITH;
if (yearsSinceStart() < 3 && teamSize < 50) return MODULAR_MONOLITH;
if (teamSize > 50) return MICROSERVICE;
if (hasServiceMeshExpertise()) return SERVICE_MESH;
return MICROSERVICE;
}本章小结
| 阶段 | 架构 | 适用 |
|---|---|---|
| 1 | 单体 | 早期,小团队 |
| 2 | 模块化单体 | 业务增长 |
| 3 | 微服务 | 50+ 团队 |
| 4 | 云原生 | 复杂业务 |
| 5 | Serverless | 辅助场景 |
| 关键点 | 建议 |
|---|---|
| 演进 | 小步快跑 |
| 业务 | 驱动架构 |
| 团队 | 决定复杂度 |
| 监控 | 先行 |
动手练习
- 架构评估:评估你当前项目处于哪个阶段,设计下一步演进路径
- 模块化:把单体项目按限界上下文拆包,接口和实现分离
- 独立部署:把一个模块独立部署,验证接口可替换
- 决策树:用决策框架评估你的业务应该选哪个架构
架构篇结束