一、现象

目前我们的数据是通过OGG->Kafka->Spark Streaming->HBase。由于之前我们发现HBase的列表put无法保证顺序,因此改了程序,如果是在同一个SparkStreaming的批次里面对同一条数据进行操作,则写入HBase的数据时间戳就非常相近,只会差几毫秒,如果是不同批次则会差好几秒。此为背景。

现在有一条数据,理应先删除再插入,但是结果变成了先插入再删除,结果如下

  1. hbase(main):002:0> get 'XDGL_ACCT_PAYMENT_SCHEDULE','e5ad-***', {COLUMN=>'cf1:SQLTYPE',VERSIONS=>10}
  2. COLUMN CELL
  3. cf1:SQLTYPE timestamp=1498445308420, value=D
  4. cf1:SQLTYPE timestamp=1498445301336, value=I

其中,两条记录的时间戳换算过来正好相差了7秒

2017-06-26 10:48:21 I

2017-06-26 10:48:28 D

很明显这两条数据并没有在同一个批次得到处理,很明显Spark获取到数据的先后顺序出了点问题。

二、原因排查

2.1 SparkStreaming程序排查

首先SparkStream接收到数据后根据数据的pos排序,然后再根据主键排序。从现象看,是SparkStreaming分了两个批次才拿到,而SparkStreaming从Kafka拿数据也是顺序拿的。那么出现问题的可能性就只有两个:

1、OGG发给Kafka的数据顺序是错误的。

2、OGG发给Kafka的数据顺序是正确的,但是发到了不同的Kafka Partition。

2.2 Kafka数据验证

为了验证上面的两个猜想,我把kafka的数据再次获取出来进行分析。重点分析数据的partition、key、value。

得到的结果如下:

可以看到数据的同一个表数据写到了不同的分区,可以看到OGG的同一分区下的数据顺序是正确的。

正好说明2.1里面的第二个猜想。看来是OGG写入的时候并没有按照数据的表名写入不同的分区。

在OGG 文档

http://docs.oracle.com/goldengate/bd1221/gg-bd/GADBD/GUID-2561CA12-9BAC-454B-A2E3-2D36C5C60EE5.htm#GADBD449

中的 5.1.4 Kafka Handler Configuration 的属性 gg.handler.kafkahandler.ProducerRecordClass 里面提到了,默认使用的是oracle.goldengate.handler.kafka.DefaultProducerRecord这个类对表名进行分区的。如果要自定义的话需要实现CreateProducerRecord这个接口

原话是 The unit of data in Kafka - a ProducerRecord holds the key field with the value representing the payload. This key is used for partitioning a Kafka Producer record that holds change capture data. By default, the fully qualified table name is used to partition the records. In order to change this key or behavior, theCreateProducerRecord Kafka Handler Interface needs to be implemented and this property needs to be set to point to the fully qualified name of the custom ProducerRecord class.

然而写入kafka的结果却不是这样子的。这点让人费解。看来我们需要查看OGG的源代码。

2.3 查看OGG源码

在OGG的安装包里面有一个名叫ggjava/resources/lib/ggkafka-****.jar的文件,我们将其导入一个工程之后就可以直接看到它的源代码了。

2.3.1 生成Kafka消息类

我们直接查看oracle.goldengate.handler.kafka.DefaultProducerRecord这个类

  1. public class DefaultProducerRecord implements CreateProducerRecord {
  2. public DefaultProducerRecord() {
  3. }
  4. public ProducerRecord createProducerRecord(String topicName, Tx transaction, Op operation, byte[] data, TxOpMode handlerMode) {
  5. ProducerRecord pr;
  6. if(handlerMode.isOperationMode()) {
  7. pr = new ProducerRecord(topicName, operation.getTableName().getOriginalName().getBytes(), data);
  8. } else {
  9. pr = new ProducerRecord(topicName, (Object)null, data);
  10. }
  11. return pr;
  12. }
  13. }

这个类只返回一个ProducerRecord,这个是用于发送给Kafka的一条消息。我们先不管这个,继续看他是如何写给kafka的

2.3.2 Kafka配置类

