Springcloud 微服务实战笔记 Eureka

服务治理

服务注册

在服务治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,将主机与端口号、版本号、通信协议等一些附加信息告知注册中心,注册中心按服务名分类组织服务清单。当服务启动后,会向注册中心注册自己的服务,那么注册中心就会有一个服务清单。另外,服务注册中心还需要以心跳的方式去监测清单中的服务是否可用,若不可用需要从服务清单中剔除,达到排除故障服务的效果。

服务发现

由于在服务治理框架下运作,服务间的调用不再通过指定具体的实例地址来实现,而是通过向服务名发起请求调用实现。所以,服务调用方在调用服务提供方接口时候,并不知道具体的服务实例位置。因此,调用方需要向服务注册中心咨询服务,并获取所有服务的实例清单,以实现对具体服务实例的访问。框架为了性能等因素,不会采用每次都向服务注册中心获取服务方式,并且不同的应用场景在缓存和服务剔除等机制上也会有一些不同的实现策略。

服务治理机制

服务治理是如何运作的?

●“服务注册中心-1”和“服务注册中心-2”,它们互相注册组成了高可用集群。 ●“服务提供者”启动了两个实例,一个注册到“服务注册中心-1”上,另外一个注册到“服务注册中心-2”上。 ●还有两个“服务消费者”,它们也都分别只指向了一个注册中心。

服务提供者

服务注册

“服务提供者”在启动的时候会通过发送REST请求的方式将自己注册到Eureka Server上,同时带上了自身服务的一些元数据信息。Eureka Server接收到这个REST请求之后,将元数据信息存储在一个双层结构Map中,其中第一层的key是服务名,第二层的key是具体服务的实例名。
 

服务同步

如架构图中所示,这里的两个服务提供者分别注册到了两个不同的服务注册中心上,也就是说,它们的信息分别被两个服务注册中心所维护。此时,由于服务注册中心之间因互相注册为服务,当服务提供者发送注册请求到一个服务注册中心时,它会将该请求转发给集群中相连的其他注册中心,从而实现注册中心之间的服务同步。通过服务同步,两个服务提供者的服务信息就可以通过这两台服务注册中心中的任意一台获取到。
 

服务续约

在注册完服务之后,服务提供者会维护一个心跳用来持续告诉Eureka Server:“我还活着”,以防止 Eureka Server 的“剔除任务”将该服务实例从服务列表中排除出去,我们称该操作为服务续约(Renew)。

关于服务续约有两个重要属性,我们可以关注并根据需要来进行调整:
eureka.instance.lease-renewal-interval-in-seconds=30  // 定义服务续 约 任 务 的 调 用 间 隔 时 间 , 默 认 为 30 秒 
eureka.instance.lease-expiration-duration-in-seconds=90 // 定义服务失效的时间,默认为90秒

服务消费者

获取服务

到这里,在服务注册中心已经注册了一个服务,并且该服务有两个实例。当我们启动服务消费者的时候,它会发送一个 REST 请求给服务注册中心,来获取上面注册的服务清单。为了性能考虑,EurekaServer会维护一份只读的服务清单来返回给客户端,同时该缓存清单会每隔30秒更新一次。

获取服务是服务消费者的基础,所以必须确保eureka.client.fetchregistry=true参数没有被修改成false,该值默认为true。

若希望修改缓存清 单 的 更 新 时 间 , 可 以 通 过 eureka.client.registry-fetch-intervalseconds=30参数进行修改,该参数默认值为30,单位为秒。
 

服务调用

服务消费者在获取服务清单后,通过服务名可以获得具体提供服务的实例名和该实例的元数据信息。因为有这些服务实例的详细信息,所以客户端可以根据自己的需要决定具体调用哪个实例,在Ribbon中会默认采用轮询的方式进行调用,从而实现客户端的负载均衡。
 

服务下线

在系统运行过程中必然会面临关闭或重启服务的某个实例的情况,在服务关闭期间,我们自然不希望客户端会继续调用关闭了的实例。所以在客户端程序中,当服务实例进行正常的关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务端在接收到请求之后,将该服务状态置为下线(DOWN),并把该下线事件传播出去。
 

服务注册中心

失效剔除

有些时候,我们的服务实例并不一定会正常下线,可能由于内存溢出、网络故障等原因使得服务不能正常工作,而服务注册中心并未收到“服务下线”的请求。为了从服务列表中将这些无法提供服务的实例剔除,Eureka Server在启动的时候会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除出去。
 

自我保护

