目录

1.基本架构

1.1 Eureka三个核心要素

1.2 服务治理机制

1.2.1 服务提供者

1.2.2 服务消费者

1.2.3 服务注册中心

2.源码分析

2.1 Region、Zone

2.2 serviceUrls

2.3 服务注册、服务获取和服务续约

2.4 服务注册中心


本节笔记深入地去了解eureka的机制及原理等内容。

1.基本架构


1.1 Eureka三个核心要素

  • 服务注册中心:Eureka提供的服务端,提供服务发现和注册功能,联系项目eureka-demo
  • 服务提供者:提供服务的应用,将自己的服务注册到Eureka注册中心,供其他服务调用,联系项目yasuo-demo。
  • 服务消费者:消费者应用从注册中心获取已注册的服务列表,进而去调用提供者的服务,可以用ribbon实现,也可以用feign实现。联系项目ruiwen-demo。

消费者和提供者并不是必须区别开,很多服务既是消费者也是提供者。


1.2 服务治理机制

上图为Eureka的基本架构图,服务注册中心1和2相互注册组成了高可用注册中心,服务提供者注册在两个注册中心上(可以是一个项目启动两个端口不同的实例分别注册,也可以看作两个不同项目分别注册,也可以是一个实例同时注册两个注册中心)服务消费者也关联两个注册中心获取服务列表以供消费者调用提供者(消费者的可能情况同提供者)。

1.2.1 服务提供者

1.服务注册

提供者启动后会发送REST请求到Eureka注册中心,请求中包含了自身的元数据信息。Eureka接收请求后将元数据信息保存在一个双层Map中,第一层的key是服务名,第二层key是具体的服务实例名:

结构:(“key(服务名)”:(“key具体服务的实例名”:“元数据”))

提供者的配置中eureka.client.register-with-eureka(是否注册到eureka)必须为true或者默认。

2.服务同步

架构图中,有两个服务提供者实例,分别注册到两个不同的服务注册中心,即它们的信息分别被两个不同的服务注册中心所维护,因为服务注册中心之间实现了同步(即当一个服务提供者发送注册请求到一个服务注册中心时,它就会将请求转发到集群中的与之相连的服务注册中心),所以通过这两台服务注册中心的任意一台都可以获取该服务提供者的信息(服务列表)。

3.服务续约

注册完成之后,服务提供者还会维护一个心跳,告诉Eureka Server:“我还活着”,以防止被Eureka Server剔除。默认情况下,续约任务间隔为30秒,服务失效的时间为90秒,可以通过配置文件设置时间。

eureka:instance:lease-renewal-interval-in-seconds: 30 #服务续约心跳发送间隔时间(单位:秒)lease-expiration-duration-in-seconds: 90 #服务失效时间(单位:秒)

4.服务下线  

当实例正常关闭时,会向注册中心发送一个REST请求,告知注册中心我要关闭了,注册中心收到请求后就会把该服务的状态置为down,并将此事件传播出去,避免其他服务调用已经失效的服务实例。

1.2.2 服务消费者

1.获取服务

消费者启动时,同提供者一样会发送一个REST请求到注册中心,此请求用于获取已被注册的服务信息,为了性能考虑,Eureka Server会维护一份只读服务清单返回给调用方,同时每30秒更新一次清单列表。消费者必须保证eureka.client.fetch-registry(从注册中心获取服务清单)为true或者默认。如果想要修改获取清单的间隔时间,可以通过修改配置eureka.client.registry-fetch-interval-seconds=30来进行修改。

eureka:client:registry-fetch-interval-seconds: 30 #从注册中心获取服务清单的间隔时间(单位:秒)

2.服务调用

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

Eureka中有Region和Zone两个概念,一个Region中可以包括多个Zone,每个服务客户端需要被注册到一个Zone中,所以客户端对应一个Region和一个Zone。在进行服务调用的时候,优先访问同处一个Zone中的服务提供方,若访问不到,则访问其他Zone。Region和Zone在后面的笔记介绍。

1.2.3 服务注册中心

1.失效剔除

有时候服务实例异常(例如网络故障)下线, 此时服务注册中心并没有收到“服务下线”的请求,为了从列表中将这些无法提供服务的实例剔除,Eureka Server在启动时会创建一个定时任务,默认每隔一段时间(默认为60秒),将当前清单中超时(默认90秒)没有续约的服务剔除出去。

2.自我保护机制

