EUREKA 整理的部分源码

无服务注册中心

接口调用者通过nginx或ipvs等中间件等调用接口服务,并对接口服务集群做负载均衡策略,若针对服务内部之间的调用,若接口实例下线,nginx等并不能感知到,故针对于这样的调用方式,在服务之间并不合理。

无服务注册中心存在的问题

  1. 接口服务实例下线,nginx并不能感知服务已经下线。需要感知服务的健康情况
  2. 接口调用方,大规模迁移,修改nginx等配置文件相对繁琐。增删服务时,注册中心需要明确的感知到。
  3. 接口系统服务器不固定,随时可能增删机器。
  4. 接口调用方无法知晓服务具体的ip和port地址。(除非手工调整接口调用者的代码)。

Eureka的作用

流程说明

  • 服务提供者启动时:定时向EurekaServer注册自己的服务信息(服务名、IP、端口…等的)
  • 服务消费者启动时:后台定时拉去Eureka-Server中存储的服务信息。
    EUREKA 整理的部分源码_第1张图片

如何集成Eureka

启动服务端

  • springCloud集成式的方法,程序入口添加注解:@EnableEurekaServer

客户端集成

  • 引入SpringCloud中Eureka相关依赖

  • 增加SpringCloud相关配置

  • 通过@EnableDiscoveryClient注解,开启服务注册与发现功能

    查看zone的概念
    查看Eureka的自我保护机制

Eureka核心知识

  • 启动时服务如何注册到Eureka的?
  • 服务端如果保存这些信息?
  • 消费者如何根据服务名称发现服务实例?
  • 如何构建可用的eureka集群?
  • 心跳和服务剔除机制是什么?
  • Eureka自我保护模式是什么?

启动时通过后台任务,注册到EurekaServer,内容包含有:服务器名、ip、端口

eureka.instance.instanted 实例唯一ID
eureka.client.serviceUrl Eureka客户端的地址
eureka.client.registerWithEureka 是否注册到eureka上
eureka.client.fetchRegistry 是否拉去服务

EurekaClientAutoConfiguration

  • 定位1
		@org.springframework.cloud.context.config.annotation.RefreshScope
        @Lazy  /// 返回的是一个EurekaClient  说明就是在此处构建了一个EurekaClient  且是以懒加载的形式构建的
        public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config, EurekaInstanceConfig instance, @Autowired(required = false) HealthCheckHandler healthCheckHandler) {
        	// eurekaApplicationInfoManager方法 返回的ApplicationInfoManager 作为参数传到改方法中 还将EurekaInstanceConfig 作为参数加载了进来 
            ApplicationInfoManager appManager;
            if (AopUtils.isAopProxy(manager)) {
                appManager = (ApplicationInfoManager)ProxyUtils.getTargetObject(manager);
            } else {
                appManager = manager;
            }
			// 定位2 创建 CloudEurekaClient 方法
            CloudEurekaClient cloudEurekaClient = new CloudEurekaClient(appManager, config, this.optionalArgs, this.context);
            cloudEurekaClient.registerHealthCheck(healthCheckHandler);
            return cloudEurekaClient;
        }
        // 下面这个方法加载的结果是为上个方法提供的
 		@Bean
        @ConditionalOnMissingBean(
            value = {ApplicationInfoManager.class},
            search = SearchStrategy.CURRENT
        )
        @org.springframework.cloud.context.config.annotation.RefreshScope
        @Lazy
        public ApplicationInfoManager eurekaApplicationInfoManager(EurekaInstanceConfig config) {
            InstanceInfo instanceInfo = (new InstanceInfoFactory()).create(config); // 创建了一个Instance对象  在这边加载和读取所有的Instance配置 如果我们没有配置,他将会默认的创建一个Instance配置
            return new ApplicationInfoManager(config, instanceInfo); // 将对象放入ApplicationInfoManager中
        }
  • 查看create中具体做了什么
public InstanceInfo create(EurekaInstanceConfig config) {
        Builder leaseInfoBuilder = Builder.newBuilder().setRenewalIntervalInSecs(config.getLeaseRenewalIntervalInSeconds()).setDurationInSecs(config.getLeaseExpirationDurationInSeconds());
        com.netflix.appinfo.InstanceInfo.Builder builder = com.netflix.appinfo.InstanceInfo.Builder.newBuilder();
        String namespace = config.getNamespace();
        if (!namespace.endsWith(".")) {
            namespace = namespace + ".";
        }
        
		// 在这边做了一堆的builder 将config这个配置放入其中
        builder.setNamespace(namespace).setAppName(config.getAppname()).setInstanceId(config.getInstanceId()).setAppGroupName(config.getAppGroupName()).setDataCenterInfo(config.getDataCenterInfo()).setIPAddr(config.getIpAddress()).setHostName(config.getHostName(false)).setPort(config.getNonSecurePort()).enablePort(PortType.UNSECURE, config.isNonSecurePortEnabled()).setSecurePort(config.getSecurePort()).enablePort(PortType.SECURE, config.getSecurePortEnabled()).setVIPAddress(config.getVirtualHostName()).setSecureVIPAddress(config.getSecureVirtualHostName()).setHomePageUrl(config.getHomePageUrlPath(), config.getHomePageUrl()).setStatusPageUrl(config.getStatusPageUrlPath(), config.getStatusPageUrl()).setHealthCheckUrls(config.getHealthCheckUrlPath(), config.getHealthCheckUrl(), config.getSecureHealthCheckUrl()).setASGName(config.getASGName());
        if (!config.isInstanceEnabledOnit()) {
            InstanceStatus initialStatus = InstanceStatus.STARTING;
            if (log.isInfoEnabled()) {
                log.info("Setting initial instance status as: " + initialStatus);
            }

            builder.setStatus(initialStatus);
        } else if (log.isInfoEnabled()) {
            log.info("Setting initial instance status as: " + InstanceStatus.UP + ". This may be too early for the instance to advertise itself as available. You would instead want to control this via a healthcheck handler.");
        }

        Iterator var9 = config.getMetadataMap().entrySet().iterator();

        while(var9.hasNext()) {
            Entry<String, String> mapEntry = (Entry)var9.next();
            String key = (String)mapEntry.getKey();
            String value = (String)mapEntry.getValue();
            if (value != null && !value.isEmpty()) {
                builder.add(key, value);
            }
        }

        InstanceInfo instanceInfo = builder.build();
        instanceInfo.setLeaseInfo(leaseInfoBuilder.build());
        return instanceInfo;
    }
  • 识别config配置
public interface EurekaInstanceConfig { //EurekaInstanceConfig 是一个接口,找到其实现类:
}
	.....// 省略代码 
  • EurekaInstanceConfig的实现类 EurekaInstanceConfigBean
