Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >CMQ消费者报错,无法获取本机ip地址问题排查

CMQ消费者报错,无法获取本机ip地址问题排查

原创
作者头像
haimingli
修改于 2020-10-24 16:02:47
修改于 2020-10-24 16:02:47
1.7K10
代码可运行
举报
运行总次数:0
代码可运行

背景

腾讯云消息队列(Cloud Message Queue,CMQ)是一种分布式消息队列服务,它能够提供可靠的基于消息的异步通信机制,能够将分布式部署的不同应用(或同一应用的不同组件)之间的收发消息,存储在可靠有效的 CMQ 队列中,防止消息丢失。CMQ 支持多进程同时读写,收发互不干扰,无需各应用或组件始终处于运行状态。

可是有一天遇到一个问题,一个客户使用同样的消费者代码在三台CVM上面部署应用,其中有一台无法消费任何消息,运行报错,对于java这种Write once,run anywhere的语言来说,是很奇怪的,根据以往经验,这往往是环境问题。那么就需要我们仔细分析一下问题的根本原因。

问题排查原因及解决方案

我们先来看看报错的截图:

初步看来是RequestIdHelper这个类初始化失败,这种问题往往是静态代码块或者实例变量初始化异常造成。接着仔细查看异常堆栈,从中发现了问题,根源就是消费者静态代码块中用于获取ip地址构造RequestId的代码抛了异常,这句代码就是InetAddress.getLocalHost(),一句简单的代码,造成了严重的问题,整个消费者无法正常消费消息。

原因分析

为什么一句简单的InetAddress.getLocalHost()会抛出异常呢,我们分析下JDK的源代码,我们在源码中加注释分析:

代码语言:java
AI代码解释
复制
 public static InetAddress getLocalHost() throws UnknownHostException {

        SecurityManager security = System.getSecurityManager();
        try {
            // 1.获取hotname,这是个native方法,hotspot中实现非常简单,
            直接系统调用gethostname,如果调用失败,那么获取硬编码值‘localhost’
            String local = impl.getLocalHostName();

            if (security != null) {
                security.checkConnect(local, -1);
            }

            if (local.equals("localhost")) {
                return impl.loopbackAddress();
            }

            InetAddress ret = null;
            synchronized (cacheLock) {
                long now = System.currentTimeMillis();
                // 2. 如果命中cachedLocalHost,直接用缓存值
                if (cachedLocalHost != null) {
                    if ((now - cacheTime) < maxCacheTime) // Less than 5s old?
                        ret = cachedLocalHost;
                    else
                        cachedLocalHost = null;
                }

                // we are calling getAddressesFromNameService directly
                // to avoid getting localHost from cache
                if (ret == null) {
                    InetAddress[] localAddrs;
                    try {
                        // 3. 下面的逻辑是去缓存或者系统调用获取地址
                        localAddrs =
                                InetAddress.getAddressesFromNameService(local, null);
                    } catch (UnknownHostException uhe) {
                        // Rethrow with a more informative error message.
                        UnknownHostException uhe2 =
                                new UnknownHostException(local + ": " +
                                        uhe.getMessage());
                        uhe2.initCause(uhe);
                        throw uhe2;
                    }
                    cachedLocalHost = localAddrs[0];
                    cacheTime = now;
                    ret = localAddrs[0];
                }
            }
            return ret;
        } catch (java.lang.SecurityException e) {
            return impl.loopbackAddress();
        }
    }

那么从上面代码来看,唯一出问题的可能就是在第三步InetAddress.getAddressesFromNameService,里面的逻辑无非就是从缓存中查找或者调用Inet4AddressImpl.lookupAllHostAddr。问题可能就出在这个lookupAllHostAddr方法。lookupAllHostAddr

代码如下:

代码语言:c
代码运行次数:0
运行
AI代码解释
复制
/*
 * Find an internet address for a given hostname.  Note that this
 * code only works for addresses of type INET. The translation
 * of %d.%d.%d.%d to an address (int) occurs in java now, so the
 * String "host" shouldn't *ever* be a %d.%d.%d.%d string
 *
 * Class:     java_net_Inet4AddressImpl
 * Method:    lookupAllHostAddr
 * Signature: (Ljava/lang/String;)[[B
 */

JNIEXPORT jobjectArray JNICALL
Java_java_net_Inet4AddressImpl_lookupAllHostAddr(JNIEnv *env, jobject this,
                                                jstring host) {
    ...
    ...
    // 系统调用
    error = getaddrinfo(hostname, NULL, &hints, &res);

    if (error) {
        /* report error */
        ThrowUnknownHostExceptionWithGaiError(env, hostname, error);
        JNU_ReleaseStringPlatformChars(env, host, hostname);
        return NULL;
    } else {
        ....
    }
    ...
}

上面去除一些无关逻辑,可以看到,查询hostname的ip地址的是一个系统调用方法getaddrinfo。如果查不到,那么就可能抛出异常。那么接着分析下这个getaddrinfo是如何执行的。下面写一段代码,准备使用strace分析分析。测试代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <arpa/inet.h>

int main(int argc, char **argv)
{
    int ret = -1;
    struct addrinfo *res;
    struct addrinfo hint;
    struct addrinfo *curr;
    char ipstr[16];   

    if (argc != 2) {
        printf("parameter error\n");
        return -1;
    }

    bzero(&hint, sizeof(hint));
    hint.ai_family = AF_INET;
    hint.ai_socktype = SOCK_STREAM;

    ret = getaddrinfo(argv[1], NULL, &hint, &res);
     printf("getaddrinfo finish\n");
    if (ret != 0) 
    {
        printf("getaddrinfo error\n");
        return -1;
    }

    for (curr = res; curr != NULL; curr = curr->ai_next) 
    {
        inet_ntop(AF_INET,&(((struct sockaddr_in *)(curr->ai_addr))->sin_addr), ipstr, 16);
        printf("%s\n", ipstr);
    }

    freeaddrinfo(res);

    return 0;
}

测试代码中简单调用getaddrinfo方法,代码编写后,在腾讯云上申请一台CVM,系统环境为:Linux version 3.10.0-1062.18.1.el7.x86_64 (mockbuild@kbuilder.bsys.centos.org) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC) ) #1 SMP Tue Mar 17 23:49:17 UTC 2020。然后执行 gcc testGetAddr.c -o testGetAddr,编译得到可执行程序。再执行strace ./testGetAddr {hostname},看到系统调用过程, 省略无关语句整理如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
...
...
open("/lib64/libresolv.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/etc/host.conf", O_RDONLY|O_CLOEXEC) = 3
open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 3
open("/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libnss_files.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/etc/hosts", O_RDONLY|O_CLOEXEC)  = 3
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
...
open("/lib64/libnss_dns.so.2", O_RDONLY|O_CLOEXEC) = 3
...
uname({sysname="Linux", nodename="efg", ...}) = 0
socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_ROUTE) = 3
setsockopt(3, SOL_SOCKET, SO_PASSCRED, [1], 4) = 0
setsockopt(3, SOL_NETLINK, 3, [1], 4)   = 0
bind(3, {sa_family=AF_NETLINK, pid=0, groups=00000000}, 16) = 0
getsockname(3, {sa_family=AF_NETLINK, pid=8933, groups=00000000}, [12]) = 0
sendto(3, "\x18\x00\x00\x00\x16\x00\x05\x03\x01\x00\x00\x00\x00\x00\x00\x00\x02\x20\x00\x00\x00\x00\x00\x00", 24, 0, {sa_family=AF_NETLINK, pid=0, groups=00000000}, 16) = 24
clock_gettime(CLOCK_MONOTONIC, {63428, 128822240}) = 0
recvmsg(3, {msg_name(0)=NULL, msg_iov(1)=[{NULL, 0}], msg_controllen=56, [{cmsg_len=20, cmsg_level=SOL_NETLINK, cmsg_type=3}, {cmsg_len=28, cmsg_level=SOL_SOCKET, cmsg_type=SCM_CREDENTIALS, {pid=0, uid=0, gid=0}}], msg_flags=MSG_TRUNC}, MSG_PEEK|MSG_TRUNC) = 164
recvmsg(3, {msg_name(0)=NULL, msg_iov(1)=[{"\x4c\x00\x00\x00\x14\x00\x02\x00\x01\x00\x00\x00\xe5\x22\x00\x00\x02\x08\x80\xfe\x01\x00\x00\x00\x08\x00\x01\x00\x7f\x00\x00\x01"..., 328}], msg_controllen=56, [{cmsg_len=20, cmsg_level=SOL_NETLINK, cmsg_type=3}, {cmsg_len=28, cmsg_level=SOL_SOCKET, cmsg_type=SCM_CREDENTIALS, {pid=0, uid=0, gid=0}}], msg_flags=0}, MSG_TRUNC) = 164
clock_gettime(CLOCK_MONOTONIC, {63428, 128967588}) = 0
...

