【Spring Cloud H 版教程】第三篇:注册中心组件 Eureka 之进阶

通过前面的一节课,我们已经学习了 Eureka 的基本使用,Eureka 作为微服务的注册中心组件,在服务发现机制的实现上包含了很多细节特性,这节课我们就来学习一下 Eureka 关于这方面的内容:

  • 自我保护机制
  • 健康检查
  • DiscoveryClient
  • 保护注册中心
  • 使用IP注册
  • 集群配置

1.自我保护机制

1.1 默认自我保护机制

上一节课我们提到过,服务注册完成之后默认情况下会每隔 30 秒向 Eureka Server 发送服务续约心跳,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 可能会不正确地声明实例是状态为UP。 
续约值比期望阈值要少,因此仅出于安全性考虑,实例不会过期。

图片: https://uploader.shimo.im/f/85WG4y9Ie9bttf3a.png
紧接着直接杀死客户端服务进程,你会惊讶的发现 Eureka Server 并没有剔除掉该服务,这是为什么呢?
图片: https://uploader.shimo.im/f/aIbNbi98TJrwJVjh.png

1.2 自我保护机制原理

这是因为触发了 Eureka 的自我保护机制。Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 会将这些实例保护起来,让这些实例不会过期,假如在这段保护期内服务真的被关闭了,别的服务仍然可以获取到服务的地址信息,并对其发起远程调用,就会出现调用失败的情况,所以我们在服务间远程调用时必须要有容错机制,关于容错部分内容会在后面的章节详细介绍。

1.3 续约期望值和实际值

在注册中心管理台我们可以看到和 Eureka 自我保护机制相关的两个值,Renews threshold 和 Renews (last min) 。
图片: https://uploader.shimo.im/f/C1ZEHY3qV9aJPnT7.png

  • Renews threshold:Eureka Server 一分钟内接收到所有客户端实例的心跳总数期望值。

  • Renews (last min):Eureka Server 一分钟内所有客户端实例发送心跳总数的实际值。

1.4 自我保护机制触发条件

Eureka 触发自我保护机制的条件非常简单,在一分钟内 Renews (last min) <= Renews threshold

期望值:Renews threshold = (int) ( n + 1 ) x 2 x 0.85
实际值:Renews (last min) = n x 2
n:服务数量,Eureka Server 单机模式下也被算入期待值,所以是 n + 1,但是通常我们会关闭自我注册,所以实际值是 n x 2 。集群模式下 Eureka Server 之间会相互发送心跳。

所以在服务数量较少时很容易触发自我保护机制:

图片: https://uploader.shimo.im/f/d9J4yT8nvig6Obi7.png
在这里插入图片描述

1.5 关闭自我保护机制

如果不想使用 Eureka 的自我保护机制可以 Eureka Server 加上相关配置:

eureka:
  server:
    enable-self-preservation: false #设为false,关闭自我保护(默认开启) 
    eviction-interval-timer-in-ms: 3000 # 清理间隔(单位毫秒,默认是60*1000)

关闭自我保护机制重启服务之后,会在控制台看到提示保护模式已经关闭的警告信息:

THE SELF PRESERVATION MODE IS TURNED OFF. 
THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.
自我保护机制已经关闭。在网络或者其他问题出现的情况下,可能无法保护实例。

图片: https://uploader.shimo.im/f/A6VQEB0wSaga7T8H.png
关闭了 Eureka 自我保护机制之后客户端服务在 90 秒续约时间过期之后就会被剔除下线。续约时间和续约过期时间同样可以修改,在客户端服务添加以下配置即可:

eureka:
  instance:
    lease-renewal-interval-in-seconds: 2 #续约更新时间间隔(默认30秒)
    lease-expiration-duration-in-seconds: 6 #续约过期时间(默认90秒)

2.健康检查

2.1服务健康状态

Eureka Server 使用四种类型值来表示服务的状态:

  • UNKNOWN: 未知
  • UP: 可用
  • DOWN: 不可用
  • OUT_OF_SERVICE: 服务终止

2.2启用健康检查

Eureka 客户端通过心跳维持和 Eureka Server 的连接,告知自己的状态,但是一个服务是否可用并不是单由心跳说了算,比如服务自身的接口已经不能用了,或者数据库连接中断了等等,好在 Eureka 配合 Spring Boot Actuator 提供了一个健康检查机制,能够根据服务自身的状态及时通知到 Eureka Server。为了使用健康检查机制需要在 POM 加入 spring-boot-starter-actuator 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Eureka 客户端默认开启了健康检查功能:

eureka:
  client:
    healthcheck:
      enabled: true #健康检查(默认开启)

下面我们来模拟一下数据库连接出错的情况,在Eureka 客户端程序加入以下代码:

@Component
public class MyDBConfig {

