还是先提出几个疑问,看本篇文章前最好看过Eureka高可用之Client重试机制:RetryableEurekaHttpClient,要知道Eureka Client只会向一个Server节点进行注册(心跳、状态改变等类似),注册失败时才会尝试下一个server节点。当然正是由于这种机制,才会有Eureka Server的复制行为,个人认为,Eureka Client向每个Eureka Server都发送注册、心跳等事件,会更好的保证一致性
1、如果有4个Eureka Server集群节点,一个Client向其中一个Server进行注册(或者心跳、状态改变事件等),那么这个server是怎样通知剩余3个server集群节点的?
2、一个Eureka Server在收到其他server节点发送的复制信息时,它是怎么样处理的,它会把收到的复制信息继续向其它节点复制吗?
先看一下PeerAwareInstanceRegistryImpl的继承类图,它继承了抽象的实例注册,实现了复制实例注册(能意识到临节点的实例注册),那么它就具备注册实例信息,还能复制给临节点的功能
直接去PeerAwareInstanceRegistryImpl里面看代码,看看是如何将实例信息复制给临节点的(只列出register方法,heartBeat等类似),在收到client的注册信息时,isReplication为false,而当收到其他Eureka Server节点的复制信息时,isReplication为true
@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) {
//默认租约90s,如果用户更改了心跳周期等,使用用户自定义的租约
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
//调用父类的注册,注册到本地双层Map中
super.register(info, leaseDuration, isReplication);
//本地注册完成后,向其他节点复制,注意isReplication这个属性
//用来判断是client发来的注册,还是其他Eureka Server临节点复制过来的注册
//如果是复制过来的注册信息,那么就不再向其他Eureka Server节点进行传播复制
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}
super.register(info, leaseDuration, isReplication)先不介绍,这个方法是把实例信息保存到自己的内存中,重点看replicateToPeers方法,在这个方法中,有个for循环遍历了所有的Eureka Server临节点,然后向他们复制这些信息
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) {
//如果没有Eureka Server临节点,或者是别的Eureka Server复制过来的信息
//那么就不再向其他临节点进行复制,
//也就是说既然收到了复制过来的信息,那么其他eureka server节点也会收到
//所以就没必要再去发送一遍复制了,return。
return;
}
//遍历所有的Eureka Server邻节点,向它们复制register、cancel等信息
for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
// If the url represents this host, do not replicate to yourself.
//如果这个节点url是自己的,那么不向自身复制
if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
continue;
}
replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
}
} finally {
tracer.stop();
}
}
一个Eureka Server在收到了Client的注册等信息时,会挨个通知其他Eureka Server临节点,复制的流程图也就是下面这个样子
那么再来看看for循环里面的replicateInstanceActionsToPeers方法,在循环里面,根据请求类型action的不同,调用不同PeerEurekaNode的方法,重点还是这个异常处理。
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) {
//由于此方法是循环复制操作,如果发生异常不进行处理,直接抛出,那么就不会向后面的节点复制了
//比如有10个Eureka Server节点,再向第2个复制的时候抛出异常,后面8个节点就收不到复制信息
//这个地方,只是做log,并没有抛出异常
logger.error("Cannot replicate information to {} for action {}", node.getServiceUrl(), action.name(), t);
}
}