SpringCloud入坑记-深入Eureka

SpringCloud入坑记-深入Eureka_第1张图片

前言

如果还不了解Eureka,那么应该先从上一篇开始 SpringCloud入坑记-初识Eureka。搭建一个Eureka Server已经注册一个实例,才算有一个初步的认知。

自我保护

在实践过程中,很可能会在页面遇到下面一段红色文字:
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

这是Eureka server的一种自我保护机制。

Eureka Server与Client之间是通过心跳确认对方是否存活。而现实环境中,网络的抖动或者闪断可能导致心跳未能如约而至,而服务本身是健康的,如果此时Eureka server将实例去除,就会导致正常的服务无法被调用到,要是网络抖动比较频繁会使得程序很不稳定。极端一点,发送心跳时网络掉线导致注册的服务信息被删除,所有服务都失效无法访问了。
Eureka引入了自我保护老解决这个问题,当最近一分钟接收到的心跳次数小于一个值的时候,就禁止服务被删除,保护注册信息,同时显示一段文字提示。

不过也有几个问题:服务停掉了,还可以在Eureka Server上看到;服务上线了,Eureka Server上还不能及时获取到。
在项目开发时,这个功能会干扰我们的集成,可以通过下面的参数关掉:
eureka.server.enableSelfPreservation=false

Eureka Server HA 高可用

在分布式系统中,服务注册中心是最重要的基础部分

单个Eureka Server

上一节已经实现的Eureka Server与Client可以通过下面这个图表示:

SpringCloud入坑记-深入Eureka_第2张图片

可以看到,Client注册到Server上。
如果多个服务的话,可以都注册到Server上,就可以用下面这个图表示:

SpringCloud入坑记-深入Eureka_第3张图片

很容易就发现,Eureka Server是很重要的一环,服务ClientA要与服务ClientB通信,不直接找对方,而是通过中间桥梁 Eureka Server。要是Eureka Server宕机,服务Client A、B、C…就无法注册,于是需要做高可用的Eureka Server

两个Eureka Server

Eureka Server支持一种P2P模式,可以在ServerA中注册ServerB,它们就会相互协同工作。

SpringCloud入坑记-深入Eureka_第4张图片

复制之前的项目ms-a1-eureka-server,命名为ms-b1-eureka-server,仅仅修改其中的配置文件application.yml

server:
  port: 8761
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:8762/eureka

可以看到,对于ServerA(端口8761)注册ServerB的信息(端口8762)。
再次复制之前的项目ms-a1-eureka-server,命名为ms-b2-eureka-server,仅仅修改其中的配置文件application.yml

server:
  port: 8762
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:8761/eureka

ServerB(端口8762)注册ServerA的信息(端口8761)。

分别启动两个项目,在Eureka Server的控制台可以看到如下信息:

SpringCloud入坑记-深入Eureka_第5张图片

和之前的控制面板显示的信息不一样。

对于客户端,也需要相应的配置来适应Eureka Server的高可用部署。

复制之前的项目ms-a1-product-plain,命名为ms-b1-product-plain,仅仅修改其中的配置文件application.yml

server:
  port: 8080
spring:
  application:
    name: product-plain
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka

将所有的Eureka Server列表都写在了配置里,用逗号隔开。启动该项目,可以在两个Eureke Server的页面看到注册的信息。停掉任意一个Eureka Server,对于商品服务"product-plain"没有影响。
SpringCloud入坑记-深入Eureka_第6张图片
如果有多个服务的话,可以都注册到Server上
SpringCloud入坑记-深入Eureka_第7张图片

三个Eureka Server

在实际的生产中,2台Eureka Server是不够的,一般需要3台或者更多。
对于三台Eureka Server,需要Eureka Server两两之间相互配合。

SpringCloud入坑记-深入Eureka_第8张图片

这时,对于ServerA就需要如下配置application.yml

server:
  port: 8761
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:8762/eureka,http://localhost:8763/eureka

在Client端,需要将所有的Server配置好:

server:
  port: 8080
spring:
  application:
    name: product-plain
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka,http://localhost:8763/eureka

Eureka Server故障分析

上面讲过Eureka Server可以做高可用,但是这就能保证万无一失了吗?
有一个理论叫墨菲定律,它的一个观点可以解释为:如果你担心某种情况会发生,那么它极有可能会发生,久而久之一定会发生。

Eureka Server 全部宕机

最坏的情况就是所有的Eureka Server全部宕机,如果此时Client还没有启动,那么服务可以启动,但是会一直打印如下错误信息:
com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused: connect
com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server
这样该服务无法被其他服务感知到,不能调用其他服务,也不会被其他服务调用到。属于一个孤立的服务,一般属于无用服务。

