服务调用方式

RPC和HTTP

无论是微服务还是SOA,都面临着服务间的远程调用。那么服务间的远程调用方式有哪些呢?

常见的远程调用方式有以下2种:

  • RPC:Remote Produce Call远程过程调用,类似的还有 。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo (12不再维护、17年维护权交给apache),都是RPC的典型代表
  • Http:http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议,也可以用来进行远程服务调用。缺点是消息封装臃肿,优势是对服务的提供和调用方没有任何技术限定,自由灵活,更符合微服务理念。现在热门的Rest风格,就可以通过http协议来实现。

跨域调用

跨域特指前端页面调用后端api,即前端页面在一个服务器,后端api在另外一个服务器,是浏览器安全保护行为,与后端没有关系。一般在前后端分离的项目中要解决跨域问题。解决跨域一般有以下几种方式:
(1)ajax+jsonp
(2)proxytable
(3)@CrossOrigin
(4)nginx代理
(5)response.setHeader(“Access-Control-Allow-Origin”, “*”);

远程调用

远程调用技术特指后端不同服务器之间的调用,例如在A服务的api中调用B服务的api。以下的技术都可以完成A服务调用B服务:
(1)dubbo+zookeeper
(2)springcloud中的eureka+feign
(3)httpclient/okhttp3
(4)spring中的RestTemplate
(5)webservice

搭建项目

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_开发语言

点击下一步

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_ide_02

修改后

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_ide_03

点击下一步

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_spring_04

点击下一步

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_dubbo_05

点击完成

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_ide_06

创建spring-provider、spring-consumer模块

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_开发语言_07

点击模块,选择Maven

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_spring_08

点击下一步,填写模块名称

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_java_09

点击完成

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_ide_10

在spring-provider、spring-consumer模块中POM中引入WEB模块

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

增加配置文件application.yml

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_java_11

spring-provider

application.yml

server:
  port: 8180

ProviderController

@RestController
public class ProviderController {

    @Value("${server.port}")
    private String port;

    @RequestMapping(value = "/provider/{id}")
    public String provider(@PathVariable String id){
        return "provider id = " + id + " port = " + port;
    }
}

运行结果

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_spring_12

spring-consumer

application.yml

server:
  port: 8280

ConsumerController

@RestController
public class ConsumerController {

    @RequestMapping(value = "/consumer/{id}")
    public String consumer(@PathVariable String id){
        return "consumer ";
    }
}

运行结果

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_开发语言_13

HttpClient和OkHttp3性能比

  • client连接为单例:
    单例模式下,HttpClient的响应速度要更快一些,单位为毫秒,性能差异相差不大
  • 非单例模式下,OkHttp的性能更好,HttpClient创建连接比较耗时,因为多数情况下这些资源都会写成单例模式。
  • HttpClient+okhttp+URLConnection

HttpClien

<dependency>
    <groupId>commons-httpclient</groupId>
    <artifactId>commons-httpclient</artifactId>
    <version>3.1</version>
</dependency>
/**
     * @title consumerHttp
     *
     * @param: id
     * @updateTime 2022/11/9 15:09
     * @return: java.lang.String
     * @throws
     * @Description: HttpClient调用方式
     */
    @RequestMapping(value = "/consumerHttp/{id}")
    public String consumerHttp(@PathVariable String id){
        String consumer = null;
        String url = "http://localhost:8180/provider/" + id;

        HttpClient httpClient = new HttpClient();
        GetMethod getMethod = new GetMethod(url);
        try {
            httpClient.executeMethod(getMethod);
            byte[] body = getMethod.getResponseBody();
            consumer = new String(body, "UTF-8");
        } catch (IOException e) {
            return "HttpClient consumer exception";
        }
        return "HttpClient consumer " + consumer;
    }

运行结果:

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_开发语言_14

okhttp3

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.2</version>
</dependency>
/**
     * @title consumerok
     *
     * @param: id
     * @updateTime 2022/11/9 15:25
     * @return: java.lang.String
     * @throws
     * @Description: OkHttpClient调用方式
     */
    @RequestMapping(value = "/consumerok/{id}")
    public String consumerok(@PathVariable String id){
        String consumer = null;
        String url = "http://localhost:8180/provider/" + id;

        OkHttpClient okHttpClient = new OkHttpClient();
        Request request = new Request.Builder().url(url).build();
        Response response = null;
        try {
            response = okHttpClient.newCall(request).execute();
            consumer = response.body().string();
        } catch (IOException e) {
            return "OkHttpClient consumer exception";
        }
        return "OkHttpClient consumer " + consumer;
    }

