当前位置:   article > 正文

Eureka之源码剖析_eureka calc

eureka calc

附:SpringCloud之系列汇总跳转地址

本篇文章以源码的角度来深入理解Eureka

Eureka的治理机制

  • 服务提供者
    • 服务注册:启动的时候会通过发送REST请求的方式将自己注册到Eureka Server上,同时带上了自身服务的一些元数据信息。
    • 服务续约:在注册完服务之后,服务提供者会维护一个心跳用来持续告诉Eureka Server: "我还活着 ” 。
    • 服务下线:当服务实例进行正常的关闭操作时,它会触发一个服务下线的REST请求给Eureka Server, 告诉服务注册中心:“我要下线了 ”。
  • 服务消费者
    • 获取服务:当我们启动服务消费者的时候,它会发送一个REST请求给服务注册中心,来获取上面注册的服务清单
    • 服务调用:服务消费者在获取服务清单后,通过服务名可以获得具体提供服务的实例名和该实例的元数据信息。在进行服务调用的时候,优先访问同处一个Zone中的服务提供方

如果看到SpringCloud的某个服务配置了没有"注册"到Eureka-Server也不用过于惊讶(但是它是可以获取Eureka服务清单的),很可能只是作者把该服务认作为单纯的服务消费者,单纯的服务消费者无需对外提供服务,也就无须注册到Eureka中了。

  • Eureka Server(服务注册中心):
    • 失效剔除:默认每隔一段时间(默认为60秒) 将当前清单中超时(默认为90秒)没有续约的服务剔除出去
    • 自我保护:EurekaServer 在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%(通常由于网络不稳定导致)。 Eureka Server会将当前的实例注册信息保护起来, 让这些实例不会过期,尽可能保护这些注册信息

Eureka的高可用架构

从图可以看出在这个体系中,有2个角色,即Eureka Server和Eureka Client。而Eureka Client又分为Applicaton Server和Application Client,即服务提供者和服务消费者。 每个区域有一个Eureka集群,并且每个区域至少有一个Eureka服务器可以处理区域故障,以防服务器瘫痪。

Eureka Server:

接收Application Server 的register,renew,calcel请求,并且将注册信息同步到其他的Eureka Server节点,维护服务注册表。

Application Server:

启动时发送register请求,而后定时发送renew心跳,下线时发送calcel请求

Application Client:

定时从Eureka Server拉取Application Server的注册信息,并维护一个本地服务注册表

Application Server向Eureka Server注册,并将自己的一些客户端信息发送Eureka Server。然后,Application Server通过向Eureka Server发送心跳(每30秒)来续约服务的。 如果Application Server持续不能续约,那么,它将在大约90秒内从服务器注册表中删除。 注册信息和续订被复制到集群中的Eureka Server所有节点。 来自任何区域的Application Client都可以查找注册表信息(每30秒发生一次)。根据这些注册表信息,Application Client可以远程调用Applicaton Server来消费服务。

缓存机制

Eureka Server端,三级缓存:registry、readWriteCacheMap、readOnlyCacheMap,保存服务注册信息

Application Client端,二级缓存:localRegionApps、upServerListZoneMap、保存服务注册信息

https://blog.csdn.net/qq_24313635/article/details/103892348

Register服务注册

服务注册,即Application Server向Eureka Server提交自己的服务信息,包括IP地址、端口、service ID等信息。如果Application Server没有写service ID,则默认为 ${spring.application.name}。

服务注册其实很简单,在Application Server启动的时候,将自身的服务的信息发送到Eureka Server。现在来简单的阅读下源码。在Maven的依赖包下,找到eureka-client-1.6.2.jar包。在com.netflix.discovery包下有个DiscoveryClient类,该类包含了Eureka Client向Eureka Server的相关方法。其中DiscoveryClient实现了EurekaClient接口,并且它是一个单例模式,而Eureka Client继承了LookupService接口。它们之间的关系如图所示。

