post在线请求网站源码分享(post 网页)

大家好,如果您还对post在线请求网站源码分享不太了解,没有关系,今天就由本站为大家分享post在线请求网站源码分享的知识,包括post 网页的问题都会给大家分析到,还望可以解决大家的问题,下面我们就开始吧!

0.环境

eureka版本:1.10.11SpringCloud:2020.0.2SpringBoot:2.4.4测试代码:github.com/hsfxuebao/s…

1.前言

本文主要是解析下SpringCloud整合EurekaClient的源码,这块代码比较多,而且都是些简单代码,我们稍微看下就行,这就是介绍下EurekaClient初始化过程,不管你SpringCloud怎样封装,底层还是EurekaClient的内容,初始化过程包括下面:

去EurekaServer拉取全量注册表,创建定时任务,包括定时去EurekaServer上增量拉取注册表信息,定时renew(服务续约)。服务注册

2.SpringCloud整合EurekaClient启动入口

要看SpringCloud怎样整合EurekaClient,就需要找到它们的自动装配配置类在spring-cloud-starter-netflix-eureka-client依赖的pom文件中,在依赖pom文件中有spring-cloud-netflix-eureka-client,在这个里面能够找到spring.factories文件,这个文件是springspi文件。

核心就是EurekaClientAutoConfiguration这个自动装配类:

@Configuration(proxyBeanMethods=false)\n@EnableConfigurationProperties\n@ConditionalOnClass(EurekaClientConfig.class)\n@ConditionalOnProperty(value=&34;,matchIfMissing=true)\n@ConditionalOnDiscoveryEnabled\n@AutoConfigureBefore({CommonsClientAutoConfiguration.class,ServiceRegistryAutoConfiguration.class})\n@AutoConfigureAfter(name={&34;,\n&34;,\n&34;,\n&34;})\npublicclassEurekaClientAutoConfiguration{\n}

2.1封装配置文件的类

2.1.1EurekaClientConfigBean

@Bean\n@ConditionalOnMissingBean(value=EurekaClientConfig.class,search=SearchStrategy.CURRENT)\npublicEurekaClientConfigBeaneurekaClientConfigBean(ConfigurableEnvironmentenv){\nreturnnewEurekaClientConfigBean();\n}

其读取的是eureka.client前辍的配置信息。这个类已经被@ConfigurationProperties注解了,所以这些配置信息可以被自动封装并注册到容器。

2.1.2EurekaInstanceConfigBean

@Bean\n@ConditionalOnMissingBean(value=EurekaInstanceConfig.class,search=SearchStrategy.CURRENT)\npublicEurekaInstanceConfigBeaneurekaInstanceConfigBean(InetUtilsinetUtils,\nManagementMetadataProvidermanagementMetadataProvider){\n}

其读取的是eureka.instance的属性值。这个类也已经被@ConfigurationProperties注解了,所以这些配置信息可以被自动封装并注册到容器。

2.2EurekaClient

接下来,看看核心类EurekaClient是怎么注入进去的?在EurekaClientAutoConfiguration文件中,我们发现有两个地方都可以注入EurekaClient,分别为:

@Configuration(proxyBeanMethods=false)\n@ConditionalOnMissingRefreshScope\nprotectedstaticclassEurekaClientConfiguration{\n\n@Bean(destroyMethod=&34;)\n@ConditionalOnMissingBean(value=EurekaClient.class,search=SearchStrategy.CURRENT)\npublicEurekaClienteurekaClient(ApplicationInfoManagermanager,EurekaClientConfigconfig){\nreturnnewCloudEurekaClient(manager,config,this.optionalArgs,this.context);\n}\n}\n\n//另一个是:\n@Configuration(proxyBeanMethods=false)\n@ConditionalOnRefreshScope\nprotectedstaticclassRefreshableEurekaClientConfiguration{\n\n@Bean(destroyMethod=&34;)\n@ConditionalOnMissingBean(value=EurekaClient.class,search=SearchStrategy.CURRENT)\n@org.springframework.cloud.context.config.annotation.RefreshScope\n@Lazy\npublicEurekaClienteurekaClient(ApplicationInfoManagermanager,EurekaClientConfigconfig,\nEurekaInstanceConfiginstance,@Autowired(required=false)HealthCheckHandlerhealthCheckHandler){\n}\n\n}

