关于xxl-job中任务调度获取IP问题_xxljob注册服务器ip不可用-程序员宅基地

技术标签: ip网络  java  

多网卡未能获取到正确的IP

在xxl-job中(目前最新版本),获取IP是通过ipUtils类中的getIp()方法获取的,追到核心方法发现获取IP首先获取的是localhost,如果本地没取到IP,那么会去获取所有网卡接口,依次遍历。

先看核心源码:

private static InetAddress getLocalAddress0() {
    
        //获取本地地址
        InetAddress localAddress = null;
        try {
    
            localAddress = InetAddress.getLocalHost();
            InetAddress addressItem = toValidAddress(localAddress);
            if (addressItem != null) {
    
                return addressItem;
            }
        } catch (Throwable e) {
    
            logger.error(e.getMessage(), e);
        }
         //获取所有网卡接口
        try {
    
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            if (null == interfaces) {
    
                return localAddress;
            }
            while (interfaces.hasMoreElements()) {
    
                try {
    
                    NetworkInterface network = interfaces.nextElement();
                    //过滤掉回环IP、虚拟IP、未打开的IP
                    if (network.isLoopback() || network.isVirtual() || !network.isUp()) {
    
                        continue;
                    }
                    Enumeration<InetAddress> addresses = network.getInetAddresses();
                    while (addresses.hasMoreElements()) {
    
                        try {
    
                            InetAddress addressItem = toValidAddress(addresses.nextElement());
                            if (addressItem != null) {
    
                                //地址有效性校验
                                try {
    
                                    if(addressItem.isReachable(100)){
    
                                        return addressItem;
                                    }
                                } catch (IOException e) {
    
                                    // ignore
                                }
                            }
                        } catch (Throwable e) {
    
                            logger.error(e.getMessage(), e);
                        }
                    }
                } catch (Throwable e) {
    
                    logger.error(e.getMessage(), e);
                }
            }
        } catch (Throwable e) {
    
            logger.error(e.getMessage(), e);
        }
        return localAddress;
    }

在追踪一下InetAddress.getLocalHost()代码:

