mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1771 字
5 分钟
RAID 与纠删码
2025-09-12

一块磁盘的年故障率约 2–4%。一个 50 块盘的机柜,每年至少坏一块——这不是概率问题,是时间问题。那块盘上的数据怎么办?RAID 1 做镜像,50% 的容量浪费在冗余上;RAID 5 只用一块盘的容量做校验,但坏两块盘数据就全丢了。纠删码用数学换空间:10 块数据盘 + 4 块校验盘,任意丢失 4 块盘数据都能恢复——存储效率 71%,远超镜像的 50%。

冗余是数据安全的地基。RAID 和纠删码是冗余的两种实现——前者简单直接,后者用 Reed-Solomon 编码在存储效率与容错能力之间找到更优解。

一、RAID 基础#

1.1 RAID 级别概览#

graph TB subgraph RAID级别["RAID 级别"] RAID0["RAID 0<br/>条带化<br/>无冗余"] RAID1["RAID 1<br/>镜像<br/>完整冗余"] RAID5["RAID 5<br/>条带+校验<br/>1 盘冗余"] RAID6["RAID 6<br/>条带+双校验<br/>2 盘冗余"] RAID10["RAID 10<br/>镜像+条带<br/>完整冗余"] end RAID0 --> RAID5 --> RAID6 RAID1 --> RAID10 style RAID0 fill:#ffcdd2,stroke:#c62828 style RAID1 fill:#c8e6c9,stroke:#2e7d32 style RAID5 fill:#e3f2fd,stroke:#1565c0 style RAID6 fill:#fff9c4,stroke:#f9a825 style RAID10 fill:#c8e6c9,stroke:#2e7d32

1.2 RAID 级别对比#

级别最少磁盘容错磁盘空间利用率读性能写性能适用场景
RAID 020100%最好最好临时数据
RAID 12150%一般关键数据
RAID 531(N-1)/N一般通用
RAID 642(N-2)/N较差高可靠
RAID 1041/组50%数据库

二、RAID 级别详解#

2.1 RAID 0:条带化#

graph LR subgraph RAID0["RAID 0:条带化"] DATA["数据块"] --> D0["Disk 0<br/>Block 0, 4, 8"] DATA --> D1["Disk 1<br/>Block 1, 5, 9"] DATA --> D2["Disk 2<br/>Block 2, 6, 10"] DATA --> D3["Disk 3<br/>Block 3, 7, 11"] end style D0 fill:#c8e6c9,stroke:#2e7d32 style D1 fill:#e3f2fd,stroke:#1565c0 style D2 fill:#fff9c4,stroke:#f9a825 style D3 fill:#ffe0b2,stroke:#e65100
  • 优点:最高性能和空间利用率
  • 缺点:任何一块盘故障,所有数据丢失
  • 适用:临时数据、缓存

2.2 RAID 1:镜像#

graph LR subgraph RAID1["RAID 1:镜像"] DATA["数据"] --> D0["Disk 0<br/>完整数据副本"] DATA --> D1["Disk 1<br/>完整数据副本"] end style D0 fill:#c8e6c9,stroke:#2e7d32 style D1 fill:#c8e6c9,stroke:#2e7d32
  • 优点:最高容错,读性能翻倍
  • 缺点:空间利用率 50%
  • 适用:操作系统盘、关键数据库

2.3 RAID 5:条带 + 校验#

graph LR subgraph RAID5["RAID 5:条带 + 校验"] S0["Stripe 0"] --> D0_0["Disk 0: D0"] S0 --> D1_0["Disk 1: D1"] S0 --> D2_0["Disk 2: P0=D0⊕D1"] S1["Stripe 1"] --> D0_1["Disk 0: P1=D2⊕D3"] S1 --> D1_1["Disk 1: D2"] S1 --> D2_1["Disk 2: D3"] end style D0_0 fill:#c8e6c9,stroke:#2e7d32 style D1_0 fill:#e3f2fd,stroke:#1565c0 style D2_0 fill:#fff9c4,stroke:#f9a825
# RAID 5 校验计算
def raid5_parity(data_blocks):
"""计算 RAID 5 校验块"""
parity = 0
for block in data_blocks:
parity ^= block # XOR 运算
return parity
# 重建:如果 Disk 1 故障
# D1 = P0 ⊕ D0 = (D0 ⊕ D1) ⊕ D0 = D1
def raid5_reconstruct(surviving_blocks, parity_block):
"""从校验块重建丢失数据"""
result = parity_block
for block in surviving_blocks:
result ^= block
return result
  • 优点:空间利用率高,容错 1 盘
  • 缺点:写入需计算校验(写惩罚),重建慢
  • 适用:通用文件服务器

