Spring Cloud(三):服务发现之Eureka注册中心(2)-集群、配置、监控

  分布式微服务在生产环境必须搭建集群来保证高可用,集群至少需要搭建两台服务器。

  Eureka 的集群搭建配置非常简单,每一台 Eureka 只需在配置中指定另外多个 Eureka 的地址就可实现集群的搭建。官方文档:12. Service Discovery: Eureka Server

Eureka 集群搭建

Eureka 可以运行多个实例,并让实例彼些注册,可使 Eureka 更具伸缩性和可用性。实现上,这是 Eureka 默认就支持的,只需在另外的 Eureka 添加一个有效的 serviceurl

集群说明

一个 Eureka Server 应用,部署到两台服务器上,一台为 master,另一个为 slave,通过运行不同的环境参数来启动集群实例。

  • 将 master 注册到 Slave 上。
  • 将 slave 注册到 master 上。

如果是三台服务器,分别将每一台注册到另外两台上,依次类推。

搭建配置

新增 2 个属性配置文件,一个为 master 环境配置,一个为 slave 环境配置;默认启动 master,slave 通过设置运行环境参数启动。

示例的 Demo 集成 Spring Security,增加了 Eureka Server 的 Web 控制台登录安全认证。

  1. 主配置文件:application.properties

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    spring.profiles.active=master
    spring.application.name=eureka-server

    #因自己就是注册中心,所以关闭向注册中心注册自己
    #eureka.client.register-with-eureka=false
    #因注册中心的职责是维护实例,并不需要去检索服务,所以关闭
    #eureka.client.fetch-registry=false
    #设置 eureka server 同步失败的等待时间 默认 5分,在此期间,不向客户端提供服务注册信息
    eureka.server.wait-time-in-ms-when-sync-empty=0
    #设置 eureka server 同步失败的重试次数 默认为 5 次
    eureka.server.number-of-replication-retries=5
    # 扫描失效服务的间隔时间(单位毫秒,默认是60*1000)即60秒
    eureka.server.eviction-interval-timer-in-ms=5000

    #认证账号密码
    spring.security.user.name=admin
    spring.security.user.password=123456
  2. master 配置文件:application-master.properties

    1
    2
    3
    4
    5
    server.port=8761
    eureka.instance.hostname=eureka.master.com
    #注册中心地址
    #eureka.client.service-url.defaultZone=http://eureka.slave.com:8762/eureka/
    eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka.slave.com:8762/eureka/
  3. slave 配置文件:application-slave.properties

    1
    2
    3
    4
    5
    server.port=8762
    eureka.instance.hostname=eureka.slave.com
    #注册中心地址
    #eureka.client.service-url.defaultZone=http://eureka.master.com:8761/eureka/
    eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka.master.com:8761/eureka/
  4. 关闭 Spring Security 的 csrf 防护

    Spring Security 默认开启了 CSRF(跨站请求伪造) 防护,其它实例在请求注册时会失败并报错,需要禁用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * @name: SpringSecurityConfig
    * @desc: SpringSecurity配置类
    **/
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity.csrf().ignoringAntMatchers("/eureka/**");
    }
    }
  5. hosts 域名映射

    1
    2
    3
    #eureka server master/slave
    127.0.0.1 eureka.master.com
    127.0.0.1 eureka.slave.com

    设置 Eureka 实例的主机名: eureka.instance.hostname ,也可使用环境变量在运行时设置主机名,如下:

    1
    2
    eureka.instance.hostname = eureka.master.com
    eureka.instance.hostname = ${HOST_NAME}

    某些情况下,会优先使用 IP 地址来通知服务,而不是主机名。如在阿里云服务器,应用部署在内网,使用的内网IP地址来注册和通知。
    设置 eureka.instance.preferIpAddresstrue,当应用向 Eureka 注册时,链接优先使用其 IP 地址,而不是主机名,如果无法确定主机名,则将 IP 地址通知给 Eureka;在 Eureka Server 的 Web 控制台的实例注册列表中,实例的链接地址改为了 IP 地址。

  6. IDEA 添加运行 slave 环境

    菜单:Run → Edit Configurations → 左侧导航点击【+】,选择 Spring Boot → Configuration:选择入口文件,在 Active profile 输入:slave → 【Apply】→ 【OK】;
    或者在 Configuration → Environment → Program arguments 输入:–spring.profiles.active=slave 来指定 slave 环境。

  7. 或者将 Eureka Server 注册中心打成 jar 包,通过 java 命令指定运行环境,如下:

    1
    2
    java -jar eureka-server.jar --spring.profiles.active=master
    java -jar eureka-server.jar --spring.profiles.active=slave
  8. master 和 slave 分别启动成功后,打开浏览器分别登录这两个服务的 Web 控制台。

    DS Replicas 可以看到集群其它服务实例的主机名。

    Instances currently registered with Eureka 可以看到 Eureka Server 集群其它实例主状态信息(包含多个实例ID:主机名:端口)

    General Info 可以看到 registered-replicasavailable-replicasslave 实例的 url 地址。

