Skip to content

6. Eureka的服务注册与发现

6.1 Eureka基础介绍

1. 什么是服务治理

Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务治理

在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。

2.什么是服务注册与发现

Eureka采用了CS的设计架构,Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。 在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址)) 下左图是Eureka系统架构,右图是Dubbo的架构,请对比

3.Eureka包含两个组件

Eureka Server和Eureka Client

Eureka Server提供服务注册服务 各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。

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

6.2 Eureka常用属性配置

Spring Cloud Eureka 属性作用

配置参数默认值说明
服务注册中心配置Bean类:org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean
eureka.server.enable-self-preservationfalse关闭注册中心的保护机制,Eureka 会统计15分钟之内心跳失败的比例低于85%将会触发保护机制,不剔除服务提供者,如果关闭服务注册中心将不可用的实例正确剔除
服务实例类配置Bean类:org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean
eureka.instance.prefer-ip-addressfalse不使用主机名来定义注册中心的地址,而使用IP地址的形式,如果设置了 eureka.instance.ip-address 属性,则使用该属性配置的IP,否则自动获取除环路IP外的第一个IP地址
eureka.instance.ip-addressIP地址
eureka.instance.hostname设置当前实例的主机名称
eureka.instance.appname服务名,默认取 spring.application.name 配置值,如果没有则为 unknown
eureka.instance.lease-renewal-interval-in-seconds30定义服务续约任务(心跳)的调用间隔,单位:秒
eureka.instance.lease-expiration-duration-in-seconds90定义服务失效的时间,单位:秒
eureka.instance.status-page-url-path/info状态页面的URL,相对路径,默认使用 HTTP 访问,如果需要使用 HTTPS则需要使用绝对路径配置
eureka.instance.status-page-url状态页面的URL,绝对路径
eureka.instance.health-check-url-path/health健康检查页面的URL,相对路径,默认使用 HTTP 访问,如果需要使用 HTTPS则需要使用绝对路径配置
eureka.instance.health-check-url健康检查页面的URL,绝对路径
服务注册类配置Bean类:org.springframework.cloud.netflix.eureka.EurekaClientConfigBean
eureka.client.service-url.指定服务注册中心地址,类型为 HashMap,并设置有一组默认值,默认的Key为 defaultZone;默认的Value为 http://localhost:8761/eureka ,如果服务注册中心为高可用集群时,多个注册中心地址以逗号分隔。 如果服务注册中心加入了安全验证,这里配置的地址格式为: http://<username>:<password>@localhost:8761/eureka 其中 <username> 为安全校验的用户名;<password> 为该用户的密码
eureka.client.fetch-registerytrue检索服务;false不会向Eureka Server注册中心获取注册信息
eureka.client.registery-fetch-interval-seconds30从Eureka服务器端获取注册信息的间隔时间,单位:秒
eureka.client.register-with-eurekatrue启动服务注册;false不会向Eureka Server注册中心注册自己的信息
eureka.client.eureka-server-connect-timeout-seconds5连接 Eureka Server 的超时时间,单位:秒
eureka.client.eureka-server-read-timeout-seconds8读取 Eureka Server 信息的超时时间,单位:秒
eureka.client.filter-only-up-instancestrue获取实例时是否过滤,只保留UP状态的实例
eureka.client.eureka-connection-idle-timeout-seconds30Eureka 服务端连接空闲关闭时间,单位:秒
eureka.client.eureka-server-total-connections200从Eureka 客户端到所有Eureka服务端的连接总数
eureka.client.eureka-server-total-connections-per-host50从Eureka客户端到每个Eureka服务主机的连接总数

6.3 单机搭建Eureka

1.搭建Eureka

  • 建Module
  • 改POM
xml
 <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-java1</artifactId>
        <groupId>com.neuedu.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>server-eureka-7001</artifactId>


    <dependencies>
        <!--eureka-server-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <!--boot web actuator-->
        <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.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
      
    </dependencies>


</project>

关于Eureka的依赖

