(二)微服务注册与发现

微服务注册与发现

1 服务调用

1.1 需求场景

学生查询已下单股票列表时,需要去股票服务中获取股票详情,补全股票信息。

1.2 单体应用服务间调用

所有接口在同一容器同一应用上下文中,可能直接调用。

在这里插入图片描述

1.3 微服务化后服务间调用

服务相互独立,发布在不同容器中,需要通过远程调用。

在这里插入图片描述

1.4 微服务化带来的问题

1.服务消费者配置依赖了服务提供者的网络地址(ip和端口),当服务提供者网路地址发生变化,会导致服务消费者修改配置,重新发布。

2.消费者只能配置单点服务提供者网络地址,导致单点问题,哪怕是通过nginx代理之后,服务提供者集群节点发生变化,也需要修改nginx配置,并重新发布

2 服务注册与发现

要想解决这些问题,服务消费者需要一个强大的服务注册和发现机制,服务消费者使用这种机制获取服务提供者的网络信息,服务消费者可以动态感知到服务提供者网络变化,无需修改配置。
服务注册和发现结构大致如下:

微服务的网路地址都有服务注册中心管理,提供一下功能:
1.微服务信息存储:用来记录每个微服务的信息,列入微服务的唯一标示,网络信息(ip和端口);

2.提供服务的注册,注销和发现功能,注册和注销用于每个微服务提供者信息变化管理,发现功能用于服务消费者获取可用的微服务提供者信息;

3.服务检查机制,服务的注册和注销是由服务提供者主动发起变更请求,服务提供者正常情况下没问题,当出现宕机,网络问题导致服务提供者不能发起请求或者请求丢失,导致已注册服务信息无法,所以服务注册中心需要对已注册的服务做定时的检查机制。当某个微服务实例长时间无法访问,需要移除该实例。

spring cloud 给我们提供了多个注册中心组件的支持,例如Eureka,zk等,下面就来讲讲官方推荐的Eureka。

2.1 Eureka介绍

Eureka是Netflix开源的服务发现组件,本身是一个基于REST的服务。它包含Server和Client两部分。SpringCloud将它集成在子项目Spring Cloud Netflix中,从而实现微服务的注册与发现。

2.2 Eureka 高可用架构

官方提供的Eureka 高可用架构是基于AWS(Amazon Web Services)服务架构设计的。通常在我们这种非AWS环境下,us-east-1c、us-east-1d和us-east-1e可理解成不同的机房。

Eureka 高可用架构中:

  • Application Service相当于服务提供者
  • Application Client相当于服务消费者
  • Application Client 通过发现Eureka中 Application Service注册的网络信息实现远程调用(make remote call)

Eureka包含两个组件:Eureka Server和Eureka Client。

  • Eureka Server提供服务注册(Register)、续费(Renew)、注销(Cancel)、发现(Get Register)和同步(Replicate)的RESTful API,供Eureka Client 调用。
  • Eureka Client是一个Java客户端,用于简化与Eureka Server的交互。
  • 如果Eureka Server在一定时间内没有接收到某个微服务实例的续约(Renew)心跳(默认30秒),Eureka Server将会注销该实例(默认90秒)。
  • 在高可用Eureka 架构中,Eureka Server同时默认也是Eureka Client,多个Eureka Server通过复制同步(Replicate)服务注册信息
  • Eureka Client会缓存服务注册表中的信息。这种方式有一定的优势——首先,微服务无须每次请求都查询Eureka Server,从而降低了Eureka Server的压力;其次,即使Eureka Server所有节点都宕掉,服务消费者

3 Eureka 实战

结合前面服务调用中学生和股票服务之间的业务场景集成Eureka,并将股票服务(服务提供者)注册到Eureka Server中

3.1 编写一个Eureka Server

1.创建一个ArtifactId是finace-training-eureka-server的Maven工程,并为项目添加以下依赖。

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

2.在配置文件application.yml中添加如下内容。

server:  
	port: 8761
eureka:  
    client:    
        registerWithEureka: false    
        fetchRegistry: false    
        serviceUrl:      
            defaultZone: http://localhost:8761/eureka/

简要讲解一下 application.yml中的配置属性:

eureka.client.registerWithEureka:表示是否将自己注册到Eureka Server,默认为true。由于当前应用就是Eureka Server,故而设为false。
eureka.client.fetchRegistry:表示是否从Eureka Server获取注册信息,默认为true。因为这是一个单点的Eureka Server,不需要同步其他的Eureka Server节点的数据,故而设为false。
eureka.client.serviceUrl.defaultZone:设置与Eureka Server交互的地址,查询服务与注册服务都需要依赖