运行结果:

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_dubbo_15

Spring的RestTemplate

RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。RestTemplate 继承InterceptingHttpAccessor 并且实现了 RestOperations 接口,其中 RestOperations 接口定义了基本的 RESTful 操作,这些操作在 RestTemplate 中都得到了实现

常用方法:

HTTP Method

常用方法

描述

GET

getForObject

发起 GET 请求响应对象

GET

getForEntity

发起 GET 请求响应结果、包含响应对象、请求头、状态码等 HTTP 协议详细内容

POST

postForObject

发起 POST 请求响应对象

POST

postForEntity

发起 POST 请求响应结果、包含响应对象、请求头、状态码等 HTTP 协议详细内容

DELETE

delete

发起 HTTP 的 DELETE 方法请求

PUT

put

发起 HTTP 的 PUT 方法请求

声明restTemplateBean方式一

@Bean
public RestTemplate getRestTemplate(){
    return new RestTemplate();
}

声明restTemplateBean方式二

@Bean
public RestTemplate getRestTemplate(RestTemplateBuilder builder){
    return builder.build();
}

restTemplate远程调用

@Autowired
    private RestTemplate restTemplate;

    /**
     * @title consumerRest
     *
     * @param: id
     * @updateTime 2022/11/9 15:28
     * @return: java.lang.String
     * @throws
     * @Description: restTemplate调用方式
     */
    @RequestMapping(value = "/consumerRest/{id}")
    public String consumerRest(@PathVariable String id){
        String url = "http://localhost:8180/provider/" + id;
        String consumer = restTemplate.getForObject(url, String.class);
        return "restTemplate consumer " + consumer;
    }

运行结果:

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_ide_16

SpringCloud

微服务是一种架构方式,最终肯定需要技术架构去实施。

微服务的实现方式很多,目前Spring Cloud比较流行。为什么?

  • 后台硬:作为Spring家族的一员,有整个Spring全家桶靠山,背景十分强大。
  • 技术强:Spring作为Java领域的前辈,可以说是功力深厚,有强力的技术团队支撑。
  • 群众基础好:大多数程序员的成长都伴随着Spring框架,现在有几家公司开发不用Spring?SpringCloud与Spring的各个框架无缝整合,对大家来说一切都是熟悉的配方,熟悉的味道。
  • 使用方便:相信大家都体会到了SpringBoot给我们开发带来的便利,而SpringCloud完全支持SpringBoot的开发,用很少的配置就能完成微服务框架的搭建。

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_spring_17

简介

SpringCloud是Spring旗下的项目之一:

Spring最擅长的就是集成,把世界上最好的框架拿过来,集成到自己的项目中。

SpringCloud也是一样,它将现在非常流行的一些技术整合到一起,实现了诸如:配置管理,服务发现,智能路由,负载均衡,熔断器,控制总线,集群状态等等功能。其主要涉及的组件包括:

  • Eureka:服务治理组件,包含服务注册中心,服务注册与发现机制的实现。(服务治理,服务注册/发现)
  • Zuul(gateway):网关组件,提供智能路由,访问过滤功能
  • Ribbon:客户端负载均衡的服务调用组件(客户端负载)
  • Feign(open feign):服务调用,给予Ribbon和Hystrix的声明式服务调用组件 (声明式服务调用)
  • Hystrix:容错管理组件,实现断路器模式,帮助服务依赖中出现的延迟和为故障提供强大的容错能力。(熔断、断路器,容错)

版本

因为Spring Cloud不同其他独立项目,它拥有很多子项目的大项目。所以它的版本是版本名+版本号 (如Angel.SR6)。

版本名:是伦敦的地铁名

版本号:SR(Service Releases)是固定的 ,大概意思是稳定版本。后面会有一个递增的数字。

所以 Edgware.SR3就是Edgware的第3个Release版本。

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_ide_18

Eureka注册中心

原理图

  • Eureka:就是服务注册中心(可以是一个集群),对外暴露自己的地址
  • 提供者:启动后向Eureka注册自己信息(地址,提供什么服务)
  • 消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新
  • 心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_dubbo_19