要是服务在成功启动,并且注册到了Eureka Server以后,全部的Eureka Server才宕机,此时客户端依然会报错,但是行为略有不同。客户端本地会维护一个所有服务的注册列表信息,在源代码DiscoveryClient中可以看到如下声明
private final AtomicReference localRegionApps;

它保存了一份其他服务的注册信息,通过CacheRefreshThread来定时刷新,时间间隔为renewalIntervalInSecs,默认为30S。如果刷新时,所有的Eureka Server不可用,那么就不刷新列表。服务间可以正常通信,但是无法感知变化,要是有服务下线,其他服务无法获知。

Eureka Server 部分宕机

Eureka Server之间是Peer to Peer的同步,它比Master-Slave模式更为稳定一些。Master-Slave中Slave宕机不影响系统,要是Master宕机,需要重新选举Master机器,Zookeeper便是这种机制。P2P模式不涉及到选举,只是Eureka Server之间的同步会报错,不影响正常使用。

客户端这边,在配置文件中存有所有Eureka Server的地址:
defaultZone: http://server1/eureka,http://server2/eureka,http://server3/eureka

在拉取注册列表信息的时候,客户端会尝试依次从这些服务器中获取信息,获取不到就去下一个服务器中找。目前Eureka Server 部分宕机,所以肯定可以获取到。其次,在客户端还维护了一个不可达的Eureka Server列表,之后就不从这些服务器拉取信息。要是该列表中的Eureka Server数目达到一定的值,就将其清空
还有一点,客户端在拉取注册信息的时候,不是每次都从第一个开始,这样所有的请求压力都集中在第一台服务器上,而其他的Eureka Server不干活。实际上,这个请求是随机的,保证所有的Eureka Server能均匀地处理客户端的拉取请求。

Eureka Server的REST API

Eureka Server暴露了一些API可以直接调用,大部分时候,我们用不到这些API,但是在某些场景下,可以定制化一些东西。

先列举几个接口:

操作 http method http request
注册新服务 POST /eureka/apps/{appId}
注销服务 DELETE /eureka/apps/{appId}/{instanceId}
心跳 PUT /eureka/apps/{appId}/{instanceId}
查询所有实例 GET /eureka/apps
查询单个实例 GET /eureka/apps/{appId}

还有很多接口可以调用,在Eureka的GitHub上有详细的API列表。

有什么用呢?试想一个场景,我们要定制化Eureka Server的页面,将其嵌入到我们现有的监控平台,直接把Eureka Server现有的页面加入其中显得风格不搭,就可以通过上面提供的API来自定义界面,还可以添加更多的功能。

例如,我访问http://localhost:8761/eureka/apps/PRODUCT-PLAIN,就可以拿到如下信息:

<application>
<name>PRODUCT-PLAINname>
<instance>
<instanceId>192.168.2.200:product-plain:8080instanceId>
<hostName>192.168.2.200hostName>
<app>PRODUCT-PLAINapp>
<ipAddr>192.168.2.200ipAddr>
<status>UPstatus>
<overriddenstatus>UNKNOWNoverriddenstatus>
<port enabled="true">8080port>
<securePort enabled="false">443securePort>
<countryId>1countryId>
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
<name>MyOwnname>
dataCenterInfo>
<leaseInfo>
<renewalIntervalInSecs>30renewalIntervalInSecs>
<durationInSecs>90durationInSecs>
<registrationTimestamp>1555842871524registrationTimestamp>
<lastRenewalTimestamp>1555848452274lastRenewalTimestamp>
<evictionTimestamp>0evictionTimestamp>
<serviceUpTimestamp>1555842870859serviceUpTimestamp>
leaseInfo>
<metadata>
<management.port>8080management.port>
<jmx.port>52858jmx.port>
metadata>
<homePageUrl>http://192.168.2.200:8080/homePageUrl>
<statusPageUrl>http://192.168.2.200:8080/actuator/infostatusPageUrl>
<healthCheckUrl>http://192.168.2.200:8080/actuator/healthhealthCheckUrl>
<vipAddress>product-plainvipAddress>
<secureVipAddress>product-plainsecureVipAddress>
<isCoordinatingDiscoveryServer>falseisCoordinatingDiscoveryServer>
<lastUpdatedTimestamp>1555842871524lastUpdatedTimestamp>
<lastDirtyTimestamp>1555842870720lastDirtyTimestamp>
<actionType>ADDEDactionType>
instance>
application>

未完待续

Eureka还有很多东西可以讨论,需要在实际运用中慢慢体会。在下一节中,我将引入Ribbon这一组件。

项目代码托管于Github。

你可能感兴趣的:(SpringCloud,SpringCloud入坑记)