在DiscoveryClient类有一个服务注册的方法register(),该方法是通过Http请求向Eureka Server注册。其代码如下:

  1. boolean register() throws Throwable {
  2. logger.info(PREFIX + appPathIdentifier + ": registering service...");
  3. EurekaHttpResponse<Void> httpResponse;
  4. try {
  5. httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
  6. } catch (Exception e) {
  7. logger.warn("{} - registration failed {}", PREFIX + appPathIdentifier, e.getMessage(), e);
  8. throw e;
  9. }
  10. if (logger.isInfoEnabled()) {
  11. logger.info("{} - registration status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
  12. }
  13. return httpResponse.getStatusCode() == 204;
  14. }

在DiscoveryClient类继续追踪register()方法,它被InstanceInfoReplicator 类的run()方法调用,其中InstanceInfoReplicator实现了Runnable接口,run()方法代码如下:

  1. public void run() {
  2. try {
  3. discoveryClient.refreshInstanceInfo();
  4. Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
  5. if (dirtyTimestamp != null) {
  6. discoveryClient.register();
  7. instanceInfo.unsetIsDirty(dirtyTimestamp);
  8. }
  9. } catch (Throwable t) {
  10. logger.warn("There was a problem with the instance info replicator", t);
  11. } finally {
  12. Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
  13. scheduledPeriodicRef.set(next);
  14. }
  15. }

而InstanceInfoReplicator类是在DiscoveryClient初始化过程中使用的,其中有一个initScheduledTasks()方法。该方法主要开启了获取服务注册列表的信息,如果需要向Eureka Server注册,则开启注册,同时开启了定时向Eureka Server服务续约的定时任务,具体代码如下:

  1. private void initScheduledTasks() {
  2. ...//省略了任务调度获取注册列表的代码
  3. if (clientConfig.shouldRegisterWithEureka()) {
  4. ...
  5. // Heartbeat timer
  6. scheduler.schedule(
  7. new TimedSupervisorTask(
  8. "heartbeat",
  9. scheduler,
  10. heartbeatExecutor,
  11. renewalIntervalInSecs,
  12. TimeUnit.SECONDS,
  13. expBackOffBound,
  14. new HeartbeatThread()
  15. ),
  16. renewalIntervalInSecs, TimeUnit.SECONDS);
  17. // InstanceInfo replicator
  18. instanceInfoReplicator = new InstanceInfoReplicator(
  19. this,
  20. instanceInfo,
  21. clientConfig.getInstanceInfoReplicationIntervalSeconds(),
  22. 2); // burstSize
  23. statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
  24. @Override
  25. public String getId() {
  26. return "statusChangeListener";
  27. }
  28. @Override
  29. public void notify(StatusChangeEvent statusChangeEvent) {
  30. instanceInfoReplicator.onDemandUpdate();
  31. }
  32. };
  33. ...
  34. }

然后在来看Eureka server端的代码,在Maven的eureka-core:1.6.2的jar包下。打开com.netflix.eureka包,很轻松的就发现了又一个EurekaBootStrap的类,BootStrapContext具有最先初始化的权限,所以先看这个类。

  1. protected void initEurekaServerContext() throws Exception {
  2. ...//省略代码
  3. PeerAwareInstanceRegistry registry;
  4. if (isAws(applicationInfoManager.getInfo())) {
  5. ...//省略代码,如果是AWS的代码
  6. } else {
  7. registry = new PeerAwareInstanceRegistryImpl(
  8. eurekaServerConfig,
  9. eurekaClient.getEurekaClientConfig(),
  10. serverCodecs,
  11. eurekaClient
  12. );
  13. }
  14. PeerEurekaNodes peerEurekaNodes = getPeerEurekaNodes(
  15. registry,
  16. eurekaServerConfig,
  17. eurekaClient.getEurekaClientConfig(),
  18. serverCodecs,
  19. applicationInfoManager
  20. );
  21. }

其中PeerAwareInstanceRegistryImpl和PeerEurekaNodes两个类看其命名,应该和服务注册以及Eureka Server高可用有关。先追踪PeerAwareInstanceRegistryImpl类,在该类有个register()方法,该方法提供了注册,并且将注册后信息同步到其他的Eureka Server服务。代码如下:

  1. public void register(final InstanceInfo info, final boolean isReplication) {
  2. int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
  3. if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
  4. leaseDuration = info.getLeaseInfo().getDurationInSecs();
  5. }
  6. super.register(info, leaseDuration, isReplication);
  7. replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
  8. }