@ConfigurationProperties("eureka.instance") // 装载以eureka.instance 开头的配置
public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig, EnvironmentAware {
    private static final String UNKNOWN = "unknown";
    private HostInfo hostInfo;
	......// 省略代码
    public String getHostname() {
        return this.getHostName(false);
    }
    ......// 省略代码
}
  • 定位2 创建 CloudEurekaClient 方法
    public CloudEurekaClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs<?> args, ApplicationEventPublisher publisher) {
    	// 定位3 追中父类信息
        super(applicationInfoManager, config, args); // 调用了一个父类的构造方法 传入的参数是我们的配置applicationInfoManager 同时将我们的实例信息也传了过来
        this.cacheRefreshedCount = new AtomicLong(0L);
        this.eurekaHttpClient = new AtomicReference();
        this.applicationInfoManager = applicationInfoManager;
        this.publisher = publisher;
        this.eurekaTransportField = ReflectionUtils.findField(DiscoveryClient.class, "eurekaTransport");
        ReflectionUtils.makeAccessible(this.eurekaTransportField);
    }
  • 定位3 追中父类信息
    public DiscoveryClient(ApplicationInfoManager applicationInfoManager, final EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args) {
        this(applicationInfoManager, config, args, new Provider<BackupRegistry>() {
            private volatile BackupRegistry backupRegistryInstance;
            // 代码都在装载配置 
            // 定位4 何时将值set进配置中 
            public synchronized BackupRegistry get() {
                if (this.backupRegistryInstance == null) {
                    String backupRegistryClassName = config.getBackupRegistryImpl();
                    if (null != backupRegistryClassName) {
                        try {
                            this.backupRegistryInstance = (BackupRegistry)Class.forName(backupRegistryClassName).newInstance();
                            DiscoveryClient.logger.info("Enabled backup registry of type {}", this.backupRegistryInstance.getClass());
                        } catch (InstantiationException var3) {
                            DiscoveryClient.logger.error("Error instantiating BackupRegistry.", var3);
                        } catch (IllegalAccessException var4) {
                            DiscoveryClient.logger.error("Error instantiating BackupRegistry.", var4);
                        } catch (ClassNotFoundException var5) {
                            DiscoveryClient.logger.error("Error instantiating BackupRegistry.", var5);
                        }
                    }

                    if (this.backupRegistryInstance == null) {
                        DiscoveryClient.logger.warn("Using default backup registry implementation which does not do anything.");
                        this.backupRegistryInstance = new NotImplementedRegistryImpl();
                    }
                }

                return this.backupRegistryInstance;
            }
        });
    }
     
 	@Inject  // 查询改注解的作用
 	// 构造方法中各种各样的参数都是由配置好的IOC容器提供的  Client的一些配置参数和必要的东西都是在这个里面获取到的
    DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, Provider<BackupRegistry> backupRegistryProvider) {
        this.RECONCILE_HASH_CODES_MISMATCH = Monitors.newCounter("DiscoveryClient_ReconcileHashCodeMismatch");
        
        ......// todo 省略代码
        
        this.remoteRegionsRef = new AtomicReference(this.remoteRegionsToFetch.get() == null ? null : ((String)this.remoteRegionsToFetch.get()).split(","));
        
        // 定位5 在这里开始做相应的判断
        if (config.shouldFetchRegistry()) {
            this.registryStalenessMonitor = new ThresholdLevelsMetric(this, "eurekaClient.registry.lastUpdateSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
        } else {
            this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
        }

        if (config.shouldRegisterWithEureka()) {
            this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, "eurekaClient.registration.lastHeartbeatSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
        } else {
            this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
        }
		......// todo 省略代码
        logger.info("Initializing Eureka in region {}", this.clientConfig.getRegion());
        if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
            logger.info("Client configured to neither register nor query for data.");
            
            logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}", this.initTimestampMs, this.getApplications().size());
        } else {
            try {
            	// 定位6 出现了线程池 newScheduledThreadPool 在固定的时间执行一次  定时任务线程池
                this.scheduler = Executors.newScheduledThreadPool(2, (new ThreadFactoryBuilder()).setNameFormat("DiscoveryClient-%d").setDaemon(true).build());
                
                // heartbeatExecutor heartbeat心跳  心跳线程池
                this.heartbeatExecutor = new ThreadPoolExecutor(1, this.clientConfig.getHeartbeatExecutorThreadPoolSize(), 0L, TimeUnit.SECONDS, new SynchronousQueue(), (new ThreadFactoryBuilder()).setNameFormat("DiscoveryClient-HeartbeatExecutor-%d").setDaemon(true).build());
                
				//cacheRefreshExecutor  刷新缓存的定时任务
                this.cacheRefreshExecutor = new ThreadPoolExecutor(1, this.clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0L, TimeUnit.SECONDS, new SynchronousQueue(), (new ThreadFactoryBuilder()).setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d").setDaemon(true).build());
            ......// todo 省略代码
            
            // 定位7 在此处初始化所有的定时任务			
            this.initScheduledTasks();

            try {
                Monitors.registerObject(this);
            } catch (Throwable var7) {
                logger.warn("Cannot register timers", var7);
            }

            DiscoveryManager.getInstance().setDiscoveryClient(this);
            DiscoveryManager.getInstance().setEurekaClientConfig(config);
            this.initTimestampMs = System.currentTimeMillis();
            logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}", this.initTimestampMs, this.getApplications().size());
        }
    }

  • 定位7 在此处初始化所有的定时任务
    private void initScheduledTasks() { // 在这里将线程一一启动,但是并未发现服务的注册流程 思考:什么时候开始注册服务,服务在什么情境下可以被注册进去  ps:伴随着心跳检测机制  定位8 心跳检测线程
        int renewalIntervalInSecs;
        int expBackOffBound;
        if (this.clientConfig.shouldFetchRegistry()) {
            renewalIntervalInSecs = this.clientConfig.getRegistryFetchIntervalSeconds();
            expBackOffBound = this.clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
            this.scheduler.schedule(new TimedSupervisorTask("cacheRefresh", this.scheduler, this.cacheRefreshExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.CacheRefreshThread()), (long)renewalIntervalInSecs, TimeUnit.SECONDS);
        }

        if (this.clientConfig.shouldRegisterWithEureka()) {
            renewalIntervalInSecs = this.instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
            expBackOffBound = this.clientConfig.getHeartbeatExecutorExponentialBackOffBound();
            logger.info("Starting heartbeat executor: renew interval is: {}", renewalIntervalInSecs);
          
          	// 定位8 心跳检测线程 new DiscoveryClient.HeartbeatThread()) 查看线程创建过程
            this.scheduler.schedule(new TimedSupervisorTask("heartbeat", this.scheduler, this.heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.HeartbeatThread()), (long)renewalIntervalInSecs, TimeUnit.SECONDS);
            this.instanceInfoReplicator = new InstanceInfoReplicator(this, this.instanceInfo, this.clientConfig.getInstanceInfoReplicationIntervalSeconds(), 2);
            this.statusChangeListener = new StatusChangeListener() {
                public String getId() {
                    return "statusChangeListener";
                }

                public void notify(StatusChangeEvent statusChangeEvent) {
                    if (InstanceStatus.DOWN != statusChangeEvent.getStatus() && InstanceStatus.DOWN != statusChangeEvent.getPreviousStatus()) {
                        DiscoveryClient.logger.info("Saw local status change event {}", statusChangeEvent);
                    } else {
                        DiscoveryClient.logger.warn("Saw local status change event {}", statusChangeEvent);
                    }

                    DiscoveryClient.this.instanceInfoReplicator.onDemandUpdate();
                }
            };
            if (this.clientConfig.shouldOnDemandUpdateStatusChange()) {
                this.applicationInfoManager.registerStatusChangeListener(this.statusChangeListener);
            }

            this.instanceInfoReplicator.start(this.clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
        } else {
            logger.info("Not registering with Eureka server per configuration");
        }

    }


  • 定位 8 追踪心跳检测线程
 private class HeartbeatThread implements Runnable {
        private HeartbeatThread() {
        }

        public void run() {
            if (DiscoveryClient.this.renew()) { // 定位9 追中renew方法
                DiscoveryClient.this.lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
            }

        }
    }
  • 定位9 追踪renew方法
boolean renew() {
        try {
        	// 定位10  InstanceInfo(之前我们看到的InstanceInfo) EurekaHttpResponse 定义了statusCode
            EurekaHttpResponse<InstanceInfo> httpResponse = this.eurekaTransport.registrationClient.sendHeartBeat(this.instanceInfo.getAppName(), this.instanceInfo.getId(), this.instanceInfo, (InstanceStatus)null);
            logger.debug("DiscoveryClient_{} - Heartbeat status: {}", this.appPathIdentifier, httpResponse.getStatusCode());
            // 定位11 NOT_FOUND 404 未找打 执行注册
            if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
                this.REREGISTER_COUNTER.increment(); // 定位12 在此处对数字进行加加减减(暂时未弄清楚其作用)
                logger.info("DiscoveryClient_{} - Re-registering apps/{}", this.appPathIdentifier, this.instanceInfo.getAppName());
                long timestamp = this.instanceInfo.setIsDirtyWithTime();
                boolean success = this.register(); // 定位13 在此处进行注册
                if (success) {
                    this.instanceInfo.unsetIsDirty(timestamp);
                }

                return success;
            } else {
                return httpResponse.getStatusCode() == Status.OK.getStatusCode();
            }
        } catch (Throwable var5) {
            logger.error("DiscoveryClient_{} - was unable to send heartbeat!", this.appPathIdentifier, var5);
            return false;
        }
    }
  • 说明

    在Client初始化时,或初始化一系列定时任务,这些定时任务中包含了心跳的定时任务,心跳定时任务启动后会向服务中心发起一次请求,判断当前的服务实例有没有被注册到Eureka上面,如果没有注册,将返回一个404回来,返回404之后,就会进入到我的注册中来;如果返回的不是404,则会判断当前的服务实例是否正常

  • 以上就是我们所关心的,, Eureka是如何将服务注册上的(此处解决了第一个问题:启动时服务如何注册到Eureka的)