注意事项

  1. 集群部署 Eureka Server ,多个注册中心实例, spring.application.name 值必须一致,所以最好放在主配置文件里。

  2. 集群部署时,eureka.client.register-with-eurekaeureka.client.fetch-registry 如果显式配置必须为 true(默认值),可不显式配置使用默认值。

    这两项在 Eureka Server 注册中心部署 单实例 时,显式配置为 false,因只做服务发现和维护实例,不需要向自己注册为客户端实例,也不需要作为客户端来获取注册表(检索服务)。

  3. 集群部署时,主机名不能使用 localhost,即注册中心地址(service-url)不能使用 localhost,否则集群不能互相发现。

  4. 集群部署时,在本地调试,编辑 hosts 设置不同主机名(hostname)映射到本地 IP

如果上以注意事项配置不对,要搭建集群的实例就不能关联,集群就不可用,注册的实例地址 URL 会显示在 General Infounavailable-replicas 项目里。

客户端配置

客户端通过配置 eureka.client.service-url.defaultZone 来指定注册中心地址,当注册中心有多个节点时,该配置的值可为多个节点的地址,多个地址用英文逗号隔开。

1
eureka.client.service-url.defaultZone=http://admin:123456@eureka.master.com:8761/eureka,http://admin:123456@eureka.slave.com:8762/eureka

Web 控制台

  1. master Web 控制台
    eureka master
  2. slave Web 控制台
    eureka slave

Demo 源码

源码:SpringCloud-Example/eureka-cluster/

Eureka 更多配置

自我保护机制

Eureka 提供了 自我保护 模式,模认开启,防止节点之间因网络故障不能正常通信(心跳),但服务是正常运行的情况下,不再删除注册表中的数据。当网络故障恢复后,节点之间通信正常,节点自动退出保护模式。

Eureka 注册中心服务器有个续约期的概念,客户端通过定期发送心跳来告诉服务器我还活着。

续约期有两个参数:Renews threshold 和 Renews (last min)

Renews threshold 描述
Renews threshold Eureka Server 期望每分钟收到客户端续约的总数(心跳阀值)
Renews (last min) Eureka Server 最近一分钟收到客户端续约的总数(心跳数)

如果出现 Renews (last min) < Renews threshold,则表示有客户端服务断开了,但不会立即从注册表中删除该客户端服务的信息,而是进入保护模式。

如果在 Web 控制台看到如下所示内容,表示进入了保护模式:

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.enable-self-preservation=false

#自我保护系数(默认0.85),自我保护模式必须开启
eureka.server.renewal-percent-threshold=0.49

则会报如下信息:
THE SELF PRESERVATION MODE IS TURNED OFF. THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.

页面状态和健康指标

Eureka 实例的状态页和健康指标采集依赖于 Spring Boot Actuator ,采集自默认端点 /info/health 端点,也是 Actuator 默认使用的端点。

