微服务笔记之Eureka(1)

1、Eureka是什么?

Eureka由Netflix开源,并被Pivatal集成到SpringCloud体系中,它是基于 RestfulAPI 风格开发的服务注册与发现组件,它是一个服务注册中心。

服务注册中心本质上是为了解耦服务提供者和服务消费者。

在服务注册中心之前,服务消费者调用服务提供者,会把服务提供者的url地址硬编码到代码中(代码如下),这样做的坏处是不方便后期维护,以及不知道服务提供者的状态信息。


@RestController
@RequestMapping("/page")
public class PageController {
@Resource
private RestTemplate restTemplate;

@GetMapping("/query/{id}")
public Products getProductById(@PathVariable int id){
Products products = restTemplate.getForObject("http://localhost:9999/product/query/" + id, Products.class);
return products;
}
}
 

详细代码

2、服务注册中心如何解耦?

分布式微服务架构中服务注册中心用于存储服务提供者地址信息、服务发布相关的属性信息,消费者通过主动查询和被动通知的方式获取服务提供者的地址信息,而不再需要通过硬编码方式得到提供者的

地址信息。消费者只需要知道当前系统发布了那些服务,而不需要知道服务具体存在于什么位置, 这就是透明化路由。如下图:

 上图说明:

  • 服务提供者、服务消费者启动
  • 服务提供者将相关服务信息主动注册到注册中心
  • 服务消费者获取服务注册信息:服务消费者直接调用服务提供者
    • pull模式:服务消费者可以主动拉取可用的服务提供者清单
    • push模式:服务消费者订阅服务(当服务提供者有变化时,注册中心也会主动推送更新后的服务清单给消费者
  • 服务消费者直接调用服务提供者

注册中心也需要完成服务提供者的健康监控,当发现服务提供者失效时需要及时剔除;

3、那么讲完服务注册中心,我们来看看Eureka的基础架构

Eureka交互流程

Eureka 包含两个组件:Eureka Server 和 Eureka Client,Eureka Client是一个Java客户端,用于简化与Eureka Server的交互;Eureka Server提供服务发现的能力,各个微服务启动时,会通过EurekaClient向Eureka Server 进行注册自己的信息(例如网络信息),Eureka Server会存储该服务的信息;

  • Application Service作为服务提供者向Eureka Server中注册服务,Eureka Server接受到注册事件会在集群和分区中进行数据同步,ApplicationClient作为消费端(服务消费者)可以从Eureka Server中获取到服务注册信息,进行服务调用。
  • 微服务启动后,会周期性地向Eureka Server发送心跳(默认周期为30秒,默认Eureka Server 90S会将还没有续约的给剔除)以续约自己的信息
  • Eureka Server在一定时间内没有接收到某个微服务节点的心跳,Eureka Server将会注销该微服务节点(默认90秒)
  • 每个Eureka Server同时也是Eureka Client,多个Eureka Server之间通过复制的方式完成服务 注册列表的同步
  • Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者

 总之,Eureka通过心跳检测、健康检查和客户端缓存等机制,提高系统的灵活性、可伸缩性和高可用性。

4、Eureka服务端详解

4.1、服务下线

  当服务正常关闭操作时,会发送服务下线的REST请求给EurekaServer

  服务中心接收到请求后,将该服务置为下线状态

4.2、失效剔除

  Eureka Server会定时(间隔值是eureka.server.eviction-interval-timer-in-ms,默认60s)进行检查,如果发现实例在一定时间(此值由客户端设置的eureka.instance.lease-expiration-duration-in-seconds定义,默认值为90s)内没有收到心跳,则会注销此实例

4.3、自我保护机制

  自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使Eureka集群更加的健壮、稳定的运行。自我保护机制的工作机制是:如果在15分钟内超过85%的客户端节点都没有正常心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制,此时会出现一下几种情况:

  • Eureka Server不再从注册列表中移除,因为长时间没收到心跳而应该过期的服务
  • Eureka Server仍然能够接收新服务的注册和查询请求,但是不会被同步到其他节点上,保证当前节点依然可用
  • 当网络稳定时,当前Eureka Server新的注册信息会被同步到其他节点中
  因此Eureka Server可以很好的应对因网络故障导致部分节点失联的情况,而不会想ZK那样,如果有一半不可用的情况,会导致整个集群不可用而变成瘫痪
4.4、为什么会有自我保护机制
  默认情况下,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳, Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通 信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。
  如果心跳失败比例在 15 分钟之内低于 85%,服务中心页面会显示如下提示信息
  我们在单机测试的时候很容易满足心跳失败比例在 15 分钟之内低于 85%,这个时候就会触发 Eureka 的保护机制,一旦开启了保护机制(默认开启),则服务注册中心维护的服务实例就不是那么准确了, 此时我们通过修改Eureka Server的配置文件来关闭保护机制,这样可以确保注册中心中不可用的实例被及时的剔除(不推荐)。
5、Eureka客户端详解
5.1、服务注册详解(服务提供者)
  • 当我们导入eureka-client依赖坐标,配置Eureka服务注册中心地址
  • 服务在启动时会向注册中心发起注册请求,并携带服务元数据信息
  • Eureka注册中心会把服务的信息保存在Map中(ConcurrentHashMap)

5.2、服务续约详解(服务提供者)

  服务每隔30s回向注册中心续约一次,如果没有续约,租约在90s后到期,然后服务会被失效。每隔30s的续约操作我们称之为心跳检测。

  • Eureka Client:30s续约一次,在Eureka Server更新自己的状态(Client端进行配置)
  • Eureka Server:90s还没有进行续约,将该微服务实例从服务注册表(Map)剔除(Client端进行配置)
  • Eureka Client: 30s拉取服务最新的注册表并缓存到本地(Client端进行配置)

通过以下配置

#向Eureka服务中心集群注册服务
eureka:
    instance:
        # 租约续约间隔时间,默认30秒
        lease-renewal-interval-in-seconds: 30
        # 租约到期,服务时效时间,默认值90秒,服务超过90秒没有发生心跳,EurekaServer会将服务从列 表移除
        lease-expiration-duration-in-seconds: 90

5.3、获取服务列表详解

每个30s服务会从注册中心拉取一份服务列表,这个时间可以通过配置修改。但往往不需要我们调整

  • 服务消费者启动时,从Eureka Server服务列表获取只读备份,缓存到本地
  • 每隔30s会重新获取并更新数据
  • 每隔30s可以通过配置eureka.client.registry-fetch-interval-seconds修改
#向Eureka服务中心集群注册服务
eureka:
    client:
        # 每隔多久拉取一次服务列表 
        registry-fetch-interval-seconds: 30

6、Eureka的元数据

Eureka元数据有两种:标准元数据和自定义元数据

6.1、标准元数据

它包含主机名、IP地址、端口号等信息,这些信息都会被发布在服务注册表中,用于服务之间的调用

 6.2、自定义元数据

它可以使用eureka.instance.metadata-map配置,符合KEY/VALUE的存储格式。这些元数据可以在远程客户端中访问

通过以下代码调试获取

@RestController
@RequestMapping("/metadata")
public class MetaDataController {
    
    @Resource
    private DiscoveryClient discoveryClient;
    
    @GetMapping("/show")
    public Map<String,String> getMetaData(){
        List<ServiceInstance> instances = discoveryClient.getInstances("hy-service-product");
        ServiceInstance serviceInstance = instances.get(0);
        Map<String, String> metadata = serviceInstance.getMetadata();
        return metadata;
    }
}

 

 

 7、Eureka的应用

 项目结构

第一步、在父目录的pom文件中引入下面内容

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>Greenwich.RELEASE</version>
      <type>pom</type>
      <scope>import</scope>
      </dependency>
  </dependencies>
</dependencyManagement>

<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.1.6.RELEASE</version>
</parent>

其中dependency里面的version内容要和parent里面的version要对应,否则会报错

 第二步:在两个Eureka服务的pom文件引入

<!--引入Eureka Server-->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

第三步:配置Eureka服务下面的yml文件(记得服务端口号要修改)

server:
  port: 11111
spring:
  application:
    name: hy-cloud-eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
eureka:
  instance:
    hostname: HyCloudEurekaServiceA
  client:
    service-url: # 客户端与EurekaServer交互的地址,如果是集群,也需要写其它Server的地址
      defaultZone: http://HyCloudEurekaServiceB:22222/eureka/
    register-with-eureka: true # 自己就是服务不需要注册自己
    fetch-registry: true #自己就是服务不需要从Eureka Server获取服务信息,默认为true,置为false

第四步:启动类

@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }
}