ps:暂时不看server端代码

EurekaServerAutoConfiguration

  • 定位1
@Configuration
@Import({EurekaServerInitializerConfiguration.class})
@ConditionalOnBean({Marker.class})
@EnableConfigurationProperties({EurekaDashboardProperties.class, InstanceRegistryProperties.class})
@PropertySource({"classpath:/eureka/server.properties"})
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter { 
    private static final String[] EUREKA_PACKAGES = new String[]{"com.netflix.discovery", "com.netflix.eureka"};
    @Autowired
    private ApplicationInfoManager applicationInfoManager;
    @Autowired
    private EurekaServerConfig eurekaServerConfig; // 在此处读取配置信息 在其下面都有详细加载读取config
    @Autowired
    private EurekaClientConfig eurekaClientConfig;
    @Autowired
    private EurekaClient eurekaClient;
    @Autowired
    private InstanceRegistryProperties instanceRegistryProperties;
    public static final CloudJacksonJson JACKSON_JSON = new CloudJacksonJson();
    ...//省略代码
    private static CodecWrapper getFullJson(EurekaServerConfig serverConfig) {
        CodecWrapper codec = CodecWrappers.getCodec(serverConfig.getJsonCodecName());
        return codec == null ? CodecWrappers.getCodec(JACKSON_JSON.codecName()) : codec;
    }
    ...// 省略代码
}
  • EurekaServerConfig的实现类EurekaServerConfigBean
@ConfigurationProperties("eureka.server")  
public class EurekaServerConfigBean implements EurekaServerConfig {
    public static final String PREFIX = "eureka.server";  // 读取以eureka.server开头的属性 如果没有配置,则使用默认值
    private static final int MINUTES = 60000;
    @Autowired(
        required = false
    )
    PropertyResolver propertyResolver;
    private String aWSAccessId;
    private String aWSSecretKey;
    private int eIPBindRebindRetries = 3;
    private int eIPBindingRetryIntervalMs = 300000;
    private int eIPBindingRetryIntervalMsWhenUnbound = 60000;
    private boolean enableSelfPreservation = true;
    private double renewalPercentThreshold = 0.85D;
    private int renewalThresholdUpdateIntervalMs = 900000;
    private int peerEurekaNodesUpdateIntervalMs = 600000;
    private int numberOfReplicationRetries = 5;
    private int peerEurekaStatusRefreshTimeIntervalMs = 30000;
    private int waitTimeInMsWhenSyncEmpty = 300000;
    private int peerNodeConnectTimeoutMs = 200;
    private int peerNodeReadTimeoutMs = 200;
    ..... // 省略代码
    
    }

