问题发现:
某日夜里,收到线上业务报警短信,spark-client连接metastore服务失败,报出如下日志:
Caused by: org.apache.thrift.transport.TTransportException: java.net.SocketException: 断开的管道
at org.apache.thrift.transport.TIOStreamTransport.flush(TIOStreamTransport.java:161)
at org.apache.thrift.TServiceClient.sendBase(TServiceClient.java:73)
at org.apache.thrift.TServiceClient.sendBase(TServiceClient.java:62)
at org.apache.hadoop.hive.metastore.api.ThriftHiveMetastore$Client.send_get_database(ThriftHiveMetastore.java:660)
at org.apache.hadoop.hive.metastore.api.ThriftHiveMetastore$Client.get_database(ThriftHiveMetastore.java:652)
at org.apache.hadoop.hive.metastore.HiveMetaStoreClient.getDatabase(HiveMetaStoreClient.java:1187)
at org.apache.hadoop.hive.metastore.client.impl.CombinedMetastoreClient.getDatabase(CombinedMetastoreClient.java:587)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.hadoop.hive.metastore.RetryingMetaStoreClient.invoke(RetryingMetaStoreClient.java:152)
at com.sun.proxy.$Proxy5.getDatabase(Unknown Source)
at org.apache.hadoop.hive.ql.metadata.Hive.getDatabase(Hive.java:1311)
... 30 more
Caused by: java.net.SocketException: 断开的管道
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:109)
at java.net.SocketOutputStream.write(SocketOutputStream.java:153)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
at org.apache.thrift.transport.TIOStreamTransport.flush(TIOStreamTransport.java:159)
... 43 more
但看这个日志看不出来什么问题,只好据以往的经验,先重启下metastore服务。
在metastore服务重启的过程中,报出来如下问题(日志中敏感的用户名/IP已脱敏):
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
at org.apache.hadoop.hive.metastore.MetaStoreUtils.newInstance(MetaStoreUtils.java:1521)
... 14 more
Caused by: MetaException(message:Metastore contains multiple versions (3) [ version = 1.2.0, comment = Set by MetaStore xx@xx.xx.xx.xx ] [ version = 1.2.0, comment = Set by MetaStore xx@xx.xx.xx.xx] [ version = 1.2.0, comment = Set by MetaStore xx@xx.xx.xx.xx ])
at org.apache.hadoop.hive.metastore.ObjectStore.getMSchemaVersion(ObjectStore.java:6745)
at org.apache.hadoop.hive.metastore.ObjectStore.getMetaStoreSchemaVersion(ObjectStore.java:6703)
at org.apache.hadoop.hive.metastore.ObjectStore.checkSchema(ObjectStore.java:6661)
at org.apache.hadoop.hive.metastore.ObjectStore.verifySchema(ObjectStore.java:6645)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
说明的确是metastore服务出问题了。
问题排查:
metastore的报错信息中提到【Metastore contains multiple versions (3) 】,根据关键字直接去hive的源码中搜索,在hive-metastore模块,org.apache.hadoop.hive.metastore.ObjectStore.java 中查到有这么一段逻辑:
code中的逻辑是:去元数据库中查询version数据,如果查到的记录数 > 1,就会抛出【Metastore contains multiple versions 】的异常。
这就找到了线索,去元数据库中的VERSION表一看,的确是有三条数据(敏感信息已脱敏):
mysql> select * from VERSION limit 10;
+--------+----------------+-------------------------------------+
| VER_ID | SCHEMA_VERSION | VERSION_COMMENT |
+--------+----------------+-------------------------------------+
| 1 | 1.2.0 | Set by MetaStore xx@xx.xx.xx.xx |
| 2 | 1.2.0 | Set by MetaStore xx@xx.xx.xx.xx |
| 7 | 1.2.0 | Set by MetaStore xx@xx.xx.xx.xx |
+--------+----------------+-------------------------------------+
3 rows in set (0.00 sec)
mysql>
这样异常的原因就找到了:元数据库的VERSION表出现了多条记录(这个表正常是空或1条数据),导致metastore在初始化时,校验不通过,所以metastore服务启动失败,继而报出了文章一开始spark-client的问题。
解决办法:
删除VERSION表中的多余数据,只保留一条,重启metastore服务,发现metastore服务恢复正常启动,spark-client也可以正常连接了,故障修复,线上业务也恢复正常。
源码追踪:
至于VERSION表中为什么会出现多条重复记录,这个需要先弄清楚VERSION表的数据是什么时候插入的。 在Hive的源码中(还是刚才的ObjectStore.java文件),有这么一个方法,是负责在metastore初始化时,往VERSION表中写入数据的:
@Override
public void setMetaStoreSchemaVersion(String schemaVersion, String comment) throws MetaException {
MVersionTable mSchemaVer;
boolean commited = false;
boolean recordVersion =
HiveConf.getBoolVar(getConf(), HiveConf.ConfVars.METASTORE_SCHEMA_VERIFICATION_RECORD_VERSION);
if (!recordVersion) {
LOG.warn("setMetaStoreSchemaVersion called but recording version is disabled: " +
"version = " + schemaVersion + ", comment = " + comment);
return;
}
try {
mSchemaVer = getMSchemaVersion();
} catch (NoSuchObjectException e) {
// if the version doesn't exist, then create it
mSchemaVer = new MVersionTable();
}
mSchemaVer.setSchemaVersion(schemaVersion);
mSchemaVer.setVersionComment(comment);
try {
openTransaction();
pm.makePersistent(mSchemaVer);
commited = commitTransaction();
} finally {
if (!commited) {
rollbackTransaction();
}
}
}
这个逻辑是:先从VERSION表里获取数据,如果已有数据,则进行更新,如果没有,则插入。
按理说,这个逻辑没有什么问题,而且上游方法还加了方法锁,应该不会出现重复插入的现象。
可现实情况的确是出现了数据重复的现象。这是什么原因呢?愁死个人了,找不到原因,没法跟领导解释呀。
脑子里突然想起一件事,这件事至关重要:我们的metastore节点是多节点部署的(当初由于任务量巨大,为了缓解单节点metastore的负载压力,所以搭建了多个metastore节点,连接同一个MySQL服务器,并配置了定期重启的机制)。突然就看到了黎明的曙光:单个metastore服务不会出现重复插入的现象,但是多个metastore会呀!如果在某个特定的时间节点,两台metastore节点同时进行初始化,同时走到了setMetaStoreSchemaVersion() 方法,就有可能出现同时插入数据的情况。(上游方法加的锁只能在自己这个服务内起作用,影响不到另外一个metastore服务)。
总结:
总结下故障发生的过程:
1. 某个时刻,两台metastore节点同时重启,在初始化过程中都往VERSION表里插入了一条数据,造成VERSION表的数据条数 > 1;
2. metastore 的初始化仍在进行后续的步骤,进行到getMetaStoreSchemaVersion()时,从VERSION表中获取数据,由于记录数 > 1 , 所以抛出【Metastore contains multiple versions (3)】的异常,导致metastore初始化失败,继而导致spark-client访问metastore失败。
解决方法:
1. 手动将VERSION表中的重复数据删除,重启metastore服务。
2. 多metastore节点可能会重复插入VERSION表的问题貌似无解(虽然出现的几率很低),一个简单可行的解决办法就是把metastore定时重启的时间岔开,不要让两个metastore节点在同一时刻重启。