xml
 
<!--以前的老版本(当前使用2018) -->
<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
 
<!--现在新版本 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
  • 写YML
yml
server:
  port7001

eureka:
  instance:
    hostname: localhost #eureka服务端的实例名称
  client:
    #false表示不向注册中心注册自己。
    register-with-eureka: false
    #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    fetch-registry: false
    service-url:
    #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
    defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  • 主启动
    • @EnableEurekaServer
java
package org.jshand;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;


@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApp7001 {
    private static final Logger log = LoggerFactory.getLogger(EurekaServerApp7001.class);

    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApp7001.class, args);
        log.info("Eureka Server Started (EurekaServerApp7001服务端启动成功)");
    }
}
  • 测试 http://localhost:7001/ 结果页面

如果Idea中没有出现Service视图,找到项目目录.idea>workspace.xml

xml
<component name="RunDashboard">
    <option name="configurationTypes">
      <set>
        <option value="SpringBootApplicationConfigurationType" />
      </set>
    </option>
  </component>

Spring Cloud Eureka 属性作用

配置参数默认值说明
服务注册中心配置Bean类:org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean
eureka.server.enable-self-preservationfalse关闭注册中心的保护机制,Eureka 会统计15分钟之内心跳失败的比例低于85%将会触发保护机制,不剔除服务提供者,如果关闭服务注册中心将不可用的实例正确剔除
服务实例类配置Bean类:org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean
eureka.instance.prefer-ip-addressfalse不使用主机名来定义注册中心的地址,而使用IP地址的形式,如果设置了 eureka.instance.ip-address 属性,则使用该属性配置的IP,否则自动获取除环路IP外的第一个IP地址
eureka.instance.ip-addressIP地址
eureka.instance.hostname设置当前实例的主机名称
eureka.instance.appname服务名,默认取 spring.application.name 配置值,如果没有则为 unknown
eureka.instance.lease-renewal-interval-in-seconds30定义服务续约任务(心跳)的调用间隔,单位:秒
eureka.instance.lease-expiration-duration-in-seconds90定义服务失效的时间,单位:秒
eureka.instance.status-page-url-path/info状态页面的URL,相对路径,默认使用 HTTP 访问,如果需要使用 HTTPS则需要使用绝对路径配置
eureka.instance.status-page-url状态页面的URL,绝对路径
eureka.instance.health-check-url-path/health健康检查页面的URL,相对路径,默认使用 HTTP 访问,如果需要使用 HTTPS则需要使用绝对路径配置
eureka.instance.health-check-url健康检查页面的URL,绝对路径
服务注册类配置Bean类:org.springframework.cloud.netflix.eureka.EurekaClientConfigBean
eureka.client.service-url.指定服务注册中心地址,类型为 HashMap,并设置有一组默认值,默认的Key为 defaultZone;默认的Value为 http://localhost:8761/eureka ,如果服务注册中心为高可用集群时,多个注册中心地址以逗号分隔。 如果服务注册中心加入了安全验证,这里配置的地址格式为: http://<username>:<password>@localhost:8761/eureka 其中 <username> 为安全校验的用户名;<password> 为该用户的密码
eureka.client.fetch-registerytrue检索服务;false不会向Eureka Server注册中心获取注册信息
eureka.client.registery-fetch-interval-seconds30从Eureka服务器端获取注册信息的间隔时间,单位:秒
eureka.client.register-with-eurekatrue启动服务注册;false不会向Eureka Server注册中心注册自己的信息
eureka.client.eureka-server-connect-timeout-seconds5连接 Eureka Server 的超时时间,单位:秒
eureka.client.eureka-server-read-timeout-seconds8读取 Eureka Server 信息的超时时间,单位:秒
eureka.client.filter-only-up-instancestrue获取实例时是否过滤,只保留UP状态的实例
eureka.client.eureka-connection-idle-timeout-seconds30Eureka 服务端连接空闲关闭时间,单位:秒
eureka.client.eureka-server-total-connections200从Eureka 客户端到所有Eureka服务端的连接总数
eureka.client.eureka-server-total-connections-per-host50从Eureka客户端到每个Eureka服务主机的连接总数

