目录

1 介绍

2 Eureka原理解析

2.1 总体介绍

2.2 Eureka客户端与服务端的交互过程

3 使用Eureka

3.1 搭建eureka-server服务端

3.2 将服务提供者provider_service,注册到eureka注册中心

3.3 将服务消费者consumer_service,注册到eureka注册中心

3.4 消费者通过Eureka访问服务提供者


1 介绍

Spirng Cloud Eureka 包含了服务端组件、客户端组件。
服务端与客户端均采用java编写,所以Eureka主要适用于通过java实现的分布式系统,或是JVM兼容语言构建的系统。

2 Eureka原理解析

2.1 总体介绍

Eureka架构中的三个核心角色

  • 服务注册中心:Eureka服务端应用,提供服务管理(注册、发现)功能
  • 服务提供者:对外发布服务,供消费者调用 ,要求统一对外提供Rest风格服务
  • 服务消费者:服务提供者的调用方。从注册中心获取服务列表,通过列表调用服务提供者。

Spring怎么指定服务注册到注册中心的名称_服务提供者

2.2 Eureka客户端与服务端的交互过程

  • 服务注册(register)

Eureka Client会通过发送REST请求的方式,向Eureka Server注册自己的服务。
注册时,提供自身的元数据,比如ip地址、端口、运行状况指标、主页地址等信息。
Eureka Server接收到注册请求后,就会把这些元数据信息存储在一个双层的Map中。 

注意:服务注册默认使用主机名而不是ip,如果想注册ip到注册中心,就在对应服务的配置文件中输入如下内容:

# 默认注册时使用的是主机名,想用ip进行注册添加如下配置
# ip地址
eureka.instance.ip-address: 127.0.0.1
# 更倾向于使用ip,而不是host名
eureka.instance.prefer-ip-address: true
  • 服务续约(renew)

在服务注册后,Eureka Client会维护一个心跳来持续通知Eureka Server,说明服务一直处于可用状态,防止被剔除。
默认每隔30秒 `eureka.instance.lease-renewal-interval-in-seconds` 发送一次心跳来进行服务续约(续约:每隔一段时间,由Client发请求到server,告诉server“我是健康的服务”)。

# 配置到消费者或服务提供者:租约续约间隔时间,默认30秒
eureka.instance.lease-renewal-interval-in-seconds: 30
  • 获取服务列表(get registry)

服务消费者(Eureka Client)在启动的时候,会发送一个REST请求给Eureka Server,获取注册中心的服务清单,并且缓存在客户端本地。
同时,为了性能及安全性考虑,Eureka Server会每隔30秒更新一次缓存中的服务清单。 

# 配置到消费者或服务提供者:每隔多久获取服务中心列表,(只读备份)
eureka.client.registry-fetch-interval-seconds: 30
  • 服务调用

服务消费者在获取到服务清单后,可以根据清单中的服务信息,查找到该服务的地址,从而进行访问(远程调用)。

  • 服务下线(cancel)

当Eureka Client需要关闭或重启时,就不希望在这个时间段内再有请求进来
所以,就需要提前先发送REST请求给Eureka Server,告诉Eureka Server自己要下线了
Eureka Server在收到请求后,就会把该服务状态置为下线(DOWN),并把该下线事件传播出去。 

服务下线的操作可以对服务执行shutdown的命令,如果是在IDEA中,就点那个exit按钮,而不是点那个红色的停止服务按钮,这个似乎不怎么重要,就不去演示了。

  • 失效剔除(evict)

服务实例可能会因为网络故障等原因,导致不能提供服务,而此时该实例也没有发送请求给Eureka Server来进行服务下线。
所以,还需要有服务剔除的机制。
Eureka Server在启动的时候会创建一个定时任务,每隔一段时间(默认60秒,这个时间怎么改还不清楚),从当前服务清单中把超时没有续约(默认90秒`eureka.instance.lease-expiration-duration-in-seconds`)的服务剔除。

总结:作为不再续约且时间超过90s的服务,针对这些服务,每隔60s统一进行一次删除,也就是说一个服务从挂掉到被从注册中心删除至少要经过150s,最多经过180s(服务在心跳检测开始前一瞬间挂掉)。

# 配置到消费者或服务提供者:租约到期,服务时效时间,默认值90秒
eureka.instance.lease-expiration-duration-in-seconds: 90
  •  自我保护

既然Eureka Server会定时剔除超时没有续约的服务,那就有可能出现一种场景:
网络一段时间内发生了异常,所有的服务都没能够进行续约,Eureka Server就把所有的服务都剔除了,这样显然不太合理。
所以,就有了自我保护机制。自我保护机制是,当在短时间内,统计续约失败的比例,如果达到一定阈值,则会触发自我保护的机制
在该机制下,Eureka Server不会剔除任何的微服务,等到正常后,再退出自我保护机制。
自我保护开关:eureka.server.enable-self-preservation: false
在自我保护模式下,不会剔除任何服务实例,从而保证绝大多数服务的可用性

#配置到注册中心:向Eureka服务中心集群注册服务
eureka.server.enable-self-preservation: false # 关闭自我保护模式(默认值是打开)

