怀化冰山涯IT网站建设公司,海口 网站建设,如何用织梦cms做网站,做图片格式跟尺度用哪个网站好服务治理
服务注册
在服务治理框架中#xff0c;通常都会构建一个注册中心#xff0c;每个服务单元向注册中心登记自己提供的服务#xff0c;将主机与端口号、版本号、通信协议等一些附加信息告知注册中心#xff0c;注册中心按服务名分类组织服务清单。当服务启动后通常都会构建一个注册中心每个服务单元向注册中心登记自己提供的服务将主机与端口号、版本号、通信协议等一些附加信息告知注册中心注册中心按服务名分类组织服务清单。当服务启动后会向注册中心注册自己的服务那么注册中心就会有一个服务清单。另外服务注册中心还需要以心跳的方式去监测清单中的服务是否可用若不可用需要从服务清单中剔除达到排除故障服务的效果。 服务发现
由于在服务治理框架下运作服务间的调用不再通过指定具体的实例地址来实现而是通过向服务名发起请求调用实现。所以服务调用方在调用服务提供方接口时候并不知道具体的服务实例位置。因此调用方需要向服务注册中心咨询服务并获取所有服务的实例清单以实现对具体服务实例的访问。框架为了性能等因素不会采用每次都向服务注册中心获取服务方式并且不同的应用场景在缓存和服务剔除等机制上也会有一些不同的实现策略。 服务治理机制 服务治理是如何运作的
●“服务注册中心-1”和“服务注册中心-2”它们互相注册组成了高可用集群。 ●“服务提供者”启动了两个实例一个注册到“服务注册中心-1”上另外一个注册到“服务注册中心-2”上。 ●还有两个“服务消费者”它们也都分别只指向了一个注册中心。
服务提供者
服务注册
“服务提供者”在启动的时候会通过发送REST请求的方式将自己注册到Eureka Server上同时带上了自身服务的一些元数据信息。Eureka Server接收到这个REST请求之后将元数据信息存储在一个双层结构Map中其中第一层的key是服务名第二层的key是具体服务的实例名。
服务同步
如架构图中所示这里的两个服务提供者分别注册到了两个不同的服务注册中心上也就是说它们的信息分别被两个服务注册中心所维护。此时由于服务注册中心之间因互相注册为服务当服务提供者发送注册请求到一个服务注册中心时它会将该请求转发给集群中相连的其他注册中心从而实现注册中心之间的服务同步。通过服务同步两个服务提供者的服务信息就可以通过这两台服务注册中心中的任意一台获取到。
服务续约
在注册完服务之后服务提供者会维护一个心跳用来持续告诉Eureka Server:“我还活着”以防止 Eureka Server 的“剔除任务”将该服务实例从服务列表中排除出去我们称该操作为服务续约Renew。
关于服务续约有两个重要属性我们可以关注并根据需要来进行调整 eureka.instance.lease-renewal-interval-in-seconds30 // 定义服务续 约 任 务 的 调 用 间 隔 时 间 默 认 为 30 秒 eureka.instance.lease-expiration-duration-in-seconds90 // 定义服务失效的时间默认为90秒 服务消费者
获取服务
到这里在服务注册中心已经注册了一个服务并且该服务有两个实例。当我们启动服务消费者的时候它会发送一个 REST 请求给服务注册中心来获取上面注册的服务清单。为了性能考虑EurekaServer会维护一份只读的服务清单来返回给客户端同时该缓存清单会每隔30秒更新一次。
获取服务是服务消费者的基础所以必须确保eureka.client.fetchregistrytrue参数没有被修改成false该值默认为true。
若希望修改缓存清 单 的 更 新 时 间 可 以 通 过 eureka.client.registry-fetch-intervalseconds30参数进行修改该参数默认值为30单位为秒。
服务调用
服务消费者在获取服务清单后通过服务名可以获得具体提供服务的实例名和该实例的元数据信息。因为有这些服务实例的详细信息所以客户端可以根据自己的需要决定具体调用哪个实例在Ribbon中会默认采用轮询的方式进行调用从而实现客户端的负载均衡。
服务下线
在系统运行过程中必然会面临关闭或重启服务的某个实例的情况在服务关闭期间我们自然不希望客户端会继续调用关闭了的实例。所以在客户端程序中当服务实例进行正常的关闭操作时它会触发一个服务下线的REST请求给Eureka Server告诉服务注册中心“我要下线了”。服务端在接收到请求之后将该服务状态置为下线DOWN并把该下线事件传播出去。
服务注册中心
失效剔除
有些时候我们的服务实例并不一定会正常下线可能由于内存溢出、网络故障等原因使得服务不能正常工作而服务注册中心并未收到“服务下线”的请求。为了从服务列表中将这些无法提供服务的实例剔除Eureka Server在启动的时候会创建一个定时任务默认每隔一段时间默认为60秒将当前清单中超时默认为90秒没有续约的服务剔除出去。
自我保护
当我们在本地调试基于Eureka的程序时基本上都会碰到这样一个问题在服务注册中心的信息面板中出现类似下面的红色警告信息EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMINGINSTANCES ARE UP WHEN THEYRE NOT.RENEWALS ARELESSER THAN THRESHOLD AND HENCE THE INSTANCES ARENOT BEING EXPIRED JUST TO BE SAFE.实际上该警告就是触发了Eureka Server的自我保护机制。之前我们介绍过服务注册到Eureka Server之后会维护一个心跳连接告诉Eureka Server自己还活着。Eureka Server在运行期间会统计心跳失败的比例在15分钟之内是否低于85%如果出现低于的情况在单机调试的时候很容易满足实际在生产环境上通常是由于网络不稳定导致,Eureka Server 会将当前的实例注册信息保护起来让这些实例不会过期尽可能保护这些注册信息。但是在这段保护期间内实例若出现问题那么客户端很容易拿到实际已经不存在的服务实例会出现调用失败的情况所以客户端必须要有容错机制比如可以使用请求重试、断路器等机制。
源码分析
注册中心客户端主类配置EnableDiscoveryClient org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient实现了org.springframework.cloud.client.discovery.DiscoveryClient接口 EurekaDiscoveryClient是对org.springframework.cloud.client.discovery.DiscoveryClient接口的实现是实现对Eureka发现服务的封装该实现类依赖了com.netflix.discovery.EurekaClient接口EurekaClient接口继承了LookupService接口。
如何获取Eureka服务地址列表
DiscoveryClient方法com.netflix.discovery.DiscoveryClient#getDiscoveryServiceUrls --- com.netflix.discovery.endpoint.EndpointUtils#getServiceUrlsFromConfig
public static ListString getServiceUrlsFromConfig(EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) {ListString orderedUrls new ArrayListString();String region getRegion(clientConfig);String[] availZones clientConfig.getAvailabilityZones(clientConfig.getRegion());if (availZones null || availZones.length 0) {availZones new String[1];availZones[0] DEFAULT_ZONE;}logger.debug(The availability zone for the given region {} are {}, region, availZones);int myZoneOffset getZoneOffset(instanceZone, preferSameZone, availZones);ListString serviceUrls clientConfig.getEurekaServerServiceUrls(availZones[myZoneOffset]);if (serviceUrls ! null) {orderedUrls.addAll(serviceUrls);}int currentOffset myZoneOffset (availZones.length - 1) ? 0 : (myZoneOffset 1);while (currentOffset ! myZoneOffset) {serviceUrls clientConfig.getEurekaServerServiceUrls(availZones[currentOffset]);if (serviceUrls ! null) {orderedUrls.addAll(serviceUrls);}if (currentOffset (availZones.length - 1)) {currentOffset 0;} else {currentOffset;}}if (orderedUrls.size() 1) {throw new IllegalArgumentException(DiscoveryClient: invalid serviceUrl specified!);}return orderedUrls;}调用真正获取ServiceUrls方法为getEurekaServerServiceUrls在EurekaClientConfigBean类的方法org.springframework.cloud.netflix.eureka.EurekaClientConfigBean#getEurekaServerServiceUrls实现
public ListString getEurekaServerServiceUrls(String myZone) {String serviceUrls this.serviceUrl.get(myZone);if (serviceUrls null || serviceUrls.isEmpty()) {serviceUrls this.serviceUrl.get(DEFAULT_ZONE);}if (!StringUtils.isEmpty(serviceUrls)) {final String[] serviceUrlsSplit StringUtils.commaDelimitedListToStringArray(serviceUrls);ListString eurekaServiceUrls new ArrayList(serviceUrlsSplit.length);for (String eurekaServiceUrl : serviceUrlsSplit) {if (!endsWithSlash(eurekaServiceUrl)) {eurekaServiceUrl /;}eurekaServiceUrls.add(eurekaServiceUrl.trim());}return eurekaServiceUrls;}return new ArrayList();}服务注册
DiscoveryClient类构造函数com.netflix.discovery.DiscoveryClient#DiscoveryClient(com.netflix.appinfo.ApplicationInfoManager, com.netflix.discovery.EurekaClientConfig, com.netflix.discovery.AbstractDiscoveryClientOptionalArgs, javax.inject.Providercom.netflix.discovery.BackupRegistry, com.netflix.discovery.shared.resolver.EndpointRandomizer) 中调用了com.netflix.discovery.DiscoveryClient#initScheduledTasks方法主要实现
com.netflix.discovery.DiscoveryClient#initScheduledTaskscom.netflix.discovery.DiscoveryClient#initScheduledTasksInstanceInfoReplicatorprivate void initScheduledTasks() {if (clientConfig.shouldFetchRegistry()) {// registry cache refresh timerint registryFetchIntervalSeconds clientConfig.getRegistryFetchIntervalSeconds();int expBackOffBound clientConfig.getCacheRefreshExecutorExponentialBackOffBound();cacheRefreshTask new TimedSupervisorTask(cacheRefresh,scheduler,cacheRefreshExecutor,registryFetchIntervalSeconds,TimeUnit.SECONDS,expBackOffBound,new CacheRefreshThread());scheduler.schedule(cacheRefreshTask,registryFetchIntervalSeconds, TimeUnit.SECONDS);}if (clientConfig.shouldRegisterWithEureka()) {int renewalIntervalInSecs instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();int expBackOffBound clientConfig.getHeartbeatExecutorExponentialBackOffBound();logger.info(Starting heartbeat executor: renew interval is: {}, renewalIntervalInSecs);// Heartbeat timerheartbeatTask new TimedSupervisorTask(heartbeat,scheduler,heartbeatExecutor,renewalIntervalInSecs,TimeUnit.SECONDS,expBackOffBound,new HeartbeatThread());scheduler.schedule(heartbeatTask,renewalIntervalInSecs, TimeUnit.SECONDS);// InstanceInfo replicatorinstanceInfoReplicator new InstanceInfoReplicator(this,instanceInfo,clientConfig.getInstanceInfoReplicationIntervalSeconds(),2); // burstSizestatusChangeListener new ApplicationInfoManager.StatusChangeListener() {Overridepublic String getId() {return statusChangeListener;}Overridepublic void notify(StatusChangeEvent statusChangeEvent) {if (InstanceStatus.DOWN statusChangeEvent.getStatus() ||InstanceStatus.DOWN statusChangeEvent.getPreviousStatus()) {// log at warn level if DOWN was involvedlogger.warn(Saw local status change event {}, statusChangeEvent);} else {logger.info(Saw local status change event {}, statusChangeEvent);}instanceInfoReplicator.onDemandUpdate();}};if (clientConfig.shouldOnDemandUpdateStatusChange()) {applicationInfoManager.registerStatusChangeListener(statusChangeListener);}instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());} else {logger.info(Not registering with Eureka server per configuration);}}可看出有两个if判断第一个是shouldFetchRegistry是否去Eureka服务获取eureka注册信息第二个shouldRegisterWithEureka是否去注册eureka信息以便让其他实例发现。此处是服务注册关注第二个if里面实现内容发现有个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);}}主要实现com.netflix.discovery.DiscoveryClient#register /*** Register with the eureka service by making the appropriate REST call.*/boolean register() throws Throwable {logger.info(PREFIX {}: registering service..., appPathIdentifier);EurekaHttpResponseVoid httpResponse;try {httpResponse eurekaTransport.registrationClient.register(instanceInfo);} catch (Exception e) {logger.warn(PREFIX {} - registration failed {}, appPathIdentifier, e.getMessage(), e);throw e;}if (logger.isInfoEnabled()) {logger.info(PREFIX {} - registration status: {}, appPathIdentifier, httpResponse.getStatusCode());}return httpResponse.getStatusCode() Status.NO_CONTENT.getStatusCode();}此处便是eureka服务注册的核心可发现注册操作是通过REST请求方式进行的
顺便说明
RESTful是HTTP接口调用的一种特殊实现遵循REST架构风格的规范能够提供更加标准化、统一化、可读性和易用性的API设计。RESTful调用相对于HTTP接口调用来说具有更加清晰明了、易于理解和维护的API设计扩展性和灵活性也更强。 服务获取与服务续约
com.netflix.discovery.DiscoveryClient#initScheduledTasks方法中可看出有两个定时任务一个是服务获取 和 服务续约
// 服务获取
cacheRefreshTask new TimedSupervisorTask(cacheRefresh,scheduler,cacheRefreshExecutor,registryFetchIntervalSeconds,TimeUnit.SECONDS,expBackOffBound,new CacheRefreshThread());scheduler.schedule(cacheRefreshTask,registryFetchIntervalSeconds, TimeUnit.SECONDS);// 服务续约heartbeatTask new TimedSupervisorTask(heartbeat,scheduler,heartbeatExecutor,renewalIntervalInSecs,TimeUnit.SECONDS,expBackOffBound,new HeartbeatThread());scheduler.schedule(heartbeatTask,renewalIntervalInSecs, TimeUnit.SECONDS);服务获取主要实现com.netflix.discovery.DiscoveryClient.CacheRefreshThread
class CacheRefreshThread implements Runnable {public void run() {refreshRegistry();}}VisibleForTestingvoid refreshRegistry() {try {boolean isFetchingRemoteRegionRegistries isFetchingRemoteRegionRegistries();boolean remoteRegionsModified false;// This makes sure that a dynamic change to remote regions to fetch is honored.String latestRemoteRegions clientConfig.fetchRegistryForRemoteRegions();if (null ! latestRemoteRegions) {String currentRemoteRegions remoteRegionsToFetch.get();if (!latestRemoteRegions.equals(currentRemoteRegions)) {// Both remoteRegionsToFetch and AzToRegionMapper.regionsToFetch need to be in syncsynchronized (instanceRegionChecker.getAzToRegionMapper()) {if (remoteRegionsToFetch.compareAndSet(currentRemoteRegions, latestRemoteRegions)) {String[] remoteRegions latestRemoteRegions.split(,);remoteRegionsRef.set(remoteRegions);instanceRegionChecker.getAzToRegionMapper().setRegionsToFetch(remoteRegions);remoteRegionsModified true;} else {logger.info(Remote regions to fetch modified concurrently, ignoring change from {} to {}, currentRemoteRegions, latestRemoteRegions);}}} else {// Just refresh mapping to reflect any DNS/Property changeinstanceRegionChecker.getAzToRegionMapper().refreshMapping();}}boolean success fetchRegistry(remoteRegionsModified);if (success) {registrySize localRegionApps.get().size();lastSuccessfulRegistryFetchTimestamp System.currentTimeMillis();}if (logger.isDebugEnabled()) {StringBuilder allAppsHashCodes new StringBuilder();allAppsHashCodes.append(Local region apps hashcode: );allAppsHashCodes.append(localRegionApps.get().getAppsHashCode());allAppsHashCodes.append(, is fetching remote regions? );allAppsHashCodes.append(isFetchingRemoteRegionRegistries);for (Map.EntryString, Applications entry : remoteRegionVsApps.entrySet()) {allAppsHashCodes.append(, Remote region: );allAppsHashCodes.append(entry.getKey());allAppsHashCodes.append( , apps hashcode: );allAppsHashCodes.append(entry.getValue().getAppsHashCode());}logger.debug(Completed cache refresh task for discovery. All Apps hash code is {} ,allAppsHashCodes);}} catch (Throwable e) {logger.error(Cannot fetch registry from server, e);}}服务续约主要实现com.netflix.discovery.DiscoveryClient.HeartbeatThread
private class HeartbeatThread implements Runnable {public void run() {if (renew()) {lastSuccessfulHeartbeatTimestamp System.currentTimeMillis();}}}/*** Renew with the eureka service by making the appropriate REST call*/boolean renew() {EurekaHttpResponseInstanceInfo httpResponse;try {httpResponse eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);logger.debug(PREFIX {} - Heartbeat status: {}, appPathIdentifier, httpResponse.getStatusCode());if (httpResponse.getStatusCode() Status.NOT_FOUND.getStatusCode()) {REREGISTER_COUNTER.increment();logger.info(PREFIX {} - Re-registering apps/{}, appPathIdentifier, instanceInfo.getAppName());long timestamp instanceInfo.setIsDirtyWithTime();boolean success register();if (success) {instanceInfo.unsetIsDirty(timestamp);}return success;}return httpResponse.getStatusCode() Status.OK.getStatusCode();} catch (Throwable e) {logger.error(PREFIX {} - was unable to send heartbeat!, appPathIdentifier, e);return false;}}服务续约只是发送了一个REST请求 服务注册中心处理
服务注册 /*** Registers information about a particular instance for an* {link com.netflix.discovery.shared.Application}.** param info* {link InstanceInfo} information of the instance.* param isReplication* a header parameter containing information whether this is* replicated from other nodes.*/POSTConsumes({application/json, application/xml})public Response addInstance(InstanceInfo info,HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {logger.debug(Registering instance {} (replication{}), info.getId(), isReplication);// validate that the instanceinfo contains all the necessary required fieldsif (isBlank(info.getId())) {return Response.status(400).entity(Missing instanceId).build();} else if (isBlank(info.getHostName())) {return Response.status(400).entity(Missing hostname).build();} else if (isBlank(info.getIPAddr())) {return Response.status(400).entity(Missing ip address).build();} else if (isBlank(info.getAppName())) {return Response.status(400).entity(Missing appName).build();} else if (!appName.equals(info.getAppName())) {return Response.status(400).entity(Mismatched appName, expecting appName but was info.getAppName()).build();} else if (info.getDataCenterInfo() null) {return Response.status(400).entity(Missing dataCenterInfo).build();} else if (info.getDataCenterInfo().getName() null) {return Response.status(400).entity(Missing dataCenterInfo Name).build();}// handle cases where clients may be registering with bad DataCenterInfo with missing dataDataCenterInfo dataCenterInfo info.getDataCenterInfo();if (dataCenterInfo instanceof UniqueIdentifier) {String dataCenterInfoId ((UniqueIdentifier) dataCenterInfo).getId();if (isBlank(dataCenterInfoId)) {boolean experimental true.equalsIgnoreCase(serverConfig.getExperimental(registration.validation.dataCenterInfoId));if (experimental) {String entity DataCenterInfo of type dataCenterInfo.getClass() must contain a valid id;return Response.status(400).entity(entity).build();} else if (dataCenterInfo instanceof AmazonInfo) {AmazonInfo amazonInfo (AmazonInfo) dataCenterInfo;String effectiveId amazonInfo.get(AmazonInfo.MetaDataKey.instanceId);if (effectiveId null) {amazonInfo.getMetadata().put(AmazonInfo.MetaDataKey.instanceId.getName(), info.getId());}} else {logger.warn(Registering DataCenterInfo of type {} without an appropriate id, dataCenterInfo.getClass());}}}registry.register(info, true.equals(isReplication));return Response.status(204).build(); // 204 to be backwards compatible}最终调用了org.springframework.cloud.netflix.eureka.server.InstanceRegistry#register(com.netflix.appinfo.InstanceInfo, boolean)方法
Overridepublic void register(final InstanceInfo info, final boolean isReplication) {handleRegistration(info, resolveInstanceLeaseDuration(info), isReplication);super.register(info, isReplication);}private void handleRegistration(InstanceInfo info, int leaseDuration,boolean isReplication) {log(register info.getAppName() , vip info.getVIPAddress() , leaseDuration leaseDuration , isReplication isReplication);publishEvent(new EurekaInstanceRegisteredEvent(this, info, leaseDuration,isReplication));}com.netflix.eureka.registry.AbstractInstanceRegistry#register
ConcurrentHashMap/*** Registers a new instance with a given duration.** see com.netflix.eureka.lease.LeaseManager#register(java.lang.Object, int, boolean)*/public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {try {read.lock();MapString, LeaseInstanceInfo gMap registry.get(registrant.getAppName());REGISTER.increment(isReplication);if (gMap null) {final ConcurrentHashMapString, LeaseInstanceInfo gNewMap new ConcurrentHashMapString, LeaseInstanceInfo();gMap registry.putIfAbsent(registrant.getAppName(), gNewMap);if (gMap null) {gMap gNewMap;}}LeaseInstanceInfo existingLease gMap.get(registrant.getId());// Retain the last dirty timestamp without overwriting it, if there is already a leaseif (existingLease ! null (existingLease.getHolder() ! null)) {Long existingLastDirtyTimestamp existingLease.getHolder().getLastDirtyTimestamp();Long registrationLastDirtyTimestamp registrant.getLastDirtyTimestamp();logger.debug(Existing lease found (existing{}, provided{}, existingLastDirtyTimestamp, registrationLastDirtyTimestamp);// this is a instead of a because if the timestamps are equal, we still take the remote transmitted// InstanceInfo instead of the server local copy.if (existingLastDirtyTimestamp registrationLastDirtyTimestamp) {logger.warn(There is an existing lease and the existing leases dirty timestamp {} is greater than the one that is being registered {}, existingLastDirtyTimestamp, registrationLastDirtyTimestamp);logger.warn(Using the existing instanceInfo instead of the new instanceInfo as the registrant);registrant existingLease.getHolder();}} else {// The lease does not exist and hence it is a new registrationsynchronized (lock) {if (this.expectedNumberOfClientsSendingRenews 0) {// Since the client wants to register it, increase the number of clients sending renewsthis.expectedNumberOfClientsSendingRenews this.expectedNumberOfClientsSendingRenews 1;updateRenewsPerMinThreshold();}}logger.debug(No previous lease information found; it is new registration);}LeaseInstanceInfo lease new LeaseInstanceInfo(registrant, leaseDuration);if (existingLease ! null) {lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());}gMap.put(registrant.getId(), lease);recentRegisteredQueue.add(new PairLong, String(System.currentTimeMillis(),registrant.getAppName() ( registrant.getId() )));// This is where the initial state transfer of overridden status happensif (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {logger.debug(Found overridden status {} for instance {}. Checking to see if needs to be add to the overrides, registrant.getOverriddenStatus(), registrant.getId());if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {logger.info(Not found overridden id {} and hence adding it, registrant.getId());overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());}}InstanceStatus overriddenStatusFromMap overriddenInstanceStatusMap.get(registrant.getId());if (overriddenStatusFromMap ! null) {logger.info(Storing overridden status {} from map, overriddenStatusFromMap);registrant.setOverriddenStatus(overriddenStatusFromMap);}// Set the status based on the overridden status rulesInstanceStatus overriddenInstanceStatus getOverriddenInstanceStatus(registrant, existingLease, isReplication);registrant.setStatusWithoutDirty(overriddenInstanceStatus);// If the lease is registered with UP status, set lease service up timestampif (InstanceStatus.UP.equals(registrant.getStatus())) {lease.serviceUp();}registrant.setActionType(ActionType.ADDED);recentlyChangedQueue.add(new RecentlyChangedItem(lease));registrant.setLastUpdatedTimestamp();invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());logger.info(Registered instance {}/{} with status {} (replication{}),registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication);} finally {read.unlock();}}可发现是先将事件传播出去(通过Spring的事件监听方式)再调用父类中(com.netflix.eureka.registry.AbstractInstanceRegistry#register)的注册实现。
通过源码发现实例元数据信息室存储在一个ConcurrentHashMap中
private final ConcurrentHashMapString, MapString, Lease registry new ConcurrentHashMapString, MapString, Lease();
第一层key为存储服务名 (registrant.getAppName())
第二层key为存储实例名(registrant.getId()) 参考
Spring Cloud微服务实战