上面系统调用主要保留了打开的文件,可以看到先调用libresolv.so.2动态链接库,再打开文件open("/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3,用于判断从host文件还是从dns server获取地址。由于本机修改了hostname为"efg",以及没有在hosts文件中设置hostname的ip,通过host文件肯定是获取不到地址信息的。那么程序接下来就通过NETLINK的单播形式,给内核发送消息,并尝试得到地址。如果还是得不到的话,那么就会查看/etc/host.conf的内容,本机的是multi on,表示libresolv.so.2需要获取所有ip地址,解析器就会根据/etc/resolv.conf里面指定的所有nameserver获取地址,大概过程就是这样。

所以,InetAddress.getLocalHost实现的步骤总结如下:

* 是否设置好了hostname,如果没有设置,返回hostname为localhost,ip地址硬编码写死127.0.0.1的地址信息实体。

* 如果设置了hostname,那么看看java本地缓存有没有地址信息,如果有,返回地址信息,如果没有,则执行一些列检查逻辑,纠正错误逻辑(如特殊的hostname有它特定的处理),如果是个特殊的hostname,直接返回。

* 执行系统调用getaddrinfo,打开/etc/nsswitch.conf,判断先查host还是先从dns server查,本例子中是则根据名字在hosts文件中查找,找不到,先和内核空间进程通过单播形式通信,尝试获取,如果失败,则使用DNS客户端进行域名解析处理 * 打开文件/etc/services,查找服务 * 打开etc/host.conf 该配置文件为域名解析顺序配置文件,设定解析顺序方式 * 打开/etc/resolv.conf配置文件,该文件用于指定解析的DNS服务器,得dns server * 打开/etc/hosts 文件,查询主机名 * hosts中找不到记录,从nameserver进行主机名称解析。

那么一台机器找不到ip地址,就有可能是上面步骤出了问题。

解决方案/最佳实践

如何解决呢,最简单的办法就是在hosts文件做兜底方案,设置hostname的ip地址即可,或者干脆删除hostname也行,但是这种方案有三个问题,一个是ip地址可能会被瞎写,一个是不同系统设置不同,可能会导致像这次故障一样应用起不来,最后一种就是查询过程中有getnameinfo走dns解析,假如这里网络异常不但可能起不来,还可能使得应用启动缓慢(特别是云函数这种场景影响会非常大)

所以,正常情况下,我们一般不会直接使用InetAddress.getLocalHost,而是通过NetworkInterface

的方式获取ip,下面代码复制自 https://cloud.tencent.com/developer/article/1610919:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

public static InetAddress getLocalHostExactAddress() {
    try {
        InetAddress candidateAddress = null;

        Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
        while (networkInterfaces.hasMoreElements()) {
            NetworkInterface iface = networkInterfaces.nextElement();
            // 该网卡接口下的ip会有多个,也需要一个个的遍历,找到自己所需要的
            for (Enumeration<InetAddress> inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); ) {
                InetAddress inetAddr = inetAddrs.nextElement();
                // 排除loopback回环类型地址(不管是IPv4还是IPv6 只要是回环地址都会返回true)
                if (!inetAddr.isLoopbackAddress()) {
                    if (inetAddr.isSiteLocalAddress()) {
                        // 如果是site-local地址,就是它了 就是我们要找的
                        // ~~~~~~~~~~~~~绝大部分情况下都会在此处返回你的ip地址值~~~~~~~~~~~~~
                        return inetAddr;
                    }

                    // 若不是site-local地址 那就记录下该地址当作候选
                    if (candidateAddress == null) {
                        candidateAddress = inetAddr;
                    }

                }
            }
        }

        // 如果出去loopback回环地之外无其它地址了,那就回退到原始方案吧
        return candidateAddress == null ? InetAddress.getLocalHost() : candidateAddress;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
1 条评论
热度
最新
杀鸡用牛刀的排查方法
杀鸡用牛刀的排查方法
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
记一次传递文件句柄引发的血案 (续)
继 记一次传递文件句柄引发的血案 之后,这个 demo 又引发了一次血案,现录如下。
海海
2022/08/19
7800
recvfrom函数
RECV(2) Linux Programmer’s Manual RECV(2)
全栈程序员站长
2022/09/15
5870
linux网络编程之socket(十六):通过UNIX域套接字传递描述符和 sendmsg/recvmsg 函数
s1mba
2017/12/28
3.1K0
linux网络编程之socket(十六):通过UNIX域套接字传递描述符和 sendmsg/recvmsg 函数
你知道 java 获取本地 ip 地址有两种方法吗?讲讲隐藏在他们背后的哪些坑
本周进行了一个关于通过 java 代码获取本机 ip 地址的线上性能优化,这篇文章做一个总结,也提供一些 java 线上优化排查思路和更进一步的思考与总结。
用户3147702
2022/06/27
3.9K0
你知道 java 获取本地 ip 地址有两种方法吗?讲讲隐藏在他们背后的哪些坑
缩略moduo库(6):acceptor
文章目录 Socket Acceptor Socket #pragma once #include "nocopyable.hpp" class InetAddress; //封装sockfd class Socket:public nocpoyable{ public: explicit Socket(int sockfd) :sockfd_(sockfd) {} ~Socket(); int fd(){ return sockfd_;}
看、未来
2021/10/09
3170
UNIX域协议(无名套接字)
关于什么是UNIX域套接字可以参考:https://cloud.tencent.com/developer/article/1018893 这里主要介绍非命名的UNIX域套接字的用法。 1.socketpair函数 先看man手册: SYNOPSIS        #include <sys/types.h>          /* See NOTES */        #include <sys/socket.h>        int socketpair(int domain, int typ
xcywt
2018/01/12
8210
Java中InetAddress的使用(二):获取本机IP地址的正确姿势【享学Java】
本文接着上文的内容,主要解答上文留下的疑问:既然不能使用InetAddress#getLocalHost()直接去获取到本机的IP地址,那么如何破呢?
YourBatman
2020/04/08
18K2
从内核看文件描述符传递的实现(基于5.9.9)
前言:文件描述符是内核提供的一个非常有用的技术,典型的在服务器中,主进程负责接收请求,然后把请求传递给子进程处理。本文分析在内核中,文件描述符传递是如何实现的。
theanarkh
2021/07/08
9200
从内核看文件描述符传递的实现(基于5.9.9)
使用golang的net包进行域名解析过程分析
我们都知道,在计算机的世界,建立连接都是需要依靠五元组的(源ip,源端口,目的ip,目的端口,协议),而在实际用户使用过程中,浏览器会帮我们识别和管理源ip和端口以及协议(http,https),协议确定后其实目的端口也就确定了(80或443). 因此整个DNS系统要解决的问题就是将用户在浏览器中输入的域名最终转换成可识别的目的ip,进而进行连接通信。下面以一个简单例子来分析下dns解析的过程.
BGBiao
2019/09/11
13.7K0
socketpair原理_pair of shoes意思
socketpair()函数用于创建一对无名的、相互连接的套接子。 如果函数成功,则返回0,创建好的套接字分别是sv[0]和sv[1];否则返回-1,错误码保存于errno中。
全栈程序员站长
2022/11/04
4320
后台并发模型改进经验分享
异步IO多线程并发模型通常由监听线程组+工作线程组构成,监听线程负责接收新连接,然后把新连接转给工作线程。
三棵老松
2019/08/26
5560
黑科技解密!实现socket进程间迁移!
今天介绍一个可以拿出去吹牛的功能:实现socket句柄在进程之间迁移!为了这篇文章,xjjdog可算下了苦功夫,半夜还在翻资料。因为需要验证后,才能证明这项技术确实是正确的。
xjjdog
2021/07/29
1.3K0
Python获取本机所有IP地址
众所周知,Python标准库socket中有可以获取本机IPV4地址的方法,下面是网上非常常见的一种用法: >>> import socket >>> hostname = socket.gethostname() >>> hostname 'DESKTOP-I734J3O' >>> socket.gethostbyname(hostname) '192.168.0.103' 上面的代码在Windows下运行良好,但是无意中发现在Mac系统下运行不正常,返回的是本机回环地址127.0.0.1,而不是真正的I
Python小屋屋主
2018/04/16
4.4K0
libuv源码阅读(21)--uvtee
uv_write_s 类型是由普通ref以及cb和一些写操作有关的信息组成,然后它需要一个 uv_stream_t* handle 来配合使用。
wanyicheng
2021/03/13
1.1K0
InetAddress.getLocalHost() 执行很慢?
根据报警信息可知,只要获取主机信息的耗时超过了阈值HOST_NAME_RESOLVE_THRESHOLD=200ms,就会提示这个信息。很明显,我们的耗时已经超过5s。同时,如果为 Mac 系统,还会贴心地提示在/etc/hosts文件中配置本地dns。
xiaoxi666
2022/10/06
5.3K3
InetAddress.getLocalHost() 执行很慢?
IP地址解析的规则
《sqlplus登录缓慢的解决》文章中出现问题的场景,是配置了/etc/resolv.conf,但是未配置/etc/hosts,为此测试了两种方式。
bisal
2021/01/20
3.8K0
Java获取本机IP
过滤回环网卡、点对点网卡、非活动网卡、虚拟网卡并要求网卡名字是eth或ens开头;再过滤回环地址,并要求是内网地址(非外网)
十毛
2019/03/27
7.6K0
UNPv1第十三章:高级IO
flag在设计上存在一个基本问题:它是按值传递的,而不是值-结果参数,因此它只能从进程向内核传递标志,内核不能向进程传递标志。
提莫队长
2019/02/21
8460
十个例子让你了解 strace 的使用技巧
tcpdump 作为计算机网络排查的一大神器,掌握了上文所说的技巧,可以让你随时随地得心应手的掌握网络应用的一举一动。
用户3147702
2022/06/27
4.9K0
十个例子让你了解 strace 的使用技巧
无法获取指向控制台的文件描述符 (couldn't get a file descriptor referring to the console)
最近收拾东西,从一堆杂物里翻出来尘封四年多的树莓派 3B 主机来,打扫打扫灰尘,接上电源,居然还能通过之前设置好的 VNC 连上。欣慰之余,开始 clone 我的 git 项目,为它们拓展一个新的平台。在执行 cnblogs 项目 (参考《博客园排名预测 》) 对应的绘图命令时,趋势图、预测图是生成了,但没有自动打开图片,这个问题经过一番探索居然解决了,这篇文章就来分享一下解决问题的过程。
海海
2022/08/31
3.7K0
无法获取指向控制台的文件描述符 (couldn't get a file descriptor referring to the console)
相关推荐
记一次传递文件句柄引发的血案 (续)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验