应用如果使用非默认上下文路径或 servlet 路径(如自定义了 server.servlet.context-path=/custom)。 Eureka 实例的状态页面路径和健康检测路径也要根着修改,示例如下。这些链接显示在客户端的元数据中,并在某些场景中用于决定是否向应用发送请求。

application.properties

1
2
3
server.servlet.context-path=/app
eureka.instance.status-page-url-path=${server.servlet.context-path}/info
eureka.instance.health-check-url-path=${server.servlet.context-path}/health

Eureka 健康检查

默认情况下,Eureka 通过客户端发送的心跳来确定客户端是否正常(up),客户端并不是根据 Spring Boot Actuator 传播应用程序的当前运行状态。

在客户端成功注册后,Eureka Server(注册中心)会一直认为客户端应用是正常运行的(设置客户端状态为 up),可通过开启客户端健康检查来修改客户端的运行状态(默认开启),从而将客户端应用的状态传播到其它 Eureka 应用(注:这块的处理机制还有疑问,待细究)。其他应用不会将流量(请求)发送到除 up 之外的其他状态的应用。

application.properties

1
eureka.client.healthcheck.enabled=true

自定义实例 ID

Eureka 实例注册的ID等于其主机名(即每个主机只有一个服务)。

  1. Spring Cloud Eureka 提供了合理的默认值,由以下环境变量组合而成:${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}}

    示例:myhost:myappname:8080

    1
    2
    3
    server.port=8080
    spring.application.name=myappname
    spring.cloud.client.hostname=myhost
  2. 还可以通过设置 eureka.instance.instanceId 唯一标识符来覆盖默认值

    示例:sakila-service1:52a65013349b6e9d2d17b6b1812fa188

    1
    eureka.instance.instance-id=${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}
  3. 配置项说明

  • spring.cloud.client.hostname:取自元数据,来自操作系统,可在配置文件显式指定,在注册中心的注册列表里作为实例ID的组成部分在页面显示,用于区分不同的实例。
  • spring.application.instance_id:实例ID,默认值是 null。
  • vcap.application.instance_id:默认是空,在Cloud Foundry中,会自动填充。
  • random.value:使用随机值。
  1. 注意主机名:hostname
  • spring.cloud.client.hostname:配置 Eureka 实例 ID,默认来自Spring Cloud元数据,取自操作系统;可显式配置。在 Eureka Web 控制台的注册列表中显示 Eureka 实例 ID 用。
  • eureka.instance.hostname:配置 Eureka 实例主机名,默认来自 Eureka 元数据,取自操作系统;可显式配置。在 Eureka Web 控制台的注册列表中Eureka 实例的跳转链接用。
    如果开启了优使用 IP地址(eureka.instance.preferIpAddress=true),则实例的跳转链接为主机的IP地址。
  • eureka.instance.virtual-host-name:给 Eureka 实例指定一个虚拟主机名,通常其他实例使用虚拟主机名查找此实例(可将此虚拟主机名视为类似于完全限定的域名,供其它服务查找此实例)。见本篇的【使用 EurekaClient】章节。
    虚拟主机名默认被设置为环境变量 spring.application.name 的值。当集群部署 Eureka Server 注册中心时时,多个节点的应用名必须是相同的,所以就有必要通过这个虚拟主机名来识别每一个节点实例。
  1. Eureka 实例名

    eureka.instance.appname 属性可以配置注册到 Eureka 的实例的名称(appName属性),如果该配置项不存在,则实例名称等于服务应用名称(spring.application.name)。

    Eureka 实例名显示在 Web 页面实例注册列表的 Application 栏,个人理解是用于对应用分组,不同实例名的服务在 Web 页面的注册列表分别显示。而多个实例名相同的服务则显示在同一行。

自定义实例跳转链接

在 Eureka Server 的 Web 控制台,注册列表显示的实例ID 的跳转链接默认是 eureka.instance.hostname:server.port/actuator/info;
或设置了IP地址优先,跳转链接是 ipAddre:server.port/actuator/info

