图片来源:https://github.com/Netflix/eureka/wiki/Eureka-at-a-glance
图片来源:《Spring Cloud 微服务架构进阶》
从上图可以看出整个Eureka是分为client(客户端)、server(服务端),其中我要对图中的几个关键过程进行简单叙述;
读取自身服务实例配置信息,封装成EurekalnstanceConfig;
从Eureka Server中拉取注册信息表;
Eukeka Client 通过 Starter 的方式引人依赖, Spring Boot 将会为项目使用以下 自动配置类
重点关注两个注解:
@AutoConfigureBefore:表明在配置3个类之前,先要完成当前类配置才可以(当前类配置在前)
@AutoConfigureAfter:表示如果想让当前配置类起作用,需要先对3个配置类进行配置(当前类配置在后)
值得注意的是在2.1.1中@AutoConfigureAfter这里是3个类,DiscoveryClientOptionalArgsConfiguration是以@Import(DiscoveryClientOptionalArgsConfiguration.class)注入的
// 这个类的作用是将配置文件中以eureka.client为前缀的配置信息进行读取后封装
@Bean
@ConditionalOnMissingBean(value = EurekaClientConfig.class,
search = SearchStrategy.CURRENT)
public EurekaClientConfigBean eurekaClientConfigBean(ConfigurableEnvironment env) {
// 前配置文件中以eureka.client为前缀的信息进行读取并封装
return new EurekaClientConfigBean();
}
// 这个类的作用是将关于eureka实例的配置信息进行读取后封装。
@Bean
@ConditionalOnMissingBean(value = EurekaInstanceConfig.class,search = SearchStrategy.CURRENT)
public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils,ManagementMetadataProvider managementMetadataProvider) {
String hostname = getProperty("eureka.instance.hostname");
boolean preferIpAddress = Boolean.parseBoolean(getProperty("eureka.instance.prefer-ip-address"));
String ipAddress = getProperty("eureka.instance.ip-address");
boolean isSecurePortEnabled = Boolean.parseBoolean(getProperty("eureka.instance.secure-port-enabled"));
因为eureka的客户端是在这里创建的
DiscoveryClient Spring Cloud 中用来进行服务发现的顶级接口
目标是进入DiscoveryClient中的下图方法中
这个方法很长,需要关注的是以下几点:
1、shouldRegisterWithEureka(对应配置eureka.client.register-with-eureka )为 true 表示Eureka Client 将注册到 Eureka Server;
2、shouldFetchRegistry(对应配置为 eureka client.fetch-register)为 true 表示 Eureka Client 将从 Eureka Server中拉取注册表信息;
如果上述的两个配置均为 false 那么 Discovery 的初始化时就直接结束,表示该客户端既不进行服务注册也不进行服务发现。
3、在初始化3个线程池(后续定时任务中会说)后,红框中的方法是去服务端拉取注册信息;
clientConfig.shouldFetchRegistry()上面有提,默认为true,接着进入到fetchRegistry(false)方法:
该方法中Applications在开篇中有解释;
进入getAndStoreFullRegistry()方法:
继续跟进,注意这里要进入AbstractJerseyEurekaHttpClient:
serviceUrl即为server地址;
从Server端拉取注册表使用的是Jersey中的GET请求;
并且在返回200之后,将 Eureka Server 中拉取注册表中所有的服务 例信息
全量拉取将 Eureka Server 中拉取注册表中所有的服务实例信息(封装在 Applications
中),并经过处理后替换掉本地注册表缓存Applications;
值得注意的是:getAndStoreFullRegistry()方法可能会被多个线程同时调用,导致新拉取的注册表被旧的注册表覆盖,产生了脏数据,因为Eureka通过Atomic的对applications的更新版本进行CAS更新,拉取到注册表信息之后对获取到的applications信息进行筛选,只保留状态为UP的服务实例信息
再次回到DiscoveryClient中,在获取完注册表信息后:
clientConfig.shouldRegisterWithEureka()表示:是否注册到Eureka;
clientConfig.shouldEnforceRegistrationAtInit()表示:在初始化的时候强制进行注册;
进入register() 注册方法:
Eureka Client 会将自身服务实例元数据(封装在 Instancelnfo 中)发送到 Eureka Server 中请求服务注册,当 Eureka Server 返回 NO_CONTENT (204)状态码时,说明服务注册成功。
值得注意的是:这里请注册是PUT方法,并且在调用的是AbstractJerseyEurekaHttpClient的register时eureka使用了装饰器模式,提升了组件的扩展性:
JerseyClient:执行rest请求的工具,eureka有RestTemplate的替换实现方案
统计信息:根据请求类型进行统计,请求时间总计、请求失败次数总计等;
重定向:返回的http code是302则表示要重定向(注册中心迁移?)更换重定向的url再次发起请求,最多重定向10次;
失败重试机制:已经请求失败的server url记录下来下次不再请求;如果失败的url过多(比如全部都失败的情况,可能是客户端网络有问题),超过阀值,则清空失败的url记录,把他们重新当成可用的url ,这里要获取注册中心地址列表,用于重试;
会话:请求失败的url记录多久,永久记录吗?使用session机制如果20分钟没请求过,则重置上面的所有机制,session有效时长20分钟左右(有随机增减)
可以通过过eureka.client.registry-fetch-interval-seconds进行设置,默认30s刷新一次
由TimedSupervisorTask提供实现,TimedSupervisorTask继承了TimerTask,执行定时任务
进入new CacheRefreshThread()中的refreshRegistry()方法
这里又回到了fetchRegistry(boolean forceFullRegistryFetch) 方法,这次走的增量拉取server端的注册表信息,上图代码后续就是说如果发现变化,就打印更新注册表之后的变化
可以通过过eureka.instance.lease-renewal-interval-in-seconds进行设置,默认30s刷新一次
由TimedSupervisorTask提供实现,TimedSupervisorTask继承了TimerTask,执行定时任务
注意这里的Status.NOT_FOUND 指的是,发送心跳实际上是将instanceinfo发送到服务端,如果服务端的注册表信息中没有我的这个instanceinfo,就会发生NOT_FOUND,如果状态是NOT_FOUND ,则重新进行注册操作
进入sendHeartBeat()方法中,在向server发送心跳续约请求时,需要关注LastDirtyTimestamp,server接收到这个请求,会对这个字段数值进行比较,详细内容将在server端中做解释
InstanceInfoReplicator类的作用是:更新和复制本地的instanceinfo到远程server,这里要说明一点,client的配置文件是可以动态修改的,这里面会通过定时任务去检查配置文件的修改并同步到server端
refreshDataCenterInfoIfRequired()主要是为了检查hostname是否发生改变;
refreshLeaseInfoIfRequired(),是为了更新租约信息,重点更进:
1、instanceInfo.getLeaseInfo()是获取当前的续约信息;
2、getLeaseExpirationDurationInSeconds()是在配置文件eureka.instance.lease-renewal-interval-in-seconds:指定客户端多少秒向服务端发一次心跳;
3、getLeaseRenewalIntervalInSeconds()是在配置文件eureka.instance.lease-renewal-interval-in-seconds:指定当前客户端多少秒内没有向服务端发送心跳,则让服务端认为其宕机,踢除掉该服务;
4、instanceInfo.setIsDirty():这个字段代表instanceInfo配置已经被修改了,并记录最新修改的时间戳
isDirtyWithTime()方法中校验了isInstanceInfoDirty字段,如果在instanceInfo配置被修改了,在上图中就被修改为了true,就需要重新注册。
也可以说如果把续约信息改掉了,则说明这个instanceinfo对server来说是一个全新的,需要注册。
一般情况下,应用服务在关闭的时候, Eureka Client会主动向 Eureka Server注销自身在注册表中的信息。
源码入口:EurekaClientAutoConfiguration->RefreshableEurekaClientConfiguration
这一段代码在最一开始创建EurekaClient代码中有一个destroyMethod = “shutdown”,跟进这个方法:
服务下线的接口地址为 apps ${APP_NAME} ${INSTANCE_INFO_ID },传递参数为服
务名和服务实例 id, HTTP 方法为 delete