Flink HBase SQL Connector RowKey/列族映射、Upsert 语义、Lookup 维表、缓存与写入缓冲

1. 核心语义:HBase 永远是 Upsert

  • HBase Connector 始终按 Upsert 模式交换 changelog(支持 UPDATE/DELETE 的那套语义)
  • 必须有 rowkey 字段(表里必须声明一个原子类型字段作为 rowkey)
  • PRIMARY KEY 必须定义在 rowkey 上;如果不写 PRIMARY KEY,connector 默认把 rowkey 当主键

一句话:HBase 表的主键就是 rowkey,Flink 也要求你按这个规则来。

2. 表结构映射:列族必须用 ROW 类型声明

HBase 的数据模型:rowkey + column family + qualifier + value

Flink SQL 里映射规则:

  • 除 ROW 类型字段之外的 单一原子类型字段 会被识别为 rowkey

  • 每个列族(family)必须声明为 ROW<...>

    • ROW 字段名 = column family 名
    • ROW 内嵌字段名 = qualifier 名

你不需要把 HBase 的所有列族/qualifier 都声明出来,只声明你查询/写入会用到的就行。

2.1 建表示例(官方格式)

sql 复制代码
CREATE TABLE hTable (
  rowkey INT,
  family1 ROW<q1 INT>,
  family2 ROW<q2 STRING, q3 BIGINT>,
  family3 ROW<q4 DOUBLE, q5 BOOLEAN, q6 STRING>,
  PRIMARY KEY (rowkey) NOT ENFORCED
) WITH (
  'connector' = 'hbase-2.2',
  'table-name' = 'mytable',
  'zookeeper.quorum' = 'localhost:2181'
);