服务端是如何保存这些信息的

  • 定位13 调用register方法 在此处进行注册
    /**
     * Register with the eureka service by making the appropriate REST call.
     */
    boolean register() throws Throwable {
        logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
        EurekaHttpResponse<Void> httpResponse;
        try {
            httpResponse = eurekaTransport.registrationClient.register(instanceInfo);  // 在此发送http请求  对register方法的实现类上面打断点进行debug调试  定位14 进行调试
        } catch (Exception e) {
            logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e);
            throw e;
        }
        if (logger.isInfoEnabled()) {
            logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode());
        }
        return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode();
    }
  • 定位14 debug调试 找到 register 的实现类 AbstractJerseyEurekaHttpClient client将自己注册到server的时候,通过 Jersey 框架 发送一个http请求 在此处找到处理接收信息的地方
	// 重写 register  方法  
    @Override
    public EurekaHttpResponse<Void> register(InstanceInfo info) {
        String urlPath = "apps/" + info.getAppName(); // 在这边拼装一个 urlPath 地址 
        ClientResponse response = null;
        try {
            Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
            addExtraHeaders(resourceBuilder); // 拼装的 urlPath   通过JerseyClient 进行发送
            response = resourceBuilder		// 在此处拿到 response 
                    .header("Accept-Encoding", "gzip")
                    .type(MediaType.APPLICATION_JSON_TYPE)
                    .accept(MediaType.APPLICATION_JSON)
                    .post(ClientResponse.class, info);
            return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
        } finally {
            if (logger.isDebugEnabled()) {
                logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
                        response == null ? "N/A" : response.getStatus());
            }
            if (response != null) {
                response.close();
            }
        }
    }

  • 接着查看服务端的配置 在 EurekaServerAutoConfiguration 类中 找到 FilterRegistrationBean 方法 ,该方法 将 jersey filter 做成一个拦截器郊游spring容器管理 客户端发送的 Jersey 请求 将有 服务端做一次拦截处理
    @Bean
    public FilterRegistrationBean jerseyFilterRegistration(Application eurekaJerseyApp) {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new ServletContainer(eurekaJerseyApp));
        bean.setOrder(2147483647);
        bean.setUrlPatterns(Collections.singletonList("/eureka/*"));
        return bean;
    }
  • 通过日志记录的方式去找调用链路
// debug级别
logging.level.org.springframework.cloud.netflix = debug
logging.level.com.netflix = debug

截取部分日志信息