这就需要分析到底哪一个注解生效了?

@ConditionalOnMissingRefreshScope

@Target({ElementType.TYPE,ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Conditional(OnMissingRefreshScopeCondition.class)

@interfaceConditionalOnMissingRefreshScope{

}

privatestaticclassOnMissingRefreshScopeConditionextendsAnyNestedCondition{

OnMissingRefreshScopeCondition(){

super(ConfigurationPhase.REGISTER_BEAN);

}

@ConditionalOnMissingClass(&34;)

staticclassMissingClass{

}

@ConditionalOnMissingBean(RefreshAutoConfiguration.class)

staticclassMissingScope{

}

@ConditionalOnProperty(value=&34;,havingValue=&34;)

staticclassOnPropertyDisabled{

}

}

大家可以看看AnyNestedCondition这个注解,意思就是只要满足任意一个条件就符合。通过分析,我们知道这三个条件都是满足的,所以这个注解不生效,这个类不生效。

@ConditionalOnRefreshScope

@Target({ElementType.TYPE,ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@ConditionalOnClass(RefreshScope.class)

@ConditionalOnBean(RefreshAutoConfiguration.class)

@ConditionalOnProperty(value=&34;,havingValue=&34;,matchIfMissing=true)

@interfaceConditionalOnRefreshScope{

}

通过这个注解EurekaClientAutoConfiguration上的注解@AutoConfigureAfter,我们知道当前类注入是在RefreshAutoConfiguration之后注入到容器中。而RefreshScope就是在RefreshAutoConfiguration之后中注入的。所以我们需要分析这个类就可以了。

@AutoConfigureAfter(name={&34;,

&34;,

&34;,

&34;})

publicclassEurekaClientAutoConfiguration{

}

2.2.1ApplicationInfoManager

@Bean\n@ConditionalOnMissingBean(value=ApplicationInfoManager.class,search=SearchStrategy.CURRENT)\npublicApplicationInfoManagereurekaApplicationInfoManager(\n\t\tEurekaInstanceConfigconfig){\n\tInstanceInfoinstanceInfo=newInstanceInfoFactory().create(config);\n\treturnnewApplicationInfoManager(config,instanceInfo);\n}

创建ApplicationInfoManager对象,这个对象主要就是管着当前实例信息,也就是instanceInfo,可以看到,在这个方法中先是创建的instanceInfo,然后将instanceInfo作为构造参数传入了ApplicationInfoManager中。

这个实例信息instanceInfo里面维护了你当前实例的ip,端口,appName等信息,注册的时候就是拿这些信息到EurekaServer上注册。

2.2.2EurekaClient

@Bean(destroyMethod=&34;)\n@ConditionalOnMissingBean(value=EurekaClient.class,search=SearchStrategy.CURRENT)\npublicEurekaClienteurekaClient(ApplicationInfoManagermanager,EurekaClientConfigconfig){\n\treturnnewCloudEurekaClient(manager,config,this.optionalArgs,\n\t\t\tthis.context);\n}

创建EurekaClient对象,这个CloudEurekaClient类是SpringCloud搞得,然后继承Eureka原生的DiscoveryClient类。

publicclassCloudEurekaClientextendsDiscoveryClient

我们可以看看它的构造

最重要的是,它调用了父类的DiscoveryClient的构造,下面重点介绍。

2.3小结

总结以上的信息,从EurekaClientAutoConfiguration等方面可罗列出如下几个比较重要的类,如下:

类名

介绍与作用

EurekaClientConfig

封装了EurekaClient与EurekaServer交互时所需要的配置信息,SpringCloud为其提供了默认配置类:EurekaClientConfigBean。

ApplicationInfoManager

作为应用信息管理器,管理服务实例类Instancenfo和服务实例配置信息类EurekaInstanceConfig。

InstanceInfo

封装了将被发送到EurekaServer进行服务注册的服务实例元数据,它在Eureka注册表中代表着一个服务实例,其他服务可通过InstanceInfo来了解该服务实例的相关信息,从而进行相关操作。

EurekaInstanceConfig

封装了EurekaClient自身服务实例的配置信息,主要用于构建InstanceInfo,通常这些信息在配置文件的eureka.instance前缀下进行设置,SpringCloud通过EurekaInstanceBean配置类提供默认配置。

DiscoveryClient

SpringCloud中定义用来做服务发现的客户端接口。

3.DiscoveryClient类的解析

3.1DiscoveryClient作用

DiscoveryClient是EurekaClient的核心类,其作用与下:

注册实例到EurekaServer中发送心跳更新与EurekaServer的续约在服务关闭时取消与EurekaServer的续约,完成服务下限获取在EurekaServer中的服务实例列表

3.2DiscoveryClient的类结构

可以先看下DiscoveryClient的类结构图:

从类结构图上可以看出DiscoveryClient类实现了EurekaCient,EurekaCient又继承了LookupService,这里看看LookupService类:

publicinterfaceLookupService<T>{\n//根据服务实例名称获取Application\nApplicationgetApplication(StringappName);\n//获取当前注册表中所有的服务实例信息\nApplicationsgetApplications();\n//根据服务实例Id获取服务实例信息\nList<InstanceInfo>getInstancesById(Stringid);\n\nInstanceInfogetNextServerFromEureka(StringvirtualHostname,booleansecure);\n}

Application是持有服务实例信息列表,它表示同一个服务的集群信息,这些服务实例乃是挂载在同一个服务名appName之下,而InstanceInfo则是代表着一个服务实例的信息,Application类代码如下:

publicclassApplication{\n\nprivatestaticRandomshuffleRandom=newRandom();\n//服务名\nprivateStringname;\n//标识服务状态\n@XStreamOmitField\nprivatevolatilebooleanisDirty=false;\n\n@XStreamImplicit\nprivatefinalSet<InstanceInfo>instances;\n\nprivatefinalAtomicReference<List<InstanceInfo>>shuffledInstances;\n\nprivatefinalMap<String,InstanceInfo>instancesMap;\n\n//……..\n}

在Application中对InstanceInfo的操作都是同步的,为的是保证其原子性。Applications则是注册表中所有服务实例的集合,其间的操作也都是同步的。EurekaClient继承了LookupService接口,为DiscoveryClient提供一个上层接口,其目的是为了Eureka1.0x到Eureka2.x的升级做过渡。

EurekaCient接口在LookupService的基础上提供了更丰富的方法,譬如:

提供做种方式获取InstanceInfo,例如根据区域、EurekaServer地址获取等。提供本地客户端(区域、可用区)的数据,这部分与AWS相关提供了为客户端注册和获取健康检查处理器的功能

除了相关查询接口外,EurekaClient提供以下的两个方法,需颇多关注:

publicinterfaceEurekaClientextendsLookupService{\n//…….\n//为EurekaClient注册健康处理器\npublicvoidregisterHealthCheck(HealthCheckHandlerhealthCheckHandler);\n//监听Client服务实例信息的更新\npublicvoidregisterEventListener(EurekaEventListenereventListener);\n}

在EurekaServer中一般是通过心跳来识别一个实例的状态,而在EurekaClient中则存在一个定时任务定时通过HealthCheckHandler检测当前Client的状态,当其状态发生变化的时候,将会触发新的注册事件,更新EurekaServer的注册表中的相关实例信息。

3.3DiscoveryClient构造函数

在DiscoveryClient的构造函数中,会有如下操作,如:服注册表信息、服务注册、初始化发送心跳、缓存刷新、注册定时任务等。因此DiscoveryClient的构造函数贯穿了EurekaClient启动阶段的各项任务。

DiscoveryClient(ApplicationInfoManagerapplicationInfoManager,EurekaClientConfigconfig,AbstractDiscoveryClientOptionalArgsargs,\nProvider<BackupRegistry>backupRegistryProvider,EndpointRandomizerendpointRandomizer){\n//省略相关信息\n}

在DiscoveryClient的构造函数中有如下几个参数:ApplicationInfoManager、EurekaClientConfig、AbstractDiscoveryClientOptionalArgs、Provider<BackupRegistry>、EndpointRandomizer。前两个参数前面已做介绍,AbstractDiscoveryClientOptionalArgs用于注入一些可选参数,BackupRegistry则充当备份注册中心的职责,EndpointRandomizer则是作为端点随机器。对DiscoveryClient的构造函数的职责做一个简单概括:

相关配置赋值,如ApplicationInfoManager、EurekaClientConfig等备份注册中心初始化,默认没有实现拉去EurekaServer注册表信息注册前预处理向EurekaServer注册自身初始化定时任务、缓存刷新、按需注册定时任务

后面将会对这些步骤中对重要点进行相关分析。

4.EurekaClient初始化

接下来我们看下DiscoveryClient是怎样初始化的(构造方法中)。代码如下:

@Inject\nDiscoveryClient(ApplicationInfoManagerapplicationInfoManager,EurekaClientConfigconfig,AbstractDiscoveryClientOptionalArgsargs,\nProvider<BackupRegistry>backupRegistryProvider,EndpointRandomizerendpointRandomizer){\n…\n\n//如果开启拉取注册表的话\nif(clientConfig.shouldFetchRegistry()){\ntry{\n//todo拉取注册表信息\nbooleanprimaryFetchRegistryResult=fetchRegistry(false);\nif(!primaryFetchRegistryResult){\nlogger.info(&34;);\n}\n…\n}\n}\n…\n//如果进行服务注册的话clientConfig.shouldEnforceRegistrationAtInit()默认false\nif(clientConfig.shouldRegisterWithEureka()&&clientConfig.shouldEnforceRegistrationAtInit()){\ntry{\n//todo进行服务注册\nif(!register()){\nthrownewIllegalStateException(&34;);\n}\n}\n…\n}\n\n//finally,initthescheduletasks(e.g.clusterresolvers,heartbeat,instanceInforeplicator,fetch\n//todo定时任务\ninitScheduledTasks();\n\n…\n}

4.1拉取注册表信息

//如果开启拉取注册表的话\nif(clientConfig.shouldFetchRegistry()){\n//拉取注册表信息\nbooleanprimaryFetchRegistryResult=fetchRegistry(false);\n}

如果开启拉取注册信息,就会调用fetchRegistry方法去EurekaServer上面拉取注册表信息。

privatebooleanfetchRegistry(booleanforceFullRegistryFetch){\n\n//Ifthedeltaisdisabledorifitisthefirsttime,getall\n//applications\nApplicationsapplications=getApplications();\nif(clientConfig.shouldDisableDelta()//关闭增量,默认false\n||(!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))\n||forceFullRegistryFetch\n||(applications==null)\n||(applications.getRegisteredApplications().size()==0)\n||(applications.getVersion()==-1))//Clientapplicationdoesnothavelatestlibrarysupportingdelta\n{\n//todo全量拉取注册表信息\ngetAndStoreFullRegistry();\n}else{\n//todo增量更新\ngetAndUpdateDelta(applications);\n}\n//设置hashCode\napplications.setAppsHashCode(applications.getReconcileHashCode());\nlogTotalInstances();\n}

可以看下最上面的注释,不启用增量或者是第一次,就拉取全量注册表信息。

不启用增量||强制全量||本地注册表是空的,这个时候就会调用getAndStoreFullRegistry方法去EurekaServer拉取全量注册表。否则的话调用getAndUpdateDelta方法获取增量注册表信息。

4.1.1全量拉取注册表信息

接下来我们看下getAndStoreFullRegistry方法,看看是怎样拉取全量注册表的。

//获取所有注册表信息\nprivatevoidgetAndStoreFullRegistry()throwsThrowable{\nlongcurrentUpdateGeneration=fetchRegistryGeneration.get();\n\nApplicationsapps=null;\n//交给网络传输组件,发起网络请求,获得响应\nEurekaHttpResponse<Applications>httpResponse=clientConfig.getRegistryRefreshSingleVipAddress()==null\n//todoapps请求url\n?eurekaTransport.queryClient.getApplications(remoteRegionsRef.get())\n:eurekaTransport.queryClient.getVip(clientConfig.getRegistryRefreshSingleVipAddress(),remoteRegionsRef.get());\nif(httpResponse.getStatusCode()==Status.OK.getStatusCode()){\napps=httpResponse.getEntity();\n}\n\nif(apps==null){\nlogger.error(&34;);\n}elseif(fetchRegistryGeneration.compareAndSet(currentUpdateGeneration,currentUpdateGeneration+1)){\n//\nlocalRegionApps.set(this.filterAndShuffle(apps));\nlogger.debug(&34;,apps.getAppsHashCode());\n}else{\nlogger.warn(&34;);\n}\n}

这里其实就是调用网络组件来发起请求,得到响应了,然后拿到所有得实例信息后,将实例信息设置到本地注册表中。我们这里再深入一点,看看eurekaTransport.queryClient.getApplications(remoteRegionsRef.get())是请求得哪个url:

@Override\npublicEurekaHttpResponse<Applications>getApplications(String…regions){\nreturngetApplicationsInternal(&34;,regions);\n}\nprivateEurekaHttpResponse<Applications>getApplicationsInternal(StringurlPath,String[]regions){\nClientResponseresponse=null;\nStringregionsParamValue=null;\ntry{\nWebResourcewebResource=jerseyClient.resource(serviceUrl).path(urlPath);\n//拼接region\nif(regions!=null&&regions.length>0){\nregionsParamValue=StringUtil.join(regions);\nwebResource=webResource.queryParam(&34;,regionsParamValue);\n}\nBuilderrequestBuilder=webResource.getRequestBuilder();\naddExtraHeaders(requestBuilder);\n//提交get请求\nresponse=requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).get(ClientResponse.class);\n\nApplicationsapplications=null;\nif(response.getStatus()==Status.OK.getStatusCode()&&response.hasEntity()){\napplications=response.getEntity(Applications.class);\n}\nreturnanEurekaHttpResponse(response.getStatus(),Applications.class)\n.headers(headersOf(response))\n.entity(applications)\n.build();\n}\n}

拉取全量注册表的请求为:GET请求,path为:apps/

4.1.2增量拉取注册表信息

getAndUpdateDelta(applications);代码如下:

privatevoidgetAndUpdateDelta(Applicationsapplications)throwsThrowable{\nlongcurrentUpdateGeneration=fetchRegistryGeneration.get();\n\nApplicationsdelta=null;\n//提交请求\nEurekaHttpResponse<Applications>httpResponse=eurekaTransport.queryClient.getDelta(remoteRegionsRef.get());\nif(httpResponse.getStatusCode()==Status.OK.getStatusCode()){\ndelta=httpResponse.getEntity();\n}\n\nif(delta==null){\n\ngetAndStoreFullRegistry();\n}elseif(fetchRegistryGeneration.compareAndSet(currentUpdateGeneration,currentUpdateGeneration+1)){\n\nStringreconcileHashCode=&34;;\nif(fetchRegistryUpdateLock.tryLock()){\ntry{\n/**\n*这里要将从Server获取到的所有变更信息更新到本地缓存。这些变\n*更信来自于两类Region:本地Region与远程Region。而本地缓存也\n*分为两类:缓存本地Region的applications与缓存所有远程Region\n*的注册信息的map(key为远程Region,value为该远程Region的注册\n*表)\n*/\n//todo\nupdateDelta(delta);\nreconcileHashCode=getReconcileHashCode(applications);\n}finally{\nfetchRegistryUpdateLock.unlock();\n}\n}\n…\n}

增量拉取注册表的请求:GET请求path为:apps/delta

然后,我们重点看一下updateDelta(delta);方法:

privatevoidupdateDelta(Applicationsdelta){\nintdeltaCount=0;\nfor(Applicationapp:delta.getRegisteredApplications()){\nfor(InstanceInfoinstance:app.getInstances()){\nApplicationsapplications=getApplications();\nStringinstanceRegion=instanceRegionChecker.getInstanceRegion(instance);\n//不是本地region,远程region\nif(!instanceRegionChecker.isLocalRegion(instanceRegion)){\nApplicationsremoteApps=remoteRegionVsApps.get(instanceRegion);\nif(null==remoteApps){\nremoteApps=newApplications();\nremoteRegionVsApps.put(instanceRegion,remoteApps);\n}\napplications=remoteApps;\n}\n\n++deltaCount;\n//有新增加的实例信息\nif(ActionType.ADDED.equals(instance.getActionType())){\nApplicationexistingApp=applications.getRegisteredApplications(instance.getAppName());\nif(existingApp==null){\napplications.addApplication(app);\n}\nlogger.debug(&34;,instance.getId(),instanceRegion);\napplications.getRegisteredApplications(instance.getAppName()).addInstance(instance);\n//有修改的\n}elseif(ActionType.MODIFIED.equals(instance.getActionType())){\nApplicationexistingApp=applications.getRegisteredApplications(instance.getAppName());\nif(existingApp==null){\napplications.addApplication(app);\n}\nlogger.debug(&34;,instance.getId());\n\napplications.getRegisteredApplications(instance.getAppName()).addInstance(instance);\n\n//有删除的\n}elseif(ActionType.DELETED.equals(instance.getActionType())){\nApplicationexistingApp=applications.getRegisteredApplications(instance.getAppName());\nif(existingApp!=null){\nlogger.debug(&34;,instance.getId());\nexistingApp.removeInstance(instance);\n/*\n*Wefindallinstancelistfromapplication(ThestatusofinstancestatusisnotonlythestatusisUPbutalsootherstatus)\n*ifinstancelistisempty,weremovetheapplication.\n*/\nif(existingApp.getInstancesAsIsFromEureka().isEmpty()){\napplications.removeApplication(existingApp);\n}\n}\n}\n}\n}\n…\n}

这个方法就是更新客户端本地的注册表信息。

4.2服务注册

//如果进行服务注册的话clientConfig.shouldEnforceRegistrationAtInit()默认false\nif(clientConfig.shouldRegisterWithEureka()&&clientConfig.shouldEnforceRegistrationAtInit()){\ntry{\n//todo进行服务注册\nif(!register()){\nthrownewIllegalStateException(&34;);\n}\n}catch(Throwableth){\nlogger.error(&34;,th.getMessage());\nthrownewIllegalStateException(th);\n}\n}

如果在这里进行服务注册的话,需要配置文件中增加下面配置(默认是false):

eureka.client.should-enforce-registration-at-init:true

所以在这里是没有服务注册的,那么服务注册是在哪里呢?在会面分析续约定时任务时完成了服务注册,不过,我们在这里也看一下服务注册的代码:

booleanregister()throwsThrowable{\nEurekaHttpResponse<Void>httpResponse;\ntry{\n//todo进行服务注册\nhttpResponse=eurekaTransport.registrationClient.register(instanceInfo);\n}\n…\n}\nreturnhttpResponse.getStatusCode()==Status.NO_CONTENT.getStatusCode();\n}

接下来看:

@Override\npublicEurekaHttpResponse<Void>register(InstanceInfoinfo){\nStringurlPath=&34;+info.getAppName();\nResponseresponse=null;\ntry{\nBuilderresourceBuilder=jerseyClient.target(serviceUrl).path(urlPath).request();\naddExtraProperties(resourceBuilder);\naddExtraHeaders(resourceBuilder);\nresponse=resourceBuilder\n.accept(MediaType.APPLICATION_JSON)\n.acceptEncoding(&34;)\n.post(Entity.json(info));\nreturnanEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();\n}finally{\nif(logger.isDebugEnabled()){\nlogger.debug(&34;,serviceUrl,urlPath,info.getId(),\nresponse==null?&34;:response.getStatus());\n}\nif(response!=null){\nresponse.close();\n}\n}\n}

服务注册:POST请求,path为:“apps/&34;cacheRefresh&run:

@Override\npublicvoidrun(){\nFuture<?>future=null;\ntry{\n//使用Future,可以设定子线程的超时时间,这样当前线程就不用无限等待了\nfuture=executor.submit(task);\nthreadPoolLevelGauge.set((long)executor.getActiveCount());\n//阻塞获取任务的执行结果\nfuture.get(timeoutMillis,TimeUnit.MILLISECONDS);//blockuntildoneortimeout\n//delay是个很有用的变量,后面会用到,这里记得每次执行任务成功都会将delay重置\ndelay.set(timeoutMillis);\nthreadPoolLevelGauge.set((long)executor.getActiveCount());\nsuccessCounter.increment();\n}catch(TimeoutExceptione){\nlogger.warn(&34;,e);\ntimeoutCounter.increment();\n\nlongcurrentDelay=delay.get();\n//任务线程超时的时候,就把delay变量翻倍,但不会超过外部调用时设定的最大延时时间\nlongnewDelay=Math.min(maxDelay,currentDelay*2);\n//设置为最新的值,考虑到多线程,所以用了CAS\ndelay.compareAndSet(currentDelay,newDelay);\n\n}catch(RejectedExecutionExceptione){\n//一旦线程池的阻塞队列中放满了待处理任务,触发了拒绝策略,就会将调度器停掉\nif(executor.isShutdown()||scheduler.isShutdown()){\nlogger.warn(&34;,e);\n}else{\nlogger.warn(&34;,e);\n}\n\nrejectedCounter.increment();\n}catch(Throwablee){\n//一旦出现未知的异常,就停掉调度器\nif(executor.isShutdown()||scheduler.isShutdown()){\nlogger.warn(&39;tacceptthetask&34;tasksupervisorthrewanexception&34;cacheRefresh&run中finally代码,在这里又重新开启了新的定时任务:

finally{\n//这里任务要么执行完毕,要么发生异常,都用cancel方法来清理任务;\nif(future!=null){\nfuture.cancel(true);\n}\n\n//只要调度器没有停止,就再指定等待时间之后在执行一次同样的任务\nif(!scheduler.isShutdown()){\n//todo下一次时间再次执行这个任务\n//这里就是周期性任务的原因:只要没有停止调度器,就再创建一次性任务,执行时间时delay的值,\n//假设外部调用时传入的超时时间为30秒(构造方法的入参timeout),最大间隔时间为50秒(构造方法的入参expBackOffBound)\n//如果最近一次任务没有超时,那么就在30秒后开始新任务,\n//如果最近一次任务超时了,那么就在50秒后开始新任务(异常处理中有个乘以二的操作,乘以二后的60秒超过了最大间隔50秒)\nscheduler.schedule(this,delay.get(),TimeUnit.MILLISECONDS);\n}\n}

这样就实现了每隔30s调用一个拉取注册表的任务。

4.3.2定时服务续约任务

privatevoidinitScheduledTasks(){\n…\n\n//开启注册\nif(clientConfig.shouldRegisterWithEureka()){\n\n//todo服务续约定时任务\n//续约间隔时间30s\nintrenewalIntervalInSecs=instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();\n//指定client从server更新注册表的最大时间间隔指数(倍数),默认为10\nintexpBackOffBound=clientConfig.getHeartbeatExecutorExponentialBackOffBound();\nlogger.info(&34;+&34;,renewalIntervalInSecs);\n\n//Heartbeattimer\n//todo续约,心跳定时任务\nheartbeatTask=newTimedSupervisorTask(\n&34;,\nscheduler,\nheartbeatExecutor,\nrenewalIntervalInSecs,\nTimeUnit.SECONDS,\nexpBackOffBound,\nnewHeartbeatThread()\n);\n//续约定时任务\nscheduler.schedule(\nheartbeatTask,\nrenewalIntervalInSecs,TimeUnit.SECONDS);

每30s执行一次服务续约。直接看下HeartbeatThread类。

privateclassHeartbeatThreadimplementsRunnable{\npublicvoidrun(){\nif(renew()){\nlastSuccessfulHeartbeatTimestamp=System.currentTimeMillis();\n}\n}\n}

走的是renew方法请求服务续约,成功后会更新lastSuccessfulHeartbeatTimestamp字段。

booleanrenew(){\nEurekaHttpResponse<InstanceInfo>httpResponse;\ntry{\nhttpResponse=eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(),instanceInfo.getId(),instanceInfo,null);\nlogger.debug(PREFIX+&34;,appPathIdentifier,httpResponse.getStatusCode());\n//如果是没有发现该实例信息的话\nif(httpResponse.getStatusCode()==Status.NOT_FOUND.getStatusCode()){\nREREGISTER_COUNTER.increment();\nlogger.info(PREFIX+&34;,appPathIdentifier,instanceInfo.getAppName());\nlongtimestamp=instanceInfo.setIsDirtyWithTime();\n//todo进行服务注册,如果我们不在配置文件中指定服务初始化就注册该服务,那么服务的注册实际是在这里注册的\nbooleansuccess=register();\nif(success){\ninstanceInfo.unsetIsDirty(timestamp);\n}\nreturnsuccess;\n}\nreturnhttpResponse.getStatusCode()==Status.OK.getStatusCode();\n}catch(Throwablee){\nlogger.error(PREFIX+&34;,appPathIdentifier,e);\nreturnfalse;\n}\n}

很简单,就是调用eurekaTransport.registrationClient.sendHeartBeat方法发送服务续约的请求,如果你实例信息在EurekaServer中不存在的话,就进行服务注册,我们再稍微看下sendHeartBeat方法,里面请求uri就是StringurlPath=“apps/”+appName+‘/’+id;

服务续约请求:PUT请求,path为:apps/{appName}/{instanceId}

4.3.3定时更新Client信息给Server任务

privatevoidinitScheduledTasks(){\n…\n//开启注册\nif(clientConfig.shouldRegisterWithEureka()){\n\n…\n//todo定时更新Client信息给服务端\n//InstanceInforeplicator\ninstanceInfoReplicator=newInstanceInfoReplicator(\nthis,\ninstanceInfo,\nclientConfig.getInstanceInfoReplicationIntervalSeconds(),\n2);//burstSize\n\nstatusChangeListener=newApplicationInfoManager.StatusChangeListener(){\n@Override\npublicStringgetId(){\nreturn&34;;\n}\n\n//监听到StatusChangeEvent事件,调用notify方法\n@Override\npublicvoidnotify(StatusChangeEventstatusChangeEvent){\nlogger.info(&34;,statusChangeEvent);\n//todo通知执行方法,这个方法就是立即向服务端发起注册请求\ninstanceInfoReplicator.onDemandUpdate();\n}\n};\n\n//向applicationInfoManager中注册状态变化事件监听器\nif(clientConfig.shouldOnDemandUpdateStatusChange()){\napplicationInfoManager.registerStatusChangeListener(statusChangeListener);\n}\n\n//todo参数默认40s\ninstanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());\n}\n…\n}

我们看下这个start启动方法:

publicvoidstart(intinitialDelayMs){\nif(started.compareAndSet(false,true)){\ninstanceInfo.setIsDirty();//forinitialregister\nFuturenext=scheduler.schedule(this,initialDelayMs,TimeUnit.SECONDS);\nscheduledPeriodicRef.set(next);\n}\n}

这里有个非常重要的点,调用了实例信息的setIsDirty方法,后面的注释说是为了初始化服务注册。

创建一个延时任务,默认是40s。看看40s执行啥东西。com.netflix.discovery.InstanceInfoReplicator34;Therewasaproblemwiththeinstanceinforeplicator&34;statusChangeListener&34;Sawlocalstatuschangeevent{}&onDemandUpdate方法,检查服务实例信息和服务状态的变化,可能会引起按需注册任务,代码如下:

publicbooleanonDemandUpdate(){\nif(rateLimiter.acquire(burstSize,allowedRatePerMinute)){\nif(!scheduler.isShutdown()){\n//提交\nscheduler.submit(newRunnable(){\n@Override\npublicvoidrun(){\nlogger.debug(&34;);\n\nFuturelatestPeriodic=scheduledPeriodicRef.get();\nif(latestPeriodic!=null&&!latestPeriodic.isDone()){\nlogger.debug(&34;);\n//取消定时任务\nlatestPeriodic.cancel(false);\n}\n//todo执行向Server端重新注册的请求\nInstanceInfoReplicator.this.run();\n}\n});\nreturntrue;\n}else{\nlogger.warn(&34;);\nreturnfalse;\n}\n}else{\nlogger.warn(&34;);\nreturnfalse;\n}\n}

InstanceInfoReplicatorrun方法检查服务实例信息和服务状态的变化,并在服务实例信息和服务状态发生变化的情况下向EurekaServer发起重新注册的请求,为了防止重新执行run方法,onDemandUpdate方法还会取消执行上次已经提交且未完成的run方法,执行最新的按需注册任务。

4.4总结

服务注册的时机

Client提交register()请求的情况有三种:

在应用启动时就可以直接进行register(),不过,需要提前在配置文件中配置在renew时,如果server端返回的是NOT_FOUND,则提交register()当Client的配置信息发生了变更,则Client提交register()

Client实例化

EurekaClient实例化的时候有几个重要步骤,分别如下:

全量拉取注册表信息,放入自己本地注册表中。创建定时任务,定时服务续约任务,默认是30s,定时更新客户端注册表信息,默认是30s,定时更新Client信息给Server端,重新服务注册,默认是40s。

参考文章

eureka-0.10.11源码(注释)springcloud-source-study学习github地址Eureka源码解析SpringCloud技术栈系列文章

好了,本文到此结束,如果可以帮助到大家,还望关注本站哦!

Published by

风君子

独自遨游何稽首 揭天掀地慰生平