服务注册与发现——Eureka

1. Eureka基础知识

1. 什么是服务治理

SpringCloud封装了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中的服务注册表中将存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。

  • Eureka Client通过注册中心进行访问

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

    Eureka Client分为两个角色,分别是:Application Service(Service Provider)和Application Client(Service Consumer)

    Application Service

    服务提供方,是注册到Eureka Server中的服务。

    Application Client

    服务消费方,通过Eureka Server发现服务,并消费。

2. 单机Eureka构建步骤

1. IDEA生成Eureka Server端服务注册中心
  • 1 、POM

    1.X和2.X的对比说明:

    SpringBoot1.X对应的SpringCloud中

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

    SpringBoot2.X对应的SpringCloud中

    <dependency>
    	<groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    
  • 3 、YAML

    server:
      port: 7001
    
    #eureka服务端的实例名称
    eureka:
      instance:
        hostname: localhost
      client:
        #false表示不向注册中心注册自己,因为自己就是注册中心
        register-with-eureka: false
        #是否从EurekaServer抓取已有的注册信息,默认为true。我的职责就是维护服务实例,并不需要去检索服务
        fetch-registry: false
        service-url:
          #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
          defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
    
  • 4、效果
    在这里插入图片描述

    可以发现,目前还没有任何服务入驻进服务注册中心中,在应用中显示:No instances available

2. 将Eureka Client端中的服务提供端注册进Eureka Server作为Service Provider
  • pom

    <!--eureka-client-->
    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> 
    </dependency>
    
  • 配置文件

    server:
      port: 8001
    
    spring:
      application:
        name: eureka_order_service
        
    #eureka客户端的实例名称(服务提供者)
    eureka:
      instance:
        hostname: localhost
      client:
        #向注册中心注册自己
        register-with-eureka: true
        #从EurekaServer抓取已有的注册信息
        fetch-registry: true
        service-url:
          #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
          defaultZone: http://localhost:7001/eureka # 入驻的服务注册中心地址
    
  • 效果

    先要启动EurekaServer,因为有了服务注册中心,具体的服务提供者才能后向其中注册自己的服务,如下图:
    在这里插入图片描述

3. 将Eureka Client端中的服务消费端注册进Eureka Server称为Service Consumer
  • Pom
<!--eureka-client-->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> 
</dependency>
  • 配置文件

    server:
      port: 80
    
    spring:
      application:
        name: eureka_consume_service
    
    #eureka客户端的实例名称(服务消费者)
    eureka:
      instance:
        hostname: localhost
      client:
        #false表示不向注册中心注册自己
        register-with-eureka: false
        #是否从EurekaServer抓取已有的注册信息
        fetch-registry: true
        service-url:
          #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
          defaultZone: http://localhost:7001/eureka # 入驻的服务注册中心地址
    
  • 效果

    如图此时80,8001两个微服务都已经注册到Eureka服务注册中心
    在这里插入图片描述

    此时,再回看最开始的Eureka系统架构,在服务注册中心和服务提供者没有集群的情况下,7001端口的微服务就对应了服务注册中心,而该服务不需要向服务注册中心注册自己,8001端口的微服务作为服务提供方入住到服务注册中心,8002端口的微服务作为服务消费方也同样注册到服务注册中心
    在这里插入图片描述

3. 集群Eureka构建步骤

1. Eureka集群原理说明

服务注册中心Eureka Server中分为 服务注册服务发现,服务注册过程将服务信息注册进服务注册中心,服务发现过程从服务注册中心上获取服务信息,而这个过程的实质就是:将服务名作为key存储,然后根据value取得服务的调用地址

整个Eureka的过程如果:

  1. 先启动Eureka注册中心
  2. 启动服务提供者服务
  3. 服务提供者服务将自身信息(比如服务地址)以别名方式注册到Eureka注册中心
  4. 消费者服务在需要调用接口时,使用服务别名到注册中心获取实际的RPC远程调用地址
  5. 消费者获得调用地址后,底层实际是利用 HttpClient 技术实现远程调用
  6. 消费者获得服务地址后会缓存字本地JVM内存中,默认每间隔30秒更新一次服务调用地址

那么微服务RPC远程服务调用最核心的是什么呢???