2.4 RAID 6:双校验#

RAID 6 使用两个独立的校验块(P 和 Q),可容忍 2 盘同时故障:

# RAID 6 Q 校验(基于 Reed-Solomon)
# P = D0 ⊕ D1 ⊕ D2 ⊕ ... ⊕ Dn-1 (XOR)
# Q = g^0*D0 ⊕ g^1*D1 ⊕ g^2*D2 ⊕ ... ⊕ g^(n-1)*Dn-1 (伽罗瓦域乘法)
# 重建 2 盘故障:
# 解线性方程组
# [1, 1, 1, ...] × [D0, D1, D2, ...]^T = P
# [g^0, g^1, g^2, ...] × [D0, D1, D2, ...]^T = Q
def raid6_reconstruct(surviving_data, p_block, q_block,
failed_indices):
"""重建 2 个丢失的数据块"""
# 在伽罗瓦域 GF(2^8) 上解线性方程组
# 使用矩阵运算
n = len(surviving_data) + len(failed_indices)
# 构建系数矩阵
matrix = build_rs_matrix(n)
# 求解
failed_data = solve_galoian_system(matrix, surviving_data,
p_block, q_block, failed_indices)
return failed_data

三、纠删码#

3.1 纠删码原理#

纠删码(Erasure Coding)是 RAID 6 的推广——将数据分为 k 个数据块,生成 m 个校验块,可容忍 m 个块故障:

graph TB subgraph 纠删码["纠删码:k+m 编码"] DATA_EC["原始数据"] --> SPLIT["分为 k 个数据块"] SPLIT --> D_BLOCKS["D0, D1, ..., D(k-1)"] D_BLOCKS --> ENCODE["编码矩阵<br/>Reed-Solomon"] ENCODE --> P_BLOCKS["P0, P1, ..., P(m-1)"] end subgraph 重建["重建:任意 k 个块恢复原始数据"] ANY_K["任意 k 个幸存块"] --> DECODE["解码矩阵"] DECODE --> ORIGINAL["恢复原始数据"] end style D_BLOCKS fill:#c8e6c9,stroke:#2e7d32 style P_BLOCKS fill:#fff9c4,stroke:#f9a825 style ANY_K fill:#e3f2fd,stroke:#1565c0
参数说明示例
k数据块数量8
m校验块数量4
k+m总块数量12
容错可容忍 m 个块故障4
空间利用率k/(k+m)8/12 = 66.7%
最小重建需要任意 k 个块8

3.2 Reed-Solomon 编码#

# Reed-Solomon 编码(简化)
import numpy as np
def reed_solomon_encode(data_blocks, k, m):
"""Reed-Solomon 编码"""
# 构建编码矩阵(Vandermonde 矩阵)
# 在伽罗瓦域 GF(2^8) 上运算
encode_matrix = build_vandermonde_matrix(k + m, k)
# 编码:parity = encode_matrix × data_blocks
parity_blocks = np.dot(encode_matrix[k:], data_blocks)
return parity_blocks
def reed_solomon_decode(surviving_blocks, surviving_indices,
k, m):
"""Reed-Solomon 解码"""
# 从幸存块构建解码矩阵
decode_matrix = build_decode_matrix(surviving_indices, k)
# 解码:original = decode_matrix × surviving_blocks
original_blocks = np.dot(decode_matrix, surviving_blocks)
return original_blocks
# 常见配置
ec_configs = {
"Ceph": {"k": 8, "m": 3, "utilization": "73%", "fault_tolerance": 3},
"MinIO": {"k": 8, "m": 4, "utilization": "67%", "fault_tolerance": 4},
"HDFS": {"k": 6, "m": 3, "utilization": "67%", "fault_tolerance": 3},
"S3": {"k": 12, "m": 4, "utilization": "75%", "fault_tolerance": 4},
}