    @Bean
    public DataSource dataSource(){
        return DataSourceBuilder.create()
                .driverClassName("com.mysql.jdbc.Driver")
                .url("jdbc:mysql://localhost:3306/mysql?useUnicode=true&characterEncoding=utf-8&useSSL=false")
                .username("root")
                .password("123456").build();//填入错误的密码
    }
}

重新启动 Eureka 客户端程序控制台会出现数据库连接错误异常,但是程序仍可以启动成功,并且注册上了Eureka Server,查看 Eureka Server 管理台可以看到服务的状态不再是 UP 。
图片: https://uploader.shimo.im/f/zm8zozlxIZEZYZv0.png
也可以通过请求 URL 的方式获取到客户端实例的状态,例如 http://ip:port/actuator/health :

图片: https://uploader.shimo.im/f/KZqwtMCrkXUGmBHp.png

2.3自定义健康检查

Eureka 允许用户自定义根据自己的判断,改变服务的运行状态,并通知到 Eureka Server 。

第一步:自定义健康检查指示器,实现 HealthIndicator 接口。

/**
* 自定义监控检查指示器
**/
@Slf4j
@Component
public class MyHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        log.info("state: {}",EurekaClientController.SERVICE_STATE);
        if(EurekaClientController.SERVICE_STATE){
            return new Health.Builder(Status.UP).build();
        }else{
            return new Health.Builder(Status.DOWN).build();
        }
    }
}
@Slf4j
@RestController
public class EurekaClientController {
    
    /**服务状态*/
    public static Boolean SERVICE_STATE = false;

    /**
    * 通过接口直接修改服务运行状态
    */
    @GetMapping("/setServiceState/{state}")
    public void setServiceState(@PathVariable("state") Boolean state) {
        SERVICE_STATE = state;
        log.info("修改服务当前运行状态,state:{}",state);
    }

}

第二步:定义健康检查处理器,将健康状态通知到 Eureka Server 。

@Slf4j
@Component
public class MyHealthCheckHandler implements HealthCheckHandler {

    @Autowired
    private MyHealthIndicator myHealthIndicator;

    @Override
    public InstanceInfo.InstanceStatus getStatus(InstanceInfo.InstanceStatus instanceStatus) {
        Status status = myHealthIndicator.health().getStatus();
        if(Status.UP.equals(status)){
            return InstanceInfo.InstanceStatus.UP;
        }else{
            return InstanceInfo.InstanceStatus.DOWN;
        }
    }

}

默认情况下客户端服务每隔 30 秒执行一次自身健康检查,可以通过以下配置修改该默认值:

eureka:
  client:
    instance-info-replication-interval-seconds: 10 #实例状态同步到eureka

第三步:重启 Eureka Server 和 Eureka Client ,动态修改服务状态。

图片: https://uploader.shimo.im/f/3DOhU7sCyFUYrcjH.png

这里我设置客户端服务默认状态为 DOWN,可以通过请求接口动态调整服务的运行状态:

图片: https://uploader.shimo.im/f/fSkwiKMjzS4KLKib.png

3.DiscoveryClient

3.1使用DiscoveryClient获取服务信息

由于我们还没有介绍服务远程调用的组件,我们可以使用 Spring Cloud 标准的服务发现接口 DiscoveryClient 获取到所有注册上 Eureka Server 的服务实例状态,达到模拟服务状态是否对远程服务起作用的目的。

@Slf4j
@RestController
public class EurekaClientController {

    /**
    * 获取Eureka Server 所有注册实例的状态
    **/
    @GetMapping("/getInstanceList")
    public List<ServiceInstance> getInstanceList (){
        List<ServiceInstance> instanceList = new ArrayList<>();
        discoveryClient.getServices().forEach( serviceId -> {
            List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
            instanceList.addAll(instances);
        });
        return instanceList;
    }

}

当服务状态为 UP 时,通过 DiscoveryClient 获取到服务实例的状态也是 UP :
在这里插入图片描述
当主动切换服务状态为 DOWN 大约 30 秒之后,通过 DiscoveryClient 获取实例状态时返回值为空,这侧面验证了在 DOWN 情况下,远程服务是无法从注册中心获取到该实例信息的。
在这里插入图片描述

3.2修改拉取默认值

默认情况下,客户端服务每 30 秒会主动拉取一次 Eureka Server 上所有注册实例的地址信息,可以通过以下配置修改该拉取间隔默认值:

eureka:
  client:
    registry-fetch-interval-seconds: 10 #eureka客户端获取服务列表时间间隔配置 默认30秒

4.密码访问

4.1注册中心安全性

默认情况下 Eureka Server 允许任何客户端服务对其发起注册操作,并且任何人一旦知道 Eureka Server 的目标地址便可以访问其管理台,这是非常不安全的,所以生产环境我们需要配合 spring-boot-starter-security 来保护 Eureka Server。

4.2 整合security保护注册中心