2. 将提供者注册到Eureka

  • 改POM
xml

<!--eureka-client-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • 写YML
yml
server:
  port: 8081

spring:
  application:
    name: payment

eureka:
  client:
      #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: true
      #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
        defaultZone: http://localhost:7001/eureka
  instance:
    #在注册中心,控制台,访问的时候 地址IP地址(超链接的地址)
    prefer-ip-address: true
    #主机名字,在列表中使用
    hostname: 192.168.77.11
    #在eureka列表中查看微服务时使用如下格式
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
  • 主启动 @EnableEurekaClient
  • 测试 先要启动EurekaServer http://localhost:7001/ 微服务注册名配置说明
  • 自我保护机制

Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 会将这些实例保护起来,让这些实例不会过期,但是在保护期内如果服务刚好这个服务提供者非正常下线了,此时服务消费者就会拿到一个无效的服务实例,此时会调用失败,对于这个问题需要服务消费者端要有一些容错机制,如重试,断路器等。

我们在单机测试的时候很容易满足心跳失败比例在 15 分钟之内低于 85%,这个时候就会触发 Eureka 的保护机制,一旦开启了保护机制,则服务注册中心维护的服务实例就不是那么准确了,此时我们可以使用eureka.server.enable-self-preservation=false来关闭保护机制,这样可以确保注册中心中不可用的实例被及时的剔除。

3. 在Eureka中注册消费者

  • POM
xml
<!--eureka-client-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • YML
yml
server:
  port: 80
spring:
  application:
    name: order

eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    #在注册中心,控制台,访问的时候 地址IP地址(超链接的地址)
    prefer-ip-address: true
    #主机名字,在列表中使用
    hostname: 192.168.77.11
    #在eureka列表中查看微服务时使用如下格式
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
  • 主启动 @EnableEurekaClient
  • 测试 先要启动EurekaServer,7001服务 再要启动服务提供者provider,8001服务 eureka服务器

4. 使用Eureka+RestTemplate做服务调用

声明RestTemplate对象

在注入的RestTemplate中声明@LoadBanlance

@LoadBalanced //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力(默认是ribbon)

java
@Configuration
public class Appconfig {

    @Bean
    @LoadBalanced  //负载均衡,封装了 负载的策略,
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

}

服务调用

在调用的时候将ip:port替换成 application.name

java
@GetMapping("/order/{id}")
    CommonResult<Payment> order(@PathVariable("id") String orderId){

        //调用支付的接口
        String url = OrderConstants.SERVICE_PAYMENT+"/pay/"+orderId;
        CommonResult<Payment> result = restTemplate.getForObject(url, CommonResult.class);

        return result;
    }

5.模拟服务集群

上面模拟的是一个服务提供者,为了负载提高系统可用性,此时可以将服务提供者多部署几台,分别上线。

cloud-provider-payment-8081代码复制一份,修改端口为8082,并添加相同的方法

java
 /**
     *  http://127.0.0.1:8081/provider/echo/clouds
     */
    @Value("${server.port}")
    private String port;
    
    @GetMapping("/echo/{param}")
    String echo(@PathVariable String param){
        return param+"-provider:"+port;
    }

在服务消费方添加方法调用echo方法

java
@GetMapping("/echo/{param}")
String echo(@PathVariable String param){
    String url = OrderConstants.SERVICE_PAYMENT+"/echo/"+param;
    String result = restTemplate.getForObject(url, String.class);
    return result;
}

通过浏览器 直接访问服务消费者,http://192.168.77.11/consumer/echo/aaa,结果会在8081和8082之间切换

aaa-provider:8082aaa-provider:8081

6.4 手动的服务发现Discovery

在App类上添加@EnableDiscoveryClient注解

使用DiscoveryClient手动访问服务;

java
package com.neuedu.cloud.controller;