2019-05-31 11:43:10.044 DEBUG 11832 --- [io-10000-exec-3] o.s.c.n.eureka.server.InstanceRegistry   : renew HELLOSERVER serverId localhost:helloserver:8002, isReplication {}false  // 注册时信息
2019-05-31 11:43:10.044 DEBUG 11832 --- [io-10000-exec-3] c.n.e.registry.AbstractInstanceRegistry  : Fetching applications registry with remote regions: false, Regions argument []
2019-05-31 11:43:10.044 DEBUG 11832 --- [io-10000-exec-3] c.n.e.registry.AbstractInstanceRegistry  : Processing override status using rule: [com.netflix.eureka.registry.rule.DownOrStartingRule, com.netflix.eureka.registry.rule.OverrideExistsRule, com.netflix.eureka.registry.rule.LeaseExistsRule, com.netflix.eureka.registry.rule.AlwaysMatchInstanceStatusRule]
2019-05-31 11:43:10.044 DEBUG 11832 --- [io-10000-exec-3] c.n.e.registry.rule.LeaseExistsRule      : There is already an existing lease with status UP  for instance localhost:helloserver:8002
2019-05-31 11:43:10.044 DEBUG 11832 --- [io-10000-exec-3] c.n.eureka.resources.InstanceResource    : Found (Renew): HELLOSERVER - localhost:helloserver:8002; reply status=200
2019-05-31 11:43:10.547 DEBUG 11832 --- [et_localhost-16] c.n.d.shared.MonitoredConnectionManager  : Get connection: {}->http://localhost:10000, timeout = 200  // 返回的状态吗
2019-05-31 11:43:10.547 DEBUG 11832 --- [et_localhost-16] c.n.d.shared.NamedConnectionPool         : [{}->http://localhost:10000] total kept alive: 1, total issued: 0, total allocated: 1 out of 1000
2019-05-31 11:43:10.547 DEBUG 11832 --- [et_localhost-16] c.n.d.shared.NamedConnectionPool         : Getting free connection [{}->http://localhost:10000][null]
2019-05-31 11:43:10.549 DEBUG 11832 --- [io-10000-exec-9] c.n.d.util.DeserializerStringCache       : clearing global-level cache with size 0
2019-05-31 11:43:10.549 DEBUG 11832 --- [io-10000-exec-9] c.n.d.util.DeserializerStringCache       : clearing app-level serialization cache with size 0
2019-05-31 11:43:10.549 DEBUG 11832 --- [io-10000-exec-9] o.s.c.n.eureka.server.InstanceRegistry   : renew HELLOSERVER serverId localhost:helloserver:8002, isReplication {}true
2019-05-31 11:43:10.549 DEBUG 11832 --- [io-10000-exec-9] c.n.e.registry.AbstractInstanceRegistry  : Fetching applications registry with remote regions: false, Regions argument []
2019-05-31 11:43:10.549 DEBUG 11832 --- [io-10000-exec-9] c.n.e.registry.AbstractInstanceRegistry  : Processing override status using rule: [com.netflix.eureka.registry.rule.DownOrStartingRule, com.netflix.eureka.registry.rule.OverrideExistsRule, com.netflix.eureka.registry.rule.LeaseExistsRule, com.netflix.eureka.registry.rule.AlwaysMatchInstanceStatusRule]
2019-05-31 11:43:10.549 DEBUG 11832 --- [io-10000-exec-9] c.n.e.r.r.AlwaysMatchInstanceStatusRule  : Returning the default instance status UP for instance localhost:helloserver:8002
2019-05-31 11:43:10.549 DEBUG 11832 --- [io-10000-exec-9] c.n.eureka.resources.InstanceResource    : Found (Renew): HELLOSERVER - localhost:helloserver:8002; reply status=200
  • 通过日志信息我们可以猜测 接受请求的地方可能在 InstanceResource 中 ,InstanceResource 定义了相应的请求,同时将Response返回回去
@Produces({"application/xml", "application/json"})
public class InstanceResource {	
	@GET
    public Response getInstanceInfo() {
        InstanceInfo appInfo = registry
                .getInstanceByAppAndId(app.getName(), id); // 定位15 找 app 的 getName   app 指的是 ApplicationResource
        if (appInfo != null) {
            logger.debug("Found: {} - {}", app.getName(), id);
            return Response.ok(appInfo).build();
        } else {
            logger.debug("Not Found: {} - {}", app.getName(), id);
            return Response.status(Status.NOT_FOUND).build();
        }
    }

}
  • ApplicationResource 在此处帮助我们处理不同的请求,如 GET请求 ,但是这边并不是接受请求的地址
    @GET
    public Response getApplication(@PathParam("version") String version,
                                   @HeaderParam("Accept") final String acceptHeader,
                                   @HeaderParam(EurekaAccept.HTTP_X_EUREKA_ACCEPT) String eurekaAccept) {
        if (!registry.shouldAllowAccess(false)) {
            return Response.status(Status.FORBIDDEN).build();
        }

        EurekaMonitors.GET_APPLICATION.increment();

        CurrentRequestVersion.set(Version.toEnum(version));
        KeyType keyType = Key.KeyType.JSON;
        if (acceptHeader == null || !acceptHeader.contains("json")) {
            keyType = Key.KeyType.XML;
        }

        Key cacheKey = new Key(
                Key.EntityType.Application,
                appName,
                keyType,
                CurrentRequestVersion.get(),
                EurekaAccept.fromString(eurekaAccept)
        );

        String payLoad = responseCache.get(cacheKey);

        if (payLoad != null) {
            logger.debug("Found: {}", appName);
            return Response.ok(payLoad).build();
        } else {
            logger.debug("Not Found: {}", appName);
            return Response.status(Status.NOT_FOUND).build();
        }
    }

  • 查看 jersey 框架 ApplicationsResource 源码 上述的 ApplicationResource 的请求地址由此而来 如 GET请求
    @GET
    public Response getContainers(@PathParam("version") String version,
                                  @HeaderParam(HEADER_ACCEPT) String acceptHeader,
                                  @HeaderParam(HEADER_ACCEPT_ENCODING) String acceptEncoding,
                                  @HeaderParam(EurekaAccept.HTTP_X_EUREKA_ACCEPT) String eurekaAccept,
                                  @Context UriInfo uriInfo,
                                  @Nullable @QueryParam("regions") String regionsStr) {

        boolean isRemoteRegionRequested = null != regionsStr && !regionsStr.isEmpty();
        String[] regions = null;
        if (!isRemoteRegionRequested) {
            EurekaMonitors.GET_ALL.increment();
        } else {
            regions = regionsStr.toLowerCase().split(",");
            Arrays.sort(regions); // So we don't have different caches for same regions queried in different order.
            EurekaMonitors.GET_ALL_WITH_REMOTE_REGIONS.increment();
        }

        // Check if the server allows the access to the registry. The server can
        // restrict access if it is not
        // ready to serve traffic depending on various reasons.
        if (!registry.shouldAllowAccess(isRemoteRegionRequested)) {
            return Response.status(Status.FORBIDDEN).build();
        }
        CurrentRequestVersion.set(Version.toEnum(version));
        KeyType keyType = Key.KeyType.JSON;
        String returnMediaType = MediaType.APPLICATION_JSON;
        if (acceptHeader == null || !acceptHeader.contains(HEADER_JSON_VALUE)) {
            keyType = Key.KeyType.XML;
            returnMediaType = MediaType.APPLICATION_XML;
        }

        Key cacheKey = new Key(Key.EntityType.Application,
                ResponseCacheImpl.ALL_APPS,
                keyType, CurrentRequestVersion.get(), EurekaAccept.fromString(eurekaAccept), regions
        );

        Response response;
        if (acceptEncoding != null && acceptEncoding.contains(HEADER_GZIP_VALUE)) {
            response = Response.ok(responseCache.getGZIP(cacheKey))
                    .header(HEADER_CONTENT_ENCODING, HEADER_GZIP_VALUE)
                    .header(HEADER_CONTENT_TYPE, returnMediaType)
                    .build();
        } else {
            response = Response.ok(responseCache.get(cacheKey))
                    .build();
        }
        return response;
    }

  • appId 全部交由ApplicationResource 进行处理 将请求地址一次又一次的分发 例如接收到POST请求:
    @Path("{appId}")
    public ApplicationResource getApplicationResource(
            @PathParam("version") String version,
            @PathParam("appId") String appId) {
        CurrentRequestVersion.set(Version.toEnum(version));
        return new ApplicationResource(appId, serverConfig, registry);
    }
  • 接收到POST请求 最后会调用一次 register 方法
    @POST
    @Consumes({"application/json", "application/xml"})
    public Response addInstance(InstanceInfo info,
                                @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
        logger.debug("Registering instance {} (replication={})", info.getId(), isReplication);
        // validate that the instanceinfo contains all the necessary required fields
		//  省略代码

        registry.register(info, "true".equals(isReplication)); // 定位14 追踪 register 方法
        return Response.status(204).build();  // 204 to be backwards compatible
    }

  • 定位15 register 是一个接口,追踪其实现类 InstanceRegistry ,在 register 可以发现其调用了父类(PeerAwareInstanceRegistryImpl) 的 register 方法
	public void register(final InstanceInfo info, final boolean isReplication) {
        this.handleRegistration(info, this.resolveInstanceLeaseDuration(info), isReplication);
        super.register(info, isReplication); // 定位16
    }
  • 定位16 PeerAwareInstanceRegistryImpl 继承了 AbstractInstanceRegistry 类 实现了 PeerAwareInstanceRegistry 接口
    @Override
    public void register(final InstanceInfo info, final boolean isReplication) {
        int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
        if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
            leaseDuration = info.getLeaseInfo().getDurationInSecs();
        }
        super.register(info, leaseDuration, isReplication); // 定位17 在此又一次调用了父类的 register 方法
        replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication); // 高可用的同步机制方法
    }
  • 定位17 AbstractInstanceRegistry 实现了 InstanceRegistry 接口 (PS:源码抽空再写注解)
    /**
     * Registers a new instance with a given duration.
     *
     * @see com.netflix.eureka.lease.LeaseManager#register(java.lang.Object, int, boolean)
     */
    public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
        try {
            read.lock();  // 在此将 读 锁住 
            // AbstractInstanceRegistry 类中定义表的 registry  , registry 是一个 ConcurrentHashMap
            //    private final ConcurrentHashMap>> registry = new ConcurrentHashMap>>();
            Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName()); // 在此  通过一个registry 去获取一个Map
            REGISTER.increment(isReplication);
            if (gMap == null) {
                final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();  // 到了这里我们就应该明白,这个服务实例是通过一个 ConcurrentHashMap 来维护的
                gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
                if (gMap == null) {
                    gMap = gNewMap;
                }
            }
            Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
            // Retain the last dirty timestamp without overwriting it, if there is already a lease
            if (existingLease != null && (existingLease.getHolder() != null)) {
                Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
                Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
                logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);

                // this is a > instead of a >= because if the timestamps are equal, we still take the remote transmitted
                // InstanceInfo instead of the server local copy.
                if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
                    logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater" +
                            " than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
                    logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant");
                    registrant = existingLease.getHolder();
                }
            } else {
                // The lease does not exist and hence it is a new registration
                synchronized (lock) {
                    if (this.expectedNumberOfClientsSendingRenews > 0) {
                        // Since the client wants to register it, increase the number of clients sending renews
                        this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
                        updateRenewsPerMinThreshold();
                    }
                }
                logger.debug("No previous lease information found; it is new registration");
            }
            Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
            if (existingLease != null) {
                lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
            }
            gMap.put(registrant.getId(), lease);
            synchronized (recentRegisteredQueue) {
                recentRegisteredQueue.add(new Pair<Long, String>(
                        System.currentTimeMillis(),
                        registrant.getAppName() + "(" + registrant.getId() + ")"));
            }
            // This is where the initial state transfer of overridden status happens
            if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
                logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the "
                                + "overrides", registrant.getOverriddenStatus(), registrant.getId());
                if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {
                    logger.info("Not found overridden id {} and hence adding it", registrant.getId());
                    overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
                }
            }
            InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());
            if (overriddenStatusFromMap != null) {
                logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
                registrant.setOverriddenStatus(overriddenStatusFromMap);
            }

            // Set the status based on the overridden status rules
            InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);
            registrant.setStatusWithoutDirty(overriddenInstanceStatus);

            // If the lease is registered with UP status, set lease service up timestamp
            if (InstanceStatus.UP.equals(registrant.getStatus())) {
                lease.serviceUp();
            }
            registrant.setActionType(ActionType.ADDED);
            recentlyChangedQueue.add(new RecentlyChangedItem(lease));
            registrant.setLastUpdatedTimestamp();
            invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
            logger.info("Registered instance {}/{} with status {} (replication={})",
                    registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication);
        } finally {
            read.unlock();
        }
    }

