系统设计:单体不够用了之后

系统设计我没按教科书顺序做。

这次做系统架构演进,从单体到分布式,。

最初的困境

单体的局限

// 单体应用的结构
/app
  /controllers
    - user.js
    - order.js
    - payment.js
  /models
    - user.js
    - order.js
    - payment.js
  /services
    - email.js
    - notification.js
  /routes.js

问题是:

  • 代码耦合严重,改一处影响多处
  • 部署麻烦,一个小改动要部署整个应用
  • 团队协作困难,多人修改同一代码库
  • 扩展性差,整体部署或不部署
# 部署一次要停机 30 分钟
npm run build
pm2 stop myapp
rsync -av dist/ server:/var/www/
pm2 restart myapp

什么时候需要拆分

评估标准

团队规模

  • <5 人:单体就好
  • 5-20 人:考虑拆分
  • 20 人:必须拆分

业务复杂度

  • 简单 CRUD:单体就好
  • 业务逻辑复杂:考虑拆分

流量规模

  • 小流量:单体就好
  • 高流量:考虑拆分

拆分策略

垂直拆分

// 按业务功能拆分
/user-service
  - 用户管理
  - 权限管理

/order-service
  - 订单处理
  - 订单查询

/payment-service
  - 支付处理
  - 退款管理

水平拆分

// 按流量拆分
/user-service-master
/user-service-slave-1
/user-service-slave-2

分布式挑战

数据一致性

# 分布式事务问题
def process_order(user_id, amount):
    # 1. 扣减库存
    inventory.reduce_stock(amount)
    
    # 2. 创建订单
    order = order_service.create_order(user_id, amount)
    
    # 3. 支付
    payment_service.charge(user_id, amount)
    
    # 问题:如果支付失败,库存已经扣了

解决:使用 Saga 模式或 TCC。

# Saga 模式
class CreateOrderSaga:
    def execute(self, user_id, amount):
        try:
            # 步骤 1:扣减库存
            inventory_service.reduce_stock(amount)
            
            # 步骤 2:创建订单
            order = order_service.create_order(user_id, amount)
            
            # 步骤 3:支付
            payment_service.charge(user_id, amount)
            
            return order
        
        except Exception as e:
            # 补偿操作
            inventory_service.restore_stock(amount)
            raise e

服务发现

// 服务发现问题
const users = [
  'http://user-service-1:3000',
  'http://user-service-2:3000',
  'http://user-service-3:3000'
];

// 简单的负载均衡
function getUserService() {
  return users[Math.floor(Math.random() * users.length)];
}

// 问题:服务挂了怎么办?

解决:使用服务注册中心。

// 使用 Consul 或 Eureka
const consul = require('consul')();

// 服务注册
async function registerService(serviceName, serviceUrl) {
  await consul.agent.service.register({
    name: serviceName,
    address: serviceUrl,
    check: {
      http: `http://${serviceUrl}/health`,
      interval: '10s'
    }
  });
}

// 服务发现
async function discoverService(serviceName) {
  const services = await consul.agent.service.list();
  return services[serviceName];
}

消息队列

# 服务解耦
from celery import Celery

app = Celery('orders', broker='redis://localhost:6379/0')

@app.task
def process_order(order_id):
    order = get_order(order_id)
    
    # 异步处理
    send_notification(order.user_id, '订单已创建')
    update_statistics(order)
    send_analytics(order)

踩过的坑

坑一:过度拆分

一开始把所有东西都拆成独立服务,结果管理复杂。

解决:合并相关服务,减少服务数量。

坑二:数据拆分不清晰

数据拆分不清晰,导致数据访问复杂。

解决:按照业务域拆分数据,明确所有权。

坑三:监控困难

服务多了,监控变得困难。

解决:使用分布式追踪和集中监控。

# 使用 Jaeger 追踪请求
from opentelemetry import trace
from opentelemetry.exporter.jaeger import JaegerExporter

tracer = trace.get_tracer(__name__)

@tracer.start_as_current_span("process_order")
def process_order(order_id):
    with tracer.start_as_span("get_order"):
        order = get_order(order_id)
    
    with tracer.start_as_span("process_payment"):
        process_payment(order)

写在最后

系统设计这东西,不只是技术,是业务和团队的映射。

解决了

  • 扩展性问题
  • 团队协作问题
  • 部署效率问题

带来了

  • 复杂度增加
  • 运维成本
  • 数据一致性挑战

拆分之前先评估:

  • 业务规模
  • 团队规模
  • 技术能力
  • 运维成本

不是所有系统都需要拆分,有时候单体架构更简单。


这次系统设计改造花了两个月,从单体到分布式。改造完成后,系统吞吐量提升了 5 倍,可用性从 99.5% 提升到 99.9%。

可用性说明:本文发布于 2021 年 1 月,距今已超过五年。文中涉及的软件版本、接口、下载地址、命令参数和操作界面可能已经发生变化,部分方案在当前环境下可能失效。请结合官方最新文档核对后再操作,生产环境使用前务必先行验证。

版权声明: 本文首发于 指尖魔法屋-系统设计:单体不够用了之后https://blog.thinkmoon.cn/post/102-system-design-monolith-distributed-architecture/) 转载或引用必须申明原指尖魔法屋来源及源地址!