import com.netflix.discovery.converters.Auto;
import com.neuedu.cloud.common.OrderConstants;
import com.neueud.cloud.common.CommonResult;
import com.neueud.cloud.entity.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

/**
 * 项目 spring-cloud-java1
 *
 * @author 张金山
 * @version 1.0
 * 说明 订单接口
 * @date 2021/6/23 13:36
 */
@RestController
@Slf4j
public class OrderControlelr {
    @Autowired
    RestTemplate restTemplate; //才有能力将应用的 名字 转换成 实际 ip地址


    /**
     * http://192.168.77.11/order/506
     * @param orderId
     * @return
     */
    @GetMapping("/order/{id}")
    CommonResult<Payment> order(@PathVariable("id") String orderId){

        //调用支付的接口
        String url = OrderConstants.SERVICE_PAYMENT+"/pay/"+orderId;
        CommonResult<Payment> result = restTemplate.getForObject(url, CommonResult.class);

        return result;
    }

    @GetMapping("/echo/{param}")
    String echo(@PathVariable String param){
        String url = OrderConstants.SERVICE_PAYMENT+"/echo/"+param;
        String result = restTemplate.getForObject(url, String.class);
        return result;
    }


    /**
     * 手动发现服务
      */
    @Autowired
    DiscoveryClient discoveryClient;  //httpclient

    /**
     * 手动发现服务
     * @return
     */
    @GetMapping("/discoveryClient")
    String discoveryClient(){
        List<String> services = discoveryClient.getServices();
        StringBuffer stringBuffer = new StringBuffer();

        for (String service : services) {
           log.info("服务名称:{}",service);
            stringBuffer.append("服务名称:"+service);

            List<ServiceInstance> instance = discoveryClient.getInstances(service);
            for (ServiceInstance serviceInstance : instance) {
                /***
                 * http://192.168.77.11:80/order/506
                 */

                log.info("ServiceId:{}, getHost:{},Port :{}",
                        serviceInstance.getServiceId(),
                        serviceInstance.getHost(),
                        serviceInstance.getPort()
                );
            }

        }
        return stringBuffer.toString();
    }

}

6.5 集群搭建Eureka

为了实现Eureka的高可用,搭建Eureka注册中心集群 ,实现负载均衡+故障容错

准备工作

找到C:\Windows\System32\drivers\etc路径下的hosts文件,或者使用swichhosts软件 修改映射配置添加进hosts文件

127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
sh
C:\Users\Administrator>ping eureka7001.com

正在 Ping eureka7001.com [127.0.0.1] 具有 32 字节的数据:
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=64
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=64
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=64
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=64

127.0.0.1 Ping 统计信息:
    数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
    最短 = 0ms,最长 = 0ms,平均 = 0ms

C:\Users\Administrator>
  • 新建server-eureka-7002

  • 改POM ,跟7001相同

  • 写YML(以前单机)

    • 端口
    • hostname
    • 7001 需要修改defaultZone
    • 7002 需要修改defaultZone
  • 在微服务中配置注册到集群中(yml)

    yml
          defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  # 集群版
  • 测试,先启动两个Eureka服务

  • 查看两个Eureka控制台,是否存在服务 http://eureka7001.com:7001/http://eureka7002.com:7002/

  • 在启动order、payment三个微服务。

  • 再测试将注册中心的某一个停止,测试远程服务调用是否可用

6.6 自我保护模式

什么会产生Eureka自我保护机制? 为了防止EurekaClient可以正常运行,但是 与 EurekaServer网络不通情况下,EurekaServer不会立刻将EurekaClient服务剔除

什么是自我保护模式? 默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。

在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。 它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着

综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。

如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式:

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客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 30

服务端

\# 关闭自我保护机制,保证不可用的服务被及时剔除
enable-self-preservation: false
\# 如果2秒内没有收到某个微服务的心跳,那就剔除该微服务,单位为毫秒
eviction-interval-timer-in-ms: 2000

Released under the MIT License.