服务消费者是如何拉取服务实例的

  • 我们拿到是的ConcurrentHashMap 里面的值 回到 DiscoveryClient 类中,我们可以发现 里面有一个内部类 CacheRefreshThread 定时刷新缓存 定期向服务端拉起服务信息 然后将其放入本地缓存中
    /**
     * The task that fetches the registry information at specified intervals.
     *
     */
    class CacheRefreshThread implements Runnable {
        public void run() {
            refreshRegistry();
        }
    }

高可用集群

  • 在eureka的高可用状态下,这些注册中心是对等的,他们会相互将注册在自己的实例同步到其他的注册中心上去 下面实例是高可用集群注册中心配置,为了减少代码量,通过 — 分割符将三份配置写到了一起

spring:
  profiles:
    active: dev
## 通过更改不停的 spring.profiles.active 属性 启动不通的客户端

---
spring:
  application:
    name: eureka-peer
  profiles: dev

server:
  port: 10000

eureka:
  instance:
    hostname: dev
    instance-id: dev
  client:
    fetch-registry: false
    register-with-eureka: false
    service-url:
#      defaultZone: http://localhost:10000/eureka/,http://localhost:10001/eureka/,http://localhost:10002/eureka/
      defaultZone: http://localhost:10000/eureka/
  server:
    wait-time-in-ms-when-sync-empty: 0
    enable-self-preservation: true
    peer-eureka-nodes-update-interval-ms: 10000





---
spring:
  profiles: dev1
  application:
    name: eureka-peer2
server:
  port: 10001


eureka:
  instance:
    hostname: dev1
    instance-id: dev1
  client:
    fetch-registry: false
    register-with-eureka: false
    service-url:
      defaultZone: http://localhost:10000/eureka/,http://localhost:10001/eureka/,http://localhost:10002/eureka/
  server:
    wait-time-in-ms-when-sync-empty: 0
    enable-self-preservation: true
    peer-eureka-nodes-update-interval-ms: 10000
---
spring:
  profiles: dev2
  application:
    name: eureka-peer3
server:
  port: 10002


eureka:
  instance:
    hostname: dev2
    instance-id: dev2
  client:
    fetch-registry: false
    register-with-eureka: false
    service-url:
      defaultZone: http://localhost:10000/eureka/,http://localhost:10001/eureka/,http://localhost:10002/eureka/
  server:
    wait-time-in-ms-when-sync-empty: 0
    enable-self-preservation: true
    peer-eureka-nodes-update-interval-ms: 10000