Eureka Server在运行期间,如果检测到十五分钟内心跳失败服务数量超过总数量的85%,这种情况会被判断为注册中心方由于网络波动导致无法获取实例心跳,Eureka Server就会将当前实例注册信息保护起来,让这些实例不会过期。在单机的情况之下容易触发自我保护机制。当触发自我保护机制时,Spring Eureka面板页面出现下面情况:

当然可以通过配置手动关闭:

eureka:server:#关闭自我保护机制,保证不可用服务被及时剔除enable-self-preservation: false

2.源码分析

前面简单介绍了Eureka的运行机制,为了更深入的了解eureka,下面结合源码来展示各通信行为是如何实现的。

当一个普通应用注册到Eureka的注册中心或者从注册中心获取服务列表时,项目所需要的有关Eureka的配置主要在于两个方面:

  • 应用主类xxxApplication类中添加@EnableDiscoveryClient注解
  • 在配置文件中使用eureka.client.service-url.defaultZone表明注册中心地址

所以先看看@EnableDiscoveryClient注解的源码,具体如下:

boolean autoRegister() default true;表示希望自动注册服务。既然这个注解的名字叫EnableDiscoveryClient,说明主要是为了支持DiscoveryClient的使用,全局搜索一下DiscoveryClient这个类,其中org.springframework.cloud.client.discovery包下有一个DiscoveryClient类,com.netflix.discovery包下也有一个DiscoveryClient类,前者是spring cloud的接口,主要作用是通过服务id来获取服务实例:

org.springframework.cloud.client.discovery.DiscoveryClient有一个实现类EurekaDiscoveryClient,EurekaDiscoveryClient就是netflix包中的内容了,EurekaDiscoveryClient是对Eureka发现服务的封装,所以它依赖了EurekaClient类:

言归正传,来看看真正负责服务发现的类,com.netflix.discovery.DiscoveryClient:

可以看到该类实现了EurekaClient接口,然后再查看EurekaClient的整体继承关系,其中绿色虚线是实现,蓝色实线是继承,虚线是注解:

回到DiscoveryClient类,通过头部注解可以了解该类的大致信息:

The class that is instrumental for interactions with Eureka Server
译:用于于与Eureka Server进行交互的类Eureka Client is responsible for Registering the instance with Eureka Server Renewal of the lease with Eureka Server.
译:Eureka Client负责向Eureka Server注册实例,并向Eureka Server续订租约。Cancellation of the lease from Eureka Server during shutdown.
译:(服务/客户端)关闭期间从Eureka Server取消租约。Querying the list of services/instances registered with Eureka Server.
译:查询已在Eureka Server中注册的服务/实例列表。Eureka Clientneeds a configured list of Eureka Server urls to talk to.
译:Eureka Client需要配置一个Eureka Server URL列表用于交互。These urls are typically amazon elastic eips which do not change.
译:这些url列表通常是不会变更的亚马逊弹性eip。All of the functions defined above fail-over to other urls specified in the list in the case of failure.
译:如果发生故障,上面定义的所有功能都将故障转移到列表中指定的其他URL。

头注释中包含了EurekaClient的功能及执行功能所需要的条件,从它所需条件开始分析,就是需要配置配置一个Eureka Server URL列表用于交互,从之前的简单案例中,可以知道Eureka根据配置文件中的eureka.client.service-url.defaultZone可以获得URL列表并依次来加载相关属性。通过搜索getServiceUrls,在EurekaClient类中可以找到通过两个渠道获取Urls的三个方法:getServiceUrlsFromConfig(两个)和getServiceUrlsFromDNS。同时也可以看到,这三个方法都被打上了@Deprecated注解,也就是被弃用了。

通过注释中的@link注解,可以定位到netflix包下的一个工具类EndpointUtils,在EndpointUtils类中,同样搜索到了三个方法:getServiceUrlsFromConfig、getServiceUrlsFromDNS和getServiceUrlsMapFromConfig,其中getServiceUrlsFromConfig和getServiceUrlsMapFromConfig的基本逻辑是一样的,只是一个返回list一个返回map,代码如下:

public static Map<String, List<String>> getServiceUrlsMapFromConfig(EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) {//返回的地址MapMap<String, List<String>> orderedUrls = new LinkedHashMap<>();//获取regionString region = getRegion(clientConfig);//获取所有处于在线的zone--beginString[] 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);//获取所有处于在线的zone--end//加载Eureka Sever的具体地址--beginint myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones);String zone = availZones[myZoneOffset];List<String> serviceUrls = clientConfig.getEurekaServerServiceUrls(zone);if (serviceUrls != null) {orderedUrls.put(zone, serviceUrls);}//加载Eureka Sever的具体地址--end//一堆校验(省略)………………………………………………return orderedUrls;}

2.1 Region、Zone

从源码中可以看到,客户端依次加载了region和zone。

  • 上面第六行代码,进入getRegion方法可以看到:
public static String getRegion(EurekaClientConfig clientConfig) {String region = clientConfig.getRegion(); //配置文件中读取regionif (region == null) {region = DEFAULT_REGION;}region = region.trim().toLowerCase();  //去除空格 转换为小写return region;}

客户端从配置文件中读取了一个Region返回,这个region是由当前服务定义的,如果没有定义,则赋值为DEFAULT_REGION,所以一个服务只有一个region,可以通过eureka.client.region属性在配置文件中定义:

  • 获取完region后就是getAvailabilityZones,进入该方法可以看到:
public String[] getAvailabilityZones(String region) {String value = this.availabilityZones.get(region);if (value == null) {value = DEFAULT_ZONE;}return value.split(",");}

可以看到,当项目没有为region专门设置zone的时候,会使用DEFAULT_ZONE,所以在配置文件中,我们配置了eureka.client.service-url.defaultZone属性。如果想要指定zone,可以通过eureka.client.availability-zones属性进行设置:

从方法返回中可以看到,一个项目可以配置多个zone,每个zone通过","隔开,这证明了在一个客户端中region和zone是一对多的关系。


2.2 serviceUrls

在加载了region和zone后,客户端开始加载Eureka Server的真正地址,他根据入参并按照一定的算法确定加载位于哪一个zone的serviceUrls:

 int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones);String zone = availZones[myZoneOffset];List<String> serviceUrls = clientConfig.getEurekaServerServiceUrls(zone);

具体的加载方法可以进入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<>();}

无论是加载zone还是加载serviceUrls,其方法都位于EurekaConfigBean这个实现类中,该类是EurekaClientConfig和EurekaConstants接口的实现,用于加载配置文件中的内容。当然里面就有许多值得一看的方法,目前只关注defaultzone相关内容,在getEurekaServerServiceUrls()这个方法中展示了如何解析zone的过程。其中serviceUrl是一个Map<String,String>类型的属性key为zone,value为对应的url。

当我们在微服务应用中使用Ribbon来实现服务调用时,对于zone的设置可以在负载均衡时实现区域亲和特性:Ribbon的默认策略会优先访问同客户端处于一个Zone的服务端实例,只有当一个zone中没有可用的服务端实例时,才会访问其他zone中的实例。所以通过zone属性的定义,配合实际部署的物理结构,就可以有效地设计出针对区域性故障的容错集群。


2.3 服务注册、服务获取和服务续约

