现代软件架构设计模式:单体不够用了之后

现代软件架构设计模式上手并不难,难的是稳定跑起来。

下面只记真正影响结果的部分。

单体的优势

原来的项目是单体应用,部署简单、开发快速:

# app.py
from flask import Flask
from models import db, User, Order
from services import UserService, OrderService

app = Flask(__name__)

@app.route('/users/<id>')
def get_user(id):
    user = UserService.get_user(id)
    return jsonify(user.to_dict())

@app.route('/orders/<id>')
def get_order(id):
    order = OrderService.get_order(id)
    return jsonify(order.to_dict())

优势很明显

  • 部署简单:一个 jar 包,扔到服务器上就跑
  • 开发快速:改代码本地调试,本地就是完整系统
  • 运维简单:监控一个服务,查问题在单一进程里

什么时候该拆单体

单体应用在以下情况下开始出问题:

部署时间长:改一个功能,整个应用都要重新部署,从 5 分钟变成 30 分钟

协作困难:多个团队改同一个代码库,合并冲突频发

技术栈受限:某模块想用新语言,但单体应用不允许

性能瓶颈:整体扩展,浪费资源

graph TB subgraph 单体问题 A[部署时间长] B[协作困难] C[技术栈受限] D[性能瓶颈] end A --> E[业务迭代慢] B --> F[开发效率低] C --> G[技术债务] D --> H[成本上升] style E fill:#FFB6C1 style F fill:#FFB6C1 style G fill:#FFB6C1 style H fill:#FFB6C1

迁移到微服务

服务拆分原则

不是凭感觉拆,有明确的原则:

按业务能力拆分:用户服务、订单服务、库存服务

按数据边界拆分:每个服务有自己的数据库

按团队边界拆分:一个团队负责一个服务

graph TB subgraph 原单体 A[单体应用] end subgraph 拆分后 B[用户服务<br/>用户数据库] C[订单服务<br/>订单数据库] D[库存服务<br/>库存数据库] end A -->|按业务能力拆分| B A --> C A --> D style A fill:#FFB6C1 style B fill:#90EE90 style C fill:#FFD700 style D fill:#87CEEB

服务间通信

一开始同步调用,后来改用消息队列:

# 同步调用
def create_order(user_id, items):
    # 调用库存服务
    inventory_service.check_stock(items)
    # 调用用户服务
    user_service.get_user(user_id)
    # 创建订单
    order = Order.create(user_id, items)
    return order

# 异步调用
def create_order(user_id, items):
    order = Order.create(user_id, items)
    # 发送事件
    event_bus.publish('order_created', {
        'order_id': order.id,
        'items': items
    })
    return order

异步调用解耦了服务,但引入了新的复杂度:最终一致性。

踩坑一:分布式事务

跨服务的事务处理是个难题。

传统单体应用:

# 简单事务
with db.transaction():
    order.create()
    inventory.update()
    user.update_points()

微服务下:

# 问题是:如果某步失败,如何回滚?
order_service.create_order()
inventory_service.check_stock()
user_service.update_points()

解决:最终一致性 + 补偿机制

def create_order(user_id, items):
    try:
        # 创建订单
        order = OrderService.create_order(user_id, items)

        # 检查库存
        InventoryService.check_stock(items)

        # 扣除库存
        InventoryService.deduct_stock(items)

        # 更新积分
        UserService.update_points(user_id, order.total)

        # 标记订单完成
        OrderService.complete_order(order.id)

    except Exception as e:
        # 补偿:标记订单失败,回滚已执行的操作
        OrderService.fail_order(order.id)
        CompensationService.rollback(order.id)

踩坑二:数据一致性问题

用户看到订单创建成功,但库存没扣,或者积分没加。

sequenceDiagram participant User as 用户 participant Order as 订单服务 participant Inventory as 库存服务 participant UserSvc as 用户服务 User->>Order: 创建订单 Order->>Order: 保存订单 Order->>Inventory: 扣库存 Inventory->>Inventory: 库存不足<br/>抛异常 Order->>UserSvc: 更新积分 Note over UserSvc: 但订单已保存 User->>User: 查看订单<br/>看到成功 Note over User: 但库存没扣

解决:事务补偿和最终一致性检查

踩坑三:运维复杂度上升

从一个服务变成十个服务:

监控复杂:十个服务的健康状态,一个出问题就可能影响整体

日志分散:日志分散在多个服务,查问题要聚合

配置管理:每个服务的配置独立,管理复杂

依赖管理:服务间的依赖关系复杂

服务网格的引入

为解决服务间通信的问题,引入了服务网格(Istio):

# 服务网格配置
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
  - reviews
  http:
  - match:
    - headers:
        end-user:
          exact: jason
    route:
    - destination:
        host: reviews
        subset: v2
  - route:
    - destination:
        host: reviews
        subset: v1

服务网格解决的问题

  • 服务发现:自动发现服务实例
  • 负载均衡:智能流量分配
  • 熔断降级:自动故障处理
  • 链路追踪:跨服务的调用链路
  • 安全通信:自动 mTLS 加密

什么时候用服务网格

值得用的场景

  • 服务数量多(超过 10 个)
  • 服务间调用复杂
  • 对可靠性要求高
  • 团队有 Kubernetes 和 Istio 经验

不值得用的场景

  • 服务数量少
  • 团队能力不足
  • 对延迟敏感(Sidecar 增加延迟)

写在最后

架构演进这东西,不是越新越好。

单体应用有它的优势,微服务也有它的复杂性。什么时候拆、怎么拆、拆到什么程度,都需要权衡。

核心是匹配业务需求,不是追新。


这次迁移花了半年,中间有过反复。但回头看,对于业务复杂度高的项目,微服务的优势确实明显。但对于小项目,单体还是更简单。

版权声明: 本文首发于 指尖魔法屋-现代软件架构设计模式:单体不够用了之后https://blog.thinkmoon.cn/post/22-architecture-patterns-microservices-service-mesh/) 转载或引用必须申明原指尖魔法屋来源及源地址!