其中 super.register(info, leaseDuration, isReplication)方法,点击进去到子类AbstractInstanceRegistry可以发现更多细节,其中注册列表的信息被保存在一个Map中。replicateToPeers()方法,即同步到其他Eureka Server的其他Peers节点,追踪代码,发现它会遍历循环向所有的Peers节点注册,最终执行类PeerEurekaNodes的register()方法,该方法通过执行一个任务向其他节点同步该注册信息,代码如下:

  1. public void register(final InstanceInfo info) throws Exception {
  2. long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info);
  3. batchingDispatcher.process(
  4. taskId("register", info),
  5. new InstanceReplicationTask(targetHost, Action.Register, info, null, true) {
  6. public EurekaHttpResponse<Void> execute() {
  7. return replicationClient.register(info);
  8. }
  9. },
  10. expiryTime
  11. );
  12. }

经过一系列的源码追踪,可以发现PeerAwareInstanceRegistryImpl的register()方法实现了服务的注册,并且向其他Eureka Server的Peer节点同步了该注册信息,那么register()方法被谁调用了呢?之前在Eureka Client的分析可以知道,Eureka Client是通过http来向Eureka Server注册的,那么Eureka Server肯定会提供一个注册的接口给Eureka Client调用,那么PeerAwareInstanceRegistryImpl的register()方法肯定最终会被暴露的Http接口所调用。在Idea开发工具,按住alt+鼠标左键,可以很快定位到ApplicationResource类的addInstance ()方法,即服务注册的接口,其代码如下:

  1. @POST
  2. @Consumes({"application/json", "application/xml"})
  3. public Response addInstance(InstanceInfo info,
  4. @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
  5. ...//省略代码
  6. registry.register(info, "true".equals(isReplication));
  7. return Response.status(204).build(); // 204 to be backwards compatible
  8. }

Renew服务续约

