HbaseTableSink实现点讨论:
1.HbaseTableSink实现基于HbaseSink
Sink本身实现不考虑容错性,可以直接对流数据进行条插入或批插入。如果仅考虑此那么就有悖Flink组件本身提出的容错语义(exactly once or at least once)。下面结合Flink容错语义就幂等性请求和非幂等性请求做具体展开。
1.1.幂等插入
幂等插入适用于容错恢复前后插入数据的结构、信息等没有发生变化的场景。Hbase组件本身支持幂等操作,其实现基于rowkey,相同rowkey的结果会被新的version覆盖。因此在幂等性的前提下,HbaseSink结合Flink Checkpoint机制可以实现HbaseSink不同的容错语义。
1.2.非幂等插入
非幂等插入适用于容错恢复前后插入数据的结构、信息可能发生变化的场景。对于恢复前后的数据结构、信息可能发生变化的情形,如果在依赖Hbase的幂等特性就可能会造成脏数据。 Flink对于此场景的容错提出了WAL机制,将流数据预写到mini batch并通过StateBackend序列化到外部存储。mini batch绑定对应的Checkpoint,确定Checkpoint完成再将预写的mini batch插入到对应的sink系统,确定mini batch写入完成之后删除对应的mini batch。
1.3.HbaseSink
HbaseSink结合Flink容错支持上述两种不同类型请求的实现思路。
1.3.1.HbaseSink幂等实现思路
对于幂等请求的实现可以采用one by one的条插入来实现。容错主要依赖Flink的Checkpoint。
1.3.2.HbaseSink非幂等实现思路
对于非幂等请求的实现可以采用mini batch通过Flink提供的WAL机制实现。容错主要依赖Flink的Checkpoint及WAL机制。
1.4.社区关于HbaseSink讨论
详细参见:https://github.com/apache/flink/pull/2332
2.TableSink支持AppendDataStream与RetractDataStream
对于TableSink Flink提供了三种类型的DataStream支持:AppendDataStream、RetractDataStream、UpsertDataStream。目前Flink StreamTableEnvironment中暂未提供转化UpsertDataStream的接口。因此此处主要讨论AppendDataStream与RetractDataStream。
2.1.AppendDataStream
AppendDataStream流只会输出一条处理后的结果,TableSink接收到该结果之后直接将其sink到对应的外部系统。因此AppendDataStream流的结果不支持更新操作,只能支持插入操作。
2.2.RetractDataStream
RetractDataStream流既支持插入操作也可支持更新操作。其输出的结果会带有操作标识符,对于更新的操作会接收到两条输出结果一条标记为插入一条标记为删除,对于插入的操作只会接收到一条标记为插入的记录。TaleSink根据具体的操作标识符对处理结果进行输出。
2.3.AppendDataStream VS RetractDataStream
Table(动态表)转DataStream是非常严格的过程,涉及到需要更新结果的sql(group by,partition by等)查询来说必须要转换为RetractDataStream。AppendDataStream只支持插入不支持更新,RetractDataStream既支持插入同时也支持更新。
2.4.HbaseTableSink
HbaseTableSink支持上述两种DataStream的实现思路。Hbase相同rowKey的行会被覆盖为新的version。是否需要用户指定rowKey,还是由HbaseTableSink从sql查询感知rowKey。
2.4.1.HbaseAppendTbaleSink实现思路
Hbase根据rowKey将行覆盖为最新的version。因此只有在保证指定rowKey不重的情况下才能达到Append效果。
2.4.2.HbaseRetractTbaleSink实现思路
Hbase根据rowKey将行覆盖为最新的version。对于RetractDataStream HbaseTableSink仅处理带有插入标识的记录过滤掉删除标识的记录,前提是能正确的感知到rowKey。
HbaseTableSink实现方案:
1.术语解释
- 1.1.业务幂等性请求
本文档中业务幂等性请求专指数据在Flink Checkpoint恢复前后插入到Hbase中的数据结构(对应Hbase表结构,理论上不会变化)以及对应字段的value均没有发生任何变化。例如:行字段带有时间戳在恢复前后不能保证业务幂等性请求。
- 1.2.Hbase幂等性
本文档中Hbase幂等性专指相同rowKey的单行记录无论被插入多少次在Hbase系统中该行记录仅存在一条,且被查询出来的结果始终是最新version。HbaseClient提供的方法仅put和delete支持幂等性。
- 1.3.HbaseSink幂等性
本文档中HbaseSink幂等性专指在Flink Checkpoint恢复前rowKey对应的行记录被插入Hbase系统,在恢复后即使重新插入相同rowKey的行记录最终查询出来的结果与恢复前的结果一致。HbaseSink幂等性的前提条件是业务请求幂等性与Hbase幂等性。
2.实现方案
2.1.HbaseSink实现方案
HbaseSink实现主要围绕HbaseSink幂等性分为两钟实现:HbaseSink幂等性实现与HbaseSink非幂等性实现。因此HbaseSink仅调用HbaseClient的put方法作为数据录入的方式。
2.1.1.HbaseSink幂等性实现
HbaseSink幂等性实现主要通过implement SinkFunction,数据处理方式分为同步处理和异步处理。同步处理以one by one的方式实时写实时sink处理延迟相对低对Hbase系统压力相对大,异步处理则以mini batch的方式定时或定量sink处理延迟相对较高对Hbase系统压力相对较小。无论同步还是异步均是依赖HabseClient的同步特性,同时结合Flink Checkpoint机制幂等HbaseSink能做到at least once。
2.1.2.HbaseSink非幂等性实现
HbaseSink幂等性实现主要通过extend GenericWriteAheadSink,依赖Flink提供的WAL机制非幂等HbaseSink能做到at least once。Flink WAL机制依赖Checkpoint进行异步批插入,每份mini batch绑定一个CheckpointId。mini batch通过StateBackend提前预写到外部存储(HDFS等),确定绑定的Checkpoint已完成再将预写的mini batch sink到Hbase。因此非幂等HbaseSink必须开启Checkpoint且只能做到异步写延迟相对较高。
2.2.HbaseTableSink实现方案
用户在设计Hbase表的时HbaseTableSink要求rowKey字段不能作为业务字段,同时在使用HbaseTableSInk时需要提供主键或联合主键交由HbaseTableSInk组合成rowKey。
2.2.1.HbaseAppendTbaleSink实现
由于Hbase系统的特殊性,因此在处理AppendDataStream只有用户保证指定rowKey不重的情况下才能达到Append效果。
2.2.2.HbaseRetractTbaleSink实现
HbaseRetractTbaleSink在处理RetractDataStream时仅sink带有插入标识的记录过滤掉删除标识的记录。由于Hbase系统的特殊性,对于上述操作在Hbase系统中能达到更新的效果。
Hbase相关Sink详细设计
1.HBaseSink设计
HBaseSink设计根据HBase幂等性及用户请求行为幂等性主要分为两大类:幂等HBaseSink和非幂等HBaseSink,其设计的主要目的是保证Flink程序运行过程中at least once和exactly once的容错语义以及数据最终在HBase系统中的准确性。HBase幂等性指对于同一rowKey及value的多次插入不影响最终的查询结果,用户请求行为的幂等性特指针对Checkpoint恢复前后同一条记录处理结果始终一致。
1.1.幂等HBaseSink设计
由于HBase系统本身具备幂等性,因此幂等HBaseSink主要是针对用户请求行为的幂等性提供了处理和容错方案,结合Flink Checkpoint幂等HBaseSink能做到at least once级别的容错。HBaseSinkBase对应幂等HBaseSink的实现,同时支持同步及异步处理模式。
1.1.1.HBaseSinkBase
HBaseSinkBase是幂等HBaseSink处理的核心类,主要继承了RichSinkFunction同时实现了CheckpointedFunction。为了保证at least once级别的容错因此在每次Checkpoint触发时是都会将sink中的处理结果刷进HBase中。
1.1.2.HBaseMapper
HBaseMapper主要提供了HBase table与sink数据结果的一个映射关系,同时也提供了对数据结果的格式化处理。因此对于sink的不同数据类型需要针对HBaseMapper做不同的实现。目前只是实现了Tuple类型和Row类型的HBaseMapper分别对应HBaseTupleMapper和HBaseRowMapper,需要注意对于HBaseTupleMapper和HBaseRowMapper只支持其内部字段的为基础数据类型。rowKey支持联合字段构建,为了方便数据的查询强烈建议构成rowKey的字段冗余存储HBase。
1.1.3.MutationActions.ProcessMode
HBaseSinkBase处理的模式,包括两种模式同步和异步。异步支持指定时间间隔、指定cache大小、指定记录条数提交,不指定处理模式默认采用异步处理模式(默认时间间隔5s、cache大小2m、记录数5000)。为了保证数据可靠性此处的处理模式不依赖HBaseClient的提交方式,HBaseClient采用的均是同步写入。
1.1.4.MutationActions
MutationActions支持异步批提交和同步条处理。异步模式根据异步触发条件将数据切分成mini batch且批次号单调递增,组装mini batch数据通过一次rpc请求HBase插入。同步请求则是支持条数据rpc请求HBase插入。为了保证写HBase数据的可靠性,默认HBase的预写机制是打开的。
1.2.非幂等HBaseSink设计
为了解决用户请求行为在Checkpoint恢复前后的非幂等性场景或者业务要求数据处理达到exactly once级别的场景。HBaseWriteAheadSink对应非幂等HBaseSink的实现,结合Flink Checkpoint其能达到exactly once级别的容错同时其工作依赖Checkpoit定时批插入数据。
1.2.1.HBaseWriteAheadSink
HBaseWriteAheadSink是非幂等HBaseSink处理的核心类,主要实现了GenericWriteAheadSink。GenericWriteAheadSink该类是Flink提供的用来实现处理数据预写的基类。为了保证sink exactly once Flink提出了WAL机制,该机制主要依赖Flink Checkpoint在sink数据到对应系统之前优先将处理后的数据通过StateBacked预写到外部系统形成预写日志。接收到Checkpoint之后将预写的元数据信息快照保存,并在确定本次Checkpoint完成之后再根据预写日志将记录插入sink系统。完成数据插入之后再移除预写日志并记录已经写入sink系统的Checkpoint信息。
1.2.2.HBaseCommitter
HBaseCommitter记录并提交已经写入HBase系统的Checkpoint信息到HBase committer table。committer table的结构仅包含一个rowKey和一个列,rowKey的结构jobUser_jobName_subTaskId_operatorId,列的结构completeCheckpointId。用于容错恢复时检测是否有预写日志需要插入HBase系统,committer table表内的数据不由Flink管理即Flink只负责写入或更新数据不负责清理。
1.2.3.HBaseMapper
详解1.1.2
1.2.4.MutationActions
详解1.1.4
2.HBaseTableSink设计
HBaseTableSink主要包含两种类型:HBaseAppendTableSink和HBaseRetractTableSink。HBaseTableSink基于HBaseSink
2.1.HBaseAppendTableSink设计
HBaseAppendTableSink仅支持处理AppendDataStream,实现AppendStreamTableSink。接收到AppendDataStream构建HBaseSinkBase(详解1.1)。
2.2.HBaseRetractTableSink设计
HBaseRetractTableSink仅支持处理RetractDataStream,实现RetractStreamTableSink。介于HBase系统本身的幂等性,仅执行插入的操作过滤撤销的操作,因此在构建HBaseSinkBase之前对RetractDataStream进行一次过滤将撤销操作的数据记录去除。然后在构建HBaseSinkBase(详解1.1)。
3.HBaseSink使用说明
HBaseSink是构建HBase相关sink的工具类,包括幂等与非幂等的实现。对容错性要就不是苛刻的任务建议使用幂等HBaseSink,幂等HBaseSink建议使用异步模式发送减小对HBase rpc请求的压力,同样对于HBaseTableSink也是如此。
3.1.幂等HBaseSink使用说明
env.enableCheckpointing(10 * 1000); //幂等HBaseSink支持at least once语义的前提
MutationActions.ProcessMode pMode = new MutationActions.ProcessMode(true, 2097152, 1000, 5 * 1000);//构建幂等HBaseSink的处理模式
HBaseMapper<Tuple5<String, Long, String, String, Long>> hMapper = new HBaseTupleMapper<>();//构建HBaseMapper
hMapper.addRowKey(BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addColumn("f", "id", BasicTypeInfo.LONG_TYPE_INFO);
hMapper.addColumn("f", "name", BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addColumn("f", "school", BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addColumn("f", "score", BasicTypeInfo.LONG_TYPE_INFO);
/**注意tuple对应字段类型与对应table表字段的关系及顺序
也可以指定tuple对应字段与table表字段的顺序
hMapper.addRowKey(1, BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addColumn(2, "f", "id", BasicTypeInfo.LONG_TYPE_INFO);
hMapper.addColumn(3, "f", "name", BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addColumn(4, "f", "school", BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addColumn(5, "f", "score", BasicTypeInfo.LONG_TYPE_INFO);
同时还可以指定联合字段为rowK
hMapper.addRowKey(1, BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addRowKey(2, BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addRowKey(4, BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addColumn(2, "f", "id", BasicTypeInfo.LONG_TYPE_INFO);
hMapper.addColumn(3, "f", "name", BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addColumn(4, "f", "school", BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addColumn(5, "f", "score", BasicTypeInfo.LONG_TYPE_INFO);
*/
Map<String, Object> hbConfig = new HashMap<>();//HBase相关的配置
hbConfig.put(HConstants.ZOOKEEPER_QUORUM, "namenode1-sit.com,namenode2-sit.com,slave01-sit.com");
hbConfig.put(HConstants.ZOOKEEPER_CLIENT_PORT, 2015);
hbConfig.put(HConstants.ZOOKEEPER_ZNODE_PARENT, "/hbase");
HBaseSink.addSink(source)
.setHBaseConfig(hbConfig) //指定HBase配置项
.setHBaseMapper(hMapper) //指定HBaseMapper
.setTableName("namespace:test") //指定表名
.setTableOwner("user") //指定表所属用户,没有可以不指定
.setProcessMode(pMode) //指定ProcessMode
.setLogRowkeyNullOnly(true) //true表示rowKey为null时只打印日志不会抛异常,默认为false ;对于列字段为null时不会被插入到hbase系统
.build() //构建HBaseSink
.setParallelism(1); //设置并行度等
3.2.非幂等HBaseSink使用说明
env.enableCheckpointing(10 * 1000); //非幂等HBaseSink运行及支持exactly once的前提
HBaseMapper<Tuple5<String, Long, String, String, Long>> hMapper = new HBaseTupleMapper<>();//构建HBaseMapper
hMapper.addRowKey(BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addColumn("f", "id", BasicTypeInfo.LONG_TYPE_INFO);
hMapper.addColumn("f", "name", BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addColumn("f", "school", BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addColumn("f", "score", BasicTypeInfo.LONG_TYPE_INFO);
/**注意tuple对应字段类型与对应table表字段的关系及顺序
也可以指定tuple对应字段与table表字段的顺序
hMapper.addRowKey(1, BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addColumn(2, "f", "id", BasicTypeInfo.LONG_TYPE_INFO);
hMapper.addColumn(3, "f", "name", BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addColumn(4, "f", "school", BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addColumn(5, "f", "score", BasicTypeInfo.LONG_TYPE_INFO);
同时还可以指定联合字段为rowK
hMapper.addRowKey(1, BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addRowKey(2, BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addRowKey(4, BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addColumn(2, "f", "id", BasicTypeInfo.LONG_TYPE_INFO);
hMapper.addColumn(3, "f", "name", BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addColumn(4, "f", "school", BasicTypeInfo.STRING_TYPE_INFO);
hMapper.addColumn(5, "f", "score", BasicTypeInfo.LONG_TYPE_INFO);
*/
Map<String, Object> hbConfig = new HashMap<>();//HBase相关的配置
hbConfig.put(HConstants.ZOOKEEPER_QUORUM, "namenode1-sit.com,namenode2-sit.com,slave01-sit.com");
hbConfig.put(HConstants.ZOOKEEPER_CLIENT_PORT, 2015);
hbConfig.put(HConstants.ZOOKEEPER_ZNODE_PARENT, "/hbase");
HBaseSink.addSink(source)
.enableWriteAheadLog() //启用WAL机制支持非幂等业务请求场景或者exactly once业务场景
.setHBaseConfig(hbConfig) //指定HBase配置项
.setHBaseMapper(hMapper) //指定HBaseMapper
.setTableName("namespace:test") //指定表名
.setTableOwner("user") //指定表所属用户,没有可以不指定
//.setProcessMode(pMode) 启用WAL之后ProcessMode失效
.setLogRowkeyNullOnly(true) //true表示rowKey为null时只打印日志不会抛异常,默认为false ;对于列字段为null时不会被插入到hbase系统
.build() //构建HBaseSink
.setParallelism(1); //设置并行度等
3.3.HBaseTableSink使用说明
val queryTable = tableEnv.sqlQuery(
"""
|select t.com,t.id,t.name,t.school,count(t.score) as score
|from
|sourceTable as t
|group by t.com, t.name, t.school, t.id
""".stripMargin)
val hMapper = new HBaseTableSinkMapper //构建HBaseTableSinkMapper
hMapper.addRowKey("com", BasicTypeInfo.STRING_TYPE_INFO)
hMapper.addColumn("id", "f", "id", BasicTypeInfo.LONG_TYPE_INFO) //指定查询表对应字段与HBase table对应映射
hMapper.addColumn("name", "f", "name", BasicTypeInfo.STRING_TYPE_INFO)
hMapper.addColumn("school", "f", "school", BasicTypeInfo.STRING_TYPE_INFO)
hMapper.addColumn("score", "f", "score", BasicTypeInfo.LONG_TYPE_INFO)
MutationActions.ProcessMode pMode = new MutationActions.ProcessMode(true, 2097152, 1000, 5 * 1000);//构建ProcessMode
val logRowkeyNullOnly:Boolean = true //指定rowKey为null时是否仅打印日志不抛异常
val tableName: String = "ns_bigdata:cxl_flink_sink_table" //指定表名
val tableOwner: String = "bigdata" //指定表所属用户,没有可以不指定
val isWriteAheadLogEnabled = false; //是否启用非幂等HBaseSink
val logRowkeyNullOnly = false; //rowKey为null时是否只打印日志
val hbConfig = new util.HashMap[String, Object] //HBase相关配置设置
hbConfig.put(HConstants.ZOOKEEPER_QUORUM, "namenode1-sit.com,namenode2-sit.com,slave01-sit.com")
hbConfig.put(HConstants.ZOOKEEPER_CLIENT_PORT, Integer.valueOf(2015))
hbConfig.put(HConstants.ZOOKEEPER_ZNODE_PARENT, "/hbase")
val hbTableSink = new HBaseAppendTableSink(tableName, tableOwner, hbConfig, hMapper, pMode)
queryTable.writeToSink(hbTableSink)