跳到主要内容

单体架构

单体架构(Monolithic Architecture)是最传统、最简单的软件架构风格。在这种架构中,所有的功能模块都打包在一个应用程序中,统一部署和运行。

什么是单体架构?

单体架构是指将应用的所有功能——包括用户界面、业务逻辑、数据访问层等——都集成在一个单一的代码库中,编译打包后部署为一个独立的运行单元。

┌─────────────────────────────────────────────────────────────┐
│ 单体应用程序 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 用户模块 │ │ 订单模块 │ │ 商品模块 │ │
│ │ UserModule │ │ OrderModule │ │ProductModule│ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └────────────────┼────────────────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ 共享数据库 │ │
│ │ (Shared Database) │ │
│ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

单体架构的特点

优点

优点说明
开发简单所有代码在一个项目中,IDE 支持好,调试方便
部署容易只有一个部署单元,发布流程简单
性能较好模块间通过方法调用通信,没有网络开销
事务处理简单使用本地事务即可保证数据一致性
测试方便可以端到端测试,不需要模拟服务依赖
技术栈统一整个应用使用相同的技术栈,降低学习成本

缺点

缺点说明
可维护性差随着业务发展,代码库会变得庞大复杂
技术栈锁定难以引入新技术,升级成本高
扩展受限只能整体扩展,无法针对热点模块单独扩容
部署风险高任何小改动都需要重新部署整个应用
团队效率低多人协作时容易产生代码冲突
可靠性问题单点故障可能导致整个系统不可用

单体架构的代码组织

经典的分层结构

my-monolithic-app/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ ├── MyApplication.java # 应用入口
│ │ │ ├── controller/ # 控制器层
│ │ │ │ ├── UserController.java
│ │ │ │ ├── OrderController.java
│ │ │ │ └── ProductController.java
│ │ │ ├── service/ # 业务逻辑层
│ │ │ │ ├── UserService.java
│ │ │ │ ├── OrderService.java
│ │ │ │ └── ProductService.java
│ │ │ ├── repository/ # 数据访问层
│ │ │ │ ├── UserRepository.java
│ │ │ │ ├── OrderRepository.java
│ │ │ │ └── ProductRepository.java
│ │ │ ├── entity/ # 实体类
│ │ │ │ ├── User.java
│ │ │ │ ├── Order.java
│ │ │ │ └── Product.java
│ │ │ └── config/ # 配置类
│ │ └── resources/
│ │ ├── application.yml
│ │ └── static/
│ └── test/
│ └── java/
└── pom.xml / build.gradle

模块化单体(Modular Monolith)

为了缓解传统单体架构的问题,可以采用模块化单体的方式,在保持单体部署的同时,通过清晰的模块边界提高代码组织性。

// 模块接口定义 - 明确模块间的契约
public interface UserModule {
User getUserById(Long userId);
boolean validateUser(Long userId);
}

// 模块内部实现细节不暴露
@Service
class UserModuleImpl implements UserModule {
@Autowired
private UserRepository userRepository;

@Override
public User getUserById(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException(userId));
}

@Override
public boolean validateUser(Long userId) {
return userRepository.existsById(userId);
}
}

// 其他模块通过接口使用 User 模块
@Service
public class OrderService {
private final UserModule userModule; // 依赖接口而非实现
private final OrderRepository orderRepository;

public Order createOrder(Long userId, OrderRequest request) {
// 通过模块接口验证用户
if (!userModule.validateUser(userId)) {
throw new InvalidUserException(userId);
}

// 创建订单逻辑
Order order = new Order();
order.setUserId(userId);
// ... 其他设置

return orderRepository.save(order);
}
}

单体架构的演进

从混乱到有序

单体架构随着业务发展会经历不同的阶段:

阶段1: 简单单体          阶段2: 模块化单体          阶段3: 分布式服务
┌─────────────┐ ┌─────────────┐ ┌─────┐ ┌─────┐ ┌─────┐
│ 所有代码 │ │ 用户模块 │ │用户 │ │订单 │ │商品 │
│ 混在一起 │ → │ 订单模块 │ → │服务 │ │服务 │ │服务 │
│ │ │ 商品模块 │ └─────┘ └─────┘ └─────┘
└─────────────┘ └─────────────┘

演进时机判断

什么时候应该考虑从单体架构演进?

继续保留单体的信号

  • 代码库小于 10 万行
  • 团队规模小于 10 人
  • 部署频率每周少于 2 次
  • 系统性能满足业务需求

考虑拆分的信号

  • 构建时间超过 10 分钟
  • 启动时间超过 5 分钟
  • 不同模块的变更频率差异很大
  • 团队规模超过 15 人,协作困难
  • 需要独立扩展特定功能模块

单体架构的最佳实践

1. 清晰的包结构