可以自定义这个跳转地址:eureka.instance.status-page-url=http://eureka.server.xxxx.com

心跳周期和心跳超时

Eureka 实例注册注册中心,通过 serviceUrl 定期向注册中心服务器发送心跳以报告实例仍处活动状态(up,正常运行),默认间隔是 30 秒。

在实例、服务器和客户端在其本地缓中具有相同的元数据之前,客户端是无法发现服务的(这个过程可能需要 3次 心跳)。

可以通过设置 eureka.instance.lease-renewal-interval-in-seconds 来更改心跳周期。将值设置小于 30 秒会加快客户端与其他服务的连接。

1
2
3
4
//客户端发送心跳频率为每10秒一次
eureka.instance.lease-renewal-interval-in-seconds=5
//Eureka Server收到最后一个心跳后删除此实例之前的等待时长
eureka.instance.lease-expiration-duration-in-seconds=10

leaseExpirationDurationInSeconds 的值至少要大于 leaseRenewalIntervalInSeconds 指定的值。

leaseExpirationDurationInSeconds 值若设置的太长,可能存在实例不在活动状态,仍将流量路由到该实例;若设置的太小,出现网络临时故障时(快速恢复),该实例可能会失去通信。

若要修改缓存清单的更新时间,可以通过以下参数修改,默认是 30秒:

1
eureka.client.registry-fetch-interval-seconds=30

注意:修改心跳频率和心跳超时可在开发环境使用,以加快实例状态更新;在生产环境中,最好还是使用默认值。

更多关注实例、服务、客户端的配置信息可参考源码中的配置类:
org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean,
org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean,
org.springframework.cloud.netflix.eureka.EurekaClientConfigBean。

EurekaClient 的使用

应用如果是 Eureka Client 应用,可以使用原生的 com.netflix.discovery.EurekaClient 从 Eureka Server 查找获取其它 Eureka 实例(注意不是 Spring Cloud DiscoveryClient) 。

1
2
3
4
5
6
7
8
@Qualifier("eurekaClient")
@Autowired
private EurekaClient discoveryClient;

public String serviceUrl() {
InstanceInfo instance = discoveryClient.getNextServerFromEureka("virtualHostName", false);
return instance.getHomePageUrl();
}

virtualHostName 取的是环境变量 eureka.instance.virtual-host-name 的值。

DiscoveryClient 的使用

可以不使用原始的 Netflix EurekaClient ,Spring Cloud 重新封装了 DiscoveryClient 用于发现获取服务实例。Spring Cloud 支持 Feig 和 RestTemplate 通过 Eureka 服务的ID(VIPs-vipAddress) 代替 物理URL 进行 HTTP 通信。

Ribbon 配置一个固定的物理服务器列表,使用 <client>.ribbon.listOfServer设置多个物理服务器地址或多个主机名,通过逗号分隔,<client>是客户端ID。【这块的理解和使用待研究Ribbon时深究】

还可以使用 org.springframework.cloud.client.discovery.DiscoveryClient ,他提供了一个简单的 API(不是 Netflix) 来发现客户端,示例如下:

1
2
3
4
5
6
7
8
9
10
@Autowired
private DiscoveryClient discoveryClient;

public String serviceUrl() {
List<ServiceInstance> list = discoveryClient.getInstances("virtualHostName");
if (list != null && list.size() > 0 ) {
return list.get(0).getUri();
}
return null;
}

Eureka 排除 Jersey

EurekaClient 默认使用 Jersey 来进行 HTTP 通信,可以排除 Jersey 依赖;自动切换使用 Spring Cloud 默认自动配置 Spring RestTemplate 进行客户端通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<exclusions>
<exclusion>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-apache-client4</artifactId>
</exclusion>
</exclusions>
</dependency>

EurekaClient 元数据

Eureka 实例和客户端有标准的元数据,包括主机名、IP地址、端口号、状态页和运行健康检查信息 ;这些信息随客户端注册发布到服务注册表中,其它客户端使用这些元数据联系服务。