在discoveryClient中寻找与服务注册有关的内容,在构造器中,DiscoveryClient会调用一个initScheduledTasks()方法,从命名就可以看出这是一个初始化方法,那么我们可以从这个方法入手,源码如下:

 /*** Initializes all scheduled tasks.*/private void initScheduledTasks() {if (clientConfig.shouldFetchRegistry()) {  //*******判断1*******// registry cache refresh timerint registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();scheduler.schedule(new TimedSupervisorTask("cacheRefresh",scheduler,cacheRefreshExecutor,registryFetchIntervalSeconds,TimeUnit.SECONDS,expBackOffBound,new CacheRefreshThread()  //*******关键点1*******),registryFetchIntervalSeconds, TimeUnit.SECONDS);}if (clientConfig.shouldRegisterWithEureka()) {  //*******判断2*******int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);// Heartbeat timerscheduler.schedule(new TimedSupervisorTask("heartbeat",scheduler,heartbeatExecutor,renewalIntervalInSecs,TimeUnit.SECONDS,expBackOffBound,new HeartbeatThread()),renewalIntervalInSecs, TimeUnit.SECONDS);  //*******关键点2*******// InstanceInfo replicatorinstanceInfoReplicator = new InstanceInfoReplicator( //*******关键点3*******this,instanceInfo,clientConfig.getInstanceInfoReplicationIntervalSeconds(),2); // burstSizestatusChangeListener = new ApplicationInfoManager.StatusChangeListener() {@Overridepublic String getId() {return "statusChangeListener";}

在这段代码中,出现两个重要的if判断,首先看判断1处的if判断:

if (clientConfig.shouldFetchRegistry()),顾名思义,获取配置中参数eureka.client.fetch-registery(是否获取服务列表)的值,如果是服务消费者,那么这个值就为true,进入判断代码块后,首先会从配置中获取一个参数:registeryFetchIntervalSeconds:对应配置参数eureka.client.registry-fetch-interval-seconds,即从服务注册中心获取服务列表的间隔时间,默认是30。之后会执行定时任务CacheRefreshThread(),为什么叫CacheRefreshThread呢?因为客户端所持有的服务列表会缓存起来,到了一定时间(即上面的eureka.client.registry-fetch-interval-seconds)后会更新缓存并重新从注册中心获取新的服务列表,因此在关键点1处有一个“cache”开头的方法,负责服务获取。

然后再看判断2处的if判断:

if(clientConfig.shouldRegisterWithEureka()),可以看出,此处判断参数eureka.client.register-with-eureka(是否将自己注册到服务注册中心,默认true),如果为true,那么就意味着需要做两件事:服务注册与服务续约。那么我们看代码进入判断2的if判断之后,首先看关键点2处的代码,很明显这是一个定时任务,从renewalIntervalInSecs变量可以看出,这是心跳机制的定时任务,renewalIntervalInSecs变量则是从配置文件参数eureka.instance.lease-renewal-interval-in-seconds的值(默认为30),含义是心跳间隔时间,而HeartbeatThread()方法就是续约方法,HeartbeatThread()方法代码如下:

 /*** The heartbeat task that renews the lease in the given intervals.*/private class HeartbeatThread implements Runnable {public void run() {if (renew()) {lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();}}}

在run()方法检验了renew()方法的返回值,进去看一下源码:

/*** 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() == 404) {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() == 200;} catch (Throwable e) {logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);return false;}}

该方法就是发送Rest请求给注册中心,若返回404,则调用register()方法,这个方法就是将自己的元数据重新注册到服务中心,具体代码不贴上来了。

之后在关键点3处的代码会创建一个InstanceInfoReplicator对象,这个对象也会做一个定时任务,具体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);}}

到这里就很容易就能锁定真正负责服务注册的方法,方法名就是 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() == 204;}

通过注释可以看到该方法是通过REST请求方式向Eureka注册服务。而发起注册请求时,也就是上面代码的第8行,入参传入了一个InstanceInfo,该类位于com.netflix.appinfo包中,点进去可以发现,该对象就是服务注册时,客户端提供给服务端的服务元数据

至此,关键点1、关键点2、关键点3三处代码分别是服务获取、续约与注册功能实现。由于服务注册与续约均需要参数eureka.client.register-with-eureka为true,因此两个功能写入一个if判断中,而服务发现需要参数eureka.client.fetch-registery为true,因此单独在一个if判断中。


2.4 服务注册中心

通过前面的介绍,可以发现客户端所有的交互都是REST请求进行的。下面就看一下注册中心对这些请求的处理。Eureka Server对各类REST请求的定义都位于com.netflix.eureka.resource包下。

先从“服务注册”开始,类名为ApplicationsResource.java,该类主要负责注册流程,方法名为addInstance:

    @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方法来进行服务注册:

 public void register(final InstanceInfo info, final boolean isReplication) {this.handleRegistration(info, this.resolveInstanceLeaseDuration(info), isReplication);super.register(info, isReplication);}

进入handleRegistration方法,源码如下:

private void handleRegistration(InstanceInfo info, int leaseDuration, boolean isReplication) {this.log("register " + info.getAppName() + ", vip " + info.getVIPAddress() + ", leaseDuration " + leaseDuration + ", isReplication " + isReplication);this.publishEvent(new EurekaInstanceRegisteredEvent(this, info, leaseDuration, isReplication));}

也就是说register方法中,先调用publishEvent方法,把新注册的事件传播出去,然后调用父类AbstractInstanceRegistry中的register方法,将instanceInfo元数据信息存储在一个ConcurrentHashMap对象中,如之前所说,注册中心存储了两层Map结构,第一层Key是instanceInfo的appName服务名属性,第二层的key是instanceInfo中的instanceId实例名属性。

到此有关服务相关的源码阅览完毕,关于源码内容较多,其他方面不多进行记录。

Spring:从零开始的Cloud生活(一)——Eureka 深入理解相关推荐

  1. 【夯实Spring Cloud】Spring Cloud中的Eureka服务注册与发现详解

    本文属于[夯实Spring Cloud]系列文章,该系列旨在用通俗易懂的语言,带大家了解和学习Spring Cloud技术,希望能给读者带来一些干货.系列目录如下: [夯实Spring Cloud]D ...

  2. 基于Spring cloud Ribbon和Eureka实现客户端负载均衡

    前言 本案例将基于Spring cloud Ribbon和Eureka实现客户端负载均衡,其中Ribbon用于实现客户端负载均衡,Eureka主要是用于服务注册及发现: 传统的服务端负载均衡 常见的服 ...

  3. Spring Cloud(二) 配置Eureka Client

    前文回顾: Spring Cloud(一)Eureka Server-单体及集群搭建 本节我们将创建两个Eureka Client,注册到上节中的Eureka Server中,一个作为服务提供方,一个 ...

  4. Spring Cloud中,Eureka常见问题总结

    Spring Cloud中,Eureka常见问题总结. 1 eureka.environment: 指定环境 参考文档: 1 eureka.datacenter: 指定数据中心 参考文档: 使用配置项 ...

  5. 【Spring Cloud笔记】 Eureka通过集群实现高可用

    Eureka实现服务注册与发现,在Spring Cloud微服务中起着关键性的作用,必须保障其高可用,常规方案无非通过集群实现.这里在本地机器搭建一个伪集群环境,通过两个节点实现相互注册,并通过主备数 ...

  6. 实践出真知之Spring Cloud之基于Eureka、Ribbon、Feign的真实案例

    转载自  实践出真知之Spring Cloud之基于Eureka.Ribbon.Feign的真实案例 Eureka是Spring Cloud Eureka的简称,是Netflix提供的组件之一.通过E ...

  7. 【重难点】【分布式 01】RESTful、RPC 对比、Dubbo、Spring Cloud 对比、Eureka、Zookeeper、Consul、Nacos 对比、分布式锁

    [重难点][分布式 01]RESTful.RPC 对比.Dubbo.Spring Cloud 对比.Eureka.Zookeeper.Consul.Nacos 对比.分布式锁 文章目录 [重难点][分 ...

  8. Spring Cloud Netflix之Eureka上篇

    前言:Spring Cloud NetFlix这个项目对NetFlix中一些久经考验靠谱的服务发现,熔断,网关,智能路由,以及负载均衡等做了封装,并通过注解的或简单配置的方式提供给Spring Clo ...

  9. 破甲两千六 Spring Cloud 教程(三):添加Spring Cloud 的 Netflix Eureka 插件,实现服务端、客户端的发现与注册

    写在前面: Spring Cloud 为开发人员提供了快速构建分布式系统的一些工具,包括配置管理.服务发现.断路器.路由.微代理.事件总线.全局锁.决策竞选.分布式会话等等. 5大常用组件: 服务发现 ...

最新文章

  1. html-表单初级验证
  2. 查看eclipse 内存使用情况
  3. mysql数据库原理与应用武洪萍第三张答案_mysql数据库武洪萍版第四章习题与答案...
  4. GitHub的SSH免密连接
  5. javascript高级编程笔记04(基本概念)
  6. Oracle动态执行表不可访问解决方法
  7. 冲刺一阶段———个人总结08
  8. 利用PLTS将校准用的“trace”一分为二 操作指南
  9. 如何前后端分离的架构中使用Shiro框架
  10. kubernetes 创建pod /merged/dev/shm: invalid argument
  11. foobar2000播放的一些使用技巧
  12. linux tomcat bin目录,Tomcat bin目录详解-bin文件怎么打开
  13. 生物特征识别六大技术,你知道多少?
  14. oracle 数据库中order by 的一些高级用法
  15. C语言实现大数据的加法
  16. Java系统线上生产问题排查一把梭
  17. ARM服务器搭建 我的世界(MC) 1.18.2 版私服教程
  18. 人工智能技术--四要素分析
  19. 【读书笔记】Haskell函数式编程入门 I 基础篇 2. 类型系统和函数
  20. 深度学习实践与大数据实践【二】

热门文章

  1. unity | 竖排显示文字text
  2. 【openSSH】通过证书文件免密码远程登录
  3. 第 11 场双周赛-5089. 安排会议日程(双指针)
  4. 怎么设置uboot从u盘启动linux,RT5350使用uboot从U盘启动linux顺利
  5. 怎么去阅读文献-转自小木虫
  6. Delphi设计模式之Wapper模式, Factory
  7. Go语言爱好者周刊:第 142 期
  8. BZOJ4892: [Tjoi2017]dna
  9. 用“找回你”,查找手机号的前身都被注册过些什么……
  10. 微信、QQ等第三方账号登录的具体思路