02.Spring Cloud 之 Eureka

1. 概述

1.1 Eureka 是什么

Eureka 是 Netflix 的一个子模块,也是核心模块之一。Eureka 是一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。服务注册与发现对于微服务架构来说是非常重要的,有了服务与发现与注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件了。功能类似于 dubbo 的注册中心,比如 zookeeper。

1.2 Eureka 组件

1.2.1 Eureka Server

各个节点启动后,会在 EurekaServer 中进行注册,这样 EurekaServer 中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到

1.2.2 Eureka Client

EurekaClient 是一个 Java 客户端,用于简化 EurekaServer 的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会在 EurekaServer 发送心跳(默认周期为 30 秒)。如果 EurekaServer 在多个心跳周期内没有接收到某个节点的心跳,EurekaServer 将会从服务注册中把这个节点移除(默认 90 秒)

1.3 Eureka 自我保护机制

自我保护机制

1.3.1 自我保护机制

默认情况下,EurekaServer 在 90 秒内没有检测到服务列表中的某微服务,则会自动将该微服务从服务列表中删除。但很多情况下并不是该微服务节点(主机)出了问题,而是由于网络抖动等原因使该微服务无法被 EurekaServer 发现,即无法检测到该微服务主机的心跳。若在短暂时间内网络恢复正常,但由于 EurekaServer 的服务列表中已经没有该微服务,所以该微服务已经无法提供服务了。

在短时间内若 EurekaServer 丢失较多微服务,即 EurekaServer 收到的心跳数量小于阈值,为了保证系统的可用性(AP),给那些由于网络抖动而被认为宕机的客户端 “重新复活” 的机会,Eureka 会自动进入自我保护模式:服务列表只可读取、写入,不可执行删除操作。当 EurekaServer 收到的心跳数量恢复到阈值以上时,其会自动退出 Self Preservation 模式。

1.3.2 默认值修改

启动自我保护的阈值因子默认为 0.85,即 85%。即 EurekaServer 收到的心跳数量若小于应该收到数量的 85% 时,会启动自我保护机制,使用 eureka.server.renewal-percent-threshold=0.75 可以更改自我保护机制的阈值

自我保护机制默认是开启的,可以通过修改 EurekaServer 中配置文件来关闭,但不建议关闭,使用 erueka.server.enable-self-preervation=false 禁用自我保护模式。

1.3.3 自我保护机制阈值

Renews threshold:Eureka Server 期待每分钟收到 client 端发送的续约总数,为了计算这个数,系统会统计从当前时刻开始向前的这 15 分钟内接收到的总续约数(一个瞬时值),假设其为 count,然后再根据阈值因子计算出其阈值续约数为 num = count * 0.85,然后在平均到每分钟即是这个值,Renews threshold = num / 15

Renews (last min):Eureka Server 实际在最后一分钟收到的 client 端发送的续约的数量,其也是一个瞬时值

当 Renews (last min) < Renews threshold 时,会启动自我保护机制

1.4 Eureka VS ZooKeeper

1.4.1 ZooKeeper 保证 CP

当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接 down 掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是 zk 会出现这样一种情况,当 master 节点因为网络故障与其他节点失去联系时,剩余节点会重新进行 leader 选举。问题在于,选举 leader 的时间太长,30~120 s,且选举期间整个 ZK 集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得 ZK 集群失去 master 节点是较大概率发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。

1.4.2 Eureka 保证 AP

Eureka 看明白了这一点,因此在设计时就优先保证可用性。Eureka 各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而 Eureka 的客户端在向某个 Eureka 注册时发现连接失败,则会自动切换至其他节点,只要有一台 Eureka 在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka 还有一种自我保护机制,如果在 15 分钟内超过 85% 的节点都没有正常的心跳,那么 Eureka 就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:

  1. Eureka 不再从注册列表中移除因为长时间没收到心跳而应该过期的服务

  2. Eureka 仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)

  3. 当网络稳定时,当前实例新的注册信息会被同步到其它节点上

因此,Eureka 可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像 ZooKeeper 那样使整个注册服务瘫痪。

2. Eureka 环境搭建

2.1 Eureka server 环境搭建

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见 tutorial-spring-cloud/tutorial-spring-cloud-eureka/tutorial-spring-cloud-eureka-single-7001 工程

2.1.1 配置文件
1. pom.xml
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.1.4.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Greenwich.SR6</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
2. application.properties
# 端口号
server.port=7001
# 此实例注册到 eureka 服务端的 name
spring.application.name=tutorial-spring-cloud-eureka-single
# 禁用自我保护机制(默认开启)
eureka.server.enable-self-preservation=false
# 设置自我保护机制的阈值,默认是 0.85
eureka.server.renewal-percent-threshold=0.75
# 设置清理间隔(单位:毫秒 默认是 60*1000)
eureka.server.eviction-interval-timer-in-ms=6000
# eureka 服务端的实例名称
eureka.instance.hostname=localhost
# 指定是否向注册中心注册本机,false 表示不注册,因为本身就是 server
eureka.client.register-with-eureka=false
# 指定当前主机是否能够获取注册中心的服务注册列表,server 并不需要去检索服务
eureka.client.fetch-registry=false
# 设置 eureka server 地址
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
2.1.2 代码
1. EurekaApplication7001
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication7001 {

    public static void main(String [] args){
        SpringApplication.run(EurekaApplication7001.class, args);
    }

}

2.2 provider 环境搭建

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见 tutorial-spring-cloud/tutorial-spring-cloud-provider/tutorial-spring-cloud-eureka-single-7001 工程

