2011年,Netflix决定将整个用户数据基础设施从Oracle迁移到Cassandra。当时他们面临的是:每天500亿次读写、超过1.2亿活跃用户、需要99.999%可用性。五年后,Cassandra不仅支撑了这一负载,还帮助Netflix实现了零计划外停机。这背后的技术故事,正是分布式数据库设计的典范。
01 设计哲学:CAP定理的工程实现
1.1 Cassandra的CAP定位与一致性模型
Cassandra明确选择了 AP(可用性 + 分区容忍性) 作为基础,但通过可调节一致性实现了实际上的CAP灵活性。这一设计决策的数学基础来自分布式系统理论:
java
// Cassandra的一致性级别计算
public class ConsistencyCalculator {
// 读写一致性级别的组合决定最终一致性
public boolean isOperationConsistent(ConsistencyLevel writeCL,
ConsistencyLevel readCL,
int replicationFactor) {
// 公式:R + W > N 时提供强一致性
// R = 读取所需确认数,W = 写入所需确认数,N = 副本数
int R = getRequiredAcknowledgements(readCL, replicationFactor);
int W = getRequiredAcknowledgements(writeCL, replicationFactor);
return (R + W) > replicationFactor;
}
// 不同一致性级别所需的确认数
private int getRequiredAcknowledgements(ConsistencyLevel cl, int rf) {
switch (cl) {
case ONE: return 1;
case TWO: return 2;
case THREE: return 3;
case QUORUM: return (rf / 2) + 1; // 多数派
case LOCAL_QUORUM: return (localRF / 2) + 1; // 本地数据中心多数
case EACH_QUORUM: return rf; // 所有数据中心多数
case ALL: return rf; // 所有副本
default: return 1;
}
}
}
1.2 一致性级别的性能与安全权衡
每个一致性级别都有其特定的使用场景和性能特征:
| 一致性级别 | 写入延迟 | 读取延迟 | 数据一致性 | 适用场景 | 数学公式 |
|---|---|---|---|---|---|
| ANY | 极低 | N/A | 最弱 | 日志收集 | W=1(包括Hinted Handoff) |
| ONE | 低 | 低 | 最终一致 | 用户会话 | R/W=1 |
| LOCAL_ONE | 低 | 低 | 本地最终 | 地理分布应用 | 本地数据中心R/W=1 |
| QUORUM | 中 | 中 | 强一致 | 用户数据 | R+W > N |
| LOCAL_QUORUM | 中低 | 中低 | 本地强一致 | 区域化服务 | 本地R+W > 本地N |
| EACH_QUORUM | 高 | 高 | 全局强一致 | 金融交易 | 每个数据中心R/W=QUORUM |
| ALL | 最高 | 最高 | 线性化 | 配置管理 | R/W=N |
1.3 Hinted Handoff:可用性的秘密武器
当目标节点不可用时,Cassandra使用Hinted Handoff机制保证写入不被丢失:
python
class HintedHandoffManager:
def __init__(self):
self.hints_directory = "/var/lib/cassandra/hints"
self.max_hint_window = 3600 # 最多存储1小时的提示
def store_hint(self, target_node, mutation):
"""存储无法直接传递的写入提示"""
hint_id = f"{target_node}_{int(time.time())}"
hint_data = {
'target': target_node,
'mutation': mutation,
'timestamp': time.time(),
'ttl': self.max_hint_window
}
# 写入本地提示文件
with open(f"{self.hints_directory}/{hint_id}.hint", 'w') as f:
json.dump(hint_data, f)
# 启动后台传递线程
self.start_hint_delivery_thread(target_node)
def deliver_hints(self, target_node):
"""当目标节点恢复时传递提示"""
hints = self.load_hints_for_node(target_node)
for hint in hints:
try:
# 尝试传递突变
self.send_mutation(target_node, hint['mutation'])
self.delete_hint(hint['id'])
except Exception as e:
# 如果仍然失败,更新重试时间
self.update_hint_retry_time(hint['id'])
Hinted Handoff的工作原理:
- 检测失败:写入时发现目标节点不可达
- 本地存储:将突变存储在本地的提示文件中
- 定期重试:后台线程定期尝试传递提示
- 自动清理:成功传递或超过TTL后清理提示
02 架构深度:对等、去中心化设计
2.1 一致性哈希的工程实现
Cassandra的一致性哈希实现比理论更加复杂和优化:
java
public class TokenAllocationStrategy {
// 虚拟节点(vnode)分配算法
public Map<BigInteger, Node> allocateTokens(List<Node> nodes,
int numVirtualNodes) {
Map<BigInteger, Node> tokenRing = new TreeMap<>();
Random random = new Random();
for (Node node : nodes) {
for (int i = 0; i < numVirtualNodes; i++) {
// 生成确定性的token
String tokenInput = node.getIpAddress() + ":" + i;
BigInteger token = calculateToken(tokenInput);
// 确保token不冲突
while (tokenRing.containsKey(token)) {
token = token.add(BigInteger.ONE);
}
tokenRing.put(token, node);
}
}
return tokenRing;
}
// Murmur3哈希 - Cassandra的默认哈希算法
private BigInteger calculateToken(String key) {
// Murmur3哈希提供良好的分布特性
int hash = murmur3Hash(key.getBytes());
// 将哈希映射到2^127的环上
BigInteger maxToken = BigInteger.valueOf(2).pow(127);
BigInteger token = BigInteger.valueOf(hash & Long.MAX_VALUE);
return token.mod(maxToken);
}
}
虚拟节点的优势:
- 负载均衡:每个物理节点承载多个虚拟节点,数据分布更均匀
- 弹性扩展:添加/移除节点时,数据迁移更平滑
- 故障恢复:节点故障时,负载分散到多个其他节点
2.2 Gossip协议:去中心化的集群管理
Cassandra使用Gossip协议进行集群状态同步,实现完全的去中心化:
python
class GossipProtocol:
def __init__(self, local_node, seed_nodes):
self.local_node = local_node
self.seed_nodes = seed_nodes
self.live_nodes = {} # 存活节点
self.unreachable_nodes = {} # 不可达节点
self.version_generations = {} # 版本生成信息
def start_gossip(self):
"""启动Gossip协议"""
while True:
# 1. 随机选择对等节点
peer = self.select_random_peer()
# 2. 同步状态信息
self.exchange_gossip(peer)
# 3. 计算故障检测
self.detect_failures()
# 4. 等待下一个周期(默认1秒)
time.sleep(1)
def exchange_gossip(self, peer):
"""与对等节点交换状态信息"""
# 构建同步消息
sync_message = {
'generation': self.local_generation,
'version': self.local_version,
'heartbeat_state': self.heartbeat_state,
'application_state': self.application_state
}
# 发送并接收响应
response = self.send_sync_message(peer, sync_message)
# 合并状态信息
self.merge_states(response['states'])
# 更新本地状态版本
self.update_local_state(response['delta'])
Gossip协议的数学特性:
- 传播速度:O(log N),其中N是节点数
- 带宽消耗:每个节点每轮与固定数量节点通信
- 收敛时间:在几轮内整个集群达到一致状态
2.3 分区器:决定数据分布的关键
Cassandra支持多种分区策略,每种都有不同的分布特性:
java
public interface Partitioner {
BigInteger getToken(ByteBuffer key);
// 三种主要分区器
enum Type {
MURMUR3, // 默认:均匀分布,基于Murmur3哈希
RANDOM, // 遗留:基于MD5哈希
BYTE_ORDERED // 有序:保持键的顺序
}
}
public class Murmur3Partitioner implements Partitioner {
// 最常用的分区器实现
@Override
public BigInteger getToken(ByteBuffer key) {
// 使用Murmur3哈希算法
long hash = MurmurHash.hash3_x64_128(key, 0, key.remaining(), 0)[0];
// 规范化到2^127的范围
BigInteger token = BigInteger.valueOf(hash);
if (token.signum() < 0) {
token = token.add(BigInteger.ONE.shiftLeft(127));
}
return token;
}
// 计算键属于哪个token范围
public Range getTokenRange(ByteBuffer key) {
BigInteger token = getToken(key);
NavigableMap<BigInteger, Node> tokenRing = getTokenRing();
// 在环上找到下一个token(顺时针)
Map.Entry<BigInteger, Node> entry = tokenRing.higherEntry(token);
if (entry == null) {
// 环回处理
entry = tokenRing.firstEntry();
}
BigInteger endToken = entry.getKey();
BigInteger startToken = tokenRing.lowerKey(endToken);
if (startToken == null) {
startToken = tokenRing.lastKey();
}
return new Range(startToken, endToken);
}
}
03 存储引擎:LSM树的优化实现
3.1 Memtable:内存中的写入缓冲区
java
public class Memtable implements Comparable<Memtable> {
private final ConcurrentSkipListMap<DecoratedKey, Row> data;
private final AtomicLong sizeInBytes = new AtomicLong(0);
private final AtomicInteger rowCount = new AtomicInteger(0);
private volatile boolean isFrozen = false;
// 写入优化:使用跳表实现高效并发写入
public void put(DecoratedKey key, Row row) {
if (isFrozen) {
throw new IllegalStateException("Memtable is frozen");
}
Row previous = data.put(key, row);
// 更新大小统计
long rowSize = calculateRowSize(row);
sizeInBytes.addAndGet(rowSize);
if (previous != null) {
// 如果是更新,减去旧行大小
long previousSize = calculateRowSize(previous);
sizeInBytes.addAndGet(-previousSize);
} else {
rowCount.incrementAndGet();
}
// 检查是否达到刷新阈值
if (sizeInBytes.get() > flushThreshold) {
requestFlush();
}
}
// 内存管理:跟踪内存使用
private long calculateRowSize(Row row) {
long size = 0;
// 键大小
size += row.key.getSize();
// 列数据大小
for (ColumnData column : row.columns) {
size += column.name.length();
size += column.value.remaining();
size += 8; // timestamp大小
size += 1; // ttl标记
}
return size;
}
}
3.2 SSTable:不可变的磁盘存储格式
SSTable(Sorted String Table)是Cassandra的核心存储结构,具有精心设计的文件格式:
Data.db (主要数据文件)
├── 分区索引 (Partition Index)
│ ├── 分区键1 -> 文件偏移
│ ├── 分区键2 -> 文件偏移
│ └── ...
├── 行数据 (Row Data)
│ ├── 分区1
│ │ ├── 行墓碑 (Row Tombstone)
│ │ ├── 列数据 (Column Data)
│ │ └── 范围墓碑 (Range Tombstone)
│ └── 分区2
└── 摘要 (Summary) - 采样索引加速查找
Index.db (辅助索引)
└── 分区内索引
├── 聚集键1 -> 行内偏移
└── 聚集键2 -> 行内偏移
Filter.db (布隆过滤器)
└── 位数组 (Bit Array)
- 快速判断键是否不存在
Statistics.db (统计信息)
├── 最小/最大键
├── 键数量
├── 文件大小
└── 压缩信息
SSTable写入优化:
java
public class SSTableWriter {
// 顺序写入优化
public void writePartition(DecoratedKey key, Row row) {
// 1. 写入数据文件
long dataOffset = dataChannel.position();
writeRowData(dataChannel, row);
// 2. 更新索引
indexEntries.put(key, dataOffset);
// 3. 定期刷新到磁盘(避免小写入)
if (indexEntries.size() % INDEX_INTERVAL == 0) {
flushIndexEntries();
}
// 4. 更新布隆过滤器
bloomFilter.add(key);
// 5. 更新统计信息
updateStatistics(key, row);
}
// 批量写入优化
private void writeRowData(FileChannel channel, Row row) throws IOException {
// 使用ByteBuffer批量写入
ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
// 写入行头
writeRowHeader(buffer, row);
// 序列化列数据
for (ColumnData column : row.columns) {
writeColumn(buffer, column);
// 缓冲区满时刷新
if (!buffer.hasRemaining()) {
buffer.flip();
channel.write(buffer);
buffer.clear();
}
}
// 写入剩余数据
if (buffer.position() > 0) {
buffer.flip();
channel.write(buffer);
}
}
}
3.3 压缩策略:深入解析四种算法
SizeTieredCompactionStrategy (STCS)
java
public class SizeTieredCompactionStrategy extends AbstractCompactionStrategy {
// 桶策略:将大小相似的SSTable分组
public List<SSTableReader> getNextCompactionCandidates() {
Map<SizeTier, List<SSTableReader>> buckets = new HashMap<>();
// 按大小分组
for (SSTableReader sstable : sstables) {
SizeTier tier = getSizeTier(sstable.length());
buckets.computeIfAbsent(tier, k -> new ArrayList<>()).add(sstable);
}
// 选择最大的桶进行压缩
Optional<List<SSTableReader>> largestBucket =
buckets.values().stream()
.max(Comparator.comparingInt(List::size));
return largestBucket.filter(list -> list.size() >= 4) // 至少4个
.orElse(Collections.emptyList());
}
// 计算大小层级
private SizeTier getSizeTier(long size) {
// 指数级增长:1x, 2x, 4x, 8x, ...
long tierSize = 64 * 1024 * 1024; // 64MB基准
int tierLevel = 0;
while (size > tierSize * (1L << tierLevel)) {
tierLevel++;
}
return new SizeTier(tierLevel);
}
}
STCS优缺点:
- ✅ 写入放大最小
- ✅ 适合写密集型负载
- ❌ 读取需要检查多个SSTable
- ❌ 空间放大较大
LeveledCompactionStrategy (LCS)
java
public class LeveledCompactionStrategy extends AbstractCompactionStrategy {
private final List<Level> levels = new ArrayList<>();
public LeveledCompactionStrategy() {
// 初始化层级:L0, L1, L2, ...
// 每层大小是上一层的10倍(可配置)
long levelSize = 160 * 1024 * 1024; // L1: 160MB
levels.add(new Level(0, levelSize));
for (int i = 1; i < MAX_LEVELS; i++) {
levelSize *= 10; // 每层增长10倍
levels.add(new Level(i, levelSize));
}
}
@Override
public List<SSTableReader> getNextCompactionCandidates() {
// 优先压缩L0(来自Memtable的flush)
if (levels.get(0).size() > 4) {
return levels.get(0).getSSTables();
}
// 选择超出大小限制的层级
for (int i = 1; i < levels.size(); i++) {
Level level = levels.get(i);
if (level.size() > level.getTargetSize()) {
// 选择该层级的一个SSTable与下一层级的重叠SSTables
return selectSSTablesForLeveledCompaction(i);
}
}
return Collections.emptyList();
}
}
LCS的数学特性:
- 层级大小比:通常设为10(L1=160MB, L2=1.6GB, L3=16GB...)
- 空间放大:~1.1倍(最优)
- 写入放大:较高(每次写入可能经历多次压缩)
- 读取性能:最优(每键最多检查一个SSTable)
TimeWindowCompactionStrategy (TWCS)
java
public class TimeWindowCompactionStrategy extends AbstractCompactionStrategy {
private final long windowSize; // 时间窗口大小(如1天)
private final Map<Long, TimeWindow> windows = new TreeMap<>();
public List<SSTableReader> getNextCompactionCandidates() {
long currentTime = System.currentTimeMillis();
long currentWindow = getWindowId(currentTime);
// 获取当前时间窗口
TimeWindow window = windows.get(currentWindow);
if (window != null && window.getSSTables().size() >= 4) {
// 压缩当前窗口内的SSTables(STCS风格)
return window.getSSTables();
}
// 查找过期的窗口进行压缩
long expiredWindow = currentWindow - windowRetentionCount;
TimeWindow expired = windows.get(expiredWindow);
if (expired != null) {
// 过期窗口内的所有SSTable合并为一个
return expired.getAllSSTables();
}
return Collections.emptyList();
}
// 确定SSTable属于哪个时间窗口
private long getWindowId(long timestamp) {
// 基于数据的时间戳(而非创建时间)
long minTimestamp = getSSTableMinTimestamp();
return minTimestamp / windowSize;
}
}
TWCS适用场景:
- 时间序列数据:IoT传感器数据、日志数据
- TTL数据管理:自动过期清理
- 监控指标:按时间窗口聚合
3.4 布隆过滤器的深度优化
Cassandra的布隆过滤器实现考虑了内存效率和查询速度的平衡:
java
public class BloomFilter {
private final BitSet bitset;
private final int numHashFunctions;
private final int numBits;
// 根据预期元素数量和误判率计算参数
public BloomFilter(long expectedInsertions, double falsePositiveRate) {
// 计算所需位数:m = -n * ln(p) / (ln(2)^2)
this.numBits = (int) Math.ceil(
-expectedInsertions * Math.log(falsePositiveRate) /
(Math.log(2) * Math.log(2))
);
// 计算哈希函数数量:k = (m/n) * ln(2)
this.numHashFunctions = Math.max(1, (int) Math.round(
(double) numBits / expectedInsertions * Math.log(2)
));
this.bitset = new BitSet(numBits);
}
// 优化的哈希计算:使用两个基础哈希生成k个哈希值
private int[] getHashPositions(ByteBuffer key) {
long hash1 = murmur3_128(key, 0);
long hash2 = murmur3_128(key, hash1);
int[] positions = new int[numHashFunctions];
for (int i = 0; i < numHashFunctions; i++) {
// 双重哈希技术:hi = h1 + i * h2
long combinedHash = hash1 + i * hash2;
// 确保结果为非负
combinedHash = combinedHash & Long.MAX_VALUE;
// 映射到bit数组范围
positions[i] = (int) (combinedHash % numBits);
}
return positions;
}
// 快速"可能包含"检查
public boolean mightContain(ByteBuffer key) {
int[] positions = getHashPositions(key);
// 检查所有bit位是否都为1
for (int position : positions) {
if (!bitset.get(position)) {
return false; // 肯定不存在
}
}
return true; // 可能存在(有误判概率)
}
// 内存使用统计
public MemoryUsage getMemoryUsage() {
long bitsSize = (numBits + 7) / 8; // 转换为字节
long overhead = 16 + 8 + 4; // 对象头 + 引用 + 数组开销
return new MemoryUsage(bitsSize + overhead);
}
}
布隆过滤器的误判率计算:
误判率 p ≈ (1 - e^(-k * n / m))^k
其中:
n = 插入元素数量
m = bit数组大小
k = 哈希函数数量
优化目标:给定n和p,最小化m
3.5 二级索引的实现机制
Cassandra的二级索引采用本地索引策略,每个节点只索引自己存储的数据:
java
public class SecondaryIndex {
// 索引条目结构
static class IndexEntry {
final DecoratedKey partitionKey;
final Clustering clustering;
final ByteBuffer indexedValue;
final long timestamp;
}
// 索引表结构:indexed_value -> (partition_key, clustering)
private final ColumnFamilyStore indexCFS;
// 索引更新(与主表同步)
public void onRowUpdated(RowUpdate update) {
// 获取索引列的值
ByteBuffer indexedValue = getIndexedValue(update);
// 构建索引条目
IndexEntry newEntry = new IndexEntry(
update.partitionKey(),
update.clustering(),
indexedValue,
update.timestamp()
);
// 删除旧索引条目(如果值发生变化)
if (update.hasOldRow()) {
ByteBuffer oldValue = getIndexedValue(update.oldRow());
if (!oldValue.equals(indexedValue)) {
removeIndexEntry(oldValue, update);
}
}
// 插入新索引条目
addIndexEntry(newEntry);
}
// 索引查询
public PartitionRangeIterator search(Operator operator, ByteBuffer value) {
// 1. 扫描索引表找到匹配的主键
List<DecoratedKey> partitionKeys = scanIndexCFS(operator, value);
// 2. 从主表读取完整数据
return fetchFromPrimaryTable(partitionKeys);
}
}
二级索引的局限性:
- 本地性:只索引本地数据,查询需要访问所有节点
- 性能:适合低基数列,高基数列性能差
- 一致性:异步维护,可能有短暂不一致
04 查询执行引擎
4.1 读取路径的完整流程
java
public class ReadExecutor {
public PartitionIterator read(ReadCommand command) {
// 1. 确定需要联系的节点
List<InetAddress> endpoints = getReplicaEndpoints(command);
// 2. 根据一致性级别确定需要多少响应
int requiredResponses = calculateRequiredResponses(command.consistencyLevel());
// 3. 并行发送读取请求
List<Future<ReadResponse>> futures = sendReadRequests(endpoints, command);
// 4. 收集和合并响应
List<ReadResponse> responses = collectResponses(futures, requiredResponses);
// 5. 数据协调(解决冲突)
PartitionIterator result = reconcileResponses(responses);
// 6. 修复读(如果检测到数据不一致)
if (needsReadRepair(responses)) {
scheduleReadRepair(responses);
}
return result;
}
// 数据协调:基于时间戳的冲突解决
private Row reconcileRows(List<Row> rows) {
if (rows.isEmpty()) return null;
if (rows.size() == 1) return rows.get(0);
// 基于时间戳的Last-Write-Wins
Row reconciled = rows.get(0);
for (int i = 1; i < rows.size(); i++) {
reconciled = reconcileTwoRows(reconciled, rows.get(i));
}
return reconciled;
}
// 两行数据协调
private Row reconcileTwoRows(Row row1, Row row2) {
// 比较行级别墓碑
if (row1.hasRowLevelTombstone() || row2.hasRowLevelTombstone()) {
return resolveRowTombstones(row1, row2);
}
// 列级别协调
return reconcileColumns(row1, row2);
}
}
4.2 范围查询的优化
java
public class RangeQueryExecutor {
public PartitionRangeIterator getRangeIterator(RangeCommand command) {
// 1. 将查询范围映射到token环
List<Range<Token>> tokenRanges = getTokenRangesForQuery(command);
// 2. 为每个范围选择副本
Map<InetAddress, List<Range<Token>>> rangesByEndpoint =
groupRangesByEndpoint(tokenRanges);
// 3. 并行执行子范围查询
List<Future<PartitionRangeIterator>> futures =
executeParallelRangeQueries(rangesByEndpoint, command);
// 4. 合并结果(保持顺序)
return mergeRangeIterators(futures, command);
}
// 并行范围查询执行
private List<Future<PartitionRangeIterator>> executeParallelRangeQueries(
Map<InetAddress, List<Range<Token>>> rangesByEndpoint,
RangeCommand command) {
List<Future<PartitionRangeIterator>> futures = new ArrayList<>();
for (Map.Entry<InetAddress, List<Range<Token>>> entry : rangesByEndpoint.entrySet()) {
InetAddress endpoint = entry.getKey();
List<Range<Token>> ranges = entry.getValue();
// 为每个端点创建查询任务
Callable<PartitionRangeIterator> task = () -> {
// 建立连接
Connection connection = connect(endpoint);
// 执行查询(可能分页)
PartitionRangeIterator iterator =
executeRangeQueryOnNode(connection, ranges, command);
return iterator;
};
futures.add(executor.submit(task));
}
return futures;
}
}
05 生产部署与优化
5.1 硬件选型与配置
yaml
# 生产环境硬件配置建议
hardware_spec:
# AWS i3系列(NVMe SSD优化)
instance_type: i3.4xlarge
# 内存配置(JVM堆大小)
memory:
total: 122GB
heap_size: 32GB # 不要超过32GB,避免GC停顿
offheap_size: 90GB # 用于缓存和文件系统缓存
# 存储配置
storage:
type: nvme_ssd
capacity: 2x1900GB # RAID 0配置
iops: 400000
throughput: 2000MB/s
# 网络配置
network:
bandwidth: 10Gbps
enhanced_networking: enabled
5.2 JVM优化参数
bash
# cassandra-env.sh 中的优化配置
# 内存设置(关键!)
JVM_OPTS="$JVM_OPTS -Xms32G -Xmx32G"
JVM_OPTS="$JVM_OPTS -Xmn8G" # 年轻代大小
JVM_OPTS="$JVM_OPTS -XX:+AlwaysPreTouch"
# G1垃圾收集器优化
JVM_OPTS="$JVM_OPTS -XX:+UseG1GC"
JVM_OPTS="$JVM_OPTS -XX:MaxGCPauseMillis=500"
JVM_OPTS="$JVM_OPTS -XX:InitiatingHeapOccupancyPercent=75"
JVM_OPTS="$JVM_OPTS -XX:G1ReservePercent=15"
JVM_OPTS="$JVM_OPTS -XX:G1HeapRegionSize=32M"
# 性能优化
JVM_OPTS="$JVM_OPTS -XX:+PerfDisableSharedMem"
JVM_OPTS="$JVM_OPTS -XX:+OptimizeStringConcat"
JVM_OPTS="$JVM_OPTS -XX:+UseStringDeduplication"
# 监控和调试
JVM_OPTS="$JVM_OPTS -XX:+HeapDumpOnOutOfMemoryError"
JVM_OPTS="$JVM_OPTS -XX:HeapDumpPath=/var/lib/cassandra/heapdumps"
5.3 监控指标与告警
yaml
# Prometheus监控配置
metrics_config:
# 关键性能指标
key_metrics:
# 读取性能
read_latency:
query: 'rate(cassandra_client_request_latency_seconds_sum{operation="read"}[5m])'
threshold: '> 0.01' # P99 < 10ms
# 写入性能
write_latency:
query: 'rate(cassandra_client_request_latency_seconds_sum{operation="write"}[5m])'
threshold: '> 0.02' # P99 < 20ms
# 压缩延迟
compaction_latency:
query: 'cassandra_compaction_completed_tasks'
threshold: '< 10' # 每分钟至少10个压缩任务
# 内存使用
heap_usage:
query: 'cassandra_jvm_memory_used_bytes{area="heap"} / cassandra_jvm_memory_max_bytes{area="heap"}'
threshold: '> 0.8' # 超过80%告警
# 磁盘使用
disk_usage:
query: 'cassandra_storage_load_bytes / cassandra_storage_capacity_bytes'
threshold: '> 0.85' # 超过85%告警
5.4 多数据中心部署架构
sql
-- 多数据中心keyspace定义
CREATE KEYSPACE global_data
WITH replication = {
'class': 'NetworkTopologyStrategy',
'us-east-1': '3', -- AWS us-east-1区域,3个副本
'us-west-2': '3', -- AWS us-west-2区域,3个副本
'eu-west-1': '3', -- AWS eu-west-1区域,3个副本
'ap-northeast-1': '2' -- AWS东京区域,2个副本
};
-- 每个数据中心的本地表(降低跨区域流量)
CREATE TABLE local_sessions (
session_id uuid PRIMARY KEY,
user_id uuid,
data text,
created_at timestamp
) WITH replication = {
'class': 'NetworkTopologyStrategy',
'us-east-1': '3' -- 只在本区域复制
};
-- 使用LOCAL一致性级别减少延迟
SELECT * FROM local_sessions
WHERE session_id = ?
USING CONSISTENCY LOCAL_QUORUM;
06 高级特性与未来发展
6.1 轻量级事务(LWT)实现
java
public class LightweightTransaction {
// Paxos协议实现
public boolean compareAndSet(String key, String expected, String newValue) {
// 1. Prepare阶段
Ballot ballot = propose(key, expected);
// 2. Accept阶段
boolean accepted = accept(key, ballot, newValue);
if (!accepted) {
// 3. 冲突解决
return resolveConflict(key, expected, newValue);
}
// 4. Commit阶段
commit(key, ballot, newValue);
return true;
}
// 基于时间戳的Paxos优化
private Ballot propose(String key, String expected) {
// 生成全局唯一的ballot ID(时间戳 + 节点ID)
long timestamp = System.currentTimeMillis();
String nodeId = getLocalNodeId();
Ballot ballot = new Ballot(timestamp, nodeId);
// 查询当前状态
ReadResponse response = readForPaxos(key);
// 检查是否与期望值一致
if (!response.value.equals(expected)) {
throw new CASConflictException();
}
// 返回ballot供后续阶段使用
return ballot;
}
}
6.2 向量搜索(Cassandra 5.0+)
sql
-- 向量数据类型
CREATE TABLE product_embeddings (
product_id uuid PRIMARY KEY,
name text,
description text,
embedding vector<float, 768> -- 768维向量
);
-- 创建向量索引
CREATE CUSTOM INDEX product_embedding_idx
ON product_embeddings (embedding)
USING 'org.apache.cassandra.index.sai.StorageAttachedIndex'
WITH OPTIONS = {
'similarity_function': 'cosine'
};
-- 向量相似度搜索
SELECT product_id, name,
similarity_cosine(embedding, ?) as score
FROM product_embeddings
ORDER BY embedding ANN OF ? -- 近似最近邻搜索
LIMIT 10;
07 总结:Cassandra的适用场景与限制
7.1 适合使用Cassandra的场景
完美匹配:
- 时间序列数据:IoT传感器、监控指标、日志数据
- 用户内容存储:社交媒体的用户生成内容
- 购物车和会话数据:需要高可用的临时数据
- 产品目录和推荐系统:需要灵活模式的数据
- 消息和聊天系统:需要全球分布和低延迟
技术指标:
- 数据量:TB到PB级别
- 写入吞吐量:每秒10K到1M+操作
- 读取延迟:P99 < 10ms(本地查询)
- 可用性要求:99.99%+
7.2 不适合的场景
应该避免:
- 复杂事务:需要ACID保证的金融交易
- 复杂JOIN操作:高度规范化的关系数据
- 实时分析:需要复杂聚合和窗口函数
- 全文搜索:需要高级文本分析功能
- 小规模数据:< 1TB,节点数 < 3
7.3 迁移决策矩阵
| 考虑因素 | 选择Cassandra | 选择其他方案 |
|---|---|---|
| 数据规模 | > 1TB,持续增长 | < 100GB,稳定 |
| 写入模式 | 高吞吐,追加为主 | 更新频繁,复杂事务 |
| 读取模式 | 按主键查询,范围扫描 | 复杂JOIN,即席查询 |
| 一致性要求 | 最终一致可接受 | 需要强一致性 |
| 运维能力 | 有分布式系统经验 | 希望完全托管 |
Cassandra的真正力量在于它对大规模分布式数据管理的专注。它不试图成为万能数据库,而是在可扩展性、可用性和性能方面做到极致。当你的应用需要处理海量数据、服务全球用户、并且不能容忍停机时,Cassandra提供了经过验证的解决方案。
从Netflix的1.9亿用户资料到苹果的数万亿条iMessage,从Twitter的时间线到eBay的产品目录,Cassandra已经证明了自己作为互联网规模应用的基础设施的价值。它不是最简单的数据库,但对于正确的问题,它是最高效的解决方案之一。