当我们在本地调试基于Eureka的程序时,基本上都会碰到这样一个问题,在服务注册中心的信息面板中出现类似下面的红色警告信息:EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMINGINSTANCES ARE UP WHEN THEY'RE 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 List<String> getServiceUrlsFromConfig(EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) {List<String> orderedUrls = new ArrayList<String>();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);List<String> 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 List<String> 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);List<String> 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.Provider<com.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);EurekaHttpResponse<Void> 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.Entry<String, 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() {EurekaHttpResponse<InstanceInfo> 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.*/@POST@Consumes({"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();Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());REGISTER.increment(isReplication);if (gMap == null) {final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);if (gMap == null) {gMap = gNewMap;}}Lease<InstanceInfo> 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 lease's 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");}Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);if (existingLease != null) {lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());}gMap.put(registrant.getId(), lease);recentRegisteredQueue.add(new Pair<Long, 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 ConcurrentHashMap<String, Map<String, Lease>> registry = new ConcurrentHashMap<String, Map<String, Lease>>();

第一层key为:存储服务名 (registrant.getAppName())

第二层key为:存储实例名(registrant.getId())

参考:

Spring Cloud微服务实战

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/592303.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PS制作2寸或者1寸排版打印照

最近家人需要2寸照&#xff0c;然后之前照的摄影馆给打印的是一版1寸照纸质和一张精修的原版照&#xff0c;然后要打印2寸照的话就存在两种选择&#xff1a; 需要自己做排版打印照&#xff0c;拿去打印店打印&#xff0c;要么就是直接拿着这张精修的单照去打印店让打印店的工作…

如何删除K8S中的Pod

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

代码随想录|贪心day2

122.力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 买股票的最佳时机&#xff0c;这道题其实和53有一点像&#xff0c;因为不需要写出哪个区间卖出买进&#xff0c;所以判断prices[i] - prices[i - 1]的值的大小&#xff0c;如果这个值是正的&#…

java 打印日志的几种方式

java 打印日志的几种方式 Java 日志框架进化史日志门面与日志系统 Log4jslf4jLog4j2slf4jLogbackslf4j 一、先简单介绍五种 &#xff08;1&#xff09;最简单的方式&#xff0c;就是system.println.out(error) ,这样直接在控制台打印消息了&#xff1b; &#xff08;2&#xff…

龙格-库塔(Runge–Kutta)法 (Dopri5, Euler method,Explicit midpoint method)

在数值分析中&#xff0c;龙格-库塔方法是一系列隐式和显式迭代方法&#xff0c;其中包括欧拉方法&#xff0c; 显式中心点法&#xff0c; ,后向欧拉方法, &#xff0c;用于联立非线性方程近似解的时间离散化。[2]这些方法是由德国数学家Carl Runge和Wilhelm Kutta。 显式欧拉…

如何在iPhone设备中查看崩溃日志

​ 目录 如何在iPhone设备中查看崩溃日志 摘要 引言 导致iPhone设备崩溃的主要原因是什么&#xff1f; 使用克魔助手查看iPhone设备中的崩溃日志 奔溃日志分析 总结 摘要 本文介绍了如何在iPhone设备中查看崩溃日志&#xff0c;以便调查崩溃的原因。我们将展示三种不同的…

打破无声世界:让您的网站会说话

导语&#xff1a;在当今的互联网世界&#xff0c;技术创新日新月异&#xff0c;为广大开发者提供了无数便捷的工具。本文将为您介绍前端文本语音API&#xff0c;这一神奇的技术能够实现文字到语音的转换&#xff0c;为您的项目增添更多可能性。同时&#xff0c;我们还将为您提供…

uniapp中uview组件库的Input 输入框 的使用方法

目录 #平台差异说明 #基本使用 #输入框的类型 #可清空字符 #下划线 #前后图标 #前后插槽 API #Props #Events #Methods #Slots 去除fixed、showWordLimit、showConfirmBar、disableDefaultPadding、autosize字段 此组件为一个输入框&#xff0c;默认没有边框和样式…

UE5 VR版增强输入初体验 官方模板学习

问题 我们传统的输入方式&#xff0c;是通过编辑器设置输入操作映射&#xff0c;然后BindAction和BindAxis绑定 这边插播一条增强输入知识点&#xff0c;参考知乎大佬文章 和增强输入的VR模板教学&#xff1a;如何使用VR模板在UE5中使用增强输入系统_哔哩哔哩_bilibili 实践操…

再见2023,你好2024(附新年烟花python实现)

