被这个参数三杀了

最近接连排查了几个问题,居然都是同一个参数引起的,本文就通过实际案例讲述下该参数如何引发问题的,以及问题最终又是如何解决的~

【First Blood】


在我们的环境中,RM是基于HA的方式部署的,并且RM是基于容器的方式运行的,即两个RM运行在各自的容器中;同时,我们还开启了kerberos认证,因此两个RM的hostname配置的是域名。主要的配置信息如下所示:

<property>
    <name>yarn.resource.ha.enabled</name>
    <value>true</value>
</property>
<property>
    <name>yarn.resourcemanager.ha.automatic-failover.enable</name>
    <value>true</value>
</property>
<property>
    <name>yarn.resourcemanager.ha.rm-ids</name>
    <value>rm1,rm2</value>
</property>
<property>
    <name>yarn.resourcemanager.hostname.rm1</name>
    <value>rm-0.svc.cluster.local</value>
</property>
<property>
    <name>yarn.resourcemanager.hostname.rm2</name>
    <value>rm-1.svc.cluster.local</value>
</property>

在一次测试过程中,RM的其中一个(容器所在)节点异常宕机了,此后向RM提交了一个任务,但该任务的AM启动后就失败了,报错信息为:

java.lang.IllegalArgumentException: java.net.UnknownHostException: hadoop-resourcemanager-0.hadoop-resourcemanager.hncscwc-198.svc.cluster.local1
        at org.apache.hadoop.security.SecurityUtil.buildTokenService(SecurityUtil.java:418)
        at org.apache.hadoop.yarn.client.ClientRMProxy.getTokenService(ClientRMProxy.java:153)
        at org.apache.hadoop.yarn.client.ClientRMProxy.getAMRMTokenService(ClientRMProxy.java:138)
        at org.apache.hadoop.yarn.client.ClientRMProxy.setAMRMTokenService(ClientRMProxy.java:80)
        at org.apache.hadoop.yarn.client.ClientRMProxy.getRMAddress(ClientRMProxy.java:99)
        at org.apache.hadoop.yarn.client.ConfiguredRMFailoverProxyProvider.getProxyInternal(ConfiguredRMFailoverProxyProvider.java:76)
        at org.apache.hadoop.yarn.client.ConfiguredRMFailoverProxyProvider.getProxy(ConfiguredRMFailoverProxyProvider.java:90)
        at org.apache.hadoop.io.retry.RetryInvocationHandler$ProxyDescriptor.<init>(RetryInvocationHandler.java:197)
        at org.apache.hadoop.io.retry.RetryInvocationHandler.<init>(RetryInvocationHandler.java:317)
        at org.apache.hadoop.io.retry.RetryInvocationHandler.<init>(RetryInvocationHandler.java:311)
        at org.apache.hadoop.io.retry.RetryProxy.create(RetryProxy.java:59)
        at org.apache.hadoop.yarn.client.RMProxy.createRMProxy(RMProxy.java:120)
        at org.apache.hadoop.yarn.client.RMProxy.createRMProxy(RMProxy.java:93)
        at org.apache.hadoop.yarn.client.ClientRMProxy.createRMProxy(ClientRMProxy.java:72)
        at org.apache.hadoop.mapreduce.v2.app.rm.RMCommunicator.createSchedulerProxy(RMCommunicator.java:311)
        at org.apache.hadoop.mapreduce.v2.app.rm.RMCommunicator.serviceStart(RMCommunicator.java:117)
        at org.apache.hadoop.mapreduce.v2.app.rm.RMContainerAllocator.serviceStart(RMContainerAllocator.java:263)

RM配置了高可用,其中一个宕机,另外一个RM也确实提升为Active了,任务也能正确提交和调度,但为什么运行就报错了呢?

顺着报错的堆栈信息,走读相关的代码,我们发现了问题的所在。

