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 小时前
sql之计算
数据库·sql
yumgpkpm5 小时前
Acceldata ODP 3.3.6.4 vs CDP Private Cloud Base 7.3.2 对比
大数据·运维·服务器·hadoop·华为·zookeeper·hbase
imDwAaY10 小时前
一条 SQL 发出去之后,MySQL 到底做了什么?
sql·mysql
刘胡子大叔21 小时前
SQL 脚本的导入顺序
数据库·sql
yumgpkpm1 天前
Acceldata ODP(Open Data Platform)3.3.6.4(RHEL9)保姆级完整安装手册
大数据·人工智能·hive·hadoop·kafka·hbase·cloudera
starzy19901 天前
Flink JobManager启动流程源码深度剖析:从ClusterEntrypoint到JobMaster启动的完整链路
大数据·flink
lvqinglou1 天前
学习Sql-part1 for婷姐
sql·学习·性能优化
用户3610588626121 天前
Flink Time 之 WaterMark 水位线原理剖析:从生成机制到传播触发与调优实战
大数据·flink
冰暮流星1 天前
sql之is not null运算符
数据库·sql·oracle