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;
}

可以看到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);
    }
}
@RestController
@RequestMapping("/user")
public class UserController {
    
    

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public User queryById(@PathVariable("id") Long id) {
    
    
        return userService.queryById(id);
    }
}

在启动服务之后,使用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();
}

在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;
}

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

3.3 Eureka 注册中心

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

image-20220822125319145

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

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

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

  1. 创建项目,添加依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    
  2. 编写启动类,加上 @EnableEurekaServer 注解

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

    server:
      port: 10086
    spring:
      application:
        name: eureka-server # eureka服务名称
    # 注册eureka服务 
    eureka:
      client:
        service-url:  # eureka地址信息
          defaultZone: http://localhost:10086/eureka
    

将应用启动后就可以访问我们配置的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.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  2. 添加注解

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

    @SpringBootApplication
    @EnableEurekaClient
    public class UserApplication {
          
          
        public static void main(String[] args) {
          
          
            SpringApplication.run(UserApplication.class, args);
        }
    }
    
  3. 配置文件

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

此时再打开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();
}
@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;
    }
}

注意在注入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

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

7. Eureka 优雅停服

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

  1. 添加依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  2. 配置文件

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

猜你喜欢

转载自blog.csdn.net/qq_62939743/article/details/126467742