《YARN任务运行中的token》中提到了yarn任务的AM在启动后,会从指定的文件中加载AMRMToken,而rm的客户端在初始化时需要给token设置服务端的地址,也就是rm的地址。

关键代码如下所示:

7d5f2ed32e612b19f493fcfe073c0ed7.png

59295fbc24cbf8e257f00f02e59ee7ef.png

在buildTokenService中,判断如果必须使用IP(userIpForTokenService),则会对rm的域名进行解析,如果无法解析出具体的ip地址,则抛出异常;异常会逐层往上抛,最终导致程序退出。

结合实际情况来分析,由于其中一个rm出现了宕机,其域名确实无法解析出对应的ip来,因此这也就是导致任务失败的根本原因。

至于useIpForTokenService的值,是由配置项hadoop.security.token.service.use_ip来决定的,默认为true,即tokenService需要使用ip,而不是域名。

最终,将该配置参数设置为false后,再次测试验证,在同样的场景下,任务可以正确提交和运行。

【Double Kill】


在上面问题解决后的第二天,重新部署环境时,发现jobHistoryServer由于无法正确进行kerberos认证,导致启动失败,具体报错信息为:

org.apache.hadoop.yarn.exceptions.YarnRuntimeException: History Server Failed to login
        at org.apache.hadoop.mapreduce.v2.hs.JobHistoryServer.serviceInit(JobHistoryServer.java:130)
        at org.apache.hadoop.service.AbstractService.init(AbstractService.java:163)
        at org.apache.hadoop.mapreduce.v2.hs.JobHistoryServer.launchJobHistoryServer(JobHistoryServer.java:231)
        at org.apache.hadoop.mapreduce.v2.hs.JobHistoryServer.main(JobHistoryServer.java:241)
              
Caused by: java.io.IOException: Login failure for hadoop/172.16.20.18@BIGDATA.COM from keytab /home/hncscwc/hadoop/etc/hadoop/hdfs.keytab: javax.security.auth.login.LoginException: Unable to obtain password from user

        at org.apache.hadoop.security.UserGroupInformation.loginUserFromKeytab(UserGroupInformation.java:1144)
        at org.apache.hadoop.security.SecurityUtil.login(SecurityUtil.java:286)
        at org.apache.hadoop.mapreduce.v2.hs.JobHistoryServer.doSecureLogin(JobHistoryServer.java:183)
        at org.apache.hadoop.mapreduce.v2.hs.JobHistoryServer.serviceInit(JobHistoryServer.java:128)
        ... 3 more
Caused by: javax.security.auth.login.LoginException: Unable to obtain password from user

        at com.sun.security.auth.module.Krb5LoginModule.promptForPass(Krb5LoginModule.java:901)
        at com.sun.security.auth.module.Krb5LoginModule.attemptAuthentication(Krb5LoginModule.java:764)
        at com.sun.security.auth.module.Krb5LoginModule.login(Krb5LoginModule.java:618)
        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 javax.security.auth.login.LoginContext.invoke(LoginContext.java:755)
        at javax.security.auth.login.LoginContext.access$000(LoginContext.java:195)
        at javax.security.auth.login.LoginContext$4.run(LoginContext.java:682)
        at javax.security.auth.login.LoginContext$4.run(LoginContext.java:680)
        at java.security.AccessController.doPrivileged(Native Method)
        at javax.security.auth.login.LoginContext.invokePriv(LoginContext.java:680)
        at javax.security.auth.login.LoginContext.login(LoginContext.java:587)
        at org.apache.hadoop.security.UserGroupInformation$HadoopLoginContext.login(UserGroupInformation.java:522)
        at org.apache.hadoop.security.UserGroupInformation.loginUserFromKeytab(UserGroupInformation.java:1135)
        ... 6 more

问题出现后,先对jobHistoryServer的配置文件进行了确认,发现与之前是一样的,并没有什么不对的地方,关键配置项如下所示:

<property>
    <name>mapreduce.jobhistory.address</name>
    <value>172.16.20.18</value>
</property>
<property>
    <name>mapreduce.jobhistory.principal</name>
    <value>hadoop/_HOST@BIGDATA.COM</value>
</property>

jobHistoryServer启动后进行kerberos登陆时,会对principal中的_HOST进行替换,替换内容为mapreduce.jobhistory.address的值,即最终以ip形式进行了替换,导致认证失败。

正常来说,向kdc登陆认证的principal应该是包含服务的主机名,而不是ip地址,并且之前在这种配置下也都没有任何问题,怎么突然就不正常了?

还是结合源码进行分析,找到了问题所在:

配置项hadoop.security.token.service.use_ip配置为true或false时,内部会产生不同的主机地址解析对象:

  • 设置为true时为StandardHostResolver

  • 设置为false时为QualifiedHostResolver

两者对于host地址为ipv4的解析有所不同

对于StandardHostResolver:

0d9e7c8d4a2b6336f57aa5fdb612ac15.png

对于QualifiedHostResolver:

80d59dba2bf2ca6c31f330a07bde56cd.png

也就是说,StandardHostResolver可以通过getByName正确解析出ip对应的主机名,而后者直接将ip返回。

之前hadoop.security.token.service.use_ip配置为true,因此配置项mapreduce.jobhistory.address即便配置为ip,也能正确解析出对应的主机名,然后在principal替换_HOST时,也是正确的。

修改上面的问题后,将配置的值改为了false,就导致了该问题的出现。

最后,我们通过将配置项mapreduce.jobhistory.address的值修改为主机名解决了该问题。

【Triple Kill】


没有问题的日志维持了两三天,再次遇到问题,这次的现象是在sparkHistory节点上向hdfs上传文件失败。

首先,我们先在其它节点上进行了同样的操作,发现向hdfs上传文件是没有任何问题的,同时查看nn/dn的进程情况,结合对应日志确认nn/dn都是正常的。

接着,我们重新回到该节点上,检查了hdfs-site.xml中的相关配置项,没有发现异常的地方,然后向nn/dn的节点执行了ping操作,确认网络也没有问题,但发现hdfs相关的所有操作都失败。

最后,通过tcpdump进行了抓包分析,发现与nn建立连接时,tcp的源端地址为127.0.0.1,这导致syn发送后,根本得不到sync ack应答。而正常情况下,源ip应该是该节点自身的ip。

9d4b50ee3b50c7c58dba6590d51b879d.png

那么这里为什么会是127.0.0.1,是谁指定的127.0.0.1?

继续通过strace来分析,发现socket创建后,主动进行了一次bind源地址的操作,并且绑定的源端地址就是127.0.0.1。

e057c2fb765150f686ad2a7e08cec786.png

进一步调整日志为TRACE后,发现是在hdfs的客户端进行了bind的操作。

22/04/19 17:10:50 TRACE ipc.ProtobufRpcEngine: 1: Call -> namenode.svc.cluster.local/172.16.22.234:9000: getFileInfo {src: "/"}
22/04/19 17:10:50 DEBUG ipc.Client: The ping interval is 60000 ms.
22/04/19 17:10:50 DEBUG ipc.Client: Connecting to namenode.svc.cluster.local/172.16.22.234/9000
22/04/19 17:10:50 TRACE secruity.SecruityUtil: Name lookup for spark-history.svc.cluster.local took 3ms.
22/04/19 17:10:50 DEBUG ipc.Client: Binding hadoop/spark-history.svc.cluster.local@BIGDATA.COM to spark-history.svc.cluster.local.localdomain/127.0.0.1

既然知道是hdfs客户端中对socket进行了源IP的绑定动作,那么就结合源码梳理下hdfs客户端向nn建立连接的逻辑:

在开启kerberos认证的场景中,客户端向nn建立连接的流程包括:创建socket,然后从ticket中解析出bind地址并进行bind操作,最后进行连接。