3.编写启动类,在启动类上添加@EnableEurekaServer注解,声明这是一个Eureka Server。

package com.myhexin.finace.training.eureka.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
      SpringApplication.run(EurekaApplication.class, args);
  }
}

启动测试

启动Eureka Server,访问http://localhost:8761/,可看到如下图所示的界面:

在这里插入图片描述

3.2 将微服务注册到Eureka Server中

1.创建一个ArtifactId是finace-training-stock的Maven工程,并为项目添加以下依赖。

<dependency>            
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>        
</dependency> 
<dependency>  
    <groupId>org.springframework.cloud</groupId>   
    <artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>

2.在配置文件application.yml中添加如下内容。

server:  
  port: 8000

spring:  
    application:
          name: finace-training-stock
eureka:
    client: 
        serviceUrl:      
            defaultZone: http://localhost:8761/eureka/
    instance:
        prefer-ip-address: true

3.编写启动类,在启动类上添加@EnableDiscoveryClient注解,声明这是一个Eureka Client

package com.myhexin.finace.training.server.stock.main;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.myhexin")
@EnableDiscoveryClient
public class ServierApplication {
	public static void main(String[] args) {
        SpringApplication.run(ServierApplication.class, args);
    }
}

也可以使用@EnableEurekaClient注解替代@EnableDiscoveryClient。在SpringCloud中,服务发现组件有多种选择,例如ZooKeeper、Consul等。@EnableDiscoveryClient为各种服务组件提供了支持,该注解是spring-cloud-commons项目的注解,是一个高度的抽象;而@EnableEurekaClient表明是Eureka的Client,该注解是spring-cloud-netfix项目中的注解,只能与Eureka一起工作。当Eureka在项目的classpath中时,两个注解没有区别。

启动测试

启动服务,访问http://localhost:8761/,可看到如下图所示的界面:

在这里插入图片描述

3.3 实现Eureka Server的高可用

Eureka Client会定时连接Eureka Server,获取服务注册表中的信息并缓存到本地。微服务在消费远程API时总是使用本地缓存中的数据。因此一般来说,即使Eureka Server发生宕机,也不会影响到服务之间的调用。但如果Eureka Server宕机时,某些微服务也出现了不可用的情况,Eureka Client中的缓存若不被更新,就可能会影响到微服务的调用,甚至影响到整个应用系统的高可用性。因此,在生产环境中,通常会部署一个高可用的Eureka Server集群。

Eureka Server可以通过运行多个实例并相互注册的方式实现高可用部署,Eureka Server实例会彼此增量地同步信息,从而确保所有节点数据一致。事实上,节点之间相互注册是Eureka Server的默认行为。

一.改造finace-training-eureka-server高可用

启动8761 finace-training-eureka-server

1、配置文件application.yml修改如下

spring: 
  application:  
    name: finace-training-eureka-server
server: 
	port: 8761
eureka:  
    client:    
        serviceUrl:
        	#将自己注册到localhost:8672这个Eureka上面去
            defaultZone: http://localhost:8762/eureka/

去除eureka.client.registerWithEureka=false 和 eureka.client.fetchRegistry=false配置项

2.启动服务

启动8762 finace-training-eureka-server

1.讲finace-training-eureka-server项目打包,部署到本机任意目录

2.将该jar包中的application.yml修改如下

spring: 
  	application: 
		name: finace-training-eureka-server
server: 
	port: 8762
eureka:  
    client:    
        serviceUrl:
            #将自己注册到localhost:8671这个Eureka上面去
            defaultZone: http://localhost:8761/eureka/

3.使用java -jar finace-training-eureka-server-0.0.1-SNAPSHOT.jar 启动

启动测试

启动服务,分别访问http://localhost:8761/和http://localhost:8762/,可看到如下图所示的界面:

在这里插入图片描述

二.将应用注册到Eureka Server集群上

1.修改finace-training-stock 的application.yml配置文件eureka.client.serviceUrl.defaultZone配置项

eureka:
    client: 
        serviceUrl:      
            defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/			

微服务即使只配置Eureka Server集群中的某个节点,也能正常注册到Eureka Server集群,因为多个Eureka Server之间的数据会相互同步。例如(之前的配置文件不做修改):

