Eureka 源码分析

总览

Eureka 分为 Server 和 Client。
而Eureka Server既作为server接受client的注册,又作为client向集群中的其他server实例注册自己。所以后文以Eureka Server为线索进行源码分析,既覆盖了server部分,也覆盖了client部分。

配置相关

通过org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration类进行自动配置。
注意该类的一个注解,
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
即,如果没有配置eureka.client.enabled,那么默认为true,开启eureka的client功能。
此类也会实例化org.springframework.cloud.netflix.eureka.EurekaClientConfigBeanorg.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean两个配置实例。
分别对应了eureka.clienteureka.instance的相关配置。eureka.client内的配置是作为client的一些行为相关的配置,如server地址,是否向server注册本client等。eureka.instance内的配置是作为instance的一些相关配置,如本实例的instanceId,hostname等。

向其他server发送请求

  1. 程序启动后,实例化bean的过程中
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();

这其中会实例化com.netflix.discovery.DiscoveryClient
DiscoveryClient的构造函数执行过程中会fetch registry。即

if (clientConfig.shouldFetchRegistry()) {
    boolean primaryFetchRegistryResult = fetchRegistry(false);

url为http://{host}:{port}/eureka/apps/
然后initScheduledTasks();
这些定时任务包括了

  • if shouldFetchRegistry ,fetch registry (默认 30秒后执行一次,然后每隔30秒执行一次)
    fetchRegistry(boolean forceFullRegistryFetch)方法内根据applications.getRegisteredApplications().size() == 0来抉择使用哪个url来fetch,http://{host}:{port}/eureka/apps/(全量更新)
    或是http://{host}:{port}/eureka/apps/delta(增量更新)
  • if shouldRegisterWithEureka
    • heartbeat (默认30秒后执行一次,然后每隔30秒执行一次)
      url为http://{host}:{port}/eureka/apps/UNKNOWN/{instanceId}?status=UP&lastDirtyTimestamp=1610865104367
    • InstanceInfoReplicator(默认40秒后执行一次)
      com.netflix.discovery.InstanceInfoReplicator
    • 注册状态变化监听器
      applicationInfoManager.registerStatusChangeListener(statusChangeListener);
      监听器现在不会被调用,接到事件时,会调用instanceInfoReplicator.onDemandUpdate();
      (被调用的过程详见2)
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
finishRefresh();

1中的实例化bean之后,会执行finishRefresh()。该方法会调用所有实现了Lifecycle接口的bean的Lifecycle.start()方法。然后依次调用
org.springframework.cloud.netflix.eureka.serviceregistry.EurekaAutoServiceRegistration#start
org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry#register
最后com.netflix.appinfo.ApplicationInfoManager#setInstanceStatusr中调用监听器。
1中注册的响应函数被调用com.netflix.discovery.InstanceInfoReplicator#onDemandUpdate
然后在新的线程中立即执行一次InstanceInfoReplicator.this.run();,然后每隔30秒周期执行一次本函数
函数中会执行discoveryClient.register();
发送http请求,url为http://{host}:{port}/eureka/apps/UNKNOWN,向server注册本实例信息。

注意
discoveryClient.register() 中的http请求发出后,eureka注册中心中就会注册上本服务,流量就会进入到服务器中
而springboot服务需要执行完TomcatWebServer.start(),才会暴露http端口号,才能实际接收http请求。见SpringBoot中Tomcat的源码分析一文
虽然二者是在不同的线程中执行的。但某些版本的springboot中,这两个函数触发顺序反了。有可能会先执行discoveryClient.register(),再执行TomcatWebServer.start()。即先注册至服务中,而此时http端口还不可用,最终导致服务的启动不平滑

  1. 发送请求的http client默认是com.netflix.discovery.shared.transport.jersey.JerseyApplicationClient
    继承了com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient抽象类
    实现了 com.netflix.discovery.shared.transport.EurekaHttpClient接口

接受请求

  1. 启动后EurekaController负责接收Eureka网页请求
  2. restfule接口
    https://github.com/Netflix/eureka/wiki/Eureka-REST-operations 文档中记录了restful接口实例。不过实际代码中使用的url与文档不同,url中不包含v2,虽然不包括v2,但默认版本是v2,所以逻辑一致。

2.1 eureka-core中的com.netflix.eureka.resources包内的*Resource类中包含了实例间通信的restful接口,功能类似controller。
2.2 spring-cloud-netflix-eureka-server-3.0.0.jar 包中的org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration#jerseyApplication方法实例化了javax.ws.rs.core.Application这个Bean。实例化的过程中会扫描2.1中提到的部分*Resource类。这些类会在后续的构造出url及其对对应的

2.3 jersey-server.jar中的com.sun.jersey.server.impl.application.WebApplicationImpl#_initiate方法末尾处

RulesMap rootRules = new RootResourceUriRules(this,
        resourceConfig, wadlFactory, injectableFactory).getRules();

这是根据2.2中扫描出的*Resource类构造出RulesMap,是一种规则映射,可以将restful请求映射到对应的处理method。

2.4 jersey-servlet.jar中的com.sun.jersey.spi.container.servlet.WebComponent#service方法中的语句_application.handleRequest(cRequest, w);,将http request与2.3中的ruleMap进行匹配,找到对应的处理method。
jersey-server.jar中的com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider.ResponseOutInvoker#_dispatch方法中通过反射调用具体的处理method。

InstanceRegistry

org.springframework.cloud.netflix.eureka.server.InstanceRegistry是核心类。
该类有一个缓存属性,是继承于父类的属性
com.netflix.eureka.registry.AbstractInstanceRegistry#responseCache,它默认使用的是com.netflix.eureka.registry.ResponseCacheImpl实现。
ResponseCacheImpl中有两个属性
ConcurrentMap readOnlyCacheMap = new ConcurrentHashMap()LoadingCache readWriteCacheMap。其中readWriteCacheMap是一个基于com.google.common.cache.LoadingCache构建的缓存。

com.netflix.eureka.registry.ResponseCacheImpl#generatePayload方法用于生成readWriteCacheMap缓存的值。缓存key为com.netflix.eureka.registry.KeygeneratePayload方法内部根据不同的Key执行不同的加载逻辑。

Eureka-CacheFillTimer线程每隔30s执行一次com.netflix.eureka.registry.ResponseCacheImpl#getCacheUpdateTask,将ResponseCacheImpl内的readWriteCacheMap中的缓存信息更新至readOnlyCacheMap

/eureka/apps/

拉取实例信息列表
http method: GET

  1. server 调用方
    调用链为依次为
getApplicationsInternal:189, AbstractJerseyEurekaHttpClient (com.netflix.discovery.shared.transport.jersey)
getApplications:167, AbstractJerseyEurekaHttpClient (com.netflix.discovery.shared.transport.jersey)
.....
getApplications:134, EurekaHttpClientDecorator (com.netflix.discovery.shared.transport.decorator)
getAndStoreFullRegistry:1101, DiscoveryClient (com.netflix.discovery)
fetchRegistry:1014, DiscoveryClient (com.netflix.discovery)
:441, DiscoveryClient (com.netflix.discovery)

server 调用方仅仅请求/eureka/apps/url,没有添加任何参数。
服务端返回的response是一个com.netflix.discovery.shared.Applications,然后将该对象处理一下之后,存储在com.netflix.discovery.DiscoveryClient#localRegionApps中。

  1. server 接收方
    handle method: com.netflix.eureka.resources.ApplicationsResource#getContainers
    该方法使用(entityType:Application, entityName:ALL_APPS,等)构造com.netflix.eureka.registry.Key,通过这个key在responseCache中查找缓存值。缓存的相关信息见上面的InstanceRegistry一节。
    如果没有缓存,使用com.netflix.eureka.registry.AbstractInstanceRegistry#getApplications生成返回值并缓存。此方法调用com.netflix.eureka.registry.AbstractInstanceRegistry#getApplicationsFromMultipleRegions,返回com.netflix.eureka.registry.AbstractInstanceRegistry#registry中的全部已注册实例信息。
    Eureka Server启动后发出的第一个请求就是这个请求,但是这时候收到的response是空列表,因为还没有任何实例在server中注册。

/eureka/apps/{appName}

向服务器注册实例信息
http method: POST

  1. server 调用方
    调用链为依次为
register:48, AbstractJerseyEurekaHttpClient (com.netflix.discovery.shared.transport.jersey)
......
register:56, EurekaHttpClientDecorator (com.netflix.discovery.shared.transport.decorator)
register:876, DiscoveryClient (com.netflix.discovery)
run:121, InstanceInfoReplicator (com.netflix.discovery)
run:101, InstanceInfoReplicator$1 (com.netflix.discovery)

server 调用方使用com.netflix.appinfo.InstanceInfo#getAppName作为url参数中的{appName},默认为UNKNOWN。POST body为InstanceInfo。

InstanceInfo实例化于org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration.RefreshableEurekaClientConfiguration#eurekaApplicationInfoManager,字段信息来源于org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean配置类。
而这个配置类又实例化于org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration#eurekaInstanceConfigBean
EurekaInstanceConfigBean的初始化过程会赋予一些一些关键字段默认值。

  • appName 默认为 UNKNOWN
  • hostInfo 默认来源于org.springframework.cloud.commons.util.InetUtils#findFirstNonLoopbackHostInfo,其中使用java.net.NetworkInterface#getNetworkInterfaces方法获取所有网卡信息。
  • ipAddress 默认为hostInfo.getIpAddress()
  • hostname 默认为hostInfo.getHostname()
  • instanceId 默认为{hostname}:{port}
    之后这些字段会被人工配置值覆盖掉。
  1. server 接收方
    handle method
    首先根据路径/eureka/apps/{appName}匹配至方法com.netflix.eureka.resources.ApplicationsResource#getApplicationResource
    该方法实例化了一个com.netflix.eureka.resources.ApplicationResource对象并返回,然后继续匹配,匹配至该对象的方法com.netflix.eureka.resources.ApplicationResource#addInstance
    注册方法
    org.springframework.cloud.netflix.eureka.server.InstanceRegistry#register()
    注册到 >> 嵌套的map中。
    注册成功后responseCache.invalidate()重置缓存,此缓存曾在上面的 InstanceRegistry 一节中提到。
    然后执行replicateToPeers(),将信息同步至集群,使用类似下面的请求来同步信息。
    http://server-peer0:8080/eureka/peerreplication/batch/

/eureka/apps/{appName}/{instanceId}?status={status}&lastDirtyTimestamp={lastDirtyTimestamp}

心跳信息
http method: PUT

  1. server 调用方
sendHeartBeat:103, AbstractJerseyEurekaHttpClient (com.netflix.discovery.shared.transport.jersey)
.....
sendHeartBeat:89, EurekaHttpClientDecorator (com.netflix.discovery.shared.transport.decorator)
renew:893, DiscoveryClient (com.netflix.discovery)
run:1457, DiscoveryClient$HeartbeatThread (com.netflix.discovery)

该请求没有http body

  1. server 接收方
    依次匹配
    com.netflix.eureka.resources.ApplicationsResource#getApplicationResource
    com.netflix.eureka.resources.ApplicationResource#getInstanceInfo
    com.netflix.eureka.resources.InstanceResource#renewLease
    刷新存活时间,同步至集群

/eureka/apps/delta

增量更新
http method: GET

  1. server 调用方
getApplicationsInternal:189, AbstractJerseyEurekaHttpClient (com.netflix.discovery.shared.transport.jersey)
getDelta:172, AbstractJerseyEurekaHttpClient (com.netflix.discovery.shared.transport.jersey)
......
getDelta:149, EurekaHttpClientDecorator (com.netflix.discovery.shared.transport.decorator)
getAndUpdateDelta:1135, DiscoveryClient (com.netflix.discovery)
fetchRegistry:1016, DiscoveryClient (com.netflix.discovery)
refreshRegistry:1531, DiscoveryClient (com.netflix.discovery)
run:1498, DiscoveryClient$CacheRefreshThread (com.netflix.discovery)

此请求的返回值和/eureka/apps/{appName}请求一样,是一个com.netflix.discovery.shared.Applications。然后通过com.netflix.discovery.DiscoveryClient#updateDelta方法将增量的变化信息存储至com.netflix.discovery.DiscoveryClient#localRegionApps中。

  1. server 接收方
    com.netflix.eureka.resources.ApplicationsResource#getContainerDifferential
    该方法使用(entityType:Application, entityName:ALL_APPS_DELTA,等)构造com.netflix.eureka.registry.Key,通过这个key在responseCache中查找缓存值。缓存的相关信息见上面的InstanceRegistry一节。
    如果没有缓存,使用com.netflix.eureka.registry.AbstractInstanceRegistry#getApplicationDeltass生成返回值并缓存。

Client

通过上文对 Server 的流程可知,Eureka Client 节点的主要逻辑均在于com.netflix.discovery.DiscoveryClient类,集群内的节点信息存储于localRegionApps属性中,使用各种get方法获取。

你可能感兴趣的:(Eureka 源码分析)