---
  • 查看 同步机制的原理 ,因为是在服务端进行同步的 故进入 EurekaServerAutoConfiguration 找相对应的bean EurekaServerContext
    @Bean
    public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs, PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {  // PeerAwareInstanceRegistry P2P形式的注册
    	// DefaultEurekaServerContext  是 EurekaServerContext 上线文的初始化
        return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs, registry, peerEurekaNodes, this.applicationInfoManager); // 进入 DefaultEurekaServerContext 里面去瞧一瞧
    }
  • DefaultEurekaServerContext 初始化方法
    @Inject
    public DefaultEurekaServerContext(EurekaServerConfig serverConfig,
                               ServerCodecs serverCodecs,
                               PeerAwareInstanceRegistry registry,
                               PeerEurekaNodes peerEurekaNodes,
                               ApplicationInfoManager applicationInfoManager) {
        this.serverConfig = serverConfig;
        this.serverCodecs = serverCodecs;
        this.registry = registry;
        this.peerEurekaNodes = peerEurekaNodes;
        this.applicationInfoManager = applicationInfoManager;
    }
    // 初始化
    @PostConstruct
    @Override
    public void initialize() {
        logger.info("Initializing ...");
        peerEurekaNodes.start();  // 定位1 
        try {
            registry.init(peerEurekaNodes);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        logger.info("Initialized");
    }
  • 定位1 进入start方法中看一下 发现其在start方法中又定义了一下线程池
    public void start() {
        taskExecutor = Executors.newSingleThreadScheduledExecutor(
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread(r, "Eureka-PeerNodesUpdater");
                        thread.setDaemon(true);
                        return thread;
                    }
                }
        );
        try {
            updatePeerEurekaNodes(resolvePeerUrls());
            Runnable peersUpdateTask = new Runnable() {
                @Override
                public void run() {
                    try {
                        updatePeerEurekaNodes(resolvePeerUrls()); // 定义了一个 updatePeerEurekaNodes 传入 resolvePeerUrls() 定位2  定位3
                    } catch (Throwable e) {
                        logger.error("Cannot update the replica Nodes", e);
                    }

                }
            };
            taskExecutor.scheduleWithFixedDelay(
                    peersUpdateTask,
                    serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
                    serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
                    TimeUnit.MILLISECONDS
            );
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
        for (PeerEurekaNode node : peerEurekaNodes) {
            logger.info("Replica node URL:  {}", node.getServiceUrl());
        }
    }
  • 定位2
    /**
     * Resolve peer URLs.
     *
     * @return peer URLs with node's own URL filtered out
     */
    protected List<String> resolvePeerUrls() {
        InstanceInfo myInfo = applicationInfoManager.getInfo(); // 获取InstanceInfo  
        String zone = InstanceInfo.getZone(clientConfig.getAvailabilityZones(clientConfig.getRegion()), myInfo); // 拿到我们定义的zone(配置文件配置的)
        // 同时将拿到的地址放入 list 集合中
        List<String> replicaUrls = EndpointUtils
                .getDiscoveryServiceUrls(clientConfig, zone, new EndpointUtils.InstanceInfoBasedUrlRandomizer(myInfo));
		
        int idx = 0;
        // 在此处进行了一波迭代 
        while (idx < replicaUrls.size()) {
            if (isThisMyUrl(replicaUrls.get(idx))) {
                replicaUrls.remove(idx);
            } else {
                idx++;
            }
        }
        return replicaUrls;
    }
  • 定位3 进入updatePeerEurekaNodes 中 干了一件事 维护新加入的实例,移除已经挂掉的实例
    /**
     * Given new set of replica URLs, destroy {@link PeerEurekaNode}s no longer available, and
     * create new ones.
     *
     * @param newPeerUrls peer node URLs; this collection should have local node's URL filtered out
     */
    protected void updatePeerEurekaNodes(List<String> newPeerUrls) {
        if (newPeerUrls.isEmpty()) {
            logger.warn("The replica size seems to be empty. Check the route 53 DNS Registry");
            return;
        }

        Set<String> toShutdown = new HashSet<>(peerEurekaNodeUrls);
        toShutdown.removeAll(newPeerUrls);
        Set<String> toAdd = new HashSet<>(newPeerUrls);
        toAdd.removeAll(peerEurekaNodeUrls);

        if (toShutdown.isEmpty() && toAdd.isEmpty()) { // No change
            return;
        }

        // Remove peers no long available
        List<PeerEurekaNode> newNodeList = new ArrayList<>(peerEurekaNodes);

        if (!toShutdown.isEmpty()) {
            logger.info("Removing no longer available peer nodes {}", toShutdown);
            int i = 0;
            while (i < newNodeList.size()) {
                PeerEurekaNode eurekaNode = newNodeList.get(i);
                if (toShutdown.contains(eurekaNode.getServiceUrl())) {
                    newNodeList.remove(i);
                    eurekaNode.shutDown();
                } else {
                    i++;
                }
            }
        }

        // Add new peers
        if (!toAdd.isEmpty()) {
            logger.info("Adding new peer nodes {}", toAdd);
            for (String peerUrl : toAdd) {
                newNodeList.add(createPeerEurekaNode(peerUrl));
            }
        }

        this.peerEurekaNodes = newNodeList;
        this.peerEurekaNodeUrls = new HashSet<>(newPeerUrls);
    }
  • 如何防止重复传播的
    @Override
    public void register(final InstanceInfo info, final boolean isReplication) {
        int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
        if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
            leaseDuration = info.getLeaseInfo().getDurationInSecs();
        }
        super.register(info, leaseDuration, isReplication);
        // 在此处调用 replicateToPeers方法 定义的事件是一个注册时间 定位1  
        // P2P 防止重复传播是通过 isReplication 来实现的
        replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
    }
  • 定位1 replicateToPeers方法 用来:
    /**
     * Replicates all eureka actions to peer eureka nodes except for replication
     * traffic to this node.
     *
     */
    private void replicateToPeers(Action action, String appName, String id,
                                  InstanceInfo info /* optional */,
                                  InstanceStatus newStatus /* optional */, boolean isReplication) {
        Stopwatch tracer = action.getTimer().start();
        try {
            if (isReplication) {
                numberOfReplicationsLastMin.increment();
            }
            // If it is a replication already, do not replicate again as this will create a poison replication
            if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
                return;
            }

            for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
                // If the url represents this host, do not replicate to yourself.
                if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
                    continue;
                }
                // 将实例一个又一个的同步给客户端
                replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node); // 定位2 
            }
        } finally {
            tracer.stop();
        }
    }
  • 定位2
    /**
     * Replicates all instance changes to peer eureka nodes except for
     * replication traffic to this node.
     *
     */
    private void replicateInstanceActionsToPeers(Action action, String appName,
                                                 String id, InstanceInfo info, InstanceStatus newStatus,
                                                 PeerEurekaNode node) {
        try {
            InstanceInfo infoFromRegistry = null;
            CurrentRequestVersion.set(Version.V2);
            switch (action) {
                case Cancel:
                    node.cancel(appName, id);
                    break;
                case Heartbeat:
                    InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);
                    infoFromRegistry = getInstanceByAppAndId(appName, id, false);
                    node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false);
                    break;
                case Register: // 如果是注册事件,他就会将其注册到相应的注册中心去
                    node.register(info);
                    break;
                case StatusUpdate:
                    infoFromRegistry = getInstanceByAppAndId(appName, id, false);
                    node.statusUpdate(appName, id, newStatus, infoFromRegistry);
                    break;
                case DeleteStatusOverride:
                    infoFromRegistry = getInstanceByAppAndId(appName, id, false);
                    node.deleteStatusOverride(appName, id, infoFromRegistry);
                    break;
            }
        } catch (Throwable t) {
            logger.error("Cannot replicate information to {} for action {}", node.getServiceUrl(), action.name(), t);
        }
    }