eureka:
    client: 
        serviceUrl:      
            defaultZone: http://localhost:8761/eureka/		

启动测试

启动服务,分别访问http://localhost:8761/和http://localhost:8762/,可看到如下图所示的界面:
在这里插入图片描述

4 深入剖析 Eureka

4.1 数据存储结构

Eureka 的数据存储结构:

在这里插入图片描述

  • Eureka Server 将服务信息放到内存,没有持久化。

  • Eureka Server为了避免同时读写内存数据结构造成的并发冲突问题,采用了多级缓存机制。ureka Client在发现(Get Register)服务信息时,先从二级缓存层中获取数据,如果获取不到,先将数据存储层中服务信息加载到缓存中,再从缓存中获取信息(具体实现:ResponseCacheImpl.getValue)。

  • 在从数据存储层加到服务信息到缓存的过程中,将服务信息数据处理成可以直接返回Eureka Client的数据放到缓存中,这样在缓存层就不需要再处理数据(具体实现:ResponseCacheImpl.readWriteCacheMap.load)。

以上实现提高了Eureka Server的处理能力和响应速度,保证了最关键的微服务注册中心的性能和可靠性。

数据存储层

rigistry 本质上是一个双层的 ConcurrentHashMap,存储在内存中的。

  • 第一层是服务应用层, key 是spring.application.name(股票服务:finace-training-stock),value 是第二层 ConcurrentHashMap;
  • 第二层服务应用实例层, key 是服务每个实例的 InstanceId,value 是 Lease 对象;
  • Lease 对象包含了实例的服务详情和服务治理相关的属性(lastRenewalTimestamp:最近一次续约(Renew)或者心跳时间)。

将finace-training-stock发布port=8000和port=8010两个服务,访问http://localhost:8761/eureka/apps (参考其他:Eureka 提供的REST端点)获取8761 Eureka 下所有的服务信息:

在这里插入图片描述

缓存一致性

既然是缓存,那必然要有更新机制,来保证数据的一致性。下面是缓存的更新机制(Eureka 缓存实现ResponseCacheImpl.java):

在这里插入图片描述

更新机制包含删除和加载两个部分,上图黑色箭头表示删除缓存的动作,绿色表示加载或触发加载的动作。

删除二级缓存时机:

  1. Eureka Client 发送 register、renew 和 cancel 请求并更新 registry 注册表之后,删除二级缓存;
  2. Eureka Server 自身的 Evict Task 剔除服务后,删除二级缓存;
  3. 二级缓存本身设置了 guava 的失效机制,隔一段时间后自己自动失效;

加载二级缓存时机:

  1. Eureka Client 发送 getRegistry 请求后,如果二级缓存中没有,就触发 guava 的 load,即从 registry 中获取原始服务信息后进行处理加工,再加载到二级缓存中。
  2. Eureka Server 更新一级缓存的时候,如果二级缓存没有数据,也会触发 guava 的 load。

更新一级缓存时机:

  1. Eureka Server 内置了一个 TimerTask,定时将二级缓存中的数据同步到一级缓存(这个动作包括了删除和加载)。

4.2 服务注册(Register)

当服务提供者启动后,Eureka Client 会启动一个循环定时(默认30s)向Eureka Server注册(同步)自己的服务信息。

Eureka Client :
在这里插入图片描述
1.服务注册(默认30s)定时任务:com.netflix.discovery.InstanceInfoReplicator

在这里插入图片描述

2.调用 Eureka Server 注册接口(POST /eureka/apps/{appID}):com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient.register()

在这里插入图片描述

Eureka Server

在这里插入图片描述

1.注册逻辑解析com.netflix.eureka.registry.AbstractInstanceRegistry.register()

4.3 服务续约(Renew)

Eureka Client 注册服务之后,会启动一个循环定时任务(默认30s)向Eureka Server发送续约请求,告诉注册中心 " awms ";

Eureka Client :

在这里插入图片描述

1.定时任务(默认 30s):com.netflix.discovery.DiscoveryClient.initScheduledTasks()

2.心跳线程:com.netflix.discovery.DiscoveryClient.HeartbeatThread

在这里插入图片描述

3.调用 Eureka Server 续约(心跳)接口(PUT /eureka/apps/{appID}/{instanceID}):

com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient.register()

在这里插入图片描述

**Eureka Server ** :

在这里插入图片描述

1.续约操作:com.netflix.eureka.lease.Lease.renew()

2.遍历集群类节点,同步续约请求:com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl.replicateToPeers()
在这里插入图片描述

