1. 依赖与运行前准备
1.1 Maven 依赖
使用 Cassandra Connector 需要显式引入依赖(不包含在 Flink 二进制发行版中):
xml
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-cassandra_2.12</artifactId>
<version>2.2.0</version>
</dependency>
注意:
- Streaming Connector 默认不随 Flink 发布
- 提交到集群前需要确保依赖已正确打包或通过
lib/方式加载
1.2 本地 Cassandra 环境
本地开发常见的两种方式:
- 官方安装(适合深入调试)
- Docker 启动(推荐)
bash
docker run -d --name cassandra -p 9042:9042 cassandra:latest
2. Cassandra Source:有界批量读取能力
Flink 提供了一个 基于 FLIP-27 的 Cassandra Bounded Source,主要用于:
- 批量读取 Cassandra 表
- 将表数据映射为 POJO DataStream
2.1 核心设计思想
- 使用 DataStax Object Mapper
- 将 Cassandra 表映射为 Java POJO
- 自动切分数据并行读取
切分策略为:
numSplits = tableSize / maxSplitMemorySize
如果表大小无法估算,则回退为:
numSplits = Source Parallelism
2.2 Cassandra Source 示例
java
ClusterBuilder clusterBuilder = new ClusterBuilder() {
@Override
protected Cluster buildCluster(Cluster.Builder builder) {
return builder
.addContactPointsWithPorts(
new InetSocketAddress(HOST, PORT)
)
.withQueryOptions(
new QueryOptions().setConsistencyLevel(CL)
)
.withSocketOptions(
new SocketOptions()
.setConnectTimeoutMillis(CONNECT_TIMEOUT)
.setReadTimeoutMillis(READ_TIMEOUT)
)
.build();
}
};
Source<Pojo> cassandraSource =
new CassandraSource(
clusterBuilder,
maxSplitMemorySize, // 可选,最小 10MB,默认 64MB
Pojo.class,
"SELECT * FROM keyspace.table;",
() -> new Mapper.Option[]{
Mapper.Option.saveNullFields(true)
}
);
DataStream<Pojo> stream =
env.fromSource(
cassandraSource,
WatermarkStrategy.noWatermarks(),
"CassandraSource"
);
适用场景说明:
- Cassandra Source 只适合批量读取
- 不适合用作实时 CDC 或持续订阅
3. Cassandra Sink:高吞吐写入的核心组件
相比 Source,Cassandra Sink 才是生产中更常用的部分。
3.1 Sink 创建方式
Cassandra Sink 通过 Builder 模式创建:
java
CassandraSink.addSink(dataStream)
.setHost("127.0.0.1")
.build();
内部基于:
- 异步写入
- 批量请求
- Cassandra 原生驱动
4. Cassandra Sink 的核心配置项
4.1 写入方式(非常重要)
Tuple 类型(必须指定 CQL)
java
.setQuery(
"INSERT INTO example.wordcount(word, count) VALUES (?, ?);"
)
- 每个 Tuple 元素映射到 PreparedStatement 参数
- 适合简单结构、临时统计结果
POJO 类型(禁止指定 Query)
- 自动使用 DataStax Mapper
- 基于注解映射表结构
- 更适合长期维护的数据模型
4.2 常用配置项速览
| 配置项 | 作用 |
|---|---|
| setClusterBuilder | 自定义一致性级别、超时、重试策略 |
| setHost | 简化连接方式 |
| setMapperOptions | 控制 POJO 映射行为 |
| setMaxConcurrentRequests | 控制并发写入压力 |
| enableIgnoreNullFields | 避免 Tombstone |
| enableWriteAheadLog | Exactly-Once 支持(实验性) |
5. Exactly-Once、Checkpoint 与 Write-Ahead Log
5.1 默认语义:At-Least-Once
在 开启 Checkpoint 的情况下:
- Cassandra Sink 默认保证 At-Least-Once
- 失败重启后,可能会重复写入
如果你的 CQL 是 幂等的(例如基于主键覆盖写),那么重复写入通常是安全的。
5.2 Write-Ahead Log:解决非确定性问题
对于 非确定性计算逻辑(例如依赖随机数、外部状态):
- 重放 Checkpoint 可能生成不同结果
- 会导致 Cassandra 数据不一致
此时可以启用 Write-Ahead Log:
java
.enableWriteAheadLog()
工作机制:
- 在 Checkpoint 时记录写入意图
- 失败重启后严格复现相同写入顺序
重要提醒:
- Write-Ahead Log 显著增加延迟
- 该功能仍处于实验阶段
- 绝大多数业务 并不需要开启
6. 实战示例:WordCount 写入 Cassandra
6.1 表结构
sql
CREATE KEYSPACE example
WITH replication = {
'class': 'SimpleStrategy',
'replication_factor': '1'
};
CREATE TABLE example.wordcount (
word text,
count bigint,
PRIMARY KEY (word)
);
6.2 Tuple 类型写入示例
java
CassandraSink.addSink(result)
.setQuery(
"INSERT INTO example.wordcount(word, count) VALUES (?, ?);"
)
.setHost("127.0.0.1")
.build();
6.3 POJO 类型写入示例
java
@Table(keyspace = "example", name = "wordcount")
public class WordCount {
@Column(name = "word")
private String word;
@Column(name = "count")
private long count;
public WordCount() {}
public WordCount(String word, long count) {
this.word = word;
this.count = count;
}
// getter / setter 省略
}
java
CassandraSink.addSink(result)
.setHost("127.0.0.1")
.setMapperOptions(
() -> new Mapper.Option[]{
Mapper.Option.saveNullFields(true)
}
)
.build();
7. 什么时候该用 Cassandra Connector?
适合的场景
- 实时聚合结果落盘
- 高写入吞吐、低更新冲突
- 主键覆盖写
- Flink → Cassandra 单向写入
不适合的场景
- 强事务一致性
- 复杂 Join 查询
- CDC / 变更流订阅
- 低延迟强一致写入
8. 小结
可以这样理解 Cassandra Connector 在 Flink 生态中的定位:
它不是 OLAP 引擎,也不是事务数据库,而是一个高吞吐、可扩展的结果落地层。
在大多数实时架构中,它更适合作为:
- Flink 计算结果的 最终存储
- 查询服务的 后端数据源
- Kafka 之后的 状态持久化节点