3.3 纠删码 vs 副本#

维度3 副本纠删码 (8+4)
空间利用率33%67%
容错2 盘4 盘
读性能3x(并行读)1x(读数据块)
写性能3x(写 3 副本)较慢(需编码)
重建性能快(从副本恢复)慢(需解码计算)
重建网络流量1x 数据量k × 数据量
Important

纠删码的核心权衡:用计算换空间。纠删码比副本节省 50%+ 的空间,但重建时需要更多 CPU 和网络流量。对于冷数据(很少访问),纠删码是更好的选择;对于热数据(频繁访问),副本提供更好的读性能。

四、故障域#

4.1 故障域层级#

graph TB subgraph 故障域["故障域层级"] DISK["磁盘故障域<br/>单盘故障"] SERVER["服务器故障域<br/>整机故障(电源/主板)"] RACK["机架故障域<br/>整架故障(交换机/电源)"] DC["数据中心故障域<br/>整机房故障(断电/火灾)"] end DISK --> SERVER --> RACK --> DC style DISK fill:#ffe0b2,stroke:#e65100 style SERVER fill:#fff9c4,stroke:#f9a825 style RACK fill:#e3f2fd,stroke:#1565c0 style DC fill:#ffcdd2,stroke:#c62828
故障域故障概率影响范围保护策略
磁盘~2%/年单盘数据RAID 5/6 或副本
服务器~5%/年整机数据跨服务器副本/纠删码
机架~0.5%/年整架数据跨机架副本/纠删码
数据中心~0.01%/年全部数据跨地域复制

4.2 数据放置策略#

# 数据放置策略
def place_data_blocks(k, m, fault_domains):
"""将数据块和校验块分散到不同故障域"""
total_blocks = k + m
# 确保同一故障域内不超过 1 个块
# 这样即使整个故障域故障,也只丢失 1 个块
if len(fault_domains) < total_blocks:
raise ValueError("故障域数量不足")
placement = {}
for i, block in enumerate(range(total_blocks)):
# 选择故障域
domain = fault_domains[i % len(fault_domains)]
placement[block] = domain
return placement
# Ceph CRUSH 算法就是这种策略的复杂版本
# 它考虑了多层故障域:host → rack → row → room → DC

五、重建性能#

5.1 重建时间估算#

# RAID/纠删码重建时间估算
def estimate_rebuild_time(disk_size_tb, disk_speed_mbps,
rebuild_method, k=8):
"""估算重建时间"""
disk_size_mb = disk_size_tb * 1024 * 1024
if rebuild_method == "raid5":
# 从 1 个校验块重建
rebuild_speed = disk_speed_mbps
time_hours = disk_size_mb / rebuild_speed / 3600
elif rebuild_method == "raid6":
# 从 2 个校验块重建
rebuild_speed = disk_speed_mbps
time_hours = disk_size_mb / rebuild_speed / 3600
elif rebuild_method == "ec_8_4":
# 纠删码:需要从 k 个块重建
# 网络流量 = k × 丢失块大小
rebuild_speed = disk_speed_mbps * k # 需要读取 k 个块
time_hours = disk_size_mb / rebuild_speed / 3600
print(f"磁盘大小: {disk_size_tb} TB")
print(f"重建方法: {rebuild_method}")
print(f"预计时间: {time_hours:.1f} 小时")
return time_hours
estimate_rebuild_time(20, 200, "raid5")
# 磁盘大小: 20 TB
# 重建方法: raid5
# 预计时间: 27.8 小时
estimate_rebuild_time(20, 200, "ec_8_4", 8)
# 磁盘大小: 20 TB
# 重建方法: ec_8_4
# 预计时间: 3.5 小时(并行读取 8 个块)

5.2 重建期间的风险#

RAID 级别重建期间容错风险
RAID 50(无容错)第二盘故障 = 全部数据丢失
RAID 61(仍可容错 1 盘)较安全
纠删码 (8+4)3(仍可容错 3 盘)很安全
Note

RAID 5 在重建期间处于”裸奔”状态——任何第二块盘故障都会导致全部数据丢失。对于大容量 HDD(20TB),重建需要 27+ 小时,期间故障概率不可忽视。这就是为什么 RAID 6 和纠删码更受欢迎。

