SpringCloud

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

2019-07-25  本文已影响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;
}
上一篇下一篇

猜你喜欢

热点阅读