首先是OGG与Kafka相关的配置类 oracle.goldengate.handler.kafka.impl.KafkaProperties 。这个类里面定义了一堆参数,我们只需要关心partitioner.class这个参数,该参数用于定义写入Kafka的时候获取分区的类。很遗憾,这个类没有该参数配置。

2.3.3 Kafka 消息发送类

这里有一个抽象类oracle.goldengate.handler.kafka.impl.AbstractKafkaProducer,他有两个子类,分别叫BlockingKafkaProducerNonBlockingKafkaProducer (默认是NonBlockingKafkaProducer)

这两个类都是直接将通过producer对象将record发送给了kafka。因此想要指导Kafka的分区信息还需要看Kafka是怎么获取分区的。

2.3.4 Kafka 分区获取方式

进入kafka的producer发送record的函数

  1. public Future<RecordMetadata> send(ProducerRecord<K, V> record) {
  2. return send(record, null);
  3. }
  4. public Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback) {
  5. // intercept the record, which can be potentially modified; this method does not throw exceptions
  6. ProducerRecord<K, V> interceptedRecord = this.interceptors == null ? record : this.interceptors.onSend(record);
  7. return doSend(interceptedRecord, callback);
  8. }

发送的方法在doSend里面,里面内容很多,请看我勾出来的这两段

由于写入的时候都没有对Record指定分区,因此这段代码的partition都为空。所以代码总会执行到 this.partitioner.partition(record.topic(), record.key(), serializedKey,record.value(), serializedValue,cluster)

该函数是kafka的Partitioner这个抽象类里面的

由于2.3.2 Kafka配置类中没有指定分区的class,因此只会使用Kafka默认的分区类org.apache.kafka.clients.producer.internals.DefaultPartitioner

private final AtomicInteger counter = new AtomicInteger(new Random().nextInt());

这里先是获取了一个随机值,然后再获取了Kafka中对应topic的可用分区列表,然后根据分区数和随机值进行取余得到分区数的值。

流程走到这里,我们基本可以得到一个结论。

  • Kafka的record指定了分区,则会使用指定的分区写入;否则进行下一个判断;
  • Kafka根据自己定义的partitioner接口进行分区,如果没指定类,则使用默认的分区则进行下一个判断;
  • Kafka获取record中的key进行分区,如果key不为空,则使用Hash分区,如果为空,基本上就是随机分配分区了。

三、结论

事情到了这里,我们可以断定,写入分区错乱的问题是因为gg.handler.kafkahandler.Mode是事务模式,导致多条消息一次发送了,无法使用表名作为key,OGG就用了null作为key发送给了Kafka,最终Kafka拿到空值之后只能随机发送给某个partition,所以才会出现这样的问题。

最终,修改了ogg的操作模式之后可以看到,写入的分区正常了。

