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

简介: 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 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可以访问。


image.png

最后返回结果


image.png


由此可见,instanceId的默认值是

${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}


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


image.png


那么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;
}


相关文章
|
7月前
|
消息中间件 NoSQL Java
Spring Cloud项目实战Spring Cloud视频教程 含源码
Spring Cloud项目实战Spring Cloud视频教程 含源码
107 1
|
17天前
|
负载均衡 Java 开发者
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
59 5
|
2月前
|
负载均衡 Java API
【Spring Cloud生态】Spring Cloud Gateway基本配置
【Spring Cloud生态】Spring Cloud Gateway基本配置
47 0
|
4月前
|
Java Spring
【Azure Spring Cloud】Spring Cloud Azure 4.0 调用Key Vault遇见认证错误 AADSTS90002: Tenant not found.
【Azure Spring Cloud】Spring Cloud Azure 4.0 调用Key Vault遇见认证错误 AADSTS90002: Tenant not found.
|
4月前
|
Java Spring 容器
【Azure Spring Cloud】在Azure Spring Apps上看见 App Memory Usage 和 jvm.menory.use 的指标的疑问及OOM
【Azure Spring Cloud】在Azure Spring Apps上看见 App Memory Usage 和 jvm.menory.use 的指标的疑问及OOM
|
4月前
|
存储 Java Spring
【Azure Spring Cloud】Azure Spring Cloud服务,如何获取应用程序日志文件呢?
【Azure Spring Cloud】Azure Spring Cloud服务,如何获取应用程序日志文件呢?
|
4月前
|
SQL Java 数据库连接
【Azure Spring Cloud】Azure Spring Cloud connect to SQL using MSI
【Azure Spring Cloud】Azure Spring Cloud connect to SQL using MSI
|
4月前
|
Java 开发工具 Spring
【Azure Spring Cloud】使用azure-spring-boot-starter-storage来上传文件报错: java.net.UnknownHostException: xxxxxxxx.blob.core.windows.net: Name or service not known
【Azure Spring Cloud】使用azure-spring-boot-starter-storage来上传文件报错: java.net.UnknownHostException: xxxxxxxx.blob.core.windows.net: Name or service not known
|
4月前
|
NoSQL Java Redis
【Azure Spring Cloud】Java Spring Cloud 应用部署到Azure上后,发现大量的 java.lang.NullPointerException: null at io.lettuce.core.protocol.CommandHandler.writeSingleCommand(CommandHandler.java:426) at ... 异常
【Azure Spring Cloud】Java Spring Cloud 应用部署到Azure上后,发现大量的 java.lang.NullPointerException: null at io.lettuce.core.protocol.CommandHandler.writeSingleCommand(CommandHandler.java:426) at ... 异常
|
4月前
|
Java Spring
【Azure 应用服务】记一次Azure Spring Cloud 的部署错误 (az spring-cloud app deploy -g dev -s testdemo -n demo -p ./hellospring-0.0.1-SNAPSHOT.jar --->>> Failed to wait for deployment instances to be ready)
【Azure 应用服务】记一次Azure Spring Cloud 的部署错误 (az spring-cloud app deploy -g dev -s testdemo -n demo -p ./hellospring-0.0.1-SNAPSHOT.jar --->>> Failed to wait for deployment instances to be ready)