• Eureka使用及原理详解


    个人博客地址:
    http://xiaohe-blog.top

    1. 什么是注册中心

    Eureka是一个注册中心,那么注册中心是什么呢?

    在微服务中,注册中心就像是一个卖房中介,房东们将自己的房子信息放在中介那里,客户在中介那里查看房子信息,看中了哪一套房,中介再牵线客户和房东签合同(因为房子不是中介的)。

    服务提供者将自己能提供的服务放在注册中心,服务调用者在注册中心查看是否有自己想要的服务,如果有,直接调用服务。但是服务本身并不是注册中心的,所以调用者必须直接调用提供者的服务。

    简而言之,注册中心就是收录服务的地方。

    未命名白板

    市面上的注册中心,大量使用的 :Eureka、Nacos、Consul、Zookeeper…

    当然注册中心不仅仅是存服务这一个作用,他还兼顾着 服务注册后如何被发现、服务异常时如何解决、服务发现后如何路由、服务如何扩展等一系列功能。

    2. Eureka注册中心

    Eureka有两个角色 :

    • Eureka Server(Eureka 服务端)。@EnableEurekaServer
    • Eureka Client(Eureka 客户端)。@EurekaEurekaClient

    其中 Eureka Client 又分为服务提供者和服务调用者,但是因为服务的提供者明天又会变成服务的调用者,所以这两者统称为客户端。(好比你家卖鱼,你是服务提供者,但是你想吃猪肉也要去买,你是服务消费者)

    3. Eureka 案例

    3.1准备工作

    这里我们使用黑马的案例。

    有两张表 :order、user,对应实体类如下 :

    @Data
    public class Order {
        private Long id;
        private Long price;
        private String name;
        private Integer num;
        private Long userId; // 对应user表中的id
        private User user;
    }
    @Data
    public class User {
        private Long id;
        private String username;
        private String address;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    可以看到order表中包含user用户信息。

    将order-service和user-service分为两个模块,给两张表提供最简单的查询功能。

    image-20220822112618483

    order 和 user 的controller分别如下 :

    @RestController
    @RequestMapping("order")
    public class OrderController {
       @Autowired
       private OrderService orderService;
    
        @GetMapping("{orderId}")
        public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
            // 根据id查询订单并返回
            return orderService.queryOrderById(orderId);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        @GetMapping("/{id}")
        public User queryById(@PathVariable("id") Long id) {
            return userService.queryById(id);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在启动服务之后,使用RESTFul风格的url路径分别访问order、user服务先试试项目是否有问题

    image-20220822113316893

    order-service和user-service分别为两个模块,那么我们如何借助Eureka实现“在order-service中调用user-service”这个功能呢?

    3.2 RestTemplate

    想要实现上述功能,如果我们能在order-service模块中发送http请求:http: //localhost:8081/user/{id} 就好了,那么我们如何在java代码中发送http请求呢?

    java提供了RestTemplate这个类来发送http请求。将RestTemplate放到spring容器中,在order-service模块中使用RestTemplate来发送 “http: //localhost:8081/user/” + order.getUserId();就可以获得完整的订单信息。

    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
    
    • 1
    • 2
    • 3
    • 4

    在OrderService中使用RestTemplate发送http请求。

    public Order queryOrderById(Long orderId) {
        // 1.查询订单
        Order order = orderMapper.findById(orderId);
        String url = "http://localhost:8081/user/" + order.getUserId();
        User user = restTemplate.getForObject(url, User.class);
        order.setUser(user);
        // 4.返回
        return order;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这里并没有用到Eureka,但是我们已经能看到这种方式的缺点了 :哪有http路径写到java代码里的?这直接深度耦合难以复用了。

    3.3 Eureka 注册中心

    刚才只是使用RestTemplate发送了http请求,现在才是Eureka的应用。

    image-20220822125319145

    既然用到了eureka,肯定要创建eureka注册中心。

    我们先创建一个Eureka注册中心模块,将所有服务注册进去,再进行调用。

    注册一个注册中心分为三部 :引入依赖,添加注解、添加配置

    1. 创建项目,添加依赖

      <dependency>
          <groupId>org.springframework.cloudgroupId>
          <artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
      dependency>
      
      • 1
      • 2
      • 3
      • 4
    2. 编写启动类,加上 @EnableEurekaServer 注解

      @SpringBootApplication
      @EnableEurekaServer
      public class EurekaApplication {
          public static void main(String[] args) {
              SpringApplication.run(EurekaApplication.class, args);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    3. 配置注册中心

      server:
        port: 10086
      spring:
        application:
          name: eureka-server # eureka服务名称
      # 注册eureka服务 
      eureka:
        client:
          service-url:  # eureka地址信息
            defaultZone: http://localhost:10086/eureka
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

    将应用启动后就可以访问我们配置的Eureka注册中心了,这里是 :http://localhost:10086/

    如下页面,红框内即为所有服务。当前我们只创建了注册中心,所以只有它一个服务。

    这也可以看到 :注册中心本身也是一个服务。为什么呢?

    当我们项目大的时候,又分为好几个注册中心,注册中心1想用注册中心2的一个服务,那么是不是就说明注册中心2本身也能提供服务。

    image-20220822131235322

    3.4 Eureka 注册服务

    现在我们可以将刚才两个order-service、user-service注册进注册中心。

    注册服务也是三步 :引入依赖,添加注解、添加配置。(给两个服务都加上)

    1. 引入依赖

      注册中心的依赖是 :eureka-server,而服务的依赖是 eureka-client

      <dependency>
          <groupId>org.springframework.cloudgroupId>
          <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
      dependency>
      
      • 1
      • 2
      • 3
      • 4
    2. 添加注解

      注册中心的注解是@EnableEurekaServer,服务的是 @EnableEurekaClient

      @SpringBootApplication
      @EnableEurekaClient
      public class UserApplication {
          public static void main(String[] args) {
              SpringApplication.run(UserApplication.class, args);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    3. 配置文件

      spring:
        application:
          name: userservice # eureka注册中心显示的名称就是应用名,所以尽量配置
      # 因为要将这个服务注册进注册中心,所以将刚才注册中心的eureka复制过来就行
      eureka:
        client:
          service-url:  # eureka地址信息
            defaultZone: http://localhost:10086/eureka
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

    此时再打开Eureka,就会出现三个服务 :eureka-server、userservice、orderservice

    image-20220822133040540

    当然,我们可以将userservice拷贝几份,将他们的端口改一下,在Eureka注册中心可以看到这些服务。

    image-20220822143324890

    3.5 Eureka 远程调用

    完成了注册中心和注册服务,就剩一个怎么在order-service中调用user-service了。

    同样要借助RestTemplate,刚才的代码几乎不用动。

    只有两处改动 :加一个注解@LoadBalanced、将url路径替换为服务路径

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    @Service
    public class OrderService {
    
        @Autowired
        private OrderMapper orderMapper;
    
        @Autowired
        private RestTemplate restTemplate;
    
        public Order queryOrderById(Long orderId) {
            // 1.查询订单
            Order order = orderMapper.findById(orderId);
            String url = "http://userservice/user/" + order.getUserId();
            User user = restTemplate.getForObject(url, User.class);
            order.setUser(user);
            // 4.返回
            return order;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    注意在注入RestTemplate时加了一个注解 :@LoadBalanced,这个就是负载均衡注解,我们刚刚注册了三个userservice服务,到底用哪一个?由负载均衡实现,为什么要使用负载均衡呢?那也不能一亿次请求只分在一个服务身上吧,肯定是谁的压力小分到谁身上。

    4. Eureka 架构原理

    官网的Eureka工作流程图 :

    20190703103823398

    Application service :服务提供者。

    Application client :服务消费者。

    • Register :将自己的IP端口注册到注册中心(Eureka)。
    • Replicate :不同的注册中心同步服务,做到集群中的数据同步。
    • Renew :服务续约,服务向注册中心发送心跳,每30s发送一次,告诉Eureka自己还活着。便于注册中心检查服务的健康状态。
    • Cancel :服务下线,在注册中心删除此服务。
    • Get Register :客户端(Client)从注册中心获取注册列表,第一次全量获取,后面为定时增量。
    • Make Remote Call : 完成服务的远程调用

    Eureka的数据存在map集合中,在内存里。消费者调用服务就是从注册中心拉取服务列表,在列表中找到需要的服务,接下来并不是借助注册中心调用服务,注册中心仅仅是注册中心,而是通过Make Remote Call 去远程调用需要的服务。

    5. CAP: 分布式一致性定理

    C : 一致性,在分布式系统中,是否立即达到数据同步效果。
    A : 可用性,在分布式系统中,其中一些节点出现问题,整个整体是否还可用。
    P : 分区容错性,在分布式系统中是否可以在有限的时间内达到数据一致的效果。

    Eureka 只完成了A和P。

    6. Eureka 自我保护

    一般情况下,服务在Eureka上注册后,每30s会发送心跳包,Eureka通过心跳来判断服务是否健康,同时会定期清理超过90s没有发送心跳包的服务。

    有两种情况会导致 Eureka Server 接收不到微服务的心跳

    • 微服务自身问题。
    • 微服务与Eureka之间的网络出现问题。

    如果是网络出现问题,微服务怎么保护自己不被误删呢?

    自我保护模式 :Eureka Server 在运行期间会去统计该服务的心跳失败比例,15分钟内心跳失败超过了85%,Eureka Server会将这个服务保护起来,同时提供一个警告,提示你去检查该服务的状态。

    自我保护模式默认是开启的,同样也可以关闭。

    eureka:
     server:
      enable-self-preservation: false # true为开启自我保护
      eviction-interval-timer-in-ms: 60000 # 清理间隔,单位毫秒,默认为1min
    
    • 1
    • 2
    • 3
    • 4

    有一个问题,在保护模式的前提下,只要一个服务消失,不管是我们主动关闭还是服务出现问题,Eureka都会将它保护起来,但是我们确实不再需要这个服务,如何不让Eureka保护它而是直接关闭该服务呢?这就涉及到Eureka的优雅停服了。

    7. Eureka 优雅停服

    有了优雅停服,我们就可以在不关闭保护模式的前提下直接将服务删除。我们通过actuator实现。

    1. 添加依赖

      <dependency>
          <groupId>org.springframework.bootgroupId>
      	<artifactId>spring-boot-starter-actuatorartifactId>
      dependency>
      
      • 1
      • 2
      • 3
      • 4
    2. 配置文件

      management:
       endpoints:
        web:
         exposure:
          include: shutdown  # 开启shutdown端点访问
       endpoint:
        shutdown:
         enabled: true  # 开启shutdown实现优雅停服
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    3. 启动后发送post请求 :http://localhost:10086/actuator/shuwdown,发现注册中心中该服务已经被删除

  • 相关阅读:
    MySQL JDBC驱动版本与数据库版本的对应关系及注意事项
    CompletableFuture使用详解
    多网卡场景数据包接收时ip匹配规则
    1159 Structure of a Binary Tree 甲级 xp_xht123
    70 QDateTime时间戳转换有误
    课堂练习4-C语言多分支结构
    VIM统计搜索关键词命令
    【源码阅读】事务源码
    37-Jenkins-多分支流水线
    I.MX6U-ALPHA开发板(SPI实验)
  • 原文地址:https://blog.csdn.net/qq_62939743/article/details/126467742