记一次OGG数据写入HBase的丢失数据原因分析的更多相关文章

  1. 简单通过java的socket&serversocket以及多线程技术实现多客户端的数据的传输,并将数据写入hbase中

    业务需求说明,由于公司数据中心处于刚开始部署的阶段,这需要涉及其它部分将数据全部汇总到数据中心,这实现的方式是同上传json文件,通过采用socket&serversocket实现传输. 其中 ...

  2. json数据写入hbase

    package main.scala.com.web.zhangyong168.cn.spark.java; import org.apache.hadoop.hbase.HBaseConfigura ...

  3. flink-----实时项目---day07-----1.Flink的checkpoint原理分析 2. 自定义两阶段提交sink(MySQL) 3 将数据写入Hbase(使用幂等性结合at least Once实现精确一次性语义) 4 ProtoBuf

    1.Flink中exactly once实现原理分析 生产者从kafka拉取数据以及消费者往kafka写数据都需要保证exactly once.目前flink中支持exactly once的sourc ...

  4. 大数据学习day34---spark14------1 redis的事务(pipeline)测试 ,2. 利用redis的pipeline实现数据统计的exactlyonce ,3 SparkStreaming中数据写入Hbase实现ExactlyOnce, 4.Spark StandAlone的执行模式,5 spark on yarn

    1 redis的事务(pipeline)测试 Redis本身对数据进行操作,单条命令是原子性的,但事务不保证原子性,且没有回滚.事务中任何命令执行失败,其余的命令仍会被执行,将Redis的多个操作放到 ...

  5. Spark Streaming实时写入数据到HBase

    一.概述 在实时应用之中,难免会遇到往NoSql数据如HBase中写入数据的情景.题主在工作中遇到如下情景,需要实时查询某个设备ID对应的账号ID数量.踩过的坑也挺多,举其中之一,如一开始选择使用NE ...

  6. Flink 使用(一)——从kafka中读取数据写入到HBASE中

    1.前言 本文是在<如何计算实时热门商品>[1]一文上做的扩展,仅在功能上验证了利用Flink消费Kafka数据,把处理后的数据写入到HBase的流程,其具体性能未做调优.此外,文中并未就 ...

  7. java实现服务端守护进程来监听客户端通过上传json文件写数据到hbase中

    1.项目介绍: 由于大数据部门涉及到其他部门将数据传到数据中心,大部分公司采用的方式是用json文件的方式传输,因此就需要编写服务端和客户端的小程序了.而我主要实现服务端的代码,也有相应的客户端的测试 ...

  8. Spark DataFrame写入HBase的常用方式

    Spark是目前最流行的分布式计算框架,而HBase则是在HDFS之上的列式分布式存储引擎,基于Spark做离线或者实时计算,数据结果保存在HBase中是目前很流行的做法.例如用户画像.单品画像.推荐 ...

  9. 【乱码】运行java -jar xx.jar存到hbase里的数据乱码

    程序在Eclipse里运行没有问题,但是打成jar包之后写入hbase里的数据会有乱码,ES里正常 经过测试,运行命令里加上-Dfile.encoding=utf-8 就可以正常写入,但是cmd命令里 ...

随机推荐

  1. POJ-3261 Milk Patterns,后缀数组+二分。。

                                                        Milk Patterns 题意:求可重叠的至少重复出现k次的最长的字串长. 这题的做法和上一题 ...

  2. jsessionid 所引起的404问题和解决方法

    问题: 在SpringMvc使用RedirectView或者"redirect:"前缀来做重定向时,Spring MVC最后会调用: response.sendRedirect(r ...

  3. 【Luogu】P3758可乐(矩阵优化DP)

    题目链接 一开始想到这可能能用矩阵优化,但以为暴力就能卡过……T成二十分 首先我们回顾一下我们的暴力转移方程 用f[i][j][0/1]表示在i时刻,j点,1不爆炸,0已爆炸的方案数,那么f[i][j ...

  4. 雅礼培训4.3 Problem A 【点分治】

    题目简述 一个\(N\)个节点的树,有\(M\)个炸弹分布在一些节点上,有各自的威力,随着其他点距离增大对其他点的伤害呈等差减小,直至为0 问每个点受到的伤害 题解 QAQ考场代码没处理好有些炸弹威力 ...

  5. ⑨要写信(codevs 1697)

    题目描述 Description 琪露诺(冰之妖精)有操控冷气的能力.能瞬间冻结小东西,比普通的妖精更危险.一直在释放冷气的她周围总是非常寒冷. 由于以下三点原因…… 琪露诺的符卡 冰符“Icicle ...

  6. hdu 1754 splay tree伸展树 初战(单点更新,区间属性查询)

    题意:与区间查询点更新,点有20W个,询问区间的最大值.曾经用线段树,1000+ms,今天的伸展树,890没ms,差不多. 第一次学习伸展树,一共花了2个单位时间,感觉伸展树真很有用,也很好玩.现在只 ...

  7. BZOJ——1606: [Usaco2008 Dec]Hay For Sale 购买干草

    http://www.lydsy.com/JudgeOnline/problem.php?id=1606 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 1 ...

  8. 框架-数据库定义MD5加密

    1.--定义Md5加密declare @pt_pwd varchar(50)set @pt_pwd = ''set @pt_pwd = substring(sys.fn_sqlvarbasetostr ...

  9. Wide & Deep Learning Model

    Generalized linear models with nonlinear feature transformations (特征工程 + 线性模型) are widely used for l ...

  10. expect实现无交互操作

    按两下tab linux总共2000个命令,,常用的200个命令. 只要文件改变了,MD5值就会变!