亲爱的朋友们&#xff1a; 写点什么呢&#xff0c;我已经停更两个月了。2023年快结束了&#xff0c;时间真的过得好快&#xff0c;总要写点什么留下纪念吧。这一年伴随着许多挑战和机会&#xff0c;给了我无数的成长和体验。坦白说&#xff0c;有时候我觉得自己好像是在时间的…

FPGA - 240102 - FPGA期末速成

TAG - F P G A 、期末、速成 FPGA、期末、速成 FPGA、期末、速成 // – 习题1 – //CPLD&#xff08;Complex Programmable Logic Device&#xff09;是 Complex PLD 的简称&#xff0c;一种较 PLD 为复杂的逻辑元件。CPLD 逻辑资源多寄存器少&#xff0c;FPGA 逻辑弱而寄存器…

Go后端开发 -- main函数 变量 常量 函数

Go后端开发 – main函数 & 变量 & 常量 & 函数 文章目录 Go后端开发 -- main函数 & 变量 & 常量 & 函数一、第一个main函数1.创建工程2.main函数解析 二、变量声明1.单变量声明2.多变量声明 三、常量1.常量的定义2.优雅的常量 iota 四、函数1.函数返回…

vim学习记录

目录 历史记录前言相关资料配置windows互换ESC和Caps Lock按键 基本操作替换字符串 历史记录 2024年1月2日, 搭建好框架,开始学习; 前言 vim使用很久了,但是都是一些基本用法,主要是用于配置Linux,进行一些简单的编写文档和程序.没有进行过大型程序开发,没有达到熟练使用的程…

OpenHarmony之HDF驱动框架

概述 HDF&#xff08;Hardware Driver Foundation&#xff09;驱动框架&#xff0c;为驱动开发者提供驱动框架能力&#xff0c;包括驱动加载、驱动服务管理、驱动消息机制和配置管理。并以组件化驱动模型作为核心设计思路&#xff0c;让驱动开发和部署更加规范&#xff0c;旨在…

小红书、抖音、视频号下载工具:随心管理个人作品集 | 开源日报 No.134

karanpratapsingh/system-design Stars: 20.6k License: NOASSERTION 这个项目是关于系统设计的。它提供了有关系统设计的课程内容&#xff0c;包括 IP、OSI 模型、TCP 和 UDP 等主题。该项目的核心优势和特点如下&#xff1a; 提供全面而高效的系统架构定义。从基础设施到数…

C#使用纯OpenCvSharp部署yolov8-pose姿态识别

【源码地址】 github地址&#xff1a;https://github.com/ultralytics/ultralytics 【算法介绍】 Yolov8-Pose算法是一种基于深度神经网络的目标检测算法&#xff0c;用于对人体姿势进行准确检测。该算法在Yolov8的基础上引入了姿势估计模块&#xff0c;通过联合检测和姿势…

安全与认证Week3

Key Management 密钥管理 密钥交换、证书 密钥的类别 密钥管理方面 密钥分发问题 密钥分发方案 简单的密钥分发&#xff1a;允许安全通信&#xff0c;但不存在先前或之后的密钥。 带机密性和身份验证的密钥分发&#xff1a;提供更高级别的安全性。 混合密钥分发 公钥分发 公开…

CodeWave智能开发平台--02--目标:文档快速阅读

CodeWave智能开发平台的02次接触-实现快速了解CodeWave平台 CodeWave参考资源 网易数帆CodeWave开发者社区课程中心 网易数帆CodeWave开发者社区文档中心 CodeWave智能开发平台-文档快速阅读指北 大家如果看了本专栏中的第一篇博客&#xff0c;应该知道我接触CodeWave不久&a…

RabbitMQ(七)ACK 消息确认机制

目录 一、简介1.1 背景1.2 定义1.3 如何查看确认/未确认的消息数&#xff1f; 二、消息确认机制的分类2.1 消息发送确认1&#xff09;ConfirmCallback方法2&#xff09;ReturnCallback方法3&#xff09;代码实现方式一&#xff1a;统一配置a.配置类a.生产者c.消费者d.测试结果 …

TypeScript中的Declare关键字的作用

详细介绍TypeScript中的“declare”关键字的作用&#xff0c;这样你对*.d.ts文件就不陌生了。 当你在TypeScript项目中打开 *.d.ts 声明文件时&#xff0c;你可能会看到 declare 。你知道 declare 是做什么的吗?如果你不知道&#xff0c;读完这篇文章&#xff0c;也许你就会知…