高可用!!!,如果注册中心只有一个,而这个注册中心出现了故障,那么整个微服务就直接GG了,整个微服务环境就不可用了,所以应该搭建Eureka注册中心集群, 实现 负载均衡 + 故障容错

那怎么实现Eureka注册中心的集群呢?用一句话总结就是——互相注册,相互守望,如下图所示:
在这里插入图片描述
服务注册中心实现相互注册,让彼此都知道对方的存在,也就是注册中心集群中的每一个注册中心都知道整个集群中的其他注册中心,比如如果有三个注册服务中心7001,7002,7003,那么就将7002和7003注册给7001, 将7002和7001注册给7003, 将7003和7001注册给7002, 以此类推,而这些个注册服务中心作为一个整体对外看做一个注册服务中心。

2. EurekaServer集群环境构建步骤
  • 参考cloud-eureka-server7001新建一个服务注册中心cloud-eureka-server7001。

  • 2 改POM,copy复制cloud-eureka-server7001的POM文件即可。

  • 3 修改映射配置

    找到C:\Windows\System32\drivers\etc路径下的hosts文件,将其内容修改成如下内容:

    # learn-spring-cloud
    127.0.0.1 eureka7001.com
    127.0.0.1 eureka7002.com
    

通过一台主机(127.0.0.1即localhost)的不同端口模拟实现不同的主机的命名(127.0.0.1:7001和127.0.0.1:7002)。

  • 4 写配置文件YML(区别于单机)

    以前单机的时候,注册中心的配置文件是这样的:

    server:
      port: 7001
    
    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/
    

而现在已经有两个注册中心,可以看做两台机器,显然 hostname 不能再叫localhost,而集群不能再这样,对于7001端口的注册中心,修改其配置文件:

  server:
    port: 7001
  
  eureka:
    instance:
      hostname: eureka7001.com #eureka服务端的实例名称
    client:
      #false表示不向注册中心注册自己
      register-with-eureka: false
      #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
      fetch-registry: false
      service-url:
        # 互相注册,相互守望
        defaultZone: http://eureka7002.com:7002/eureka/

首先更改了其服务端的实例名称,最重要的是在defaultZone中将自己注册给7002,举一反三,7002的配置文件显然如下:

server:
  port: 7002

eureka:
  instance:
    hostname: eureka7002.com #eureka服务端的实例名称
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      # 互相注册,相互守望
      defaultZone: http://eureka7001.com:7001/eureka/

这样就实现了两个服务注册中心的相互注册。

  • 效果

    如图:两个服务中心已经完成了互相注册。DS Replicas这个下面的信息就表示是这个Eureka Server相邻节点,且这些节点加上自己互为一个集群。
    在这里插入图片描述

3. 将Eureka Client的服务提供方服务8001发布到上面2台Eureka集群配置中

修改其配置文件即可

server:
  port: 8001

spring:
  application:
    name: eureka_order_service

#eureka客户端的实例名称(服务提供者)
eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

也就是将自己的微服务注册到每一个服务注册中心里去,见配置文件中的defaultZone

4. 将Eureka Client的服务消费方服务80发布到上面2台Eureka集群配置中

和上面的是一样的,只需要修改其配置文件,将自己注册进两个服务注册中心中。

下面进行测试:首先 要启动EurekaServer服务,即7001和7002服务,这样就有了服务注册中心,然后 再启动服务提供方即8001, 最后 启动服务消费方即80。如图:7001和7002已经构成集群,且都可以拉取到80和8001两个服务:

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

5. 服务提供方8001服务的集群环境构建
  • 参考8001服务新建8002服务,编写配置文件:将端口改为8002,其他和8001相同,两个微服务 对外暴露的服务名相同 均为eureka_order_service 从而构成集群

    server:
      port: 8002
    
    spring:
      application:
        name: eureka_order_service  #服务名相同
    
    #eureka客户端的实例名称(服务提供者)
    eureka:
      instance:
        hostname: localhost
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
          defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
    
  • 效果

    如图,可以看到此时服务注册中心构成集群,而相同名字的服务提供方的实际提供者已经出现了两个,分别是8001和8002,,也就是说服务提供方微服务也实现了集群。
    在这里插入图片描述
    此时再回看最开始的Eureka微服务架构图,其集群架构图对应本实例应为:
    在这里插入图片描述

6. 负载均衡

