昨天数据仓库hive中一张中间表lock,导致所有依赖这个表的任务失败。影响挺大,借此总结一下以备后面查用。
hive 目前主要有两种锁,SHARED(共享锁 S)和 Exclusive(排他锁 X)。共享锁 S 和 排他锁 X 它们之间的兼容性关系如下:
1)查询操作使用共享锁,共享锁是可以多重、并发使用的
2)修改表操作使用独占锁,它会阻止其他的查询、修改操作
3)可以对分区使用锁。
以下情况会触发锁,以及它的类型和锁定范围如下:
Hive Command |
Locks Acquired |
select .. T1 partition P1 |
S on T1, T1.P1 |
insert into T2(partition P2) select .. T1 partition P1 |
S on T2, T1, T1.P1 and X on T2.P2 |
insert into T2(partition P.Q) select .. T1 partition P1 |
S on T2, T2.P, T1, T1.P1 and X on T2.P.Q |
alter table T1 rename T2 |
X on T1 |
alter table T1 add cols |
X on T1 |
alter table T1 replace cols |
X on T1 |
alter table T1 change cols |
X on T1 |
alter table T1 concatenate | X on T1 |
alter table T1 add partition P1 |
S on T1, X on T1.P1 |
alter table T1 drop partition P1 |
S on T1, X on T1.P1 |
alter table T1 touch partition P1 |
S on T1, X on T1.P1 |
alter table T1 set serdeproperties |
S on T1 |
alter table T1 set serializer |
S on T1 |
alter table T1 set file format |
S on T1 |
alter table T1 set tblproperties |
X on T1 |
alter table T1 partition P1 concatenate | X on T1.P1 |
drop table T1 |
X on T1 |
修改hive-site.xml,配置如下:
hive.zookeeper.quorum
zk1,zk2,zk3
hive.support.concurrency
true
除此之外,还可以手动显式设置独占锁:
-- 1)锁表
lock table t1 exclusive;
-- 表被独占锁之后,将不能执行查询操作:
hive> SELECT COUNT(*) FROM people;
conflicting lock present for default@people mode SHARED
FAILED: Error in acquiring locks: locks on the underlying objects
cannot be acquired. retry after some time
-- 2)解除锁
unlock table t1;
注:Lock 是一种悲观的顺序化机制。它假设很可能发生冲突,因此在操作数据时,就加锁。
如果冲突的可能性很小,多数的锁都是不必要的。比如 Innodb 实现了一个延迟加锁的机制,来减少加锁的数量,提升性能,在代码中称为隐式锁(Implicit Lock),在本文中提到的 Hive锁默认都是隐式锁,除非手动加锁才是显式锁。
可以使用以下命令开始debug和排查锁问题:
SHOW LOCKS ;
SHOW LOCKS EXTENDED;
SHOW LOCKS PARTITION ();
SHOW LOCKS PARTITION () EXTENDED;
比如我们执行以下语句就可以知道是那个SQL锁Hive表了:
show locks hdp_ubu_zhuanzhuan_defaultdb.token extended
hive的锁在某些情况下会影响job的效率。在对数据一致性要求不高,或者已经明确了解到lock不会对job产生影响的情况下可以在session级别关闭lock的支持,又或者在表被任务循环持续读取时,insert 插入失败(建议脚本重跑一段时间范围数据时设置 sleep 间隔,避免长期持有锁,造成依赖表的任务调度失败)。我们可以通过 set hive.support.concurrency=false 参数在 session 中关闭锁,这个参数为 false 既能保证session忽略任何锁强行操作数据,又能保证session里的SQL对表不加任何锁。
场景1:离线下载分析商业cdn的qos job。
使用load data将数据导入hive中,hive的表按 dt,hour,domain进行明确分区,因此,是否有锁对数据的一致性影响不大,反而有了锁之后,job之间就会有锁竞争的问题(报错信息:conflicting lock present for table mode EXCLUSIVE)。
调整 job:
hive --database cdnlog -e "
set hive.support.concurrency=false;
load data local inpath 'file'
OVERWRITE into table chinanetcenter_log_origin
partition(dt='2013-12-17',hour='04',domain='xxx')
"
数据load时就不会有报错了,效率也高了不少
场景2:hive锁的几个配置,可以在锁冲突时 fail fast 或者 重试等待锁释放
hive.lock.numretries #重试次数
hive.lock.sleep.between.retries #重试时sleep的时间
hive默认的sleep时间是60s,比较长,在高并发场景下,可以减少这个的数值来提供job的效率。
Hive(CDH4.2.0)的锁处理流程:
1.首先对query进行编译,生成QueryPlan
2.构建读写锁对象(主要两个成员变量:LockObject,Lockmode)
对于非分区表,直接根据需要构建S或者X锁对象
对于分区表:(此处是区分input/output)
If S mode:
直接对Table/related partition 构建S对象
Else:
If 添加新分区:
构建S对象
Else
构建X对象
End
3.对锁对象进行字符表排序(避免死锁),对于同一个LockObject,先获取Execlusive
4.遍历锁对象列表,进行锁申请
While trynumber< hive.lock.numretries(default100):
创建parent(node)目录,mode= CreateMode.PERSISTENT
创建锁目录,mode=CreateMode.EPHEMERAL_SEQUENTIAL
For Child:Children
If Child已经有写锁:
获取child写锁seqno
If mode=X 并且 Child 已经有读锁
获取child读锁seqno
If childseqno>0并且小于当前seqno
释放锁
Trynumber++
Sleep(hive.lock.sleep.between.retries:default1min)
想要避免 Hive 锁造成的读、写失败问题,注意以下3点:
1、表建议设置分区,锁的粒度可以到分区,否则容易遭遇长时间锁表,尤其大字典表、单张全量表要注意。
2、建议脚本重跑一段时间范围数据时设置 sleep 间隔,避免长期持有锁,造成依赖表的任务调度失败。
3、我们可以通过 set hive.support.concurrency=false 来关闭锁,优先保证插入数据成功,虽然此时读数据会有问题。