1.背景
在数据仓库开发中,遇到了读取数据任务导致锁表问题,发现是因为补数据或月度大任务跨天运行,此时凌晨定时的写入操作就会被阻塞进入等待状态(如果超过最大等待时间会失败),直到读取任务完成写入任务才可以继续运行,导致当天结果层数据输出延迟。因此分析并总结下锁表机制原理以及解决方式。
2.锁机制及原理分析
Hive 目前主要有两种锁,SHARED(共享锁 S)和 Exclusive(排他锁 X),同时又分表锁与分区锁,分区锁为最小粒度;共享锁 S 和 排他锁 X 它们之间的兼容性矩阵关系如下:
解释:
- 1)表锁与分区锁,分区锁为最小粒度(如果表锁未锁写入的分区,是可以执行写入任务的)
- 2)查询操作使用共享锁(S锁),共享锁是可以多重、并发使用的(就是说其他查询也可以挂S锁,并发查询不会阻塞修改会阻塞)
- 3)修改表操作使用独占锁(X锁),它会阻止其他的查询、修改操作
- 4)S锁和X锁同时出现会出现死锁情况(查询和写入不可同时发生)
以下情况会出发锁,以及它的类型和锁定范围如下:
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 |
3.解决方式
Hive锁机制会影响我们数据仓库输出效率,如果在已知表/分区的解锁对正运行的任务没有影响情况下我们可以在session 中关闭锁,通常我们会在X锁操作任务加上set hive.support.concurrency=false; 这个参数为 false 既能保证session忽略任何锁强行操作数据,又能保证session里的SQL对表不加任何锁;非分区表慎用由于不加锁写入时候同时读取会导致数据一致性问题。
排查及解决流程:
show locks tableName; #查看表锁
show locks tableName partition(dt='2014-04-01'); #查看分区锁
unlock table; unlock table partition(dt='2014-04-01'); #解锁表
set hive.support.concurrency=false; #在 session 中关闭锁默认为true,
4.拓展分析
排查锁问题方式
SHOW LOCKS <TABLE_NAME>; #查看HIVE表是否被锁
SHOW LOCKS <TABLE_NAME> EXTENDED;#查看哪一个SQL锁了HIVE表
SHOW LOCKS <TABLE_NAME> PARTITION (<PARTITION_DESC>); #查看HIVE表分区是否被锁
SHOW LOCKS <TABLE_NAME> PARTITION (<PARTITION_DESC>) EXTENDED;#查看哪一个SQL锁了HIVE表分区
hive锁的几个配置,可以在锁冲突时 fail fast 或者 重试等待锁释放
(hive默认的sleep时间是60s,比较长,在高并发场景下,可以减少这个的数值来提供job的效率)
hive.lock.numretries #重试次数
hive.lock.sleep.between.retries #重试时sleep的时间
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)
5.总结:
避免Hive因为锁导致的读写任务阻塞等待或失败:
1、建议表设置分区,因为锁可以到分区粒度,防止大字典表,单张全量表类似表长时间锁表,导致长时间阻塞读写任务。
2、如果新分区被锁可以通过 set hive.support.concurrency=false 来关闭锁机制,保证新分区写入数据成功。非分区表慎用由于不加锁写入时候同时读取会导致数据一致性问题。
3、脚本重跑一段时间范围数据时设置 sleep 间隔,避免长期持有锁,造成依赖此表的下游任务调度失败(这种方式需要根据下游任务分析运行状态时间)。
6.参考资料:
[1] Hive官方文档:锁
Locking - Apache Hive - Apache Software Foundation
[2] hive lock的配置