注意:

  • rowkey 字段名可以随便起,但如果是 SQL 关键字要用反引号(rowkey
  • table-name 默认 namespace 是 default,指定 namespace 用 namespace:table

3. 写入:用 ROW(...) 构造列族

写入 HBase 时,每个列族要传一个 ROW 值:

sql 复制代码
INSERT INTO hTable
SELECT
  rowkey,
  ROW(f1q1),
  ROW(f2q2, f2q3),
  ROW(f3q4, f3q5, f3q6)
FROM T;

这里 ROW(...) 的位置要和 DDL 中列族字段的声明顺序一致。

3.1 是否写入 NULL:sink.ignore-null-value

  • 默认 sink.ignore-null-value = false:null 也会写(具体到 HBase 是空 bytes 的编码逻辑,见后文)
  • 如果你希望"字段为 null 就不覆盖 HBase 里已有值",可以考虑:
sql 复制代码
'sink.ignore-null-value' = 'true'

这在做"增量补字段"或"只更新非空字段"的场景很有用。

4. 读取:Scan 与维表 Join

4.1 扫描查询(Bounded Scan)

sql 复制代码
SELECT rowkey, family1, family3.q4, family3.q6
FROM hTable;

4.2 Temporal Join:把 HBase 当维表

sql 复制代码
SELECT *
FROM myTopic
LEFT JOIN hTable FOR SYSTEM_TIME AS OF myTopic.proctime
ON myTopic.key = hTable.rowkey;
  • Lookup Source:同步(Sync Mode)
  • 可选开启 异步 lookup(仅 hbase-2.2 支持):
sql 复制代码
'lookup.async' = 'true'

异步 lookup 的典型价值:维表查询慢或并发大时,减少算子阻塞,提高吞吐。

5. Metadata:写入时可以指定 HBase mutation 的 timestamp/ttl

HBase connector 支持两个可写元数据列(W):

  • timestamp:TIMESTAMP_LTZ(3) ------ mutation 时间戳
  • ttl:BIGINT ------ mutation TTL(毫秒)

用法要点:

  • 这是写入相关的元数据列;如果是只读列通常要 VIRTUAL,但这里是可写字段(W)

你可以在表里加上元数据列(示意):

sql 复制代码
CREATE TABLE hTable (
  rowkey INT,
  family1 ROW<q1 INT>,
  ts TIMESTAMP_LTZ(3) METADATA FROM 'timestamp',
  ttl_ms BIGINT METADATA FROM 'ttl',
  PRIMARY KEY (rowkey) NOT ENFORCED
) WITH (
  'connector'='hbase-2.2',
  'table-name'='mytable',
  'zookeeper.quorum'='localhost:2181'
);

然后 insert 时把 ts/ttl 一并写进去(适合"按业务时间回写版本"或"写入即过期"这类需求)。

6. 写入性能:三件套(max-size / max-rows / interval)

HBase sink 内置缓冲,靠批量 flush 提升吞吐:

  • sink.buffer-flush.max-size(默认 2mb)
  • sink.buffer-flush.max-rows(默认 1000)
  • sink.buffer-flush.interval(默认 1s)

含义:达到任意阈值就触发一次 flush。

建议思路:

  • 吞吐优先 :提高 max-rows / max-size,适当拉大 interval
    代价:端到端延迟增加,checkpoint/反压风险上升
  • 延迟优先 :缩小 interval(甚至 100~500ms),控制 max-rows
    代价:写入批量变小,吞吐下降,HBase 压力可能更大
  • 想完全异步依赖 interval:可以把 max-size/max-rows 置 0,再用 interval 控制(文档允许)

7. Lookup 缓存:PARTIAL 缓存怎么配

Lookup 默认不缓存(NONE)。开启:

sql 复制代码
'lookup.cache' = 'PARTIAL',
'lookup.partial-cache.max-rows' = '100000',
'lookup.partial-cache.expire-after-write' = '10 min',
'lookup.partial-cache.expire-after-access' = '5 min',
'lookup.partial-cache.caching-missing-key' = 'true'

关键点:

  • 缓存是 TaskManager 进程级,每个 TM 一份
  • caching-missing-key=true 会把"查不到"的结果也缓存,能减少热点 miss 的反复 IO,但如果维表会新增 key,可能短时间内"查不到"被缓存住
  • TTL 越短越接近实时,但外部请求越多;TTL 越长性能越好但数据更"旧"

如果你的维表更新很频繁:TTL 不要太长;或者不缓存。

8. HBase 配置透传:properties.*(Kerberos 等)

properties.* 可以透传任意 HBase 配置:

sql 复制代码
'properties.hbase.security.authentication' = 'kerberos'

Flink 会去掉 properties. 前缀,把剩余 key/value 交给底层 HBase Client。

这对生产环境很关键:Kerberos、RPC 超时、重试、连接池相关参数都靠它。

9. 数据类型映射与"空 bytes = null"规则(超级容易踩)

HBase 存 byte[],Flink HBase connector 用 org.apache.hadoop.hbase.util.Bytes 做序列化/反序列化。

核心规则:

  • 除 STRING 外的所有类型
    写入 null → 编码成 empty bytes
    读取 empty bytes → 解码成 null
  • STRING 类型例外
    empty bytes 会被当成一个"特殊 null literal",由 null-string-literal 决定(默认是 "null" 字符串)

如果你业务里字符串可能真的会出现 "null" 这样的值,建议明确设置 null-string-literal,避免歧义:

sql 复制代码
'null-string-literal' = '__HBASE_NULL__'

10. 一个更"生产味"的 DDL 模板(可直接套)

sql 复制代码
CREATE TABLE user_profile_hbase (
  user_id STRING,                                -- rowkey(原子类型字段)
  base ROW<name STRING, age INT, status BOOLEAN>, -- 列族 base
  stat ROW<uv BIGINT, pv BIGINT>,                 -- 列族 stat
  PRIMARY KEY (user_id) NOT ENFORCED
) WITH (
  'connector' = 'hbase-2.2',
  'table-name' = 'ns:user_profile',
  'zookeeper.quorum' = 'zk1:2181,zk2:2181,zk3:2181',
  'zookeeper.znode.parent' = '/hbase',
  'null-string-literal' = '__HBASE_NULL__',

  'sink.buffer-flush.max-rows' = '2000',
  'sink.buffer-flush.max-size' = '4mb',
  'sink.buffer-flush.interval' = '1s',
  'sink.ignore-null-value' = 'true',

  'lookup.async' = 'true',
  'lookup.cache' = 'PARTIAL',
  'lookup.partial-cache.max-rows' = '200000',
  'lookup.partial-cache.expire-after-write' = '10 min',
  'lookup.partial-cache.caching-missing-key' = 'true'
);

你接下来如果要把它写成发布到 CSDN 的博客,我也能按"实战场景"帮你补齐两段很关键的内容:

  • RowKey 设计与热点规避(salt/hash 前缀、时间倒排、业务分桶)
  • 一致性语义与幂等写(upsert + checkpoint + 重放时的覆盖行为,以及 null 覆盖问题怎么处理)
相关推荐
曲幽2 小时前
从0到1掌握SQL Server可编程性:让数据自己动起来
sql·sqlserver·delete·insert·function·update·trigger·procedure
無森~2 小时前
Hive核心SQL(基础)
hive·hadoop·sql
l1t2 小时前
将追赶法求连续区间的Oracle SQL改写成DuckDB
数据库·sql·oracle·duckdb
Hello.Reader2 小时前
Flink HBase SQL Connector RowKey 设计、Upsert 语义、维表 Join、缓存与写入调优
sql·flink·hbase
yumgpkpm6 小时前
银行智能数据平台在Cloudera CDH6\CDP 7\CMP 7平台下的具体使用配置流程
大数据·hive·hadoop·数据挖掘·flink·spark·cloudera
消失的旧时光-194311 小时前
第四篇(实战): 订单表索引设计实战:从慢 SQL 到毫秒级
java·数据库·sql
你才是臭弟弟20 小时前
时序数据库(TDengine TSDB)基本SQL使用
sql·时序数据库·tdengine
Gauss松鼠会21 小时前
【openGauss】学习 gsql 命令行的使用
数据库·sql·database·opengauss
老纪的技术唠嗑局1 天前
不止于替换 HBase:宝付支付借力 OceanBase,构建面向未来的“TP+AP+KV+AI”统一数据基座
人工智能·hbase·oceanbase