心跳和服务剔除机制

  • 心跳:客户端定期发送心跳请求包到EurekaServer
  • 一旦出现心跳长时间没有发送,那么eureka采用剔除机制,将服务实例改为Down状态
  • 定位1 查看Eureka server 的 初始化状态 EurekaServerInitializerConfiguration 在中调用了start方法
    public void start() {  // 
        (new Thread(new Runnable() {
            public void run() {
                try {
                	// eurekaServerBootstrap.contextInitialized 这个方法  定位 2 contextInitialized
                    EurekaServerInitializerConfiguration.this.eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
                    EurekaServerInitializerConfiguration.log.info("Started Eureka Server");
                    EurekaServerInitializerConfiguration.this.publish(new EurekaRegistryAvailableEvent(EurekaServerInitializerConfiguration.this.getEurekaServerConfig()));
                    EurekaServerInitializerConfiguration.this.running = true;
                    EurekaServerInitializerConfiguration.this.publish(new EurekaServerStartedEvent(EurekaServerInitializerConfiguration.this.getEurekaServerConfig()));
                } catch (Exception var2) {
                    EurekaServerInitializerConfiguration.log.error("Could not initialize Eureka servlet context", var2);
                }

            }
        })).start();
    }
  • 定位2 contextInitialized
    public void contextInitialized(ServletContext context) {
        try {
            this.initEurekaEnvironment();  // 初始化环境
            this.initEurekaServerContext(); // 初始化上下文 定位 3 
            context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
        } catch (Throwable var3) {
            log.error("Cannot bootstrap eureka server :", var3);
            throw new RuntimeException("Cannot bootstrap eureka server :", var3);
        }
    }
  • 定位3 initEurekaServerContext
    protected void initEurekaServerContext() throws Exception {
        JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), 10000);
        XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), 10000);
        if (this.isAws(this.applicationInfoManager.getInfo())) {
            this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig, this.eurekaClientConfig, this.registry, this.applicationInfoManager);
            this.awsBinder.start();
        }

        EurekaServerContextHolder.initialize(this.serverContext);
        log.info("Initialized server context");
        int registryCount = this.registry.syncUp(); 
        this.registry.openForTraffic(this.applicationInfoManager, registryCount); // 与注册信息相关  applicationInfoManager 注册信息  registryCount 注册次数
        EurekaMonitors.registerAllStats(); 定位4
    }
  • 定位4 openForTraffic 是一个接口 器实现类是InstanceRegistry 在其实现类中调用了父类的方法 父类 PeerAwareInstanceRegistryImpl
    @Override
    public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
        // Renewals happen every 30 seconds and for a minute it should be a factor of 2.
        this.expectedNumberOfClientsSendingRenews = count;
        updateRenewsPerMinThreshold();  // 定位5 updateRenewsPerMinThreshold 
        logger.info("Got {} instances from neighboring DS node", count);
        logger.info("Renew threshold is: {}", numberOfRenewsPerMinThreshold);
        this.startupTime = System.currentTimeMillis();
        if (count > 0) {
            this.peerInstancesTransferEmptyOnStartup = false;
        }
        DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
        boolean isAws = Name.Amazon == selfName;
        if (isAws && serverConfig.shouldPrimeAwsReplicaConnections()) {
            logger.info("Priming AWS connections for all replicas..");
            primeAwsReplicas(applicationInfoManager);
        }
        logger.info("Changing status to UP");
        applicationInfoManager.setInstanceStatus(InstanceStatus.UP); // applicationInfoManager 放置实例对象的位置  在此将实例对象进行一次UP操作
        super.postInit(); // 定位 6 
    }
  • 定位5 updateRenewsPerMinThreshold方法
    protected void updateRenewsPerMinThreshold() {
    	// 给全局变量赋值  numberOfRenewsPerMinThreshold  预置变量  getExpectedClientRenewalIntervalSeconds 发送心跳的间隔 
    	// 计算1分钟内应该发送几次心跳
        this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
                * (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
                * serverConfig.getRenewalPercentThreshold()); // 默认的时 0.85D
    }
  • 定位6
    protected void postInit() {
        renewsLastMin.start();
        if (evictionTaskRef.get() != null) {
            evictionTaskRef.get().cancel();
        }
        evictionTaskRef.set(new EvictionTask()); // 在这边又创建了一个新的线程 定位7
        evictionTimer.schedule(evictionTaskRef.get(), // 这边定义好了一个定时任务
                serverConfig.getEvictionIntervalTimerInMs(),
                serverConfig.getEvictionIntervalTimerInMs());
    }
  • 定位7
    /* visible for testing */ class EvictionTask extends TimerTask {

        private final AtomicLong lastExecutionNanosRef = new AtomicLong(0l);

        @Override
        public void run() {
            try {
                long compensationTimeMs = getCompensationTimeMs();
                logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);
                evict(compensationTimeMs); // 定位8 
            } catch (Throwable e) {
                logger.error("Could not run the evict task", e);
            }
        }
  • 定位8
    public void evict(long additionalLeaseMs) {
        logger.debug("Running the evict task");

        if (!isLeaseExpirationEnabled()) { // 判断是否开启自我保护机制 一旦开启自我保护机制,注册中心永远不将服务剔除
            logger.debug("DS: lease expiration is currently disabled.");
            return;
        }

        // We collect first all expired items, to evict them in random order. For large eviction sets,
        // if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it,
        // the impact should be evenly distributed across all applications.
        List<Lease<InstanceInfo>> expiredLeases = new ArrayList<>(); 
        // 收集所有过期的过期的服务,随机剔除
        for (Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry : registry.entrySet()) {
            Map<String, Lease<InstanceInfo>> leaseMap = groupEntry.getValue();
            if (leaseMap != null) {
                for (Entry<String, Lease<InstanceInfo>> leaseEntry : leaseMap.entrySet()) {
                    Lease<InstanceInfo> lease = leaseEntry.getValue();
                    if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) {
                        expiredLeases.add(lease);
                    }
                }
            }
        }

        // To compensate for GC pauses or drifting local time, we need to use current registry size as a base for
        // triggering self-preservation. Without that we would wipe out full registry.
        int registrySize = (int) getLocalRegistrySize();
        int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
        int evictionLimit = registrySize - registrySizeThreshold;

        int toEvict = Math.min(expiredLeases.size(), evictionLimit);
        if (toEvict > 0) {
            logger.info("Evicting {} items (expired={}, evictionLimit={})", toEvict, expiredLeases.size(), evictionLimit);

            Random random = new Random(System.currentTimeMillis());
            for (int i = 0; i < toEvict; i++) {
                // Pick a random item (Knuth shuffle algorithm)
                int next = i + random.nextInt(expiredLeases.size() - i);
                Collections.swap(expiredLeases, i, next);
                Lease<InstanceInfo> lease = expiredLeases.get(i);

                String appName = lease.getHolder().getAppName();
                String id = lease.getHolder().getId();
                EXPIRED.increment();
                logger.warn("DS: Registry: expired lease for {}/{}", appName, id);
                internalCancel(appName, id, false);  // 定位9 随机剔除服务
            }
        }
    }
  • 定位9 随机剔除服务 通过不同的判断,剔除不同类型的服务
    protected boolean internalCancel(String appName, String id, boolean isReplication) {
        try {
            read.lock();
            CANCEL.increment(isReplication);
            Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
            Lease<InstanceInfo> leaseToCancel = null;
            if (gMap != null) {
                leaseToCancel = gMap.remove(id);
            }
            synchronized (recentCanceledQueue) {
                recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));
            }
            InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id);
            if (instanceStatus != null) {
                logger.debug("Removed instance id {} from the overridden map which has value {}", id, instanceStatus.name());
            }
            if (leaseToCancel == null) {
                CANCEL_NOT_FOUND.increment(isReplication);
                logger.warn("DS: Registry: cancel failed because Lease is not registered for: {}/{}", appName, id);
                return false;
            } else {
                leaseToCancel.cancel();
                InstanceInfo instanceInfo = leaseToCancel.getHolder();
                String vip = null;
                String svip = null;
                if (instanceInfo != null) {
                    instanceInfo.setActionType(ActionType.DELETED);
                    recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel));
                    instanceInfo.setLastUpdatedTimestamp();
                    vip = instanceInfo.getVIPAddress();
                    svip = instanceInfo.getSecureVipAddress();
                }
                invalidateCache(appName, vip, svip);
                logger.info("Cancelled instance {}/{} (replication={})", appName, id, isReplication);
                return true;
            }
        } finally {
            read.unlock();
        }
    }

你可能感兴趣的:(springcloud,eureka)