4.4 服务注销(Cancel)

服务正常停止之前会向注册中心发送注销请求,告诉注册中心"AWSL"。

Eureka Client :

1.调用 Eureka Server 注销接口(DELETE /eureka/apps/{appID}/{instanceID}):

com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient.register()
在这里插入图片描述

Eureka Server :

在这里插入图片描述

1.register中存在该服务注册信息,会删除服务实例信息

2.清理缓存

4.5 服务获取(Get Register)

  • Eureka Client 会缓存获取到的服务信息,微服务首先从Eureka Client中获取缓存的服务信息,

    Eureka Client 定时(默认30s)去Eureka Server获取服务信息,更新本地缓存。

  • Eureka Client 获取服务有两种方式:全量获取和增量获取,可以通过配置eureka.client.disableDeltaForRemoteRegions,默认false,全量获取。

Eureka Client

在这里插入图片描述

1.获取服务信息(默认30s)任务,com.netflix.eureka.registry.RemoteRegionRegistry

在这里插入图片描述

2.获取服务信息代码:
在这里插入图片描述

Eureka Server

  • 全量获取

    先从缓存中获取,取不到从registry中查询到缓存再从缓存中取出来返回。

  • 增量获取

    1.在Registery 里面维护了一个ConcurrentLinkedQueue recentlyChangedQueue ;当出现服务注册(register)和注销(cancel)会将最新的服务信息保存到该队列中。

    2.定时(默认30s)任务定时清理recentlyChangedQueue 中过期的RecentlyChangedItem

    过期算法实现(默认3分钟,180s过期):

在这里插入图片描述
3.从recentlyChangedQueue 获取增量变化的服务信息

4.6 服务检查

正常停止的服务可以发送服务注册(cancel)请求注销服务,但是服务由于宕机或者网络问题导致无法发送服务注销请求,可能导致注册中心中的服务不可用,这个时候就要一种服务检查机制,去找出这些问题服务,将他们剔除。

  • Eureka Server 在启动时开启了一个服务检查任务
  • 检查任务分析

在这里插入图片描述

1.自我保护检查

当Eureka Server节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,Eureka Server就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。

自我保护开始依据:

在这里插入图片描述

  • getNumOfRenewsInLastMin :最近一分所有服务续约总数,在Renew请求中统计

  • numberOfRenewsPerMinThreshold : 自我保护阈值,期望每分钟最少服务续约最少总数

    自我保护阈值 = 服务总数 * (60S/ 客户端续约间隔(默认30s)) * 自我保护阈值因子(默认0.85)

    举例:如果有 100 个服务,续约间隔是 30S,自我保护因子 0.85

    自我保护阈值 =100 * 60 / 30 * 0.85 = 170。

    如果最近一分所有服务续约总数 = 180 > 170,则说明大量服务可用,是服务问题,需要剔除过期服务

    如果最近一分所有服务续约总数 = 150 > 170,则说明大量服务不可用,是注册中心自己的问题,进入自我

    保护模式,不进入剔除服务流程。

2.过期服务

服务默认30s续约一次,90s过期时间,也就是说超过连续三次不续约就将认为是过期服务

3.计算需要剔除的服务数

并不是所有过期的过期的服务都需要剔除,只有当过期服务数超过剔除阈值时才剔除超过阈值的服务数,

服务阈值 = 服务总数 * 自我保护因子;

需要剔除服务数 = 过期服务数 - 服务阈值

4.剔除服务

通过Knuth shuffle algorithm(洗牌算法)找到需要剔除的服务,走Eurka Server的服务注销流程。

4.7 服务同步(Replicate)

Eureka Server之间通过同步机制来保证节点之间的数据一致性。分为Eureka Server 启动时同步和运行是同步。

启动时同步

  • Eureka Server 启动时会作为Client向配置文件中配置的Eureka Server注册中心注册自己
  • Eureka Server作为Client 向Eureka Server注册中心获取服务注册信息,获取成功后走自身的注册流程将获取到的服务自身的注册表中。

在这里插入图片描述

运行时同步

Eureka Server 在收到服务注册(register),续约(renew)和注销(cancel)请求时,在完成操作向配置文件中配置的Eureka Server 转发这些请求,以做到服务在每个Eureka Server上的一致性。

com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl.relicateToPeers():

在这里插入图片描述

5 其他

5.1 为Eureka Server添加用户认证

