前言

带着问题学习,事半功倍。本文将对如下几个问题进行总结说明:

1、EurekaServer端服务注册的流程和设计模式

2、Eureka服务续约的bug

3、EurekaClient的启动流程

4、client启动后是往一个server注册还是多个server遍历注册

5、EurekaServer的三级缓存

6、一个EurekaClient宕机后,其他EurekaClient最晚多长时间后才会不再往这个宕机的服务发起请求?

Eureka在Spring Cloud组件全家桶中,处于很核心的位置,从去年格林尼治版的更新说明中就能知道,更新日志截图如下,netflix的其他组件均进入维护状态,不再添加新特性,但Eureka不包括在内。个人观点,一方面Eureka的功能实现相对比较复杂,不好随便改动,再就是位置关键,改动后影响范围广。

下面进入正文。注:Spring Cloud版本Hoxton SR1,eureka-core 1.9.13

正文 

一、EurekaServer端服务注册的流程和设计模式

服务端的入口类如下所示,不带s的类中是对单个服务/实例的操作,带s的是集合操作。服务注册入口在ApplicationResource中。

服务注册方法是ApplicationResource#addInstance,可以看到经过一些必要的判断后调用了注册方法,注意因为该请求是从客户端发起的,isReplication为空,所以register方法的第二个参数是false。

         registry.register(info, "true".equals(isReplication));
return Response.status(204).build(); // 204 to be backwards compatible
}

追踪进入PeerAwareInstanceRegistryImpl类的register方法,如下,该方法先调用了父类的注册方法,然后调的往其他服务扩散注册信息的方法replicateToPeers。

     @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(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}

继续跟进到父类AbstractInstanceRegistry,在父类的register方法中完成了对真实服务列表ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry的维护 (方法太长就不贴出来了)。

至此完成了服务注册,共涉及到三个类:ApplicationResource、PeerAwareInstanceRegistryImpl、AbstractInstanceRegistry。前两个类的register方法都是做了一些自己的事情外加调用父类的register,一个典型的责任链模式应用,一个类只负责自己的事情,然后调用上一层的方法,如果需加一个功能,只需要在对应位置加一层继承关系即可,对原有功能无侵入。

二、Eureka服务续约的bug

打开Lease租债器类,看到renew方法和isExpired方法:

 public void renew() {
lastUpdateTimestamp = System.currentTimeMillis() + duration; }
 public boolean isExpired(long additionalLeaseMs) {
return (evictionTimestamp > 0 || System.currentTimeMillis() > (lastUpdateTimestamp + duration + additionalLeaseMs));
}

续约方法每次调用都将最后修改时间变为当前时间+有效期(默认90s),而判断是否失效的方法比较的是当前时间和最后修改时间+有效期,这就导致有效期加了两次,即一个服务过了两倍的有效期时间之后才会被服务端判定为到期。其实这个事情在isExpired方法的注释中可以看到说明:

     /**
* Checks if the lease of a given {@link com.netflix.appinfo.InstanceInfo} has expired or not.
*
* Note that due to renew() doing the 'wrong" thing and setting lastUpdateTimestamp to +duration more than
* what it should be, the expiry will actually be 2 * duration. This is a minor bug and should only affect
* instances that ungracefully shutdown. Due to possible wide ranging impact to existing usage, this will
* not be fixed.
*
* @param additionalLeaseMs any additional lease time to add to the lease evaluation in ms.
*/

三、EurekaClient的启动流程

Eureka客户端只需要引入依赖加上配置,便可以自动实现服务注册。客户端的功能主要包括三部分:启动时的服务注册、服务定时续约、服务列表缓存定时更新。

客户端启动的逻辑都在DiscoveryClient的构造方法中,com.netflix.discovery.DiscoveryClient#DiscoveryClient,方法代码太长,就不粘贴代码了,只描述下流程:

初始化两个ThreadPoolExecutor:服务续约和更新缓存;

从server拉取注册信息com.netflix.discovery.DiscoveryClient#fetchRegistry;

com.netflix.discovery.DiscoveryClient#register服务注册;

启动定时器。

四、client启动后是往一个server注册还是多个server遍历注册

客户端在执行register方法注册服务时,采用装饰器模式对httpClient进行处理,其中有一个是RetryableEurekaHttpClient,在该类的execute方法中对客户端配置文件中配置的serviceUrl进行了遍历,如果第一个注册请求处理成功了,则不再重试,否则遍历serviceUrl重试。具体可见com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient#execute方法。

