为了账号安全,请及时绑定邮箱和手机立即绑定

(二)微服务注册与发现

微服务注册与发现

文章目录

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

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

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

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

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

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

服务注册和发现结构大致如下:

微服务的网路地址都有服务注册中心管理,提供一下功能:

1.微服务信息存储:用来记录每个微服务的信息,列入微服务的唯一标示,网络信息(ip和端口);

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

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

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

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

官方提供的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所有节点都宕掉,服务消费者

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

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

org.springframework.cloudspring-cloud-starter-eureka-server

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

server:port:8761eureka: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。

packagecom.myhexin.finace.training.eureka.server;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@SpringBootApplication@EnableEurekaServerpublicclassEurekaApplication{publicstaticvoidmain(String[]args){SpringApplication.run(EurekaApplication.class,args);}}

启动测试

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

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

org.springframework.bootspring-boot-starter-weborg.springframework.cloudspring-cloud-starter-eureka-server

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

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

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

packagecom.myhexin.finace.training.server.stock.main;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication(scanBasePackages=“com.myhexin”)@EnableDiscoveryClientpublicclassServierApplication{publicstaticvoidmain(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/,可看到如下图所示的界面:

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-serverserver:port:8761eureka: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-serverserver:port:8762eureka: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 Server集群中的某个节点,也能正常注册到Eureka Server集群,因为多个Eureka Server之间的数据会相互同步。例如(之前的配置文件不做修改):

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

启动测试

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

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):

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

删除二级缓存时机:

Eureka Client 发送 register、renew 和 cancel 请求并更新 registry 注册表之后,删除二级缓存;

Eureka Server 自身的 Evict Task 剔除服务后,删除二级缓存;

二级缓存本身设置了 guava 的失效机制,隔一段时间后自己自动失效;

加载二级缓存时机:

Eureka Client 发送 getRegistry 请求后,如果二级缓存中没有,就触发 guava 的 load,即从 registry 中获取原始服务信息后进行处理加工,再加载到二级缓存中。

Eureka Server 更新一级缓存的时候,如果二级缓存没有数据,也会触发 guava 的 load。

更新一级缓存时机:

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

当服务提供者启动后,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()

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()

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

Eureka Client :

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

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

Eureka Server :

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

2.清理缓存

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 获取增量变化的服务信息

正常停止的服务可以发送服务注册(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的服务注销流程。

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():

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

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

org.springframework.bootspring-boot-starter-security

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

security:basic:#开启基于HTTP basic的认证enabled:trueuser:#配置登录账号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/

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

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

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

1.忽略指定名称的网卡

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

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

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

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

spring☁️inetutils:useOnlySiteLocalInterfaces:trueeureka:instance:prefer-ip-address:true

4.手动指定IP地址

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

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

Eurka Server主要接口:

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

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

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

请求名称请求方式HTTP地址请求描述

注册新服务POST/eureka/apps/{appID}传递JSON或者XML格式参数内容,HTTP code为204时表示成功

取消注册服务DELETE/eureka/apps/{appID}/{instanceID}HTTP code为200时表示成功

发送服务心跳PUT/eureka/apps/{appID}/{instanceID}HTTP code为200时表示成功

查询所有服务GET/eureka/appsHTTP code为200时表示成功,返回XML/JSON数据内容

查询指定appID的服务列表GET/eureka/apps/{appID}HTTP code为200时表示成功,返回XML/JSON数据内容

查询指定appID&instanceIDGET/eureka/apps/{appID}/{instanceID}获取指定appID以及InstanceId的服务信息,HTTP code为200时表示成功,返回XML/JSON数据内容

查询指定instanceID服务列表GET/eureka/apps/instances/{instanceID}获取指定instanceID的服务列表,HTTP code为200时表示成功,返回XML/JSON数据内容

变更服务状态PUT/eureka/apps/{appID}/{instanceID}/status?value=DOWN服务上线、服务下线等状态变动,HTTP code为200时表示成功

变更元数据PUT/eureka/apps/{appID}/{instanceID}/metadata?key=valueHTTP code为200时表示成功

查询指定IP下的服务列表GET/eureka/vips/{vipAddress}HTTP code为200时表示成功

查询指定安全IP下的服务列表GET/eureka/svips/{svipAddress}HTTP code为200时表示成功

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

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

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

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

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消