在前面的Eureka实战中,Eureka Server是匿名访问的,也可以为Eureka Server添加基于HTTP basic的认证,这样需要登录才能访问Eureka Server。

1.在finace-training-eureka-server 添加以下依赖:

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

2.在application.yml中添加以下内容:

security:
  	basic:
        #开启基于HTTP basic的认证
        enabled: true
  	user:
  		#配置登录账号
        name: user
        #配置登录密码
        password: password123

3.启动服务

启动测试

启动服务,访问http://localhost:8761/,需要输入以上用户密码才可访问。

将微服务注册到需认证的Eureka Server

只须将eureka.client.serviceUrl.defaultZone配置为http://user:password@EUREKA_HOST:EUREKA_PORT/eureka/ 这种形式

例如:

eureka:
    client: 
        serviceUrl:      
            defaultZone: http://user:password123@localhost:8761/eureka/		

5.2 多网卡环境下的IP选择

对于多网卡的服务器,各个微服务注册到Eureka Server上的IP要如何指定呢?

指定IP在某些场景下很有用。例如某台服务器有eth0、eth1、eth2三块网卡,但是只有eth1可以被其他的服务器访问;如果Eureka Client将eth0或者eth2注册到Eureka Server上,其他微服务就无法通过这个IP调用该微服务的接口。

SpringCloud提供了按需选择IP的能力,从而避免以上的问题。下面来详细讨论。

1.忽略指定名称的网卡

spring:
    cloud:
        inetutils:
            ignored-interfaces:
                - docker0
                - veth.*
eureka:
    instance:
        prefer-ip-address: true

2.使用正则表达式,指定使用的网络地址

spring:
    cloud:
        inetutils:
            preferredNetworks:
            - 192.168
            - 10.0
eureka:
	instance:
	prefer-ip-address: true

3.只使用站点本地地址,这样就可强制使用站点本地地址。

spring:
    cloud:
        inetutils:
            useOnlySiteLocalInterfaces: true
eureka:
	instance:
		prefer-ip-address: true

4.手动指定IP地址

在某些极端场景下,可以手动指定注册到Eureka Server的微服务IP。示例:

eureka:
	instance:
		prefer-ip-address: true
		ip-address: 127.0.0.1

5.3 Eureka 提供的REST端点

Eurka Server主要接口:

  • 应用相关(/eureka/apps/):com.netflix.eureka.resources.ApplicationResource

  • 实例相关(/eureka/apps/{appID}/):com.netflix.eureka.resources.InstanceResource

可以使用XML或者JSON与这些端点通信,默认是XML

请求名称 请求方式 HTTP地址 请求描述
注册新服务 POST /eureka/apps/ 传递JSON或者XML格式参数内容,HTTP code为204时表示成功
取消注册服务 DELETE /eureka/apps/{appID}/ HTTP code为200时表示成功
发送服务心跳 PUT /eureka/apps/{appID}/ HTTP code为200时表示成功
查询所有服务 GET /eureka/apps HTTP code为200时表示成功,返回XML/JSON数据内容
查询指定appID的服务列表 GET /eureka/apps/ HTTP code为200时表示成功,返回XML/JSON数据内容
查询指定appID&instanceID GET /eureka/apps/{appID}/ 获取指定appID以及InstanceId的服务信息,HTTP code为200时表示成功,返回XML/JSON数据内容
查询指定instanceID服务列表 GET /eureka/apps/instances/ 获取指定instanceID的服务列表,HTTP code为200时表示成功,返回XML/JSON数据内容
变更服务状态 PUT /eureka/apps/{appID}/{instanceID}/status?value=DOWN 服务上线、服务下线等状态变动,HTTP code为200时表示成功
变更元数据 PUT /eureka/apps/{appID}/{instanceID}/metadata?key=value HTTP code为200时表示成功
查询指定IP下的服务列表 GET /eureka/vips/ HTTP code为200时表示成功
查询指定安全IP下的服务列表 GET /eureka/svips/ HTTP code为200时表示成功

声明

本博客所有内容仅供学习,不为商用,如有侵权,请联系博主,谢谢。

参考文献

[1] Eureka的GitHub : https://github.com/Netflix/eureka

[2] 周立. Spring Cloud与Docker微服务架构实战

[3] 马军伟. 微服务注册中心 Eureka 架构深入解读.https://www.infoq.cn/article/jlDJQ*3wtN2PcqTDyokh

posted @ 2020-03-13 18:47  小花技术大本营  阅读(609)  评论(0)    收藏  举报