SpringCloud EureKa组件

SpringCloud EureKa组件

06、Eureka介绍

6.1 初识Eureka


首先我们来解决第一个问题,服务的管理。

问题分析

​ 在刚才的案例中,user-service对外提供服务,需要对外暴露自己的地址。而user-consumer(消费者)需要记录服务提供者的地址。将来地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么,但是在日益复杂的互联网环境,一个项目肯定会拆分出十几,甚至数十个微服务。此时如果还人为管理地址,不仅开发困难,将来测试、发布上线都会非常麻烦,这与DevOps的思想是背道而驰的。

滴滴出行

网约车出现以前,人们出门打车只能叫出租车。一些私家车主想做出租司机却没有资格,被称为黑车。而很多人想要约车,但是无奈出租车太少,可能半天都等不到车,私家车很多却不敢拦,而且满大街的车,谁知道哪个才是愿意载人的。相当于一个想要,另一个想给,就是缺少引子,缺乏管理啊。

后来类似于滴滴这样的网约车平台出现了,所有想载客的私家车主全部到滴滴上进行注册,记录你的车型(服务类型),身份信息(联系方式)。这样提供服务的私家车,在滴滴那里都能找到,一目了然。

此时要叫车的人,只需要打开APP,输入你的目的地,选择车型(服务类型),滴滴自动安排一个符合需求的车到你面前,为你服务,完美!

Eureka做什么?

Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你。

同时,服务提供方与Eureka之间通过 “心跳” 机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。

这就实现了服务管理的自动注册与发现、状态监控。

6.2 原理图


基本架构

在这里插入图片描述

  • EurekaServer:就是服务注册中心(可以是一个集群),对外暴露自己的地址
  • 提供者:启动后向EurekaServer注册自己的服务信息(ip、端口、微服务名)
  • 消费者:向EurekaServer订阅服务,服务启动时会拉取一次服务列表,并且通过定时任务定期更新服务列表
  • 心跳(续约):提供者定期通过发送http请求至Eureka的方式刷新自己的状态

注意:

1、Eureka分两个部分: EurekaServer服务端 + EurekaClient客户端(服务提供者或服务消费者)

2、服务注册、服务心跳续约、服务拉取等都是通过调用eurekaServer的http接口实现

07、Eureka服务端:注册中心

目标:搭建eureka-server模块,作为注册中心。