Eureka会统计服务实例最近15分钟心跳续约的比例是否低于85%,如果低于则会触发自我保护机制,一旦触发则会出现如下页面:

Spring怎么指定服务注册到注册中心的名称_eureka_02

 

3 使用Eureka

步骤

  1. 搭建eureka服务,创建eureka_server工程
  2. 服务提供者provider_service,注册到eureka注册中心
  3. 服务消费者consumer_service,注册到eureka注册中心

注意:Eureka服务端和客户端的SpringBoot版本要一致,且SpringBoot版本要能和Eureka版本匹配上

3.1 搭建eureka-server服务端

  • 2.1.1 创建eureka_server的springboot工程

Spring怎么指定服务注册到注册中心的名称_Server_03

Spring怎么指定服务注册到注册中心的名称_Server_04

  • 2.1.2 在启动类EurekaServerApplication声明当前应用为Eureka服务,使用@EnableEurekaServer注解,实现自动配置 
@SpringBootApplication
@EnableEurekaServer //开启EurekaServer端的自动配置
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}
  • 2.1.3 编写配置文件application.yml(properties文件后缀名统一改成.yml)
#端口
server.port: 10086
#应用名称
spring.application.name: eureka-server
#注册中心地址
eureka.client.service-url.defaultZone: http://127.0.0.1:10086/eureka
# 是否抓取注册列表
eureka.client.fetch-registry: false
# 是否注册服务中心Eureka(防止自己注册自己)
eureka.client.register-with-eureka: false
  • 2.1.4 服务启动测试

Spring怎么指定服务注册到注册中心的名称_eureka_05

3.2 将服务提供者provider_service,注册到eureka注册中心

在本文的第2部分中,分别创建了服务的提供者和消费者工程,这里就派上用场了。

  • 2.2.1 在服务提供者provider_service工程中添加Eureka客户端依赖
<!--eureka客户端starter-->
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>


<!--SpringCloud,BOM,依赖清单导入,所有依赖管理的坐标-->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Greenwich.SR3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
  • 2.2.2 在启动类上开启Eureka客户端发现功能 @EnableDiscoveryClient
@SpringBootApplication
@EnableDiscoveryClient // 开启Eureka客户端发现功能
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class,args);
    }
}
  • 2.2.3 修改配置文件:指定应用名称、注册中心地址
# 端口
server.port: 9091
# 数据库连接配置信息
spring.datasource.driver-class-name: com.mysql.cj.jdbc.Driver
spring.datasource.url: jdbc:mysql://127.0.0.1:3306/springcloud?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.password: root
spring.datasource.username: root
#配置应用名称
spring.application.name: user-service
#配置注册中心地址
eureka.client.service-url.defaultZone: http://127.0.0.1:10086/eureka
  • 2.2.4 重启项目,客户端代码会自动把服务注册到EurekaServer中,在Eureka监控页面可以看到服务注册成功信息

Spring怎么指定服务注册到注册中心的名称_服务提供者_06

3.3 将服务消费者consumer_service,注册到eureka注册中心

操作同步骤2。

注意:SpringBoot和SpringCloud的版本兼容性问题,如下图:

在项目中,我的SpringBoot版本:2.1.1.RELEASE,对应SpringCloud版本:Greenwich.SR3 这样做是完全没问题的~

然而,我IDEA自动创建SpringBoot时的版本为2.5.1,按照如下图的要求,SpringCloud版本应该是2020.0.3,但是出错了。可能是我没刷新项目依赖包?或者我把SpringCloud版本号写错了?(暂时先不管)

Spring怎么指定服务注册到注册中心的名称_Server_07

3.4 消费者通过Eureka访问服务提供者

步骤

  1. 通过注册中心客户端对象DiscoveryClient,获取Eureka中注册的user-service实例列表
  2. 获取user-service服务实例对象
  3. 从实例对象中获取host地址和端口,拼接请求地址
  4. 使用RestTemplate发送请求
/**
 * 消费者的控制层,提供服务,访问提供者接口,为真实用户返回信息
 */
@RestController
public class ConsumerController {
    /**
     * 用来发http请求
     */
    @Autowired
    private RestTemplate restTemplate;

    /**
     * DiscoveryClient 注册中心客户端对象,缓存着注册列表信息
     */
    @Autowired
    private DiscoveryClient discoveryClient;

    /**
     *  通过注册中心的注册列表访问服务提供者,动态获取要调用服务的ip和端口信息
     */
    @RequestMapping("/consumer/{id}")
    public User findById(@PathVariable("id") Integer id){
        //从注册列表中获取服务实例对象(由于这个服务可能是一个集群,所以返回的是列表)
        List<ServiceInstance> instances = discoveryClient.getInstances("USER-SERVICE");
        //取出第一个元素
        ServiceInstance userServiceInstance = instances.get(0);
        //获取服务的host、port、元数据
        String host = userServiceInstance.getHost();
        int port = userServiceInstance.getPort();
        Map<String, String> metadata = userServiceInstance.getMetadata();
        System.out.println("host:"+host);
        System.out.println("port:"+port);
        System.out.println("元数据:"+metadata);
        //动态拼接地址
        String url = "http://"+host+":"+port+"user/findById?id="+id;
        User user = restTemplate.getForObject(url, User.class);
        return user;
    }
}