第一步:在 Eureka Server 项目 POM 中加入 security 依赖:

<!--访问安全-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

第二步:application.yml 配置登录用户名和密码:

spring:
  security:
    user:
      name: admin
      password: 123456

第三步:Spring Security CSRF 攻击防御过滤掉 eureka。重写 WebSecurityConfigurerAdapter的 configure 方法,因为 Spring Security 默认开启了所有 CSRF 攻击防御,需要禁用对 /eureka 的防御:

@EnableWebSecurity
class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().ignoringAntMatchers("/eureka/**"); //过滤eureka
        super.configure(http);
    }
}

第四步:验证 Eureka Server 管理台安全性是否生效。可以看到浏览器输入注册中心管理台地址后,需要先输入用户名和密码才可以访问:

图片: https://uploader.shimo.im/f/4Y1JHI5S7zcL7otq.png

第五步:验证客户端服务注册安全性是否生效。客户端服务注册地址也需要添加用户名和密码,否则将注册失败:

#连接Eureka注册中心
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://admin:123456@localhost:7001/eureka

其中地址格式为:http://用户名:密码ip:port/eureka

5.IP地址注册

默认情况下客户端服务使用主机名注册到 Eureka Server,在某些情况下,Eureka 最好公布服务的 IP 地址而不是主机名。例如当网关程序和服务程序都使用了主机名注册上 Eureka Server ,且主机名都为 localhost,假如他们不在同一台机器,那么网关程序将无法把请求转发给服务程序,因为找不到目标地址。客户端服务想要直接使用 IP 地址注册只需要将 eureka.instance.preferIpAddress 设置为 true 即可 :

eureka:
  instance:
    prefer-ip-address: true #使用ip地址注册

6.集群

6.1注册中心高可用

注册中心是非常关键的服务,假如只有一个实例节点的话,一旦发生单点故障,将对整个系统的稳定性造成毁灭性的破坏。在一个分布式微服务系统中,服务注册中心是最关键的基础设施之一,必须确保其处于高可用的状态下,才能确保微服务系统的稳定。为了维持其高可用性,使用集群部署是很好的解决方案,这也是生产环境必不可少的一项工作。Eureka 集群是通过互相注册的方式来实现高可用的,所以我们只需要将每一个 Eureke Server 节点都注册到除了自身以外的其他节点上即可完成 Eureka 集群配置。
在这里插入图片描述

6.2集群配置步骤

第一步:环境准备。我们准备在一台机器上启动两个 Eureka Server,为了区分他们俩,我们为当前主机取了两个别名:eureka1、eureka2。
图片: https://uploader.shimo.im/f/5QEw4j9YY0sFudLX.png
第二步:配置集群。Eureka Server 集群是一种相互注册的方式,集群中的每个节点将会连接集群内除了自己以外的所有节点。

7001 节点:

server:
  port: 7001
  
eureka:
  instance:
    hostname: eureka1
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://admin:123456@eureka2:7002/eureka #连接7002节点

7002 节点:

server:
  port: 7002
  
eureka:
  instance:
    hostname: eureka2
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://admin:123456@eureka1:7001/eureka #连接7001节点

第三步:验证集群效果。分别启动 7001 节点和 7002 节点,在 7001 节点管理台上可以看到 7002 节点,反之 7002 节点管理台上也可以看到 7001 节点。

图片: https://uploader.shimo.im/f/nGVvAGPcxRtPNOap.png

第四步:客户端服务连接集群。客户端程序需要将集群的所有节点地址都配置上 service-url :

#连接Eureka注册中心
eureka:
  instance:
    prefer-ip-address: true #使用ip地址注册
    instance-id: myEurekaClient #自定义实例名称
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://admin:123456@eureka1:7001/eureka,http://admin:123456@eureka2:7002/eureka #连接所有Eureka集群的节点

6.3集群配置结果

集群所有节点管理台都可以看到相同的服务实例:
图片: https://uploader.shimo.im/f/u6nt73aEKe0Elueh.png

7.小结

这节课我们学习了 Eureka 的相关特性,自我保护机制的目的是服务端为了避免客户端由于网络波动等原因而导致注册信息的丢失,建议开启;如果说自我保护是一种服务端的检查机制,那么健康检查则可以说是一种客户端自己本身的一种检查,主动通知服务端修改自身的状态,能够有效避免被调用方的线程阻塞;DiscoveryClient 可以获取到注册中心服务的信息,从而可以发起服务间远程调用,关于服务间远程调用会在后面的课程详细介绍;注册中心是一个微服务架构的基础设施,为了防止来历不明的服务注册到注册中心上,可以整合 security 避免该问题出现;最后我们介绍了 Eureka 集群的配置实现方法,注册中心的高可用关乎整个系统的稳定性,通过集群部署可以有效避免 Eureka 单点故障的问题。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值