此时,我们使用80端口的服务消费方来访问 CLOUD-PAYMENT-SERVICE 服务,输入网址http://localhost/consumer/payment/get/1,但是我们每次得到的数据都是:

{
	code: 200,
	message: "查询数据库成功, 端口号:8001",
	data: {
		id: 1,
		serial: "aaaa001"
	}
}

也就是说每次访问的具体微服务都是同个端口的服务,这明显是不符合业务逻辑的,原因就是在消费方代码中我们将服务访问地址写死了,没有实现负载均衡,这显然是不对的,所以我们应该让80访问服务名,而不是具体的服务,同时在配置文件中通过 @LoadBalanced 注解赋予RestTemplate负载均衡能力,该负载均衡默认为轮询方式,所以讲80服务的配置文件修改如下:

package cn.sher6j.springcloud.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * @author sher6j
 */
@Configuration
public class ApplicationContextConfig {
    @Bean
    @LoadBalanced//使用该注解赋予RestTemplate负载均衡的能力
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

然后重启80端口,发现每次访问 CLOUD-PAYMENT-SERVICE 服务时,具体的微服务在8001和8002之间进行轮询切换:
在这里插入图片描述
当然此时负载均衡我们还没有用到Ribbon,再Ribbon和Eureka整个后消费者可以直接调用服务而不用再关心地址和端口号,且该服务还有负载功能。

4. actuator微服务信息完善

1. 主机名称:服务名称修改

当前问题:在注册中心显示的微服务中,我们发现服务名含有主机名称,这显然不是我们希望看到的
在这里插入图片描述
怎么能解决这个问题呢,只需要修改服务提供方(8001和8002)的配置文件,向其中的eureka部分加入即可配置该服务显示的服务名称

instance:
  instance-id: myOrderService8001

最终的整体配置文件如下:

server:
  port: 8001

spring:
  application:
    name: eureka_order_service

#eureka客户端的实例名称(服务提供者)
eureka:
  instance:
    hostname: localhost
    instance-id: myOrderService8001
  client:
    #false表示不向注册中心注册自己,因为自己就是注册中心
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。我的职责就是维护服务实例,并不需要去检索服务
    fetch-registry: true
    service-url:
      #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

8002服务的修改同上,此时再访问注册中心,看到的服务具体名称中就没有主机名了,而是我们配置好的服务名称:

在这里插入图片描述

2. 访问信息有IP信息提示

当前问题:我们在鼠标移动到具体服务时,提示的地址信息中并没有服务所在具体主机的IP地址,这在开发中不方便定位具体微服务。
在这里插入图片描述
解决方式仍然是通过配置文件,在配置文件中向其中的eureka部分加入即可配置该服务访问路径可以显示IP地址:

instance:
  prefer-ip-address: true  # 访问路径可以显示IP地址

最终的配置文件如下:

server:
  port: 8001
......
eureka:
  client:
    ......
  instance:
    instance-id: payment8001
    prefer-ip-address: true  # 访问路径可以显示IP地址
......

鼠标再次移动到该服务时,可以发现,已经提示了IP地址:
在这里插入图片描述

5. 服务发现Discovery

对于注册进Eureka服务注册中心的微服务,可以通过服务发现来获取该服务的信息。有两种方法:

一、在配置文件中配置eureka.client.register-with-eureka: true

二、在主启动类上添加注解@EnableDiscoveryClient表示允许被其他微服务发现

1. 修该微服务的Controller,向其中注入DiscoveryClient,并编写相应Controller方法
package org.jun.Controller;

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.RestController;

import java.util.List;

/**
 * @author junfeng.lin
 * @date 2021/2/8 19:51
 */
@RestController
public class PaymentController {

    @Autowired
    private DiscoveryClient discoveryClient;

    @GetMapping("/payment/discovery")
    public Object discovery() {
        List<String> services = discoveryClient.getServices(); //获取服务列表的信息(所有服务名)
        for (String service : services) {
            System.out.println("=======service:" + service + "=======");
        }

        //根据微服务名称获取具体服务实例
        List<ServiceInstance> instances = discoveryClient.getInstances("eureka_order_service");
        for (ServiceInstance instance : instances) {
            System.out.println("=======" + instance.getServiceId() + "\t" + instance.getHost() + "\t" + instance.getPort() + "\t" + instance.getUri() + "=======");
        }

        return this.discoveryClient;
    }
}

DiscoveryClient对象中的 getServices 方法用于获取服务列表的信息,也就是有哪些服务,如cloud-payment-service服务, getInstances 方法用于获取服务列表对应的具体服务实例,如cloud-payment-service服务对应的8001和8002服务。

2. 修改主启动类

只需要在主启动类上添加注解@EnableDiscoveryClient,修改后的主启动类:

@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class PaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8001.class);
    }
}
3. 测试