也可以自定义元数据到注册信息中,这些自定义的元素据同样能被其它客户端访问到:

1
2
3
4
5
6
//properties 自定义元数据,key 是自定义的键
eureka.instance.metadata-map.key=value

//java 通过获取实例来获取元数据
InstanceInfo instanceInfo = eurekaClient.getNextServerFromEureka("virtualHostName", false);
String str = instanceInfo.getMetadata().get("key");

通常,自定义的元数据不会影响客户端的功能,可以用来做一些扩展信息。 Spring Cloud 为默认的元数据指定了特定含义的。

EurekaClient 区域连接

如果客户端是多区域分布式布署,则可能希望这些客户端优先使用同一区域的服务,其次再连接其它区域的服务。

首先要确保提供服务的实例部署到各个区域,可以使用 metadataMap 属性来标识当前实例所在区域。

例如 service1 分别部署到 zone1 和 zone2 两个区域,则需要配置如下:

Service 1 in Zone 1

1
2
eureka.instance.metadataMap.zone = zone1
eureka.client.preferSameZoneEureka = true

Service 1 in Zone 2

1
2
eureka.instance.metadataMap.zone = zone2
eureka.client.preferSameZoneEureka = true

Eureka Rest API

Eureka 提供了多个 REST 接口来获取实例注册信息,可以获取某个服务的实例信息。

  1. 获取注册列表中所有服务的实例信息

    地址:http://hostname:port/eureka/apps

    示例:http://eureka.master.com:8761/eureka/apps

  2. 获取某个服务的实例信息,Get 请求,浏览器输出的是 XML 格式

    地址:http://hostname:port/eureka/apps/appname

    示例:http://localhost:8761/eureka/apps/sakila-service1

  3. 以上接口也可以使用接口调试工具请求,如 Postman,指定请求和返回数据类型

    指定请求 Headers 属性:

    Content-Type:application/json
    Accept:application/json

    若开启了安全授权,设置 Authorization 头

    TYPE 设置为 Basic Auth

    输入账号密码

服务上下线监控

通常需要对服务的上下线进行监控,可能整合邮件或其他通信方式通知到用户,Eureka 提供了事件监听的方式来扩展该功能:

Eureka 支持的事件

  • EurekaInstanceRegisteredEvent 实例注册事件
  • EurekaInstanceCanceledEvent 实例下线事件
  • EurekaInstanceRenewedEvent 服务续约事件
  • EurekaRegistryAvailableEvent 注册中心启动事件
  • EurekaServerStartedEvent Eureka Server 启动事件

在集群部署情况下,每个节点都会触发事件,这时需要对下发通知进行控制,否则每个接点都发送通知。

Eureka 事件监听示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Component
public class EurekaStateChangeListener {

@EventListener
public void listen(EurekaInstanceCanceledEvent canceledEvent){
System.out.println("服务下线:" + canceledEvent.getServerId() + "\t" + canceledEvent.getAppName());
}

@EventListener
public void listen(EurekaInstanceRegisteredEvent registeredEvent){
InstanceInfo instanceInfo = registeredEvent.getInstanceInfo();
System.out.println("进行注册:" + instanceInfo.getAppName());
}

@EventListener
public void listen(EurekaInstanceRenewedEvent renewedEvent){
System.out.println("服务续约:" + renewedEvent.getServerId() + "\t" + renewedEvent.getAppName());
}

@EventListener
public void listen(EurekaRegistryAvailableEvent availableEvent){
System.out.println("注册中心启动:");
}

@EventListener
public void listen(EurekaServerStartedEvent startedEvent){
System.out.println("Eureka Server 启动");
}
}

相关参考

  1. Netflix Eureka wiki
  2. Spring Cloud Netflix

Spring Cloud(三):服务发现之Eureka注册中心(2)-集群、配置、监控

http://blog.gxitsky.com/2019/02/22/SpringCloud-03-service-discover-eureka-2/

作者

光星

发布于

2019-02-22

更新于

2022-06-17

许可协议

评论