本文翻译于:https://cwiki.apache.org/confluence/display/Hive/Hive+Transactions
需要提醒的是,当前Hive版本是 0.14.0。之所以要添加这篇文章,是为后续的文章做铺垫。
摘要:Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供简单的SQL查询功能,可以将SQL语句转换为MapReduce任务进行运行,最新版本的Hive 0.13刚刚发布,这次会有哪些改变呢?
【编者按】近日发布的Hive 0.13中采用了ACID语义的事务机制,在分区层保证事务原子性、一致性和持久性,并通过开启ZooKeeper或内存中的锁机制保证事务隔离性。数据流摄取、缓慢变化维、数据重述这些新的用例在新版本中成为了可能,当然新版Hive中也还存在一些不足,Hive新版本具体带来哪些改变呢?作者Alan Gates为我们带来了精彩分析。
以下为原文:
什么是ACID,有什么作用?
ACID代表数据库事务中的4个特性:
原子性(任何一个数据库操作要么被完整执行,要么完全不执行)
一致性(一旦应用程序执行了一个操作,操作的结果对于每一个之后的操作都是可见的)
隔离性(一个用户的操作不会对其他用户产生意料之外的副作用)
持久性(一旦一个操作被完成,这些操作也将被记录下来,即使机器或者系统出现故障,也要保证这些记录的完整性)
这些特性一直被认为是事务功能的重要组成部分。
在最近发布的Hive 0.13中,事务的原子性、一致性和持久性在分区层得到保证,隔离性则通过开启ZooKeeper或内存中可用的锁机制来保证。通过在Hive 0.13中加入事务,实现在行级提供全部的ACID语义,这样的话,一个应用程序可以添加行,而另一个应用程序可以从同一分区中读取数据,互相之间不会产生干扰。
采用ACID语义的事务机制被添加到Hive中来处理以下的用例:
- 数据流摄取( streaming ingest of data):许多用户都会使用Apache Flume、Apache Storm或Apache Kafka之类的工具,这些工具可以将数据写入到Hadoop集群,写入数据的速率达到每秒数百行,而Hive可以每隔15分钟到1个小时添加一个分区,然而,添加太多分区也经常会造成表的混乱。这些工具也可以将数据写入到现有分区中,但这又可能会导致读取数据时产生脏读(他们读取的数据在执行查询之后被修改了),而且在他们的目录中会产生的许多小文件,对NameNode造成压力。有了数据流摄取这一新功能,用例将允许读者得到数据的一致视图,同时避免产生过多的文件。
- 缓慢变化维(slow changing dimensions):在一个典型星型架构的数据仓库中,随着时间推移,维度表变化的很慢。例如,一家零售商新开一个商店时,那这个商店就需要添加“商店表”中,但也有可能只是对现有商店进行扩充,也许还会添加一些新的服务。这些变化会导致向数据仓库中插入一些新的记录或者对已有记录进行更改(具体取决于选择的策略)。Hive目前无法支持这些操作,只要INSERT ... VALUES、UPDATE和DELETE这些操纵被支持,缓慢变化维将成为可能 -- 从 Hive 0.14.0 版本开始便支持
- 数据重述(data restatement):有时候收集到的数据不正确,需要一些修正,有可能数据的第一个实例已经近似(90%的服务器报告)稍后提供的完整数据,有可能某些事务由于后续事务还需要被重述(例如:在一次交易中,客户可能会购买会员资格,并因此有权享受折扣价,包括之前的交易也需要享受这个折扣价),还有可能用户会在他们交易关系终止时,会根据合同要求删除他们的用户数据。只要INSERT ... VALUES、UPDATE和DELETE这些操作被支持,数据重述也将成为可能。
不足:
- 在Hive 0.13中,还不支持INSERT……VALUES、UPDATE和DELETE等操作,BEGIN、COMMIT和ROLLBACK等操作也还没有得到支持,这些功能计划在下一个版本中实现。
- 在Hive 0.13的第一版本中还只能支持ORC文件格式。事务能否被任何存储格式使用决定了更新或删除操作如何应用于基础记录(实际上,基础记录中有显式或隐式的行id),但到目前为止,整合工作还只能用ORC格式完成。
- 流摄取接口(见下文)还不能与Hive中已有的INSERT INTO操作很好的整合。如果表使用流摄取接口,那通过INSERT INTO添加的任何数据都会丢失。不过INSERT OVERWRITE仍然可用,而且能用于流摄取,它通过流摄取删除插入数据就像是通过其他方式将数据写入到该分区一样。
- 在Hive 0.13中,事务默认是被关闭的。请参阅下面的配置部分,Hive中的一些关键项需要进行手动配置。
- 表必须被bucketed以更好地利用这些功能。同一个系统中没有使用事务和ACID的表不需要被bucket。
- 仅能支持快照级别隔离。当给定的查询启动后,将会产生一致的数据快照。不支持脏读、已提交读取、可重复读取,以及串行化。引入BEGIN后,将支持快照隔离事务,为的是保证事务持久性,而不仅仅为了完成一次查询。其他隔离级别可以根据用户要求添加。
- 现有的Zookeeper和内存锁管理器与事务不兼容。目前还没有打算解决这一问题,要想了解如何为事务存储锁,请参见下面的基础设计。
流摄入接口
有关使用流数据摄入的详细信息,请参阅StreamingDataIngest。
语法变化
多个新命令被添加到Hive的DDL中以支持ACID和事务,一些现有的DDL也被做了一些修改。
例如:新加入的SHOW TRANSACTIONS命令,有关该命令的详细信息,请参阅ShowTransactions。
SHOW COMPACTIONS也是一个新加入的命令,详细信息请参阅ShowCompactions。
原有的SHOW LOCKS命令被修改,以提供与事务相关的新锁信息。如果你正在使用ZooKeeper或者内存锁命令,你会注意到这条命令在输出上并没有多大改变,详细信息请参阅ShowLocks。
ALTER TABLE中添加了一个新的选项,用来压缩表或分区。一般用户不需要去请求压缩,因为系统会检测到他们的需求,然后自动启动压缩。但是,如果一个表压缩被意外终止或者一个用户想要去手动压缩表,ALTER TABLE可以满足用户,提供手动启动压缩,有关详细信息,请参阅AlterTable/PartitionCompact。ALTER TABLE会将请求排入队列、压缩并返回请求,如果用户想看到压缩的进展情况,可以使用SHOW COMPACTIONS命令。
基础设计
HDFS不支持对文件进行更改。在写入者向文件进行写入操作,同时文件被其他用户读取这种情况下,它无法保证读取的一致性。为了在HDFS上提供这一功能,我们采用在其他数据仓库工具使用的标准方法,将表或分区数据存储在一组基础文件中,将新记录、更新和删除操作存储在delta文件中。为每个事务创建一组新delta文件(或者在流代理如:Flume或Storm中,为每一批事务创建一组新delta文件),更改表或分区。在读取时,读取器将基础文件和delta文件合并,应用更新和删除操作。
有时这些变化需要合并成基础文件,一组线程必须添加到Hive metastore中。他们确定什么时候需要压缩,然后执行压缩,最后进行清理(删除旧文件)。压缩的类型分两种,次要的和主要的。次要压缩采用一组现有的delta文件,并为每一次bucket重写一个delta文件。主要压缩则要为每个bucket写一个或多个delta文件,为每一次bucket重写一个新基础文件。所有压缩都在后台完成,并不妨碍数据的并发读取和写入。在一次压缩后,系统会等待直到所有旧文件的读取结束,然后删除旧的文件。
以前一个分区(或如果没有分区表的表)的所有文件都放在单个目录中。因为这些变化,所有采用ACID思想写入的分区都将有一个基础文件的目录,以及delta文件集目录。
新锁管理器DbLockManager也添加到了Hive中。该锁管理器将所有锁信息存储在metastore中,此外所有事务也存储在metastore中。这意味着事务和锁即使在服务器出现故障时也能保证持久性,为了避免客户端死机、离开或者锁挂起,锁持有者和事务启动器需要向metastore发出心跳信号(heartbeat),如果在给定的时间内服务器没有收到客户端发出的心跳信号,该锁或事务将被中止。
配置
许多新配置关键项被添加到系统用以支持事务。
配置关键项 | 默认值 | 事务启动值 | 注释 |
hive.txn.manager | org.apache.hadoop. hive.ql.lockmgr. DummyTxnManager | org.apache.hadoop. hive.ql.lockmgr. DbTxnManager | DummyTxnManager延用了Hive 0.13之前的做法,不提供事务。 |
hive.txn.timeout | 300 | 如果在这段时间内,客户端没有发出心跳信号,事务就会被宣告终止。 | |
hive.txn.max. open.batch | 1000 | 事务的最大数量,可以用open_txns()获取。 | |
hive.compactor. initiator.on | false | true (for exactly one instance of the Thrift metastore service) | 是否在该metastore实例上运行启动器和垃圾清理线程。 |
hive.compactor. worker.threads | 0 | > 0 on at least one instance of the Thrift metastore service | 有多少工作线程在该metastore实例上运行。 |
hive.compactor. worker.timeout | 86400 | 压缩作业在该时间内未完成将被宣布失败,压缩操作被重新排入队列。 | |
hive.compactor. check.interval | 300 | 检查是否有分区需要被压缩。 | |
hive.compactor. delta.num.threshold | 10 | delta目录的数目,达到该数后将触发次要压缩。 | |
hive.compactor. delta.pct.threshold | 0.1 | 与基础文件中delta文件的百分比,达到该值会触发主要压缩。(1 = 100%) | |
hive.compactor. abortedtxn.threshold | 1000 | 给定分区中已中止事务的数目,达到该数会也会触发主要压缩。 |
hive.txn.max.open.batch控制多个事务流代理,如Flume或Storm。流代理将多个词条写到单个文件中(每个Flume代理或每个Storm螺栓)。因此,增加该值可以减少流代理创建的文件数,但增加此值也增加了开放事务的数目(Hive需要追踪),这可能会影响读取的性能。
工作线程为压缩操作产生了许多MapReduce作业,而它们自身并没有做压缩。确定进行表压缩后,增加工作线程的数目会减少表压缩的时间。随着更多的MapReduce作业在后台运行,Hadoop集群的后台负载也在增加。
减小此值将减少压缩表或分区的时间。当然,首先得检查压缩是否必要,这需要为每个表或分区调用多个NameNode,降低此值可以减少NameNode的负载。
表属性
如果表的所有者不希望系统自动确定何时要压缩,那么可以手动设置表属性NO_AUTO_COMPACTION,用来阻止所有的自动压缩操作。