5.3 RAID 重建性能瓶颈#

大容量磁盘的重建面临严重的性能瓶颈,核心问题在于重建期间磁盘 I/O 争用:

# 重建期间 I/O 争用分析
def rebuild_io_contention(disk_size_tb, disk_speed_mbps, rebuild_priority=0.3):
disk_size_mb = disk_size_tb * 1024 * 1024
# 重建占用磁盘带宽的比例
rebuild_bandwidth = disk_speed_mbps * rebuild_priority
# 剩余带宽给业务 I/O
business_bandwidth = disk_speed_mbps * (1 - rebuild_priority)
# 重建时间
rebuild_hours = disk_size_mb / rebuild_bandwidth / 3600
# 业务 I/O 延迟影响
# 重建产生大量顺序读,与业务随机读争用磁盘
# HDD 上延迟可能增加 3-10 倍
latency_multiplier = 3 if disk_speed_mbps < 300 else 1.5
print(f"重建带宽占比: {rebuild_priority*100:.0f}%")
print(f"重建时间: {rebuild_hours:.1f} 小时")
print(f"业务延迟倍增: {latency_multiplier}x")
rebuild_io_contention(20, 200, 0.3)
# 重建带宽占比: 30%
# 重建时间: 92.6 小时
# 业务延迟倍增: 3x
磁盘容量重建时间(RAID 5)重建时间(EC 8+4)重建期间延迟影响
4 TB~5.6 小时~0.7 小时中等
10 TB~13.9 小时~1.7 小时较高
20 TB~27.8 小时~3.5 小时严重

5.4 Reed-Solomon 编码原理#

Reed-Solomon(RS)编码是纠删码的数学基础,其核心是在有限域(伽罗瓦域)上进行矩阵运算:

# Reed-Solomon 编码原理(简化)
# 步骤 1: 构建编码矩阵(Vandermonde 矩阵)
# [1 1 1 1 ] [D0] [D0 + D1 + D2 + D3]
# [1 g g^2 g^3] x [D1] = [D0 + g*D1 + g^2*D2 + g^3*D3]
# [1 g^2 g^4 g^6] [D2] [D0 + g^2*D1 + g^4*D2 + g^6*D3]
# [D3]
# 步骤 2: 前 k 行为单位矩阵(数据块不变),后 m 行为校验块
# [I ] [D0] [D0] (数据块)
# [ ] x [D1] = [D1] (数据块)
# [V ] [D2] [P0] (校验块)
# [D3] [P1] (校验块)
# 步骤 3: 解码——从任意 k 个幸存块构建解码矩阵
# 取幸存行组成新矩阵 M',求逆 M'^-1
# original = M'^-1 x surviving_blocks
RS 编码参数说明计算复杂度
编码矩阵乘法O(k × m)
解码矩阵求逆 + 乘法O(k^2 + k × m)
有限域GF(2^8) 或 GF(2^16)查表法加速
Warning

RAID 5 写洞(Write Hole):当 RAID 5 正在写入一个条带时发生崩溃,可能出现数据块已更新但校验块未更新的不一致状态。例如:写入 D0’ 和 P’ 时,如果 D0’ 已写入但 P’ 未写入,则 D0’ ⊕ D1 ≠ P,重建时数据错误。解决方案包括:使用日志式 RAID(ZFS)、非易失性缓存(BBU/NVCache),或直接使用 RAID 6/纠删码规避此问题。

5.5 Ceph 纠删码应用#

Ceph RADOS 原生支持纠删码存储池,配置灵活:

# Ceph 纠删码配置
# 创建纠删码 Profile
ceph osd erasure-code-profile set my-ec-profile plugin=jerasure k=8 m=3 technique=reed_sol_van crush-failure-domain=host
# 创建纠删码存储池
ceph osd pool create my-ec-pool 128 128 erasure my-ec-profile
# 覆盖写入支持(需要显式开启)
ceph osd pool set my-ec-pool allow_ec_overwrites true
# 注意:开启后不保证原子性覆盖写入,仅适合对象/文件存储
Ceph EC 参数说明推荐值
k数据块数8
m校验块数3
crush-failure-domain故障域host
plugin编码插件jerasure / isa
allow_ec_overwrites允许覆盖写仅对象存储开启

