Eureka: 字面意思是"找到了",Netflix技术栈中,做为服务注册中心对整个微服务架构起着最核心的整合做用spring
Register:服务注册,Eureka客户端向Eureka Server注册时,它提供自身的元数据,好比IP地址、端口,运行情况指示符URL,主页等json
Renew:服务续约,Eureka客户端会每隔30秒发送一次心跳来续约。 经过续约来告知Eureka Server该Eureka客户仍然存在,没有出现问题。 正常状况下,若是Eureka Server在90秒没有收到Eureka客户的续约,它会将实例从其注册表中删除。 建议不要更改续约间隔。缓存
Fetch Registries:获取注册列表信息, Eureka客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其余服务,从而进行远程调用。 该注册列表信息按期(每30秒钟)更新一次。每次返回注册列表信息可能与Eureka客户端的缓存信息不一样, Eureka客户端自动处理。 若是因为某种缘由致使注册列表信息不能及时匹配,Eureka客户端则会从新获取整个注册表信息。 Eureka服务器缓存注册列表信息,整个注册表以及每一个应用程序的信息进行了压缩,压缩内容和没有压缩的内容彻底相同。 Eureka客户端和Eureka 服务器可使用JSON / XML格式进行通信。在默认的状况下Eureka客户端使用压缩JSON格式来获取注册列表的信息。服务器
Cancel:服务下线 ,Eureka客户端在程序关闭时向Eureka服务器发送取消请求。 发送请求后,该客户端实例信息将从服务器的实例注册表中删除。该下线请求不会自动完成,它须要调用如下内容:网络
DiscoveryManager.getInstance().shutdownComponent();
Eviction 服务剔除 ,在默认的状况下,当Eureka客户端连续90秒没有向Eureka服务器发送服务续约,即心跳,Eureka服务器会将该服务实例从服务注册列表删除,即服务剔除。架构
Eureka的高可用架构并发
能够看出在这个体系中,有2个角色,即Eureka Server和Eureka Client。 Eureka Client又分为Applicaton Service和Application Client,即服务提供者和服务消费者。每一个区域有一个Eureka集群,而且每一个区域至少有一个eureka服务器能够处理区域故障,以防服务器瘫痪。app
Eureka Client向Eureka Serve注册,并将本身的一些客户端信息发送Eureka Serve。而后,Eureka Client经过向Eureka Serve发送心跳(每30秒)来续约服务的。 若是客户端持续不能续约,那么,它将在大约90秒内从服务器注册表中删除。 注册信息和续订被复制到集群中的Eureka Serve全部节点。 来自任何区域的Eureka Client均可以查找注册表信息(每30秒发生一次)。根据这些注册表信息,Application Client能够远程调用Applicaton Service来消费服务。ide
***一个eureka client注册多个指定的eureka server *** eureka.client.serviceUrl.defaultZone=http://10.201.200.72:8761/eureka/,http://10.201.200.76:8761/eureka/微服务
Register服务注册 服务注册,即Eureka Client向Eureka Server提交本身的服务信息,包括IP地址、端口、service ID等信息。若是Eureka Client没有写service ID,则默认为 ${spring.application.name}。
服务注册其实很简单,在Eureka Client启动的时候,将自身的服务的信息发送到Eureka Server。如今来简单的阅读下源码。在Maven的依赖包下,找到eureka-client-1.6.2.jar包。在com.netflix.discovery包下有个DiscoveryClient类,该类包含了Eureka Client向Eureka Server的相关方法。其中DiscoveryClient实现了EurekaClient接口,而且它是一个单例模式,而EurekaClient继承了LookupService接口。它们之间的关系如图所示。
在DiscoveryClient类有一个服务注册的方法register(),该方法是经过Http请求向Eureka Client注册。其代码以下:
/** ](https://static.oschina.net/uploads/img/201802/08132817_xC3Z.png "在这里输入图片标题") * Register with the eureka service by making the appropriate REST call. */ boolean register() throws Throwable { logger.info(PREFIX + appPathIdentifier + ": registering service..."); EurekaHttpResponse<Void> httpResponse; try { httpResponse = eurekaTransport.registrationClient.register(instanceInfo); } catch (Exception e) { logger.warn("{} - registration failed {}", PREFIX + appPathIdentifier, e.getMessage(), e); throw e; } if (logger.isInfoEnabled()) { logger.info("{} - registration status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode()); } return httpResponse.getStatusCode() == 204; }
在DiscoveryClient类继续追踪register()方法,它被InstanceInfoReplicator 类的run()方法调用,其中InstanceInfoReplicator实现了Runnable接口,run()方法代码以下:
public void run() { try { discoveryClient.refreshInstanceInfo(); Long dirtyTimestamp = instanceInfo.isDirtyWithTime(); if (dirtyTimestamp != null) { discoveryClient.register(); instanceInfo.unsetIsDirty(dirtyTimestamp); } } catch (Throwable t) { logger.warn("There was a problem with the instance info replicator", t); } finally { Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS); scheduledPeriodicRef.set(next); } }
而InstanceInfoReplicator类是在DiscoveryClient初始化过程当中使用的,其中有一个initScheduledTasks()方法。该方法主要开启了获取服务注册列表的信息,若是须要向Eureka Server注册,则开启注册,同时开启了定时向Eureka Server服务续约的定时任务,具体代码以下:
private void initScheduledTasks() { ...//省略了任务调度获取注册列表的代码 if (clientConfig.shouldRegisterWithEureka()) { ... // Heartbeat timer scheduler.schedule( new TimedSupervisorTask( "heartbeat", scheduler, heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new HeartbeatThread() ), renewalIntervalInSecs, TimeUnit.SECONDS); // InstanceInfo replicator instanceInfoReplicator = new InstanceInfoReplicator( this, instanceInfo, clientConfig.getInstanceInfoReplicationIntervalSeconds(), 2); // burstSize statusChangeListener = new ApplicationInfoManager.StatusChangeListener() { @Override public String getId() { return "statusChangeListener"; } @Override public void notify(StatusChangeEvent statusChangeEvent) { instanceInfoReplicator.onDemandUpdate(); } }; ... }
而后在来看Eureka server端的代码,在Maven的eureka-core:1.6.2的jar包下。打开com.netflix.eureka包,很轻松的就发现了又一个EurekaBootStrap的类,BootStrapContext具备最早初始化的权限,因此先看这个类。
protected void initEurekaServerContext() throws Exception { ...//省略代码 PeerAwareInstanceRegistry registry; if (isAws(applicationInfoManager.getInfo())) { ...//省略代码,若是是AWS的代码 } else { registry = new PeerAwareInstanceRegistryImpl( eurekaServerConfig, eurekaClient.getEurekaClientConfig(), serverCodecs, eurekaClient ); } PeerEurekaNodes peerEurekaNodes = getPeerEurekaNodes( registry, eurekaServerConfig, eurekaClient.getEurekaClientConfig(), serverCodecs, applicationInfoManager ); }
其中PeerAwareInstanceRegistryImpl和PeerEurekaNodes两个类看其命名,应该和服务注册以及Eureka Server高可用有关。先追踪PeerAwareInstanceRegistryImpl类,在该类有个register()方法,该方法提供了注册,而且将注册后信息同步到其余的Eureka Server服务。代码以下:
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); }
其中 super.register(info, leaseDuration, isReplication)方法,点击进去到子类AbstractInstanceRegistry能够发现更多细节,其中注册列表的信息被保存在一个Map中。replicateToPeers()方法,即同步到其余Eureka Server的其余Peers节点,追踪代码,发现它会遍历循环向全部的Peers节点注册,最终执行类PeerEurekaNodes的register()方法,该方法经过执行一个任务向其余节点同步该注册信息,代码以下:
public void register(final InstanceInfo info) throws Exception { long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info); batchingDispatcher.process( taskId("register", info), new InstanceReplicationTask(targetHost, Action.Register, info, null, true) { public EurekaHttpResponse<Void> execute() { return replicationClient.register(info); } }, expiryTime ); }
通过一系列的源码追踪,能够发现PeerAwareInstanceRegistryImpl的register()方法实现了服务的注册,而且向其余Eureka Server的Peer节点同步了该注册信息,那么register()方法被谁调用了呢?以前在Eureka Client的分析能够知道,Eureka Client是经过 http来向Eureka Server注册的,那么Eureka Server确定会提供一个注册的接口给Eureka Client调用,那么PeerAwareInstanceRegistryImpl的register()方法确定最终会被暴露的Http接口所调用。在Idea开发工具,按住alt+鼠标左键,能够很快定位到ApplicationResource类的addInstance ()方法,即服务注册的接口,其代码以下:
@POST @Consumes({"application/json", "application/xml"}) public Response addInstance(InstanceInfo info, @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) { ...//省略代码 registry.register(info, "true".equals(isReplication)); return Response.status(204).build(); // 204 to be backwards compatible }
Renew服务续约
服务续约和服务注册很是相似,经过以前的分析能够知道,服务注册在Eureka Client程序启动以后开启,并同时开启服务续约的定时任务。在eureka-client-1.6.2.jar的DiscoveryClient的类下有renew()方法,其代码以下:
boolean renew() { EurekaHttpResponse<InstanceInfo> httpResponse; try { httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null); logger.debug("{} - Heartbeat status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode()); if (httpResponse.getStatusCode() == 404) { REREGISTER_COUNTER.increment(); logger.info("{} - Re-registering apps/{}", PREFIX + appPathIdentifier, instanceInfo.getAppName()); return register(); } return httpResponse.getStatusCode() == 200; } catch (Throwable e) { logger.error("{} - was unable to send heartbeat!", PREFIX + appPathIdentifier, e); return false; } }
另外服务端的续约接口在eureka-core:1.6.2.jar的 com.netflix.eureka包下的InstanceResource类下,接口方法为renewLease(),它是REST接口。为了减小类篇幅,省略了大部分代码的展现。其中有个registry.renew()方法,即服务续约,代码以下:
@PUT public Response renewLease(...参数省略){ ... 代码省略 boolean isSuccess=registry.renew(app.getName(),id, isFromReplicaNode); ... 代码省略 }
另外服务续约有2个参数是能够配置,即Eureka Client发送续约心跳的时间参数和Eureka Server在多长时间内没有收到心跳将实例剔除的时间参数,在默认的状况下这两个参数分别为30秒和90秒,官方给的建议是不要修改,若是有特殊要求仍是能够调整的,只须要分别在Eureka Client和Eureka Server修改如下参数: eureka.instance.leaseRenewalIntervalInSeconds eureka.instance.leaseExpirationDurationInSeconds
Eureka Client注册一个实例为何这么慢 Eureka Client一启动(不是启动完成),不是当即向Eureka Server注册,它有一个延迟向服务端注册的时间,经过跟踪源码,能够发现默认的延迟时间为40秒,源码在eureka-client-1.6.2.jar的DefaultEurekaClientConfig类下,代码以下:
public int getInitialInstanceInfoReplicationIntervalSeconds() { return configInstance.getIntProperty( namespace + INITIAL_REGISTRATION_REPLICATION_DELAY_KEY, 40).get(); }
Eureka Server的响应缓存 Eureka Server维护每30秒更新的响应缓存,可经过更改配置eureka.server.responseCacheUpdateIntervalMs来修改。 因此即便实例刚刚注册,它也不会出如今调用/ eureka / apps REST端点的结果中。
Eureka Client刷新缓存 Eureka客户端保留注册表信息的缓存。 该缓存每30秒更新一次(如前所述)。 因 此,客户端决定刷新其本地缓存并发现其余新注册的实例可能须要30秒。
LoadBalancer Refresh Ribbon的负载平衡器从本地的Eureka Client获取服务注册列表信息。Ribbon自己还维护本地缓存,以免为每一个请求调用本地客户端。 此缓存每30秒刷新一次(可由ribbon.ServerListRefreshInterval配置)。 因此,可能须要30多秒才能使用新注册的实例。
综上几个因素,一个新注册的实例,特别是启动较快的实例(默认延迟40秒注册),不能立刻被Eureka Server发现。另外,刚注册的Eureka Client也不能当即被其余服务调用,由于调用方由于各类缓存没有及时的获取到新的注册列表。
Eureka 的自我保护模式 当一个新的Eureka Server出现时,它尝试从相邻节点获取全部实例注册表信息。 若是从Peer节点获取信息时出现问题,Eureka Serve会尝试其余的Peer节点。若是服务器可以成功获取全部实例,则根据该信息设置应该接收的更新阈值。
若是有任什么时候间,Eureka Serve接收到的续约低于为该值配置的百分比(默认为15分钟内低于85%),则服务器开启自我保护模式,即再也不剔除注册列表的信息。
这样作的好处就是,若是是Eureka Server自身的网络问题,致使Eureka Client的续约不上,Eureka Client的注册列表信息再也不被删除,也就是Eureka Client还能够被其余服务消费。