五、EurekaServer的三级缓存

eureka的服务端为了提高服务列表维护和读取的一致性与可用性,对服务列表的查看设置了三级缓存,入口为com.netflix.eureka.resources.ApplicationsResource#getContainers。在该方法中调用了

ResponseCacheImpl#getGZIP方法获取缓存,如下:

 public byte[] getGZIP(Key key) {
Value payload = getValue(key, shouldUseReadOnlyResponseCache);
if (payload == null) {
return null;
}
return payload.getGzipped();
}

继续跟进getValue方法:

 Value getValue(final Key key, boolean useReadOnlyCache) {
Value payload = null;
try {
if (useReadOnlyCache) {
final Value currentPayload = readOnlyCacheMap.get(key);
if (currentPayload != null) {
payload = currentPayload;
} else {
payload = readWriteCacheMap.get(key);
readOnlyCacheMap.put(key, payload);
}
} else {
payload = readWriteCacheMap.get(key);
}
} catch (Throwable t) {
logger.error("Cannot get value for key : {}", key, t);
}
return payload;
}

可以看到,这里有两个map:readOnlyCacheMap(只读缓存)、readWriteCacheMap(读写缓存),再加上AbstractInstanceRegistry#registry真实数据,总共三级map缓存。

它们使用的规则如下:只读缓存每隔30s定时从读写缓存中更新最新数据,读写缓存与真实数据是同步的,它的存在是为了减少对真实数据的读取。额外要注意,在eureka server中,读取操作用的写锁,而注册修改下线操作用的读锁。

通过三级缓存,Eureka在并发吞吐量的基础上做到了最大程度的数据一致性。这种设计思路值得学习。

六、一个EurekaClient宕机后,其他EurekaClient最晚多长时间后才会不再往这个宕机的服务发起请求?

先说下上面三级缓存场景可能产生的延迟:如果在服务端的真实服务列表中,一个服务已经被剔除了,此时最多过多长时间其他客户端才能得知到此消息?

客户端每隔30s去服务端拉取一次缓存 + 服务端只读缓存每30s同步一次读写缓存的数据,即最长需要60s后客户端才能得到最新的服务端列表数据。

再来看宕机的情况,即如果一个服务宕机,其他服务最多会经过多长时间才不会再往这个服务发送请求?

先看服务端,因为有上面第二项说的bug存在,默认服务端经过90s*2才会剔除该宕机服务,该剔除的定时器每60s执行一次,再加上上面说的客户端更新缓存的60s延迟,再加上ribbon的60s缓存,所以总计是:

90*2 + 60 + 60 + 60 = 360s,即最长可能需要6分钟。

小结

Eureka的定时器真TM多。。。

最新文章

  1. ASP.NET Core的配置(2):配置模型详解
  2. CSS中Position 的用法详解。
  3. C# 6.0那些事
  4. 设置一个POJO的某个属性的默认值
  5. struts中的helloword(1)
  6. iOS开发——JS网页交互——javaScript
  7. uboot总结:uboot配置和启动过程2(mkconfig分析)
  8. WPF中对三维模型的控制
  9. 高度-宽度关系,同一div、不同div高度与宽度关系控制函数
  10. 2017-06-19 (cp mkdir rm 运行级别及修改)
  11. Numpy 模块的应用
  12. 分布式服务化系统一致性(分布式事务、ACID、BASE、CAP)原理与解决方案
  13. HttpSenderUtil向指定 URL 发送POST方法的请求
  14. ios中键盘处理源码
  15. mysql 5.7.10 启动多实例笔记
  16. cookie和session的区别,session的生命周期,
  17. HDU_2888_Check Corners
  18. Rafy中的EventBus
  19. ionic ios 打包 真机测试常见问题
  20. poj 1743 后缀数组 求最长不重叠重复子串

热门文章

  1. git 为什么要先commit,然后pull,最后再push?而不是commit然后直接push?
  2. Python GUI——tkinter菜鸟编程(中)
  3. json的fromjson的方法使用。可以在volley中进行使用
  4. 简单分析ucenter 会员同步登录通信原理
  5. docker win10 基本指令
  6. Matlab入门(一)
  7. JAVA中的==和queals()的区别
  8. matplotlib AffineBase, Affine2DBase, Affine2D
  9. 【python实现卷积神经网络】激活函数的实现(sigmoid、softmax、tanh、relu、leakyrelu、elu、selu、softplus)
  10. Tomcat启动过程原理详解 -- 非常的报错:涉及了2个web.xml等文件的加载流程