六、实战:RAID 管理#

6.1 Linux RAID 配置#

# 使用 mdadm 创建 RAID
# RAID 1(镜像)
mdadm --create /dev/md0 --level=1 --raid-devices=2 /dev/sda1 /dev/sdb1
# RAID 5(条带+校验)
mdadm --create /dev/md1 --level=5 --raid-devices=4 \
/dev/sda1 /dev/sdb1 /dev/sdc1 /dev/sdd1
# RAID 10(镜像+条带)
mdadm --create /dev/md2 --level=10 --raid-devices=4 \
/dev/sda1 /dev/sdb1 /dev/sdc1 /dev/sdd1
# 查看 RAID 状态
cat /proc/mdstat
mdadm --detail /dev/md0
# 重建进度
# Personalities : [raid1] [raid6] [raid5] [raid10]
# md0 : active raid1 sda1[0] sdb1[1]
# 2095104 blocks super 1.2 [2/2] [UU]
# md1 : active raid5 sda1[0] sdb1[1] sdc1[2] sdd1[3]
# 6286528 blocks super 1.2 level 5, 64k chunk, algorithm 2 [4/4] [UUUU]

6.2 RAID 性能测试#

# RAID 性能基准测试
# 顺序读
fio --name=raid-seq-read --directory=/mnt/raid --rw=read --bs=1M \
--size=10G --numjobs=4 --ioengine=libaio --direct=1
# 随机写
fio --name=raid-rand-write --directory=/mnt/raid --rw=randwrite --bs=4k \
--size=10G --numjobs=4 --iodepth=32 --ioengine=libaio --direct=1
# RAID 5 写惩罚:每次写入需要 4 次 I/O
# 1. 读取旧数据块
# 2. 读取旧校验块
# 3. 写入新数据块
# 4. 写入新校验块

七、数据库 RAID 选择#

7.1 数据库 RAID 推荐#

场景推荐 RAID原因
MySQL/PostgreSQL OLTPRAID 10写性能好,容错强
MySQL/PostgreSQL OLTP(预算有限)RAID 5空间利用率高
数据仓库RAID 6 或纠删码大容量,高容错
备份归档纠删码空间利用率最高
WAL 日志盘RAID 1写性能好,容错强

7.2 RAID 对数据库写入的影响#

# RAID 写惩罚计算
def raid_write_penalty():
"""计算不同 RAID 级别的写惩罚"""
penalties = {
"RAID 0": {"full_write": 1, "random_write": 1},
"RAID 1": {"full_write": 2, "random_write": 2},
"RAID 5": {"full_write": 1, "random_write": 4},
"RAID 6": {"full_write": 1, "random_write": 6},
"RAID 10": {"full_write": 2, "random_write": 2},
}
for raid, penalty in penalties.items():
print(f"{raid:8s}: 全量写入={penalty['full_write']}x, "
f"随机写入={penalty['random_write']}x")
raid_write_penalty()
# RAID 0: 全量写入=1x, 随机写入=1x
# RAID 1: 全量写入=2x, 随机写入=2x
# RAID 5: 全量写入=1x, 随机写入=4x
# RAID 6: 全量写入=1x, 随机写入=6x
# RAID 10: 全量写入=2x, 随机写入=2x

八、总结#

主题核心要点关键词
RAID 级别RAID 0 无冗余、RAID 1 镜像、RAID 5 条带+校验、RAID 6 双校验、RAID 10 镜像+条带条带, 校验
纠删码k+m 编码,任意 k 个块可恢复,空间利用率比副本高 2 倍k+m, 空间效率
Reed-Solomon基于伽罗瓦域的编码,是 RAID 6 和纠删码的数学基础伽罗瓦域, RS 编码
故障域磁盘→服务器→机架→数据中心,数据块应分散到不同故障域故障隔离, 分散放置
重建性能RAID 5 重建期间无容错,大容量盘重建需 27+ 小时重建窗口, 无容错
数据库选择OLTP 用 RAID 10,OLAP 用 RAID 6 或纠删码RAID 10, RAID 6

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

RAID 与纠删码
https://blog.souloss.com/posts/storage/storage-raid-and-erasure-coding/
作者
Souloss
发布于
2025-09-12
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时