2.2.1 配置文件
1. pom.xml
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.1.4.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Greenwich.SR6</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
2. application.properties
# 端口号
server.port=5002
# 此实例注册到 eureka 服务端的 name
spring.application.name=tutorial-spring-cloud-provider-eureka
# 指定 eureka 服务注册中心地址
eureka.client.service-url.defaultZone=http://localhost:7001/eureka
# 此实例注册到 eureka 服务端的唯一的实例 ID
eureka.instance.instance-id=tutorial-spring-cloud-provider-eureka-5002
# 是否显示 IP 地址
eureka.instance.prefer-ip-address=true
# eureka 客户需要多长时间发送心跳给 eureka 服务器,表明它仍然活着,默认为 30 秒
eureka.instance.lease-renewal-interval-in-seconds=10
# eureka 服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为 90 秒
eureka.instance.lease-expiration-duration-in-seconds=30
# 设置 info 信息
info.app.name=tutorial-spring-cloud-provider-eureka

2.3 consumer 环境搭建

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见 tutorial-spring-cloud/tutorial-spring-cloud-consumer/tutorial-spring-cloud-consumer-eureka-6002 工程

2.3.1 配置文件
1. pom.xml
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.1.4.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Greenwich.SR6</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
2. application.properties
# 端口号
server.port=6002
# 此实例注册到 eureka 服务端的 name
spring.application.name=tutorial-spring-cloud-consumer-eureka
# 指定 eureka 服务注册中心地址
eureka.client.service-url.defaultZone=http://localhost:7001/eureka
# 此实例注册到 eureka 服务端的唯一的实例 ID
eureka.instance.instance-id=tutorial-spring-cloud-consumer-eureka-6002
# 是否显示 IP 地址
eureka.instance.prefer-ip-address=true
# eureka 客户需要多长时间发送心跳给 eureka 服务器,表明它仍然活着,默认为 30 秒
eureka.instance.lease-renewal-interval-in-seconds=10
# eureka 服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为 90 秒
eureka.instance.lease-expiration-duration-in-seconds=30
# 设置 info 信息
info.app.name=tutorial-spring-cloud-consumer-eureka
2.3.2 代码
1. ConsumerApplication6002
@SpringBootApplication
public class ConsumerApplication6002 {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication6002.class, args);
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}
2. ConsumerController
@RestController
@RequestMapping("/consumer")
public class ConsumerController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    private static final String SERVICE_PROVIDER = "http://TUTORIAL-SPRING-CLOUD-PROVIDER-EUREKA/";

    @RequestMapping("/info")
    public Map<String, String> info() {
        String url = SERVICE_PROVIDER + "provider/info";
        return restTemplate.exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference<Map<String, String>>() {
        }).getBody();
    }

    @RequestMapping("/discovery")
    public Map<String, List<String>> discoveryClient() {
        Map<String, List<String>> result = new HashMap<>(10);
        List<String> serviceIds = discoveryClient.getServices();
        serviceIds.forEach(serviceId -> {
            List<String> temp = new ArrayList<>();
            List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
            instances.forEach(serviceInstance -> temp.add(serviceInstance.getHost() + ":" + serviceInstance.getPort()));
            result.put(serviceId, temp);
        });
        return result;
    }

}

3. Eureka 集群环境搭建

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见 tutorial-spring-cloud/tutorial-spring-cloud-eureka/tutorial-spring-cloud-eureka-cluster-7002tutorial-spring-cloud/tutorial-spring-cloud-eureka/tutorial-spring-cloud-eureka-cluster-7003tutorial-spring-cloud/tutorial-spring-cloud-eureka/tutorial-spring-cloud-eureka-cluster-7004 工程

3.1 配置文件

1. 第一台 server 的 application.properties
# 端口号
server.port=7002
# 此实例注册到 eureka 服务端的 name
spring.application.name=tutorial-spring-cloud-eureka-cluster
# eureka 服务端的实例名称
eureka.instance.hostname=eureka-7002.com
# 指定是否向注册中心注册本机,false 表示不注册,因为本身就是 server
eureka.client.register-with-eureka=false
# 指定当前主机是否能够获取注册中心的服务注册列表,server 并不需要去检索服务
eureka.client.fetch-registry=false
# 指定 eureka 服务注册中心地址
eureka.client.service-url.defaultZone=http://eureka-7002.com:7002/eureka,http://eureka-7003.com:7003/eureka,http://eureka-7004.com:7004/eureka
2. 第二台 server 的 application.properties
# 端口号
server.port=7003
# 此实例注册到 eureka 服务端的 name
spring.application.name=tutorial-spring-cloud-eureka-cluster
# eureka 服务端的实例名称
eureka.instance.hostname=eureka-7003.com
# 指定是否向注册中心注册本机,false 表示不注册,因为本身就是 server
eureka.client.register-with-eureka=false
# 指定当前主机是否能够获取注册中心的服务注册列表,server 并不需要去检索服务
eureka.client.fetch-registry=false
# 指定 eureka 服务注册中心地址
eureka.client.service-url.defaultZone=http://eureka-7002.com:7002/eureka,http://eureka-7003.com:7003/eureka,http://eureka-7004.com:7004/eureka
3. 第三台 server 的 application.properties
# 端口号
server.port=7004
# 此实例注册到 eureka 服务端的 name
spring.application.name=tutorial-spring-cloud-eureka-cluster
# eureka 服务端的实例名称
eureka.instance.hostname=eureka-7004.com
# 指定是否向注册中心注册本机,false 表示不注册,因为本身就是 server
eureka.client.register-with-eureka=false
# 指定当前主机是否能够获取注册中心的服务注册列表,server 并不需要去检索服务
eureka.client.fetch-registry=false
# 指定 eureka 服务注册中心地址
eureka.client.service-url.defaultZone=http://eureka-7002.com:7002/eureka,http://eureka-7003.com:7003/eureka,http://eureka-7004.com:7004/eureka

3.2 集群效果

服务注册

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章