美文网首页SpringCloud
SpringCloud学习笔记(四)-InstanceId的生成

SpringCloud学习笔记(四)-InstanceId的生成

作者: 那些年搬过的砖 | 来源:发表于2019-07-25 11:03 被阅读0次

    Eureka Client启动时,会根据application.yml属性信息初始化配置。配置入口可查看EurekaClientAutoConfiguration类。如果指定了eureka.instance.instance-id,就使用指定的参数作为InstanceId,如果没有指定,会调用IdUtils这个工具类生成缺省的InstanceId。另外如果配置了eureka.instance.prefer-ip-address,那么客户端注册到注册中心时将决定是否采用ip来注册, 如果为true将用eureka.instance.ip-address指定的IP地址注册。

    eureka.instance后跟参数的写法实际是比较灵活的,spring会容错多种形式,例如:
    0 = "preferIpAddress"
    1 = "prefer_ip_address"
    2 = "prefer-ip-address"
    3 = "preferipaddress"
    4 = "PREFERIPADDRESS"
    5 = "PREFER_IP_ADDRESS"
    6 = "PREFER-IP-ADDRESS"

    EurekaClient的注册过程可以看另一篇SpringCloud学习笔记(一)-EurekaClient注册过程

    入口EurekaClientAutoConfiguration类路径
    spring-cloud-netflix-eureka-client-xx.jar/org.springframework.cloud.netflix.eureka/EurekaClientAutoConfiguration

    public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils,
                                                                 ManagementMetadataProvider managementMetadataProvider) throws MalformedURLException {
            PropertyResolver eurekaPropertyResolver = new RelaxedPropertyResolver(this.env, "eureka.instance.");
            String hostname = eurekaPropertyResolver.getProperty("hostname");
            //对应eureka.instance.prefer-ip-address配置
            boolean preferIpAddress = Boolean.parseBoolean(eurekaPropertyResolver.getProperty("preferIpAddress"));
            boolean isSecurePortEnabled = Boolean.parseBoolean(eurekaPropertyResolver.getProperty("securePortEnabled"));
            String serverContextPath = propertyResolver.getProperty("server.contextPath", "/");
            int serverPort = Integer.valueOf(propertyResolver.getProperty("server.port", propertyResolver.getProperty("port", "8080")));
    
            Integer managementPort = propertyResolver.getProperty("management.port", Integer.class);// nullable. should be wrapped into optional
            String managementContextPath = propertyResolver.getProperty("management.contextPath");// nullable. should be wrapped into optional
            Integer jmxPort = propertyResolver.getProperty("com.sun.management.jmxremote.port", Integer.class);//nullable
            EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils);
    
            instance.setNonSecurePort(serverPort);
            //设置缺省的InstanceId
            instance.setInstanceId(getDefaultInstanceId(propertyResolver));
            instance.setPreferIpAddress(preferIpAddress);
    
            if(isSecurePortEnabled) {
                instance.setSecurePort(serverPort);
            }
    
            if (StringUtils.hasText(hostname)) {
                instance.setHostname(hostname);
            }
            String statusPageUrlPath = eurekaPropertyResolver.getProperty("statusPageUrlPath");
            String healthCheckUrlPath = eurekaPropertyResolver.getProperty("healthCheckUrlPath");
    
            if (StringUtils.hasText(statusPageUrlPath)) {
                instance.setStatusPageUrlPath(statusPageUrlPath);
            }
            if (StringUtils.hasText(healthCheckUrlPath)) {
                instance.setHealthCheckUrlPath(healthCheckUrlPath);
            }
    
            ManagementMetadata metadata = managementMetadataProvider.get(instance, serverPort,
                    serverContextPath, managementContextPath, managementPort);
    
            if(metadata != null) {
                instance.setStatusPageUrl(metadata.getStatusPageUrl());
                instance.setHealthCheckUrl(metadata.getHealthCheckUrl());
                Map<String, String> metadataMap = instance.getMetadataMap();
                if (metadataMap.get("management.port") == null) {
                    metadataMap.put("management.port", String.valueOf(metadata.getManagementPort()));
                }
            }
    
            setupJmxPort(instance, jmxPort);
            return instance;
    }
    

    可以看到缺省InsanceId的生成是调用IdUtils这个工具类
    类路径
    spring-cloud-commons-1.3.0.RELAEASE.jar/org.springframework.cloud.commons.util.IdUtils

    public static String getDefaultInstanceId(PropertyResolver resolver) {
            RelaxedPropertyResolver relaxed = new RelaxedPropertyResolver(resolver);
            String vcapInstanceId = relaxed.getProperty("vcap.application.instance_id");
            if (StringUtils.hasText(vcapInstanceId)) {
                return vcapInstanceId;
            }
    
            String hostname = relaxed.getProperty("spring.cloud.client.hostname");
            String appName = relaxed.getProperty("spring.application.name");
    
            String namePart = combineParts(hostname, SEPARATOR, appName);
            //如果没有配置spring.application.instance_id,indexPart默认为server.port
            String indexPart = relaxed.getProperty("spring.application.instance_id",
                    relaxed.getProperty("server.port"));
    
            return combineParts(namePart, SEPARATOR, indexPart);
    }
    

    从源码可以看出,这个方法作用主要是根据hostname、appName、端口等信息拼装一个缺省的instanceId。如果只配置了eureka.instance.prefer-ip-address,而没有配置spring.application.instance_id,那么instanceId依然显示hostname,但是通过ip可以访问。

    最后返回结果


    由此可见,instanceId的默认值是
    ${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}
    根据个人对源码的理解,以及实际测试,如果配置了spring.application.instance_id,会取代server.port,并不是拼接的方式(也可能是本人对源码理解还不够全面,如果有错误之处,还请不吝指教)。在eureka监控页面可以看到instanceId。

    那么spring.cloud.client.hostname又是如何获取的呢?HostInfoEnvironmentPostProcessor类中可以看到对hostName和ipAddress的设置。

    public void postProcessEnvironment(ConfigurableEnvironment environment,
                SpringApplication application) {
            InetUtils.HostInfo hostInfo = getFirstNonLoopbackHostInfo(environment);
            LinkedHashMap<String, Object> map = new LinkedHashMap<>();
            map.put("spring.cloud.client.hostname", hostInfo.getHostname());
            map.put("spring.cloud.client.ipAddress", hostInfo.getIpAddress());
            MapPropertySource propertySource = new MapPropertySource(
                    "springCloudClientHostInfo", map);
            environment.getPropertySources().addLast(propertySource);
    }
    

    以上代码中根据InetUtils工具类提供的方法查找第一个非回送主机信息
    类路径:
    spring-cloud-commons-1.3.0.RELAEASE.jar/org.springframework.cloud.commons.util.InetUtils

    public HostInfo findFirstNonLoopbackHostInfo() {
            //查找合适的网络地址信息
            InetAddress address = findFirstNonLoopbackAddress();
            if (address != null) {
                //组装成HostInfo对象,主要是主机名和IP地址
                return convertAddress(address);
            }
            //获取的网络信息为空就用默认的
            HostInfo hostInfo = new HostInfo();
            hostInfo.setHostname(this.properties.getDefaultHostname());
            hostInfo.setIpAddress(this.properties.getDefaultIpAddress());
            return hostInfo;
    }
    

    接着看findFirstNonLoopbackAddress是如何查找合适的网络接口地址的,包含了对多网卡情况下的网络地址的选择。

    //根据InetUtils工具类提供的方法查找第一个非回送地址
    public InetAddress findFirstNonLoopbackAddress() {
            InetAddress result = null;
            try {
                int lowest = Integer.MAX_VALUE;
                //遍历所有网络接口
                for (Enumeration<NetworkInterface> nics = NetworkInterface
                        .getNetworkInterfaces(); nics.hasMoreElements();) {
                    NetworkInterface ifc = nics.nextElement();
                    //判断网络接口是否已启动并正在运行
                    if (ifc.isUp()) {
                        log.trace("Testing interface: " + ifc.getDisplayName());
                        //获取该网络接口的索引
                        if (ifc.getIndex() < lowest || result == null) {
                            lowest = ifc.getIndex();
                        }
                        else if (result != null) {
                            continue;
                        }
    
                        // @formatter:off
                        //判断该网络接口是否是被忽略的
                        if (!ignoreInterface(ifc.getDisplayName())) {
                            //获取绑定到此网络接口的InetAddress集合
                            for (Enumeration<InetAddress> addrs = ifc
                                    .getInetAddresses(); addrs.hasMoreElements();) {
                                InetAddress address = addrs.nextElement();
                                //判断是否是IPV4,并且不是回送地址,并且不是被忽略的地址
                                if (address instanceof Inet4Address
                                        && !address.isLoopbackAddress()
                                        && !ignoreAddress(address)) {
                                    log.trace("Found non-loopback interface: "
                                            + ifc.getDisplayName());
                                    result = address;
                                }
                            }
                        }
                        // @formatter:on
                    }
                }
            }
            catch (IOException ex) {
                log.error("Cannot get first non-loopback address", ex);
            }
    
            if (result != null) {
                return result;
            }
    
            try {
                //如果没有找到合适的网络接口,使用JDK自带的getLocalHost返回本机地址,
                //也就是本机配置的hostname及/etc/hosts配置的映射地址
                return InetAddress.getLocalHost();
            }
            catch (UnknownHostException e) {
                log.warn("Unable to retrieve localhost");
            }
    
            return null;
    }
    

    相关文章

      网友评论

        本文标题:SpringCloud学习笔记(四)-InstanceId的生成

        本文链接:https://www.haomeiwen.com/subject/yaqcrctx.html