7.1 实现步骤

  • 第一步:修改父工程springcloud-demo的pom文件,添加spring-cloud依赖配置,这点和加入springBoot父工程作用类似,此依赖中加入了许多依赖包的限定。

  •  <properties>
         <mapper.verion>2.1.5</mapper.verion>
         <springcloud.version>Greenwich.SR2</springcloud.version>
         <!--修改mysql版本,默认是8.X.X-->
         <mysql.version>5.1.47</mysql.version>
    </properties>
    
    <dependencyManagement>
        <dependencies>
            <!-- spring-cloud (导入pom文件)
                 scope: import 只能在<dependencyManagement>元素里面配置
            -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${springcloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            
            <!-- 通用mapper启动器 -->
            <dependency>
                <groupId>tk.mybatis</groupId>
                <artifactId>mapper-spring-boot-starter</artifactId>
                <version>${mapper.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
  • 第二步:创建模块: eureka-server

在这里插入图片描述
在这里插入图片描述

  • 第三步:配置eureka-server依赖: 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>springcloud-demo</artifactId>
            <groupId>cn.itcast</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
        <artifactId>eureka-server</artifactId>
    
        <dependencies>
            <!-- 配置eureka服务端启动器(集成了web启动器) -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            </dependency>
        </dependencies>
    </project>
    
  • 第四步:编写启动类 : EurekaServerApplication

    package cn.itcast;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
    
    @EnableEurekaServer // 声明当前应用为eureka服务(启用eureka服务)
    @SpringBootApplication
    public class EurekaServerApplication {
          
          
        public static void main(String[] args){
          
          
            SpringApplication.run(EurekaServerApplication.class, args);
        }
    }
    
  • package cn.itcast;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
    
    @EnableEurekaServer // 声明当前应用为eureka服务(启用eureka服务)
    @SpringBootApplication
    public class EurekaServerApplication {
          
          
        public static void main(String[] args){
          
          
            SpringApplication.run(EurekaServerApplication.class, args);
        }
    }
    
  • 第五步:编写配置文件: application.yml,这里要注意一点是eureka-server本身也是一个客户端(与高可用有关,后续章节会讲到),所以也需要配置服务端的地址,目前服务端就是自己,因此配置自己的地址即可。

    server:
      port: 8761 # eureka服务端,默认端口
    spring:
      application:
        name: eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
    eureka:
      client:
        service-url: # EurekaServer的地址,现在是自己的地址,如果做集群,需要写其它服务实例(节点)的地址。
          defaultZone: http://localhost:8761/eureka
        fetch-registry: false # 不拉取服务
        register-with-eureka: false # 不注册服务
    
  • 第六步:启动服务,并访问:http://127.0.0.1:8761

在这里插入图片描述
在这里插入图片描述

7.2 小结

Eureka服务端开发三要素:

  • 添加eureka服务端启动器

  • 添加eureka服务端注解

  • 添加eureka客户端配置,指定server地址

08、Eureka客户端:服务注册

目标:实现user-service服务注册到eurekaServer中。(客户端注册到服务端)

8.1 实现步骤

  • 第一步:在user-service模块中添加eureka客户端启动器依赖

    <!-- 配置eureka客户端启动器 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  • 第二步:在启动类上开启Eureka客户端,添加 @EnableDiscoveryClient 来开启Eureka客户端

    @SpringBootApplication
    @MapperScan("cn.itcast.user.mapper")
    @EnableDiscoveryClient // 开启Eureka客户端(2.1.x版本不加也行)
    public class UserApplication {
          
          
        
        public static void main(String[] args){
          
          
            SpringApplication.run(UserApplication.class, args);
        }
    }
    
  • 第三步:修改application.yml,添加eureka客户端配置

    server:
      port: 9001
    spring:
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/springcloud
        username: root
        password: root
      application:
        # 应用名称(服务id)
        name: user-service
    # 配置eureka服务端地址
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka
    

    注意:这里我们添加了spring.application.name属性来指定应用名称,将来会作为服务的id使用。

  • 第四步:重启项目,访问Eureka监控页面查看:

在这里插入图片描述
说明:我们发现user-service服务已经注册成功了。

8.2 小结

Eureka客户端开发三要素

  • eureka客户端启动器

  • eureka客户端注解

  • eureka客户端配置,指定server地址

    09、Eureka客户端:服务发现

目标:实现user-consumer消费者从eurekaServer中发现服务。(通过服务id发现服务)

9.1 实现步骤

  • 第一步:在user-consumer模块中添加eureka客户端启动器依赖

    <!-- 配置eureka客户端启动器 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  • 第二步:在启动类上开启Eureka客户端,添加 @EnableDiscoveryClient 来开启Eureka客户端

    package cn.itcast.consumer;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;
    
    @SpringBootApplication
    @EnableDiscoveryClient // 开启Eureka客户端
    public class ConsumerApplication {
          
          
    
        public static void main(String[] args){
          
          
            SpringApplication.run(ConsumerApplication.class, args);
        }
        @Bean
        public RestTemplate restTemplate(){
          
          
            return new RestTemplate();
        }
    }
    
  • 第三步:修改application.yml,添加eureka客户端配置

    server:
      port: 8080
    spring:
      application:
        name: user-consumer # 应用名称
    eureka:
      client:
        service-url: # eurekaServer地址
          defaultZone: http://localhost:8761/eureka
    
  • 第四步:修改Controller代码

    package cn.itcast.consumer.controller;
    
    import cn.itcast.consumer.pojo.User;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cloud.client.ServiceInstance;
    import org.springframework.cloud.client.discovery.DiscoveryClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    import java.util.List;
    
    @RestController
    @RequestMapping("/consumer")
    public class ConsumerController {
          
          
    
        /** 注入发现者 */
        @Autowired
        private DiscoveryClient discoveryClient;
        @Autowired
        private RestTemplate restTemplate;
        
        /** 根据主键id查询用户 */
        @GetMapping("/{id}")
        public User findOne(@PathVariable("id")Long id){
          
          
    
            // 根据服务id获取该服务的全部服务实例
            List<ServiceInstance> instances = discoveryClient
                    .getInstances("user-service");
            // 获取第一个服务实例(因为目前我们只有一个服务实例)
            ServiceInstance serviceInstance = instances.get(0);
    
            // 获取服务实例所在的主机
            String host = serviceInstance.getHost();
            // 获取服务实例所在的端口
            int port = serviceInstance.getPort();
    
            // 定义服务实例访问URL
            String url = "http://" + host + ":" + port + "/user/" + id;
            System.out.println("服务实例访问URL: " + url);
            return restTemplate.getForObject(url, User.class);
        }
    }
    
  • 第五步:访问consumer,可以发现依旧可以访问成功

9.2 小结

Eureka客户端开发三要素(同提供者)

  • eureka客户端启动器
  • eureka客户端注解
  • eureka客户端配置,指定server地址

说明:服务注册与服务发现都属于Eureka客户端,只是我们人为的把Eureka客户端分成了服务提供端与服务消费端两个角色。实际上Eureka只有服务端(注册中心) 与 客户端(服务注册或服务发现)

10、Eureka服务端:高可用

10.1 高可用介绍

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

服务同步

当存在多个Eureka Server节点时,每个节点都配置其他节点的地址,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的服务注册请求转发到集群中的其他节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。

而作为客户端,需要把信息注册到每个Eureka中:

在这里插入图片描述
如果有三个EurekaServer节点,则每一个节点都需指定其他节点的地址,例如:有三个节点端口分别为8761、8762、8763,则:

  • 8761节点的配置中指定8762和8763的地址
  • 8762节点的配置中指定8761和8763的地址
  • 8763节点的配置中指定8761和8762的地址

说明:

1、不仅仅是注册服务,心跳续约、服务下线等其他请求也会转发到其他节点。

2、若某服务A服务注册的时候,收到注册请求的节点(server1)转发请求到其他节点(server2)失败(例如网络波动),等到服务A进行心跳续约的时候,server1收到心跳续约请求,并转发到server2,server2若发现该服务在自己缓存中不存在,就会把该服务注册到自己。

3、若某服务A服务注册的时候,收到注册请求的节点(server1)转发请求到其他节点(server2),发现server2不可用,连接不上,此时server1内部有一个批处理流,会保存本次转发失败的请求,每隔1s钟重试一次,这样等server2节点恢复了,就可以收到该注册请求了,实现最终数据一致性。

在这里插入图片描述

10.2 高可用配置

我们假设要搭建两个EurekaServer的集群,端口分别为:8761和8762

10.2.1 实现步骤

  • 第一步:修改eureka-server的配置(application.yml)

    server:
      port: ${
          
          port:8761} # eureka服务端,默认端口
    spring:
      application:
        name: eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
    eureka:
      client:
        service-url: # Eureka服务地址;如果是集群则是其它服务地址,后面要加/eureka
          defaultZone: ${
          
          defaultZone:http://localhost:8761/eureka}
        fetch-registry: true # 拉取服务
        register-with-eureka: true # 注册服务
    

    说明:

    1、在上述配置文件中的${}表示在jvm启动时候若能找到对应port或者defaultZone参数则使用传入的参数,若无则使用冒号后面的默认值。

    2、把service-url的值改成了其他EurekaServer的地址,而不是自己。

    2、fetch-registry和register-with-eureka最好是都设置为true,这样server在启动的时候,会去service-url中配置的其他节点

    中拉取已有的服务列表。

  • 第二步:每一台在启动的时候指定端口port和defaultZone配置

    • 8761节点配置:

在这里插入图片描述

  • 8762节点配置
    在这里插入图片描述
    修改复制后的配置

在这里插入图片描述

  • 第三步:依次启动8761、8762节点,浏览器访问8761或8762:
    在这里插入图片描述
  • 第四步:eureka客户端修改配置,由于此时EurekaServer节点不止一个,因此user-service注册服务或者user-consumer获取服务的时候,service-url参数需要变化,这样即使某个节点不可用了,也可以使用其他的节点,实现高可用。

    # 配置eureka
    eureka:
      client:
        service-url: # EurekaServer地址,多个地址以','隔开
          defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka
    

11、Eureka客户端:服务提供者的其他配置

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

服务注册

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

  • 第一层Map的Key就是服务id,一般是配置中的 spring.application.name 属性

  • 第二层Map的key是服务的实例id。一般host+ serviceId + port,例如:localhost:user-service:9001

    值则是服务的实例对象,也就是说一个服务,可以同时启动多个不同实例,形成集群。

    Map<String, Map<String, instance>> allMap;

    Map<String, instance> instanceMap;

    instanceMap.put(“localhost:userService:9001”, instance);

    instanceMap.put(“localhost:userService:9002”, instance);

    allMap.put(“userService”, instanceMap);

服务注册时默认使用的是主机名,如果想用ip进行注册,可以在user-service中添加配置:

# 配置eureka
eureka:
  instance:
    ip-address: 127.0.0.1 # ip地址
    prefer-ip-address: true # 更倾向于使用ip,而不是host名称

修改完后先后重启user-service和user-consumer

在这里插入图片描述

服务续约

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

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

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

也就是说,默认情况下每个30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会从服务列表中剔除,这两个值在生产环境不要修改,默认即可。

12、Eureka客户端:服务消费者的其他配置

获取服务列表

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

eureka:
  client:
    registry-fetch-interval-seconds: 30 # 获取服务间隔时间(默认30秒)

13、Eureka服务端:失效剔除及自我保护

服务下线

当手动发送服务下线的REST请求给Eureka Server或debug模式下关闭服务,告诉服务注册中心:“我要下线了”。EurekaServer 接受到请求之后,将该服务置为下线状态,也就是从服务列表中剔除。

失效剔除

当服务由于内存溢出等原因变得不可用,亦或是正常关闭服务,此时服务注册中心并未收到“服务下线”的请求。服务注册中心在启动时会创建一个定时任务,每隔一段时间(默认为60秒)将当前服务列表中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。

可以通过以下参数对其进行修改,单位是毫秒。 某个服务实例宕机了,最长多久检测到并且剔除 = 60 + 90

eureka.server.eviction-interval-timer-in-ms: 60000 # eureka-server 服务剔除定时任务执行周期(毫秒)
  • 测试失效剔除步骤:

    • 修改eureka-server配置:

      eureka:
        client:
          service-url:
            defaultZone : ${
              
              defaultZone:http://localhost:8761/eureka/}
          fetch-registry: true
          register-with-eureka: true
      
        server:
          eviction-interval-timer-in-ms: 4000 # 设置4s执行一次检测定时任务
          enable-self-preservation: false # 关闭自我保护机制
      
    • 修改user-service配置:

      eureka:
        client:
          service-url:
            #配置eureka server 服务地址
            defaultZone: http://localhost:8761/eureka/
          fetch-registry: false # 拉取服务
          register-with-eureka: true # 注册服务
      
        instance:
          prefer-ip-address: true # 指定更偏向用ip
          ip-address: 127.0.0.1
          lease-renewal-interval-in-seconds: 5 # 5s发送一次心跳续约
          lease-expiration-duration-in-seconds: 15 # 15s未发送心跳续约就失效
      
    • 启动eureka-server和user-service,然后再关闭user-service

    • 那么最迟15 + 4 = 19s + 15(eureka服务剔除多加了一个失效时间)左右可以在注册中心控制台界面看到user-service服务剔除效果。

自我保护

我们同时关停多个注册的服务,可能在Eureka面板看到如下一条警告,这是触发了Eureka的自我保护机制。
在这里插入图片描述
在生产环境下,因为网络延迟等原因,EurekaServer未收到的心跳续约数量非常多,超标了,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。因此会开启自我保护机制,EurekaServer在这段时间内不会剔除任何服务实例(否则服务其实是好的,岂不是误杀了),直到网络恢复正常。生产环境下这很有效,保证了大多数服务依然可用,不过也有可能获取到失败的服务实例,因此服务调用者必须做好容错处理。

自我保护检查周期:默认每分钟检查一次

计算公式:最后一分钟实际受到的客户端实例心跳续约数(Renews ) < (每分钟应该受到心跳续约的总数 * 85%) = Renews threshold

  • Renews thresholdEureka Server 期望每分钟收到客户端实例续约的总数 *85%

  • Renews (last min)Eureka Server 最后 1 分钟实际收到客户端实例续约的总数

  • 若启动两个实例,通过计算可得出:

    每分钟应发心跳续约总数:2 * 60/30 = 4 (2为服务实例个数、一分钟60s、每个实例每30s发送一次心跳续约)

    可得出:阈值 = 4 * 85 % = 3.4,结果会取4 ,因此,如果上一分钟收到的心跳续约数< 4 便于触发自我保护

可以通过下面的配置来关闭自我保护:

eureka:
  server:
    enable-self-preservation: false # 关闭自我保护模式(缺省为打开)

在这里插入图片描述

Memorial Day is 512 days
I miss you
xiaokeai

猜你喜欢

转载自blog.csdn.net/weixin_42914989/article/details/114032066