计算与存储分离
传统架构中,计算资源和存储资源是耦合的——一台服务器既跑应用,也存数据。这种架构简单,但不够灵活。计算与存储分离,则是现代云原生架构的核心特征之一。
什么是计算存储分离
计算与存储分离是指将计算节点(运行应用代码)和存储节点(保存数据)分开部署,之间通过网络通信。
flowchart TB
subgraph Traditional["传统架构"]
direction TB
T1["服务器 1<br/>应用 + 数据库"]
T2["服务器 2<br/>应用 + 数据库"]
end
subgraph Separated["分离架构"]
direction TB
C1["计算节点 1"]
C2["计算节点 2"]
C3["计算节点 N"]
S1["存储节点 1"]
S2["存储节点 2"]
SN["对象存储"]
end
C1 -.->|"网络"| S1
C2 -.->|"网络"| S1
C3 -.->|"网络"| SN
Serverless 架构
Serverless 是计算与存储分离的极致形态。在 Serverless 架构中,开发者不需要关心服务器的存在,只关注业务代码和数据存储。
函数即服务(FaaS)
函数被触发时自动启动,执行完毕后自动销毁。按实际执行时间计费,不执行不收费。
AWS
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
exports.handler = async (event) => {
// 获取 S3 事件
const bucket = event.Records[0].s3.bucket.name;
const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
// 从对象存储读取数据
const params = { Bucket: bucket, Key: key };
const data = await s3.getObject(params).promise();
// 处理数据(纯计算)
const result = processData(data.Body);
// 写入结果到另一个存储桶
await s3.putObject({
Bucket: 'output-bucket',
Key: 'result-' + key,
Body: JSON.stringify(result)
}).promise();
return { statusCode: 200 };
};
容器即服务(CaaS)
比 FaaS 更灵活的计算形态。每个容器有独立的资源,但不需要管理底层服务器。
Kubernetes
apiVersion: v1
kind: Pod
metadata:
name: compute-pod
spec:
containers:
- name: compute
image: compute-service:latest
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
env:
- name: STORAGE_ENDPOINT
value: "http://minio-service:9000"
对象存储作为数据湖
对象存储(如 AWS S3、阿里云 OSS、MinIO)是分离架构的核心存储组件。
对象存储的特点
无限容量:理论上无限扩展,不需要预先规划存储容量。
高持久性:数据自动多副本存储,持久性通常 99.999999999%(11 个 9)。
成本低廉:相比块存储和文件存储,单位容量成本更低。
RESTful 访问:通过 HTTP API 访问,任何语言、任何平台都可以对接。
使用场景
数据湖:原始数据、日志、分析结果都可以存入对象存储,作为数据湖的底层存储。
静态资源:图片、视频、文档等静态文件天然适合对象存储,配合 CDN 实现高效分发。
备份归档:冷数据、备份数据可以使用对象存储的归档类型,成本更低。
对象存储操作示例
@Service
public class FileStorageService {
private final OSSClient ossClient;
private final String bucketName = "app-data";
// 上传文件
public String uploadFile(String key, InputStream data, long size) {
ossClient.putObject(bucketName, key, data, size);
return key;
}
// 下载文件
public InputStream downloadFile(String key) {
OSSObject object = ossClient.getObject(bucketName, key);
return object.getObjectContent();
}
// 生成预签名 URL(临时访问权限)
public String generatePresignedUrl(String key, Duration ttl) {
Date expiration = new Date(System.currentTimeMillis() + ttl.toMillis());
return ossClient.generatePresignedUrl(bucketName, key, expiration);
}
}
计算无状态化
计算与存储分离后,计算节点必须是完全无状态的——每次执行都从外部存储获取所需数据。
无状态函数设计
无状态处理函数
@Service
public class OrderProcessService {
@Autowired
private RedisTemplate<String, Order> orderCache;
@Autowired
private ProductRepository productRepository;
public ProcessResult processOrder(Long orderId) {
// 从缓存获取订单数据
Order order = orderCache.opsForValue().get("order:" + orderId);
if (order == null) {
// 缓存未命中,从对象存储加载
order = loadOrderFromStorage(orderId);
orderCache.opsForValue().set("order:" + orderId, order, 1, TimeUnit.HOURS);
}
// 业务处理(纯计算)
ProcessResult result = doProcess(order);
// 处理结果写入对象存储
saveResultToStorage(orderId, result);
return result;
}
}
状态外置
应用不再维护本地状态,所有状态存储在外部服务(Redis、数据库、对象存储)。
sequenceDiagram
participant Lambda as Lambda 函数
participant Redis as Redis 缓存
participant S3 as S3 存储
participant RDS as RDS 数据库
Lambda->>Redis: 读取会话数据
Redis-->>Lambda: 会话数据
Lambda->>S3: 读取处理所需文件
S3-->>Lambda: 文件内容
Lambda->>RDS: 查询业务数据
RDS-->>Lambda: 业务数据
Lambda->>RDS: 保存处理结果
Lambda->>S3: 归档输入数据
适用场景
计算与存储分离适合特定的工作负载和业务场景。
适合的场景
大数据分析:ETL、批处理、流处理等计算密集型任务。这类任务通常是计算瓶颈,存储带宽不是问题。
无状态微服务:业务逻辑可以完全无状态的服务。所有状态存储在外部,适合微服务架构。
弹性波动大:流量峰谷明显的业务(如电商大促、在线教育)。Serverless 按需扩容,成本最优。
事件驱动处理:图片处理、消息推送、日志分析等由事件触发的任务。
不适合的场景
低延迟敏感:网络 I/O 增加延迟,对延迟要求苛刻的场景(如高频交易)不适合。
高带宽任务:需要极高存储带宽的任务(如实时数据分析)。网络可能成为瓶颈。
状态密集型:游戏服务器、实时通信等需要大量本地状态的服务。
分离架构的优势
独立扩展
计算资源和存储资源可以独立扩展。计算密集型任务增加计算节点,数据密集型任务增加存储节点。
成本优化
Serverless 模式下,按实际使用计费。不需要为峰值预留资源,也不需要为空闲时间付费。
可靠性提升
计算节点无状态,故障后可以快速重建。存储服务通常提供高持久性保证。
运维简化
不需要管理服务器,只需关注应用代码和存储配置。云服务商负责底层运维。
分离架构的挑战
网络延迟
计算节点和存储节点通过网络通信,增加延迟。需要通过缓存、批量操作等方式优化。
批量操作优化
@Service
public class BatchProcessingService {
public void processBatch(List<Long> ids) {
// 批量从存储读取,减少网络往返
List<Order> orders = batchLoadOrders(ids);
// 批量处理
List<Result> results = orders.parallelStream()
.map(this::doProcess)
.collect(Collectors.toList());
// 批量写入
batchSaveResults(results);
}
}
数据一致性
网络分区可能导致数据不一致。需要明确业务能接受的一致性级别(强一致、最终一致)。
成本控制
Serverless 按调用计费,如果请求量巨大,成本可能高于预留实例。需要评估实际成本。
调试困难
无状态函数的调试比本地应用困难。需要完善的日志、监控和分布式追踪能力。
常见误区
误区一:分离就是不用关心服务器
计算与存储分离后,服务器换成了存储服务,但存储服务的配置、权限、生命周期管理仍然是开发者的责任。
误区二:Serverless 最便宜
Serverless 按使用计费,适合间歇性工作负载。如果函数持续运行,预留实例可能更便宜。
误区三:分离架构不需要优化
分离架构增加了网络层,网络带宽和延迟可能成为瓶颈。需要针对性优化(缓存、批量、压缩)。
误区四:无状态等于无性能
状态外置增加了数据获取延迟。合理的本地缓存策略可以兼顾无状态和高性能。
延伸思考
计算与存储分离是云原生架构的核心特征,但它不是唯一正确的架构。很多传统架构(计算和存储耦合)依然运行良好,特别是在性能敏感的场景。
选择计算与存储分离,需要评估:
- 工作负载是否适合无状态化
- 能否接受网络延迟增加
- Serverless 成本是否可接受
- 团队是否有配套的监控和调试能力
分离架构的优势在于弹性和成本优化,但需要配套的基础设施和工程能力。在没有准备好之前,冒然分离可能导致性能和运维问题。