Eureka Client注册、续期和取消注册

357 阅读4分钟

本文中源码,来自Eureka的v1.7.x版本。代码仓:github.com/Netflix/eur…

《Eureka Client的初始化》一文中,我们知道了客户端的启动流程。

现在一起来看看客户端是如何注册到Eureka server的。请考虑以下问题:

  • 服务节点注册与否,对应代码层面的表现是什么?
  • 节点启动时注册了,节点下线或宕机时,如何取消注册?

一 注册表的数据结构

image.png

1.1 Application类

DiscoveryClient类的localRegionApps,就是应用集的原子引用。

private final AtomicReference<Applications> localRegionApps = new AtomicReference<Applications>();

Applications类,就是从Eureka server返回的注册表信息。注册表是一个HashMap,key就是appName,value是Application实例。 image.png Application是对应用集群的封装,包含已注册的服务节点。 image.png

1.2 InstanceInfo类

InstanceInfo类是对服务节点信息的封装,有以下关键属性: image.png InstanceInfo.equals()中,使用ID来判断两个实例是否相同,就用到了instanceId。 image.png 在Eureka客户端中,可通过eureka.instance.instance-id配置,指定该节点的instanceId,通常配置为 ${spring.application.name}-${server.port}

当节点尚未注册,或节点状态发生变化时,应该及时同步给Eureka。用下面两个属性做了标识:

// 最新节点信息是否已同步给Eureka
@XStreamOmitField
private volatile boolean isInstanceInfoDirty = false;
// 上次节点信息变动时间戳
private volatile Long lastDirtyTimestamp = System.currentTimeMillis();

image.png

1.3 服务注册的代码表现

  • 服务注册,就是将某节点的InstanceInfo(网络地址、运行终态等信息),添加到Application.instances中。
  • 取消注册,就是将该节点的InstanceInfo,从Application.instances中移除;
  • 节点信息变更同步,就是更新Application.instances中特定instanceId的元素;

二 与Eureka的网络通信

客户端与Eureka间的网络通信组件,被封装到EurekaTransport类。 image.png

EurekaHttpClient,默认使用JerseyReplicationClient,底层使用了ApacheHttpClient。 image.png

三 客户端注册

3.1 信息来源

InstanceInfo从何而来

可以从配置、运行环境中获取信息,来创建InstanceInfo对象。然后交由ApplicationInfoManager管理,它有以下属性。

// key是ListenerId
protected final Map<String, StatusChangeListener> listeners;
private final InstanceStatusMapper instanceStatusMapper;
// 节点信息
private InstanceInfo instanceInfo;
// 客户端配置信息
private EurekaInstanceConfig config;

在创建核心类DiscoveryClient时,applicationInfoManager就是构造参数之一。 image.png

Eureka地址从何而来

要发起注册,得先知道Eureka的网络地址啊。由配置提供,如springboot中:

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

3.2 发起注册

提交心跳任务

在创建DiscoveryClient实例时,创建了3个线程池: image.png scheduler仅用于周期任务或延迟任务的调度,任务体的执行将交由其他两个线程池完成。 image.png

DiscoveryClient.initScheduledTasks()中,添加了“heartbeat”延迟任务,延迟时间即续租间隔时间,默认为30秒。 image.png

TimedSupervisorTask类

执行体是TimedSupervisorTask类,它是TimerTask的子类,真正的任务体是HeartbeatThread,该类的作用在于:

  • 对任务超时、被拒绝、执行异常,提供了数量统计与监控;
  • 异步执行任务体,使用DiscoveryClient中的heartbeatExecutor线程池;
  • 对超时时间自适应:本次心跳超时后,会将下次心跳的延迟时间放大,一旦心跳成功后,将延迟时间恢复为正常值;
  • 提交下一次的延迟任务 image.png TimedSupervisorTask简化后的run逻辑:
public void run() {
	Future future = null;
	try {
		future = executor.submit(task);
		future.get(timeoutMillis, TimeUnit.MILLISECONDS);  // block until done or timeout
		// 未超时,重置延迟时间为正常值
		delay.set(timeoutMillis);
	} catch (TimeoutException e) {
		timeoutCounter.increment();

		// 增大延迟时间为两倍
		long currentDelay = delay.get();
		long newDelay = Math.min(maxDelay, currentDelay * 2);
		delay.compareAndSet(currentDelay, newDelay);
	} catch (RejectedExecutionException e) {
		rejectedCounter.increment();
	} catch (Throwable e) {
		throwableCounter.increment();
	} finally {
           // 提交下次延迟任务
		if (!scheduler.isShutdown()) {
			scheduler.schedule(this, delay.get(), TimeUnit.MILLISECONDS);
		}
	}
}

发送心跳

HeartbeatThread是DiscoveryClient的内部类,续租则调用renew()image.png image.png 首次续租时,由于节点尚未注册,Eureka会响应404,此时立即发起注册。

心跳请求是通过JerseyReplicationClient#sendHeartBeat发出的。url和参数如下。 image.png 心跳请求时,只将节点状态、lastDirtyTimestamp传送给Eureka。

可以猜测,在Eureka server中,会将心跳请求参数lastDirtyTimestamp,与注册表中节点的lastDirtyTimestamp相比较;如果前者更大,说明客户端信息已发生变化,则响应客户端需要发起一次信息同步请求。

发起注册

调用DiscoveryClient.register(),POST请求体就是instanceInfo,url是apps/appName/idimage.png

四 客户端取消注册

DiscoveryClient#shutdown中,将节点状态设置为DOWN,调用unregister()image.png image.png 取消注册url也是apps/appName/id,与register请求的相同,只是改用DELETE请求。 image.png

五 相关配置

  • 是否注册到Eureka

    • 源码中配置名是registration.enabled,默认为true。
    • springboot中是eureka.client.registerWithEureka,默认为true。
  • 续租间隔周期

    • 源码中配置名是renewalIntervalInSecs,默认30秒。 image.png
    • springboot中配置项是eureka.instance.lease-renewal-interval-in-seconds,默认30秒