MergeDelta增加写ORC格式功能

改造目的:主要是为了支持将ods表的merge结果写成ORC文件格式

原理:使用HCatalog重写merge的mapreduce过程,HCatalog可以屏蔽hive表的文件存储格式,不需要关心hive表是使用text格式还是orc格式

需要考虑的问题:

1. 任务如何区分是要写成orc还是写成text?

HCatalog会根据目标表表的存储方式来写成相应格式,即如果目标表是text格式,写出来的就是text格式,如果目标表是orc格式则写出来的就是orc格式。

从上面来看,似乎任务本身不需要关心要写成什么格式,但是存在2个特殊情况:任务第一次运行和任务的预跑。此时mergedelta只是调用了全量读写的wormhole,并没有merge过程,因此如果目标表已经是orc了,则在这2种情况下会导致wormhole直接将text的结果写入目标表的目录中,因此任务还是需要知道目标表是什么格式,如果是orc了,则调用wormhole时不能直接写到目标表目录中。

解决方案:在任务配置中增加参数—format,可取值orc或text,如果未设定该参数则默认是text。如果—format 是orc则任务第一次运行和预跑时,wormhole需要先写入临时目录再转换为orc(需要更改wormhole,让wormhole感知到结果不能直接写入目标表的目录)。

 

2.merge过程的reduce过程中,对于主键一样的2条记录,如何判断记录是旧的记录还是delta表的新记录?

原先mergedelta会在map输出时以记录的主键字段的值作为key,记录前面加上一个字符tag标记记录属于旧记录还是delta表的新记录。由于原先写的结果是text,因此在整条记录上增减一个字符是很简单的操作,但现在map的输入和reduce的输出都是HCatRecord,可以认为是一个hashmap的结果,里面存的是字段名称和字段值的映射,如果在map输出时遍历HCatRecord拼接出整条记录并加上tag,在reduce时再拆分记录去掉tag再组装成HCatRecord,最初尝试了该方法,但是运行时报错了,发现在组装回HCatRecord时使用set设置各字段的值时必须使用和hive表该字段类型实际的类型来set,简单的说就是对于值是123的某个字段,如果该字段是int型那我在set时,必须调用set("字段名", 123)的int版本而不能调用set("字段名", "123")的String版本,而我在reduce中拿到的已经全是字符串形式,要再解成实际所属类型的样子就非常麻烦。

解决方案:

把map的输出也使用HCatRecord,问题在于如何在reduce阶段区分记录是属于原表还是delta表?有2种办法:

a.在map输出时新构造一个HCatRecord,比map输入的HCatRecord多一个项,存放tag,在reduce中再去掉该tag,目前采用的是该方法,缺点是在构造新HCatRecord时需要遍历原先的HCatRecord把所有字段内容存入新的HCatRecord中。

b.换个角度,其实在reduce中并不需要知道记录是来自原表还是delta表,只需要取最新的哪条记录即可,这可以通过比较增量字段来判断,增量字段和主键字段类似可以在任务配置中获取到。此方法优点是不需要在map中重新构造一个HCatRecord,目前来看每个使用mergedelta的任务都有增量字段,并且一定是时间戳类型,可以比较大小,但不确定以后是否会存在例外情况。

 

3.merge的reduce输出的结果的存放问题

merge的结果在reduce输出时并不能直接写入hive表最终目录,因为任务在中途可能会fail,如果直接写入最终目录那当任务运行一半fail时会导致表中数据不可用,因此需要先把结果写入一个临时的地方,等有完整结果后再移动到最终的地方。原先的做法是先写入“最终目录_tmp”这样一个临时目录,最后再移动。但使用HCatalog后,写数据的角度从原先的直接写hdfs文件变成了写入hive表,无法操作直接写入某个hdfs目录。

解决方案:由于HCatalog是hive表的角度来操作,因此只能建个临时表,先把数据写入临时表。

 

4.临时表的维护问题

原先的mergedelta由于操作的是hdfs文件,在写临时目录前先清空目录即可,因此不会存在这个问题。但是使用HCatalog后由于需要建一个临时表,会出现表结构不一致的问题。

解决表一致性的问题有2种办法:

a.和delta表一样,在建任务时就建立,每次变更表时,同时对delta表和临时表进行表结构变更。缺点是要维护的表变多。

b.每次在运行任务时都重新建一下临时表,这样即使表结构变了,在运行任务时也会建出一个相同表结构的临时表。缺点是每次运行任务都需要建一次临时表,并且为了防止临时表在上一次任务时有残留数据,所以每次必须先drop掉(if exist)临时表后再新建临时表。目前采用的是该方法

另外,为了防止“目标表_tmp”这样的表名和正式的其他表存在重名问题,将临时表建在tmp库下,表名和目标表名称一致,即bi.XX的reduce结果会写入临时表tmp.XX中。

 

5.写入临时表后,怎么把数据切换到最终表?是否可以通过改表名实现

无分区表可以通过alter table 临时表为目标表来完成,但是带分区表不行,因此还是需要从hdfs文件的角度来移动。

这个可以借鉴原先的mergedelta的方法,即写入完成后,会先把bi.XX的hdfs文件删除,然后把tmp.XX的hdfs文件rename为bi.XX的hdfs文件。

 

6. 衔接首次运行wormhole和预跑时的问题

目前mergedelta调用wormhole时主要有2种传入参数:一种是全量读写的,会传递id, time, offset这3个参数,另一种是增量读写的,会传递id, time, offset, appendColumn这4个参数。

当目标表已经是orc时,为了在调用wormhole全量读写时不直接写入最终分区,多传一个参数format(取值为orc)来区分,即id, time, offset,format,因此wormhole在接收到4个参数时,需要先特殊判断下最后一个参数的值是不是orc,以免和id, time, offset, appendColumn的情况混淆。此时wormhole也写入到delta表中(此处复用delta表),并且在wormhole写入完成后,mergedelta后续再接一个insert ... select ...的操作把delta表的数据转换为orc格式放入最终表中。

 

7.由于临时表只是临时存放一下数据,那带分区表的临时表是否也直接是全量表即可?

临时表是like原表建出来的,所以带分区表的临时表也会是分区表,并且只会写入和最终表一样的那个分区里。

 

8. 新版mergedelta如何灰度上线?

将新版mergedelta部署到一个新目录(和wormhole统一到data_load目录下),然后在mergedelta.sh中判断,如果带--format参数则使用新版(不管是接orc还是text都将是新版),否则还是运行旧版。挑选一部分任务逐步使用新版。 

其他遇到的问题:

1.HCataLog怎么读取多个hive表?

使用HCatMultiInputFormat

 

2.HCataLog怎么设置只读写某一个分区?

通过InputJobInfo设置filter来限定

 

3.调用HCatInputFormat.getTableSchema时报错job information not found in JobContext. HCatInputFormat.setInput() not called?
使用HCatMultiInputFormat时,在获取表结构时相应的也要使用HCatMultiInputFormat.getTableSchema而不是HCatInputFormat.getTableSchema

 

4.报错:getDelegationToken() can be called only in thrift (non local) mode

hive.metastore.local设置为false
并设置hive.metastore.uris(比如thrift://10.1.8.42:9083)
详见:http://blog.csdn.net/lalaguozhe/article/details/9083905

 

5.报错:2004 HCatOutputFormat not initialized, setOutput has to be called

改成使用deprecated的版本:getTableSchema(JobContext context),不要用getTableSchema(Configuration conf)版本

 

6.报错Error: org.apache.hcatalog.common.HCatException : 2010 : Invalid partition values specified : Unable to configure dynamic partitioning for storage handler, mismatch between number of partition values obtained[0] and number of partition values required[1]

写入分区表时需要指定写入的分区,如
Map<String, String> partitionValues = new HashMap<String, String>();
partitionValues.put("a", "1");
partitionValues.put("b", "1");
HCatTableInfo info = HCatTableInfo.getOutputTableInfo(dbName, tblName, partitionValues);
HCatOutputFormat.setOutput(job, info);

 

7.java代码中执行hive的drop table报错,在hive cli中正常

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'OPTION SQL_SELECT_LIMIT=DEFAULT' at line 1

mysql的版本和驱动的版本不匹配导致的,mysql驱动升级到5.1.20后正常

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值