访问地址http://localhost:8001/payment/discovery,我们可以看到获取的服务信息,即完成了服务发现:
在这里插入图片描述

后台也对服务列表进行了日志打印:

6. Eureka自我保护(CAP里面的AP分支)

1. 自我保护机制

保护模式主要用于一组客户端和EurekaServer之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务

如果在Eureka Server的首页看到以下提示,说明Eureka进入了保护模式(上面2. 单机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不会立刻清理,而是依旧会对该微服务的信息进行保存。

2. 产生原因
  • 为什么会产生Eureka自我保护机制???

    为了防止 EurekaClient可以正常运行,但是与EurekaServer网络不通 情况下,EurekaServer不会立刻将EurekaClient服务剔除。

  • 什么是自我保护模式?

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

    在自我保护模式中,EurekaServer会保护服务注册表中的信息,不再注销任何服务实例,宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。

3. 停止服务
3.1 Eureka Server禁止自我保护
  • 在EurekaServer端修改配置文件即可设置关闭自我保护机制

    eureka:
      server:
      	# 关闭自我保护机制,保证不可用服务被及时剔除
        enable-self-preservation: false
        # 清理间隔(单位:毫秒,默认是60*1000),当服务心跳失效后多久,删除服务
        eviction-interval-time-in-ms: 60000
    
  • 在EurekaClient端修改配置文件

    eureka:
      instance:
        instance-id: payment8001
        # Eureka客户单向服务端发送心跳的时间间隔,默然是30秒
        lease-renewal-interval-in-seconds: 1
        # Eureka服务端在收到最后一次心跳后等待时间上限,默然为90秒,超时将剔除服务
        lease-expiration-duration-in-seconds: 2
    

这样就会使EurekaClient客户端的微服务很快死亡。

3.2 Eureka Client通知Eureka Server(建议使用)

在Spring Cloud中,可以通过HTTP请求的方式,通知Eureka Client优雅停服,这个请求一旦发送到Eureka Client,那么Eureka Client会发送一个shutdown请求到Eureka Server,Eureka Server接收到这个shutdown请求后,会在服务列表中标记这个服务的状态为down,同时Eureka Client应用自动关闭。这个过程就是优雅停服。

如果使用了优雅停服,则不需要再关闭Eureka Server的服务保护模式。

1、POM
  优雅停服是通过Eureka Client发起的,所以需要在Eureka Client中增加新的依赖,这个依赖是autuator组件,添加下述依赖即可。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-actuator</artifactId>
</dependency>

2、修改全局配置文件:

Eureka Client默认不开启优雅停服功能,需要在全局配置文件中新增如下内容:

# 启用shutdown,优雅停服功能
endpoints.shutdown.enabled=true
# 禁用密码验证
endpoints.shutdown.sensitive=false

3、发起shutdown请求:

必须通过POST请求向Eureka Client发起一个shutdown请求。请求路径为:http://ip:port/shutdown。可以通过任意技术实现,如:HTTPClient、form表单,AJAX等。

7、Eureka Server的安全认证

Eureka Server作为Spring Cloud中的服务注册中心,如果可以任意访问的话,那么其安全性太低。所以Spring Cloud中也有为Eureka Server提供安全认证的方式。可以使用spring-boot-starter-security组件来为Eureka Server增加安全认证。

1、pom
<!-- spring boot security安全认证启动器 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
2、Eureka Server端修改配置文件
# 使用http basic安全认证语法,在集群通信中增加认证信息。  http://用户名:密码@地址:端口/eureka/
eureka.client.serviceUrl.defaultZone=http://root:123456@eurekaserver2:8001/eureka/

# 开启基于http basic的安全认证
security.basic.enabled=true
# 设置安全认证用户名
security.user.name=root
# 设置安全认证密码
security.user.password=123456

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值