入门

搭建EurekaServer

接下来我们创建一个项目,启动一个EurekaServer:

依然使用spring提供的快速搭建工具:

选择依赖:EurekaServer-服务注册中心依赖,Eureka Discovery-服务提供方和服务消费方。因为,对于eureka来说:服务提供方和服务消费方都属于客户端

parent的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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <packaging>pom</packaging>
   <modules>
      <module>spring-provider</module>
      <module>spring-consumer</module>
        <module>eureka-server</module>
    </modules>
   <groupId>com.woniu</groupId>
   <artifactId>spring-cloud</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>spring-cloud</name>
   <description>Demo project for Spring Boot</description>
   <properties>
      <java.version>1.8</java.version>
      <spring-boot.version>2.3.11.RELEASE</spring-boot.version>
      <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
   </properties>
   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter</artifactId>
      </dependency>

      <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
         <optional>true</optional>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
   </dependencies>

   <dependencyManagement>
      <dependencies>
         <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
      </dependencies>
   </dependencyManagement>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
               <excludes>
                  <exclude>
                     <groupId>org.projectlombok</groupId>
                     <artifactId>lombok</artifactId>
                  </exclude>
               </excludes>
            </configuration>
         </plugin>
      </plugins>
   </build>

</project>

eureka服务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</artifactId>
        <groupId>com.woniu</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>eureka-server</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

</project>

application.yml配置:

server:
  port: 8081 # 端口
spring:
  application:
    name: eureka-server # 应用名称,会在Eureka中显示
eureka:
  client:
    service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址。
      defaultZone: http://127.0.0.1:${server.port}/eureka
    register-with-eureka: false  # 不把自己注册到eureka服务列表
    fetch-registry: false   # 拉取eureka服务信息
    
  instance:
    prefer-ip-address: true #客户端在注册时使用自己的IP而不是主机名
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}  #实例id

修改引导类,在类上添加@EnableEurekaServer注解:

@SpringBootApplication
@EnableEurekaServer // 声明当前springboot应用是一个eureka服务中心
public class EurekaServerApplication {

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

启动服务,并访问:http://127.0.0.1:8081

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_dubbo_20

服务方

注册服务,就是在服务上添加Eureka的客户端依赖,客户端代码会自动把服务注册到EurekaServer中。

spring-provider工程

1、在pom.xml中,添加springcloud eureka-client的相关依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2、在application.yml中,添加springcloud的相关依赖

# 应用服务 WEB 访问端口
server:
  port: 8180
# 应用名称
spring:
  application:
    name: spring-provider
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8081/eureka
  instance:
    prefer-ip-address: true #注册服务的时候使用服务的IP地址
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}

3、在引导类上开启Eureka客户端功能

添加**@EnableEurekaClient或者@EnableDiscoveryClient**来开启Eureka客户端功能

@SpringBootApplication
@EnableEurekaClient
public class SpringProviderApplication {

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

或者

@SpringBootApplication
@EnableDiscoveryClient
public class SpringProviderApplication {

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

}
@EnableEurekaClient和**@EnableDiscoveryClient**区别(了解)

@EnableEurekaClient

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface EnableEurekaClient {
}

@EnableDiscoveryClient

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({EnableDiscoveryClientImportSelector.class})
public @interface EnableDiscoveryClient {
    boolean autoRegister() default true;
}

在使用Spring Cloud feign使用中在使用服务发现的时候提到了两种注解:
一种为@EnableDiscoveryClient;
一种为@EnableEurekaClient,用法上基本一致。

spring cloud中discovery service有许多种实现(eureka、consul、zookeeper等等),
@EnableDiscoveryClient基于spring-cloud-commons;
@EnableEurekaClient基于spring-cloud-netflix。
其实用更简单的话来说,就是如果选用的注册中心是eureka,那么就推荐@EnableEurekaClient, 如果是其他的注册中心,那么推荐使用@EnableDiscoveryClient。

注解@EnableEurekaClient上有@EnableDiscoveryClient注解,可以说基本就是EnableEurekaClient有@EnableDiscoveryClient的功能,另外上面的注释中提到,其实@EnableEurekaClientz注解就是一种方便使用eureka的注解而已,可以说使用其他的注册中心后,都可以使用@EnableDiscoveryClient注解,但是使用@EnableEurekaClient的情景,就是在服务采用eureka作为注册中心的时候,使用场景较为单一。

重启项目,访问Eureka监控页面查看,发现spring-provider服务说明已经注册成功了

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_java_21

消费方

1、引入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  1. 修改配置
# 应用服务 WEB 访问端口
server:
  port: 8280
# 应用名称
spring:
  application:
    name: spring-consumer
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8081/eureka
  instance:
    prefer-ip-address: true #注册服务的时候使用服务的IP地址
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
  1. 在启动类开启Eureka客户端
@SpringBootApplication
@EnableDiscoveryClient // 开启Eureka客户端
public class SpringConsumerApplication {

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