从ticket中解析出本地bind地址的具体步骤又分为:

  • 从ticket中获取principal

  • 从principal中获取主机名

  • 如果主机名为空,则不进行bind操作

  • 如果主机名非空,对主机名进行解析,如果解析后的地址非空,则进行bind操作。

对于主机名解析又分为两种情况

如果配置项"hadoop.security.token.service.use_ip"的值为true,则直接获取主机名对应的ip,如果为false,则继续按下面的逻辑解析(其本意是想要获取主机名对应的完全合规域名)

  • 如果主机名为ipv4,通过ip地址获取对应的全域名

  • 如果主机名以"."结尾,直接获取主机名对应的全域名

  • 如果主机名包含".",先在主机名末尾加上".",并继续上一步的逻辑解析,如果解析出的域名为空,则在主机名末尾依次添加"/etc/resolve.conf"中的"search"指定的域,进行主机名的解析

以实际情况来分析:

  • sparkHistory进程kerberos登陆使用的principal为"hadoop/spark-history.svc.cluster.local@BIGDATA.COM"

  • 从pincipal中解析出主机名为"spark-history.svc.cluster.local"

  • 配置项"hadoop.security.token.service.use_ip"的值为false,因此进入全域名的解析流程。主机名不是完全合规(即不是以"."结尾),但又包含了".",因此先在末尾加上".",使其成为完全合规域名,并按照该域名来解析。

  • 而由于sparkHistory所在的容器,配置了就绪探针,容器未就绪时,无法解析出任何地址。因此继续在"spark-history.svc.cluster.local."后再加上"/etc/resolve.conf"中search指定的域,便利进行全域名解析,如果其中任意一个能解析出地址,则退出循环

  • 该节点中"/etc/resolve.conf"中的search中仅有一个localdomain,因此以"spark-history.svc.cluster.local.localdomain"来解析,解析出的ip恰好就是127.0.0.1,导致了问题的出现

该节点的/etc/resolve.conf文件中之所以只有"search localdomain",怀疑是人为进行了修改导致的。最后,修改该文件,问题得以解决。

【总结】


通过这三个问题分析定位解决,对"hadoop.security.token.service.use_ip"有了较深入的理解,同时也深刻领会源码是不会骗人的

好了,这就是本文的全部内容,如果觉得本文对您有帮助,不要吝啬点赞在看转发,也欢迎加我微信交流~

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在 Android 中,如果应用程序被死,定时任务将停止。但是,你可以使用 Android 的 AlarmManager 类来实现即使在应用程序被死时也能够调度定时任务的功能。 使用 AlarmManager,你可以指定一个时间,以及要执行的任务。当指定的时间到达时,系统会向你的应用程序发送一个广播,以便启动指定的任务。在接收到广播时,你的应用程序可以执行需要执行的操作,如发送通知或启动服务。 以下是一个简单的示例代码,演示如何使用AlarmManager来运行定时任务: ``` AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(this, YourBroadcastReceiver.class); PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); // 设置每 10 分钟执行一次任务 alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 10 * 60 * 1000, pendingIntent); ``` 在这个示例中,我们使用AlarmManager的setRepeating()方法来设置一个重复的定时任务。该方法采用四个参数: - 第一个参数指定闹钟类型,这里使用RTC_WAKEUP表示在指定的时间唤醒设备。 - 第二个参数指定任务应该在何时开始执行,这里我们使用System.currentTimeMillis()来指定当前时间。 - 第三个参数是指定任务将在多长时间内执行一次,这里我们设置为每10分钟执行一次。 - 最后一个参数是一个PendingIntent,它指定要执行的任务。 当你的应用程序被死时,Android 系统会保持 AlarmManager 的定时任务运行,直到指定的时间到达。一旦指定的时间到达,系统会启动你的应用程序并发送一个广播,以便你的应用程序可以执行指定的任务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值