服务续约和服务注册非常类似,通过之前的分析可以知道,服务注册在Application Server程序启动之后开启,并同时开启服务续约的定时任务。在eureka-client-1.6.2.jar的DiscoveryClient的类下有renew()方法,其代码如下:

  1. /**
  2. * Renew with the eureka service by making the appropriate REST call
  3. */
  4. boolean renew() {
  5. EurekaHttpResponse<InstanceInfo> httpResponse;
  6. try {
  7. httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
  8. logger.debug("{} - Heartbeat status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
  9. if (httpResponse.getStatusCode() == 404) {
  10. REREGISTER_COUNTER.increment();
  11. logger.info("{} - Re-registering apps/{}", PREFIX + appPathIdentifier, instanceInfo.getAppName());
  12. return register();
  13. }
  14. return httpResponse.getStatusCode() == 200;
  15. } catch (Throwable e) {
  16. logger.error("{} - was unable to send heartbeat!", PREFIX + appPathIdentifier, e);
  17. return false;
  18. }
  19. }

另外服务端的续约接口在eureka-core:1.6.2.jar的 com.netflix.eureka包下的InstanceResource类下,接口方法为renewLease(),它是REST接口。为了减少类篇幅,省略了大部分代码的展示。其中有个registry.renew()方法,即服务续约,代码如下:

  1. @PUT
  2. public Response renewLease(...参数省略){
  3. ... 代码省略
  4. boolean isSuccess=registry.renew(app.getName(),id, isFromReplicaNode);
  5. ... 代码省略
  6. }

读者可以跟踪registry.renew的代码一直深入研究。在这里就不再多讲述。另外服务续约有2个参数是可以配置,即Application Server发送续约心跳的时间参数和Eureka Server在多长时间内没有收到心跳将实例剔除的时间参数,在默认的情况下这两个参数分别为30秒和90秒,官方给的建议是不要修改,如果有特殊要求还是可以调整的,只需要分别在Application Server和Eureka Server修改以下参数:

  1. eureka.instance.leaseRenewalIntervalInSeconds
  2. eureka.instance.leaseExpirationDurationInSeconds

最后,服务注册列表的获取、服务下线和服务剔除就不在这里进行源码跟踪解读,因为和服务注册和续约类似,有兴趣的朋友可以自己看下源码,深入理解。总的来说,通过读源码,可以发现,整体架构与前面小结的Eureka 的高可用架构图完全一致。

Eureka Client注册一个实例为什么这么慢

  • Application Server一启动(不是启动完成),不是立即向Eureka Server注册,它有一个延迟向服务端注册的时间,通过跟踪源码,可以发现默认的延迟时间为40秒,源码在eureka-client-1.6.2.jar的DefaultEurekaClientConfig类下,代码如下:
  1. public int getInitialInstanceInfoReplicationIntervalSeconds() {
  2. return configInstance.getIntProperty(
  3. namespace + INITIAL_REGISTRATION_REPLICATION_DELAY_KEY, 40).get();
  4. }
  • Eureka Server的响应缓存 Eureka Server维护每30秒更新的响应缓存,可通过更改配置eureka.server.responseCacheUpdateIntervalMs来修改。 所以即使实例刚刚注册,它也不会出现在调用/eureka /apps REST端点的结果中。

  • Eureka Server刷新缓存 Eureka客户端保留注册表信息的缓存。 该缓存每30秒更新一次(如前所述)。 因此,客户端决定刷新其本地缓存并发现其他新注册的实例可能需要30秒。

  • LoadBalancer Refresh 作为服务消费者一般配合Ribbon或Feign(Feign内部使用Ribbon)使用。Eureka Client启动后,作为服务提供者立即向Server注册,默认情况下每30s续约(renew);作为服务消费者立即向Server全量更新服务注册信息,默认情况下每30s增量更新服务注册信息;Ribbon延时1s向Client获取使用的服务注册信息,默认每30s更新使用的服务注册信息,只保存状态为UP的服务。Ribbon的负载平衡器从本地的Eureka Client获取服务注册列表信息。Ribbon本身还维护本地缓存,以避免为每个请求调用本地客户端。 此缓存每30秒刷新一次(可由ribbon.ServerListRefreshInterval配置)。 所以,可能需要30多秒才能使用新注册的实例。

 综上几个因素,一个新注册的实例,特别是启动较快的实例(默认延迟40秒注册),不能马上被Eureka Server发现。另外,刚注册的Eureka Client也不能立即被其他服务调用,因为调用方因为各种缓存没有及时的获取到新的注册列表。

Eureka的自我保护模式

当一个新的Eureka Server出现时,它尝试从相邻节点获取所有实例注册表信息。如果从Peer节点获取信息时出现问题,Eureka Server会尝试其他的Peer节点。如果服务器能够成功获取所有实例,则根据该信息设置应该接收的更新阈值。如果有任何时间,Eureka Server接收到的续约低于为该值配置的百分比(默认为15分钟内低于85%),则服务器开启自我保护模式,即不再剔除注册列表的信息。

这样做的好处就是,如果是Eureka Server自身的网络问题,导致Application Server的续约不上,Application Server的注册列表信息不再被删除,也就是Application Server还可以被Application Client消费。

附:SpringCloud之系列汇总跳转地址 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小惠珠哦/article/detail/963356
推荐阅读
相关标签
  

闽ICP备14008679号