    public static void main(String[] args) {
        SpringApplication.run(SpringProviderApplication.class, args);
    }
}
  1. 修改UserController代码,用DiscoveryClient类的方法,根据服务名称,获取服务实例:
@Autowired
    private DiscoveryClient discoveryClient; // eureka客户端,可以获取到eureka中服务的信息

    /**
     * @title consumerRestDiscovery
     *
     * @param: id
     * @updateTime 2022/11/9 15:28
     * @return: java.lang.String
     * @throws
     * @Description: 服务名称调用方式
     */
    @RequestMapping(value = "/consumerRestDiscovery/{id}")
    public String consumerRestDiscovery(@PathVariable String id){

        // 根据服务名称,获取服务实例。有可能是集群,所以是service实例集合
        List<ServiceInstance> instances = discoveryClient.getInstances("spring-provider");
        // 因为只有一个spring-provider。所以获取第一个实例
        ServiceInstance instance = instances.get(0);
        // 获取ip和端口信息,拼接成服务地址
        String baseUrl = "http://" + instance.getHost() + ":" + instance.getPort() + "/provider/" + id;
        String consumer = this.restTemplate.getForObject(baseUrl, String.class);
        return "restTemplate consumer " + consumer;
    }

重启程序

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_spring_22

运行结果:

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_spring_23

基础架构

Eureka架构中的三个核心角色:

  • 服务注册中心
    Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的eureka-server。
  • 服务提供者
    提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。本例中就是我们实现的spring-provider。
  • 服务消费者
    消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。本例中就是我们实现的spring-consumer。

高可用的Eureka Server

Eureka Server即服务的注册中心,在刚才的案例中,我们只有一个EurekaServer,事实上EurekaServer也可以是一个集群,形成高可用的Eureka中心。

服务同步

多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。

动手搭建高可用的EurekaServer

我们假设要运行两个EurekaServer的集群,端口分别为:8081和8082。只需要把eureka-server启动两次即可。

1、启动第一个eurekaServer,我们修改原来的EurekaServer配置:

server:
  port: 8081
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址。
      defaultZone: http://127.0.0.1:8082/eureka
  instance:
    prefer-ip-address: true #客户端在注册时使用自己的IP而不是主机名
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}  #实例id

所谓的高可用注册中心,其实就是把EurekaServer自己也作为一个服务进行注册,这样多个EurekaServer之间就能互相发现对方,从而形成集群。因此我们做了以下修改:

  • 把service-url的值改成了另外一台EurekaServer的地址,而不是自己

启动报错,很正常。因为8082服务没有启动:

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_spring_24

2、启动第二个eurekaServer,再次修改eureka-server的配置:

server:
  port: 8082
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址。
      defaultZone: http://127.0.0.1:8081/eureka
  instance:
    prefer-ip-address: true #客户端在注册时使用自己的IP而不是主机名
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}  #实例id

注意:idea中一个应用不能启动两次,我们需要重新配置一个启动器:复制运行的启动类即可

然后启动即可。

3、访问集群,测试:

8081服务

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_java_25

8082服务

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_ide_26

4、客户端注册服务到集群

因为EurekaServer不止一个,因此注册服务的时候,service-url参数需要变化:

eureka:
  client:
    service-url: # EurekaServer地址,多个地址以','隔开
      defaultZone: http://127.0.0.1:8081/eureka,http://127.0.0.1:8082/eureka

5、打开eureka-server监控页面

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_spring_27

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_spring_28

问题:IDEA无法同一个项目运行多次

解决方案:

  • 点击编辑配置

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_开发语言_29

  • 勾选以下等式,点击确认即可

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_开发语言_30

  • 设置成功,如图:

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_dubbo_31