// 好的实践:按功能模块组织包
com.example.ecommerce/
├── user/
│ ├── api/ # 对外暴露的接口
│ ├── service/ # 业务逻辑
│ ├── repository/ # 数据访问
│ └── domain/ # 领域模型
├── order/
│ ├── api/
│ ├── service/
│ ├── repository/
│ └── domain/
└── product/
├── api/
├── service/
├── repository/
└── domain/

// 避免:按技术层次组织包
com.example.ecommerce/
├── controller/ # 所有控制器混在一起
├── service/ # 所有服务混在一起
├── repository/ # 所有仓库混在一起
└── entity/ # 所有实体混在一起

2. 依赖管理

// 好的实践:依赖接口,使用依赖注入
@Service
public class OrderService {
private final UserService userService; // 接口
private final InventoryService inventoryService;
private final PaymentService paymentService;

// 通过构造函数注入
public OrderService(UserService userService,
InventoryService inventoryService,
PaymentService paymentService) {
this.userService = userService;
this.inventoryService = inventoryService;
this.paymentService = paymentService;
}
}

// 避免:直接实例化依赖
@Service
public class BadOrderService {
private UserService userService = new UserServiceImpl(); // 紧耦合
}

3. 数据库设计

在单体架构中,数据库设计应该遵循模块化的原则,每个模块使用独立的表空间或 schema:

-- 使用 Schema 隔离不同模块的数据
CREATE SCHEMA user_schema;
CREATE SCHEMA order_schema;
CREATE SCHEMA product_schema;

-- 用户模块表
CREATE TABLE user_schema.users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 订单模块表
CREATE TABLE order_schema.orders (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES user_schema.users(id),
order_number VARCHAR(50) UNIQUE NOT NULL,
total_amount DECIMAL(10, 2) NOT NULL,
status VARCHAR(20) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE order_schema.order_items (
id BIGSERIAL PRIMARY KEY,
order_id BIGINT NOT NULL REFERENCES order_schema.orders(id),
product_id BIGINT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10, 2) NOT NULL
);

-- 商品模块表
CREATE TABLE product_schema.products (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL,
description TEXT,
price DECIMAL(10, 2) NOT NULL,
stock INT NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

4. 配置管理

# application.yml - 按环境配置
spring:
application:
name: ecommerce-platform

profiles:
active: ${SPRING_PROFILES_ACTIVE:dev}

datasource:
url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:ecommerce}
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:postgres}

logging:
level:
com.example.ecommerce: ${LOG_LEVEL:DEBUG}
org.springframework: INFO

app:
host: ${APP_HOST:0.0.0.0}
port: ${APP_PORT:8080}
max-upload-size: 10MB

单体架构的适用场景

适合使用单体的场景

场景说明
初创公司快速验证业务想法,不需要复杂架构
小型团队3-10 人的团队,单体更简单高效
简单业务功能相对稳定,变化不频繁
预算有限没有足够资源维护复杂的分布式系统
技能不足团队缺乏 DevOps 能力
PMF 阶段寻找产品-市场匹配的阶段

成功案例

很多大型互联网公司最初也是从单体架构起步的:

  1. Amazon:最初是单体架构,后来演变为服务导向架构
  2. Netflix:从单体迁移到微服务,但不是一蹴而就
  3. eBay:单体架构支撑了很长时间的运营

关键不在于选择哪种架构,而在于:

  • 架构要匹配当前的业务阶段
  • 保持代码的清晰和可维护性
  • 为未来的演进留有余地

单体架构的监控与诊断

即使选择单体架构,也需要完善的监控体系:

// 添加应用监控
@SpringBootApplication
@Enable actuator metrics
public class EcommerceApplication {
public static void main(String[] args) {
SpringApplication.run(EcommerceApplication.class, args);
}
}

// 自定义业务指标
@Service
public class OrderMetrics {
private final MeterRegistry meterRegistry;

public OrderMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}

public void recordOrderCreated(long userId) {
meterRegistry.counter("orders.created").increment();
}

public void recordOrderAmount(double amount) {
meterRegistry.summary("orders.amount").record(amount);
}
}

常用监控指标:

  • 请求延迟:P50、P95、P99 响应时间
  • 错误率:HTTP 5xx 错误比例
  • 吞吐量:每秒请求数(RPS)
  • 资源使用:CPU、内存、连接池
  • 业务指标:订单数、用户活跃度等

总结

单体架构不是"糟糕"的架构,它是一种简单有效的选择。关键在于:

  1. 保持代码组织清晰:采用模块化结构
  2. 控制代码规模:及时拆分过大的模块
  3. 完善监控体系:及时发现性能瓶颈
  4. 准备演进路径:为未来的架构演进做好准备

"单体优先,不要一开始就过度设计。在业务证明需要之前,避免微服务的复杂性。" —— Martin Fowler

延伸阅读