/**
     * Returns the address of the local host. This is achieved by retrieving
     * the name of the host from the system, then resolving that name into
     * an {@code InetAddress}.
     *
     * <P>Note: The resolved address may be cached for a short period of time.
     * </P>
     *
     * <p>If there is a security manager, its
     * {@code checkConnect} method is called
     * with the local host name and {@code -1}
     * as its arguments to see if the operation is allowed.
     * If the operation is not allowed, an InetAddress representing
     * the loopback address is returned.
     *
     * @return     the address of the local host.
     *
     * @exception  UnknownHostException  if the local host name could not
     *             be resolved into an address.
     *
     * @see SecurityManager#checkConnect
     * @see java.net.InetAddress#getByName(java.lang.String)
     */
    public static InetAddress getLocalHost() throws UnknownHostException {
    

        SecurityManager security = System.getSecurityManager();
        try {
    
            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();
                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 {
    
                        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();
        }
    }

这时候就会发现在这个方法中获取的localAddrs[]总是获取的地一个。如果当前网卡存在2个,第一个eth0状态不可用,第二个eth1状态可用,那么它总是会取到eth0不可用IP,导致服务出现问题。

而Eureka也其实存在这种问题,导致注册的IP是一个不可用的IP,但在Spring Cloud环境下,Eureka Client并没有自己实现查找本机IP的逻辑,而是交给了Spring的InetUtils工具类中的findFirstNonLoopbackAddress()方法。
先看下findFirstNonLoopbackAddress源码:

public InetAddress findFirstNonLoopbackAddress() {
    
        InetAddress result = null;

        try {
    
            int lowest = 2147483647;
            Enumeration nics = NetworkInterface.getNetworkInterfaces();

            label61:
            while(true) {
    
                NetworkInterface ifc;
                do {
    
                    while(true) {
    
                        do {
    
                            if (!nics.hasMoreElements()) {
    
                                break label61;
                            }

                            ifc = (NetworkInterface)nics.nextElement();
                        } while(!ifc.isUp());

                        log.trace("Testing interface: " + ifc.getDisplayName());
                        if (ifc.getIndex() >= lowest && result != null) {
    
                            if (result != null) {
    
                                continue;
                            }
                            break;
                        }

                        lowest = ifc.getIndex();
                        break;
                    }
                } while(this.ignoreInterface(ifc.getDisplayName()));

                Enumeration addrs = ifc.getInetAddresses();

                while(addrs.hasMoreElements()) {
    
                    InetAddress address = (InetAddress)addrs.nextElement();
                    if (address instanceof Inet4Address && !address.isLoopbackAddress() && !this.ignoreAddress(address)) {
    
                        log.trace("Found non-loopback interface: " + ifc.getDisplayName());
                        result = address;
                    }
                }
            }
        } catch (IOException var8) {
    
            log.error("Cannot get first non-loopback address", var8);
        }

        if (result != null) {
    
            return result;
        } else {
    
            try {
    
                return InetAddress.getLocalHost();
            } catch (UnknownHostException var7) {
    
                log.warn("Unable to retrieve localhost");
                return null;
            }
        }
    }

在这段代码中,先去获取了网卡接口,再遍历并且依次判断当前地址是否是开启的,然后判断了当前网卡接口是不是以及被配置了可忽略,然后获取到地址之后判断address instanceof Inet4Address && !address.isLoopbackAddress() && !this.ignoreAddress(address),如果符合条件赋值给result,后续判断如果result不为null,则直接返回,如果为空,再去InetAddress.getLocalHost()

因此结合以上分析可得出优化点:

  • 可以尝试像Spring源码中写的一样,先去获取网卡接口,实在没有获取到再去获取InetAddress.getLocalHost(),即上下部分调换位置,避免直接获取到地址却是错误或者没有开启的地址。
  • 因为在Spring源码中看到如果是可忽略的地址,那么就可以被过滤掉。因此可以考虑结合实际配置过滤规则。而在xxl-job的IpUtils中可以设置自己想过滤或者想要的hostname,比如hostname中包含register字段的。

在Spring Cloud配置时可以选择忽略指定网卡:(此忽略代码添加到bootstrap.properties中)

# 忽略eth0, 支持正则表达式
spring.cloud.inetutils.gnored-interfaces[0]=eth0 

配置Hosts:
当在获取InetAddress.getLocalHost()时,会返回当前主机的hostname,然后会根据hostname解析出对应的IP。因此就需要配置本机的hostname和/etc/hosts文件,直接将本机的主机名映射到有效的IP地址。

服务启动时也可以指定IP:

java -jar -Dspring.cloud.inetutils.preferred-networks=xxx.xxx.xxx.xxx

借鉴此贴 https://blog.csdn.net/dickysun1987/article/details/83414147

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_29824445/article/details/108622838

智能推荐

Android Studio数据永久保存——SharedPreferences_android studio安装的软件能一直保存-程序员宅基地

文章浏览阅读2k次。数据永久保存——SharedPreferences关于数据存储:关于SharenPreferences外部类访问SharedPreferences的数据的操作:小结本文为学习类文档,通过学习B站up主longway777的视频,如有侵权,请联系博主进行删除关于数据存储:关于数据存储,从前的Android提供了四种不同的存储方式分别为:内部存储Internal file storage:A..._android studio安装的软件能一直保存

ZOJ 1610 Count the Colors (线段树区间更新)-程序员宅基地

文章浏览阅读793次。ZOJ 1610 Count the Colors (线段树区间更新)

AAudio与MMAP学习之:基本概念_aaudio mmap-程序员宅基地

文章浏览阅读1.3k次。内容转自:https://source.android.google.cn/devices/audio/aaudio 如有侵权,请告知删除。谢谢--------------------------------------------------------------------------------------------------------------------AAudio 是 Android 8.0 版本中引入的一种音频 API。Android 8.1 版本提供了增强功能,在与支持 ._aaudio mmap

Jupyter notebook中导入虚拟环境及其中的第三方库包_jupter可以使用keras虚拟环境里下载其他包-程序员宅基地

文章浏览阅读3.3k次。查找Jupyter里存在的环境一般只存在上面的python2或3,剩下的需要自己添加激活创建的虚拟环境conda activate xxx #你已经创建的虚拟环境的名字python -m ipykernel install --name xxx错误添加的虚拟环境可以去除jupyter kernelspec remove xxx在Jupyter notebook里找下..._jupter可以使用keras虚拟环境里下载其他包

故障诊断 | 一文解决,CNN-BiLSTM卷积神经网络-双向长短期记忆神经网络组合模型的故障诊断(Matlab)_cnn-bilstm模型故障诊断流程预测-程序员宅基地

文章浏览阅读701次。故障诊断 | 一文解决,CNN-BiLSTM卷积神经网络-双向长短期记忆神经网络组合模型的故障诊断(Matlab)_cnn-bilstm模型故障诊断流程预测

HelloWorld - 第一个PlayBook上的Widget应用_helloworld的playbook-程序员宅基地

文章浏览阅读2.3k次。用BlackBerry Web Plug-in for eclipse 2.5开发一个widget项目。 不带项目目录名进行压缩,为zip文件,比如HelloWorld.zip(里面只有index.html和config.xml) 从zip文件中去掉无关的目录,和文件,比如.project和.metadata,只保留必要的html/javascript文件和config.xml文件。 把HelloWorld.zip打包为Pl_helloworld的playbook

随便推点

Django的X-Frame-Options设置_django x-frame-options-程序员宅基地

文章浏览阅读9.1k次,点赞11次,收藏22次。Django的X-Frame-Options设置1. 事件起因2. 有关X-Frame-Options2.1 什么是X-Frame-Options2.2 X-Frame-Options选项3.Django有关配置3.1 Django默认的配置3.2 Django总体配置3.3 指定的网页配置4. 参考内容1. 事件起因事件的起因是这样的,我在使用Django服务的时候,想在一个已经存在某个按钮..._django x-frame-options

盛大游戏面试题目小结-程序员宅基地

文章浏览阅读3.9k次。额,第一次面试,自己知识库还没有补充足,果然面完整个人都感觉不好了,这里会议起面试的问题,并贴出总结的答案,如果有哪里理解的不对,欢迎指正。1.线程安全的队列java提供线程安全的队列,主要分两种:阻塞队列(接口 BlockingQueue)和非阻塞队列(类ConcurrentLinkedQueue) 阻塞队列就是提供阻塞操作,当操作失败时阻塞线程(ReentrantLock

Visual Studio 2022版本 B站黑马程序员C++自学分享-第一阶段(主要包括:自己敲的代码、通过注释来备注上自己对代码的理解)_c++ 黑马程序员有必要全敲一遍吗-程序员宅基地

文章浏览阅读1.1k次,点赞4次,收藏19次。黑马的课程里面分为了三大阶段一、第一阶段 C++基础语法入门 对C++有初步了解,能够有基础编程能力二、第二阶段 C++核心编程 介绍C++面向对象编程,为大型项目做铺垫三、第三阶段 C++提高编程 介绍C++泛型编程思想,以及STL的基本使用※※※我会一个阶段发一个文章,一共发布三个文章来把学习的代码还有自己在代码旁边标注的对于这些代码的理解和心得体会一起分享给大家!!!_c++ 黑马程序员有必要全敲一遍吗

怎么让终端中的Vim编辑器显示行号 for Mac OS X_mac vim默认显示行号-程序员宅基地

文章浏览阅读3.4k次。shell里面运行1echo "se nu" >> ~/.vimrc_mac vim默认显示行号

OpenGL: OpenGL渲染流程_learnopengl渲染流程图-程序员宅基地

文章浏览阅读5.7k次。物体坐标系:物体位于坐标系原点位置。视点坐标系:视点位于坐标系原点位置。 模型视图变换物体坐标系 ------------- 视点坐标系最近不怎么看ogl了,但是总有一些东西是需要记忆的:add 2012/12/11 - dizuo / peteryfren裁剪在投影时进行,透视投影的裁剪区域为棱锥,正交投影的裁剪区域为长方体。标准坐标系 可以直接进行视口变换,将x,_learnopengl渲染流程图

Part 4 App布局优化_android 布局优化 sm-程序员宅基地

文章浏览阅读285次。Part 4 App布局优化化一 Android绘制原理及工具选择1、绘制原理硬件CPU负责计算显示内容(视图的创建,布局计算,图片解码,文本绘制等)GPU负责棚格化(UI元素绘制到屏幕上,也就是将一些组件,如button,bitmap拆分成不同的像素进行显示,然后完成绘制,比较耗时)原则16ms发出VSync信号触发UI渲染大多数的Android设备屏幕刷新帧率:60Hz..._android 布局优化 sm