服务提供者

服务提供者要向EurekaServer注册服务,并且完成服务续约等工作。

服务注册

服务提供者在启动时,会检测配置属性中的:eureka.client.register-with-eureka=true参数,默认就是true。值为true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息,Eureka Server会把这些信息保存到一个双层Map结构中。

  • 第一层Map的Key就是服务id,一般是配置中的spring.application.name属性
  • 第二层Map的key是服务的实例id。一般host+ serviceId + port,例如:locahost:spring-provider:8081
  • 值则是服务的实例对象,也就是说一个服务,可以同时启动多个不同实例,形成集群。

服务续约

在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer:“我还活着”。这个我们称为服务的续约(renew);

有两个重要参数可以修改服务续约的行为:

eureka:
  instance:
    lease-expiration-duration-in-seconds: 90
    lease-renewal-interval-in-seconds: 30
  • lease-renewal-interval-in-seconds:服务续约(renew)的间隔,默认为30秒
  • lease-expiration-duration-in-seconds:服务失效时间,默认值90秒

默认情况下每个30秒服务会向注册中心发送一次心跳,证明自己还活着。超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会从服务列表中移除。

在开发时,这个值有点太长了,经常我们关掉一个服务,会发现Eureka依然认为服务在活着。所以我们在开发阶段可以适当调小。

eureka:
  instance:
    lease-expiration-duration-in-seconds: 10 # 10秒即过期
    lease-renewal-interval-in-seconds: 5 # 5秒一次心跳
    
    # 注意:从服务列表是否移除,还要看 eureka服务端的   eviction-interval-timer-in-ms:这个参数
    eviction-interval-timer-in-ms:6000  #这是6000毫秒 
    # 关闭自我保护机制  
    enable-self-preservation: false

服务消费者

获取服务列表

当服务消费者启动时,会检测eureka.client.fetch-registry=true参数的值,如果为true,则会拉取Eureka Server服务的列表只读备份,然后缓存在本地。并且每隔30秒会重新获取并更新数据。我们可以通过下面的参数来修改:

eureka:
  client:
    registry-fetch-interval-seconds: 5

在开发环境下,能够快速得到服务的最新状态,我们可以将其设置小一点。

失效剔除和自我保护

服务下线

服务进行正常关闭操作,会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务中心接受到请求之后,将该服务置为下线状态。

失效剔除

有些时候,我们的服务提供方并不一定会正常下线,可能因为内存溢出、网络故障等原因导致服务无法正常工作。Eureka Server需要将这样的服务剔除出服务列表。因此它会开启一个定时任务,每隔60秒对所有失效的服务(超过90秒未响应)进行剔除。

可以通过eureka.server.eviction-interval-timer-in-ms参数对其进行修改,单位是毫秒。

eureka:
  server:
    # 每隔多久(ms)触发一次服务剔除
    eviction-interval-timer-in-ms: 10000

原理图(了解)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2AkOwJla-1684317905505)(C:\Users\ThinkPad\AppData\Roaming\Typora\typora-user-images\image-20221110092516071.png)]

自我保护

我们关停一个服务,就会在Eureka面板看到一条警告:

spring cloud 服务间调用跟踪 springcloud服务之间的调用协议_dubbo_32

这是触发了Eureka的自我保护机制。当一个服务未按时进行心跳续约时,Eureka会统计最近15分钟心跳失败的服务实例的比例是否超过了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka就会把当前实例的注册信息保护起来,不予剔除。生产环境下这很有效,保证了大多数服务依然可用。

但是这给我们的开发带来了麻烦, 因此开发阶段我们都会关闭自我保护模式:(eureka-server)

eureka:
  server:
    enable-self-preservation: false # 如果为true,表示要保护实例,不被剔除,false关闭自我保护模式,剔除实例
    eviction-interval-timer-in-ms: 10000 # 扫描失效服务的间隔时间(缺省为60*1000ms)

如果保护实例不被剔除,而且配置了 eviction-interval-timer-in-ms: 10000,则eviction-interval-timer-in-ms参数为准,实例还是会被剔除

eureka:
  server:
    enable-self-preservation: true # 如果为true,表示要保护实例,不被剔除,false关闭自我保护模式,剔除实例
    #eviction-interval-timer-in-ms: 10000 # 扫描失效服务的间隔时间(缺省为60*1000ms)