第五步:配置两个微服务下的yml文件

page:

server:
  port: 8888 # 后期该微服务多实例,端口从9100递增(10个以内)
Spring:
  application:
    name: hy-service-page
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/hy?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
    username: "root"
    password: "123456"
eureka:
  client:
    serviceUrl: # eureka server的路径
      defaultZone: http://HyCloudEurekaServiceA:11111/eureka/,http://HyCloudEurekaServiceB:22222/eureka/
  instance:
    #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
    prefer-ip-address: true
    #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@

product:

server:
  port: 9999 # 后期该微服务多实例,端口从9100递增(10个以内)
Spring:
  application:
    name: hy-service-product
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/hy?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
    username: "root"
    password: "123456"
eureka:
  client:
    serviceUrl: # eureka server的路径
      defaultZone: http://HyCloudEurekaServiceA:11111/eureka/,http://HyCloudEurekaServiceB:22222/eureka/
  instance:
    #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
    prefer-ip-address: true
    #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@

第六步:编写Product的Controller

@RestController
@RequestMapping("/product")
public class ProductController {
    
    @Resource
    private ProductService productService;
    
    @RequestMapping("/query/{id}")
    public Products query(@PathVariable Integer id){
        return productService.findById(id);
    }
}

此处省略service代码

第七步:编写Page微服务的Controller

@RestController
@RequestMapping("/page")
public class PageController {
    @Resource
    private RestTemplate restTemplate;
    
    @Resource
    private DiscoveryClient discoveryClient;
    
    @GetMapping("/query/{id}")
    public Products getProductById(@PathVariable int id){
        List<ServiceInstance> instances = discoveryClient.getInstances("hy-service-product");
        System.out.println(instances);
        ServiceInstance serviceInstance = instances.get(0);
        String host = serviceInstance.getHost();
        int port = serviceInstance.getPort();
        System.out.println(host + " " + port);
        String url = "http://"+host +":" + port +"/product/query/" + id;
        Products products = restTemplate.getForObject(url, Products.class);
        return products;
    }
}

第八步:启动四个服务:随便登录一个Eureka的服务如下图所示,表明启动成功

 第九步:测试

 

 代码详细地址

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2022-11-01 13:43  码到成功hy  阅读(95)  评论(0编辑  收藏  举报
获取

hahah

name age option