有人说,没遇到过jar包冲突问题都不好意思说自己用过hadoop。那么,你遇到过没?最近使用flink on yarn提交任务时遇到过一则jar冲突问题,整个分析过程还挺有意思的,记录一下。
背景
近期准备对实时计算平台进行升级,调研阶段使用yarn client手动向yarn集群上提交flink任务时出现了一个小插曲。提交任务时,一直提示失败,来yarn的web控制台发现日志有报错信息,错误如下:
代码语言:javascript复制Caused by: org.apache.flink.runtime.resourcemanager.exceptions.ResourceManagerException: Cannot initialize resource provider.
at org.apache.flink.runtime.resourcemanager.active.ActiveResourceManager.initialize(ActiveResourceManager.java:124)
at org.apache.flink.runtime.resourcemanager.ResourceManager.startResourceManagerServices(ResourceManager.java:245)
at org.apache.flink.runtime.resourcemanager.ResourceManager.onStart(ResourceManager.java:229)
... 20 more
Caused by: org.apache.hadoop.yarn.exceptions.YarnRuntimeException: yarn.client.max-nodemanagers-proxies (0) can not be less than 1.
at org.apache.hadoop.yarn.client.api.impl.ContainerManagementProtocolProxy.<init>(ContainerManagementProtocolProxy.java:74)
at org.apache.hadoop.yarn.client.api.impl.NMClientImpl.serviceInit(NMClientImpl.java:136)
at org.apache.hadoop.service.AbstractService.init(AbstractService.java:163)
at org.apache.hadoop.yarn.client.api.async.impl.NMClientAsyncImpl.serviceInit(NMClientAsyncImpl.java:109)
at org.apache.hadoop.service.AbstractService.init(AbstractService.java:163)
at org.apache.flink.yarn.YarnResourceManagerDriver.initializeInternal(YarnResourceManagerDriver.java:186)
at org.apache.flink.runtime.resourcemanager.active.AbstractResourceManagerDriver.initialize(AbstractResourceManagerDriver.java:81)
at org.apache.flink.runtime.resourcemanager.active.ActiveResourceManager.initialize(ActiveResourceManager.java:122)
... 22 more
从报错信息中能清晰地看到,这是一个yarn参数的配置问题。要求yarn.client.max-nodemanagers-proxies的值不能小于1,而它的实际传入值为0,于是yarn Client在进行任务提交时初始化resource manager时抛出了异常。它的实际代码位置位于org.apache.hadoop.yarn.client.api.impl.ContainerManagementProtocolProxy中,查看了一下当前版本的ContainerManagementProtocolProxy中具体代码逻辑如下:
hadoop-yarn-client-2.4.1是flink-yarn的默认依赖,在这里会进行yarn.client.max-nodemanagers-proxies的配置检查,这个默值的值不能小于1,否则会抛出异常。如果没有手动配置过这个值,这里会使用500作为默认值。显然,我们没有对这个参数进行手动配置,那么为什么没有用500作为默认值呢?下面我们来分析一下。
分析
首先在实时计算平台使用yarn client进行任务提交时从来没有出现过这个异常,但是在这里使用yarn client手动提交时却出现了异常,这是什么原因呢?
首先想到的就是hadoop的版本依赖问题,从经验上来说这是最容易出问题的地方。
检查依赖版本
1.首先来查看实时计算平台上hadoop-yarn-client的依赖版本,查看后发现版本也是2.4.1,并无差异。2.由于两个地方都没有对yarn.client.max-nodemanagers-proxies进行手动配置,而上面从YarnConfiguration中获取yarn.client.max-nodemanagers-proxies的值时取到的不为空,导致默认填充值500没有生效,那么会不会是两个地方使用的YarnConfiguration中获取的值不同呢?于是添加日志打印输出(此处忽略这个过程),发现也并无差异。3.先是有点不太能理解了,转念一想,是不是使用了不同版本的YarnConfiguration,不同版本的有不同实现呢?查看一遍,果然如此。实时计算平台上使用的是2.4.1版本的YarnConfiguration,手动提交时使用的是2.7.4版本的YarnConfiguration。
具体原因分析
YarnConfiguration#getInt方法实际上继承自父类org.apache.hadoop.conf.Configuration#getInt方法,我们直接来看org.apache.hadoop.conf.Configuration#getInt方法的具体实现,代码如下:
代码语言:javascript复制 public int getInt(String name, int defaultValue) {
String valueString = getTrimmed(name);
if (valueString == null)
return defaultValue;
String hexString = getHexDigits(valueString);
if (hexString != null) {
return Integer.parseInt(hexString, 16);
}
return Integer.parseInt(valueString);
}
这个方法的大致逻辑是先去配置上下文中获取指定的key对应的配置值,如果没有配置的话则返回默认值。简单一看,好像并无大问题。我们进入到getTrimmed方法来看:
代码语言:javascript复制 public String getTrimmed(String name) {
String value = get(name);
if (null == value) {
return null;
} else {
return value.trim();
}
}
这个方法的注释简单翻译一下,大概是这样子的:该方法用于获取name属性对应的值,如果不存在该属性则为空。如果该key已弃用,它将返回取代已弃用的key所对应的那个key所对应的值。这个逻辑要进入到get(name)方法来看才比较清晰:
代码语言:javascript复制 public String get(String name) {
String[] names = handleDeprecation(deprecationContext.get(), name);
String result = null;
for(String n : names) {
result = substituteVars(getProps().getProperty(n));
}
return result;
}
在handleDeprecation方法内部会先查找当前需要查找的key是不是在被废弃上下文列表中,如果不在列表中但直接返回原始key值;如果在废弃列表中,则返回代替废弃key的新key。
这也就是说,如果我们查看一下yarn.client.max-nodemanagers-proxies参数在2.4.1和2.7.4两个版本中的差异就能找到为啥都没有配置该参数的情况下,一个会为空进而使用默认值,另一个不为空而没有使用默认值的原因了。
版本差异分析
我们先来看一下org.apache.hadoop.conf.Configuration#deprecationContext属性值:
它默认初始化的是defaultDeprecations数组中的一些废弃参数与新的替代参数的映射,另外其他地方可以调用addDeprecations方法来添加新的映射值。
我们直接来看2.7.4版本的YarnConfiguration:
可以清楚地看到,它会向defaultDeprecations数组中添加一组新的映射值,即废弃掉yarn.client.max-nodemanagers-proxies,用yarn.client.max-cached-nodemanagers-proxies代替,而且对新参数的默认值也有设置:
也就是说在都没有配置yarn.client.max-nodemanagers-proxies时,使用2.7.4版本的YarnConfiguration来获取yarn.client.max-nodemanagers-proxies值会返回yarn.client.max-cached-nodemanagers-proxies的值0;而使用2.4.1版本的YarnConfiguration时返回为null会在2.4.1版本的ContainerManagementProtocolProxy中使用默认值500。
当然在2.7.4版本的ContainerManagementProtocolProxy中已经做了新老key值的逻辑替换:
总结
归其原因是因为手动提交时使用的hadoop依赖不一致,使用了2.4.1版本的hadoop-yarn-api(ContainerManagementProtocolProxy依赖)却使用了2.7.4版本的hadoop-common(YarnConfiguration在这个依赖包中)。2.7.4为较新的版本,其YarnConfiguration中将yarn.client.max-nodemanagers-proxies纳入了废弃参数,并会用yarn.client.max-cached-nodemanagers-proxies(0)代替,而在2.4.1版本的ContainerManagementProtocolProxy中去获取该参数时就会取到0,从而触发异常。
解决办法也很简单,全部用2.4.1版本的hadoop依赖或者全部用2.7.4版本的依赖。