从现在微服务框架的使用来看,毫无疑问,Spring Cloud 是目前微服务架构领域的翘楚。那么,对于他,你了解多少呢?
实际上,Spring Cloud 是一个全家桶式的技术栈,基于Spring Boot进行开发,它包含了很多组件。其中有5个最核心的组件 Eureka、Ribbon、Feign、Hystrix、Zuul 。本文主讲Eureka。
Eureka是Netflix 开源的服务发现组件,Spring Cloud 将其集成在 Spring Cloud Netflix中,主要负责完成微服务架构中的服务治理功能。Spring Cloud通过为Eureka增减了Spring Boot风格的自动化配置,只需要通过简单的引入依赖和注解配置,就能让Spring Boot构建的微服务应用与Eureka服务治理体系进行整合。
它主要包括两个组件:Eureka Server(注册中心服务端) 和 Eureka Client(注册中心客户端)
服务治理:
服务治理 是服务架构中最为核心的和基础的模块。服务治理主要用来实现各个服务实例的自动化注册和发现。
多个服务进程在服务注册中心注册了自己的服务,服务注册中心需要维护一个服务清单,服务注册中心还需要以心跳的方式去监测清单中服务的可用性,如果不可用,将从服务清单中移出。
Eureka Server是Eureka提供的服务端,提供服务注册与发现的功能。服务注册中心可以为集群,(集群就是两个或以上),形成高可用的eureka注册中心。
Eureka的服务治理设计中,所有的节点既是服务的提供方,又是服务的消费方,服务注册中心也不例外。Eureka Server的高可用实际上是将自己作为服务向其他服务注册中心注册自己,
这样形成一组相互注册的服务注册中心
Service Provider是提供服务的应用,可以是Spring Boot应用,也可以是其他技术平台且遵循Eureka通信机制的应用。它将自己提供的服务注册到Eureka,以供其他应用发现。
Service Consumer是服务的消费者,它会发送一个REST请求给服务注册中心,来获取上面注册的服务清单。通过客户端负载均衡某种算法轮询服务列表,然后调用其所需要的服务,也即
调用对应的服务提供者。为了性能的考虑,Eureka Server会维护一个只读的服务清单来返回给客户端,同时该缓存清单会默认每隔30s更新一次。
注意:在实际运行时,上述三大核心要素可能是两个,甚至一个实例。Eureka客户端通常包括 Service Provider(服务提供者) 和 Service Consumer(服务消费者)
思考:在实际运行时,这三者之间是如何交互的呢?接着看下面
如上图所示,解释每个过程的含义:
1、服务注册:
Provider 在启动的时候会通过REST请求的方式将自己注册到Eureka Server(服务注册中心)上,同时带上自身服务的一些元数据信息。Eureka Server在接受到这个REST请求后,将元数据存储在一个双层结构的MAP中。其中第一层的key是服务名,第二层的key是具体服务的实例名。(Eureka信息面板中一个服务有多个实例的情况,这些内容就是以双层Map的形式存储)
注意:在服务注册时,需要确认一下eureka.client.register-with-eureka=true是否正确,如果为false是禁止向服务注册中心注册的。
2、服务同步:
服务提供者注册到了一个服务注册中心后,由于 多个服务注册中心相互注册为服务(高可用服务注册中心),当服务提供者发送注册请求到一个服务注册中心后,它会将该请求转发到其他相连的注册中心,从而实现注册中心之间的服务同步。这样就可以通过任何一台服务注册中心拿到服务提供者的服务信息。
3、服务发现 / 获取
服务注册在了服务注册中心,调用服务就不需要指定具体服务的实例地址,而是向服务名发起请求。但要注意,实际访问一个服务,还是访问的一个具体的服务实例。启动服务消费者时,它会发送一个REST请求给服务注册中心,来获取上面注册的服务清单。以实现向具体的服务实例的访问。
为了性能的考虑,Eureka Server会维护一个只读的服务清单来返回给客户端,同时该缓存清单会每隔30s更新一次。 其中两个相关的配置:
eureka.client.fetch-registry=true
eureka.client.registry-fetch-interval-seconds=30 // 缓存清单的更新时间
4、服务调用 / 消费
当服务调用方需要调用服务的时候,便根据 服务清单以某种轮询策略取出一个位置来进行服务调用(客户端负载均衡)。
而服务消费的任务由Ribbon完成。Ribbon是一个基于HTTP和TCP的客户端负载均衡器,它可以通过客户端配置的 ribbonServerList 服务端列表以 轮询的访问方式 达到负载均衡的目的。Ribbon它在Eureka服务发现的基础上,实现了一套对服务实例的选择策略,从而实现服务的消费。
对于访问实例的选择,Eureka中有Region和Zone的概念,一个Region中可以包含多个Zone, 每个服务客户端需要注册到一个Zone中,即每个服务客户端对应一个Region和一个Zone. 在进行服务调用时,优先访问处于同一个Zone中的服务提供方,若访问不到,就访问其他Zone。同一个Zone内又可以有多个实例。
思考:什么是一个Region? 什么是一个Zone?其具体应用见第五章
5、服务续约
服务提供者注册完服务后,服务提供者需要提供一个心跳检测用来持续告诉Eureka Server:『我还活着』。心跳间隔默认是30s, 防止注册中心剔除服务, 正常情况下,如果Eureka Server在90秒没有收到Eureka客户的续约,它会将实例从其注册表中删除。这个过程称为服务续约。
6、服务下线
当服务实例进行正常的关闭操作时,会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:『我要下线啦』。服务注册中心接受到请求后,将该服务状态置为下线(DOWN),并把该下线的事件传播出去。
7、服务剔除
服务实例并不一定会正常下线,比如内存溢出、网络故障等,服务注册中心并未收到下线的请求。为了从服务列表剔除这些失效了的服务,Eureka Server会在启动的时候创建一个定时任务,默认每隔一段时间(默认60s)将服务清单中超时(默认90s)没有续约的服务剔除。
(But, 再看下面的自我保护机制,并不会因为真正的剔除)
另外还需要了解的概念还有:
8、自我保护
服务注册中心的信息面板经常跳出的问题,这个警告就是触发了Eureka Server的自我保护机制。
『服务续约』中可以看到,服务注册到Eureka Server后,Provider会维护一个心跳连接,告诉Eureka Server自己还活着。Eureka Server在运行期间,会统计心跳失败比例在15min之内是否低于85%, 如果出现低于的情况,Eureka Server会将当前的实例注册信息保护起来,让这些实例不过期, 尽可能的保护这些注册信息。但是在这段事件内实例若出现问题,那么客户端就很容易拿到实际上已经不存在的服务实例,会出现调用失效的情况,所以客户端必须要有容错机制,比如可以使用请求重试、断路器等机制。
相关配置:
eureka.server.enable-self-preservation=false // 关闭保护机制,确保服务注册中心可以将不可用的实例正确剔除
其默认配置是true
注意: kill + 端口号, 这是正常的下线,会使得Eureka控制面板中的服务立即去掉
kill -9 +端口号, 这是非正常下线,Eureka控制面板中的服务不会去掉(我理解的这应该是 自我保护机制 > 服务剔除, 导致服务还在)
Eureka的配置包括Eureka客户端的配置和Eureka服务端的配置。
Eureka服务端的配置可以看类:org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean
这些参数均已eureka.server开头。
Eureka客户端的配置可以看类:org.springframework.cloud.netflix.eureka.EurekaClientConfigBean
这些参数均已eureka.client开头
在Eureka治理体系中,主要分为客户端和服务端(服务注册中心)。在高可用环境中,服务注册中心也是一个微服务,特殊性在于它既作为一个客户端,又为集群中的其他客户端提供了服务注册功能。所以,Eureka客户端的配置对象存在于所有Eureka服务治理体系下的应用实例中。
org.springframework.cloud.netflix.eureka.EurekaClientConfigBean
(在源码分析中也看到DiscoverClient的依赖类EurekaDiscoveryClient,其中的一个实现类就是EurekaClientConfigBean)
其配置信息都是以 eureka.client开头,常用的配置,指定eureka的注册中心地址:
eureka.client.serviceUrl.defaultZone=http://peer1:11111/eureka/,http://peer2:11112/eureka/
eureka.client.enabled=true eureka.client.shouldUnregisterOnShutdown=true
于是在这个类中搜索serviceUrl,看到比较有用的信息:
|
常用的配置:
org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean
常用的一个配置(服务注册中心的一个配置)
eureka.instance.hostname=peer1
EurekaInstanceConfigBean的配置信息中,大部分是对服务实例元数据的配置。
服务实例元数据: 指的是Eureka客户端在像服务注册中心发送注册请求时,用来描述自身服务信息的对象,其中包含一些标准话的元数据,比如服务名称、实例名称、实例IP、实例端口等用于服务治理的重要信息,以及一些用于负载均衡策略或是其他特殊用途的自定义元数据信息。
关于元数据的定义,就可以看com.netflix.appInfo.InstanceInfo类(配置信息通过EurekaInstanceConfigBean进行加载,但真正进行服务注册时会包装成InstanceInfo类)中详细定义,来了解原生Eureka对元数据的定义。
2.1、实例名配置:
InstanceInfo中的instanceId参数:区别同一服务中不同的实例。这样就可以不用通过修改端口号启动同一个服务的不同实例了。
eureka.instance.instanceId = ${spring.application.name}:${random.int}
默认情况下,Eureka的各个服务实例的健康检测并不是通过spring-boot-actuator模块的/health端点来实现的,而是依靠客户端心跳的方式来保持服务实例的存活。
but , 这种情况存在什么问题吗?
要理解心跳检测,检测的时客户端程序是否正常工作,但是客户端能正常工作,不代表客户端就能正常提供服务,比如客户端需要需要依赖其他的资源,比如:数据库、缓存、消息代理等。如果我们的应用无法联通这些外部资源时,实际上已经不能对外提供服务了,但是客户端心跳程序依然在运行。
在Spring Cloud Eureka中,可以通过简单的配置,把Eureka的客户端健康检测交给spring-boot-actuator模块的 /health端点,以实现更加全面 的健康状态维护。
其配置步骤:
But, Eureka的服务调用中,服务剔除功能,解释了服务注册中心如何将失效的服务剔除的过程,但是在加上自我保护机制,现在又有个/health检查,所以默认的应该是啥?
见Euerka源码分析
用户量比较大或者用户地理位置分布范围很广的项目,一般都会有多个机房。这个时候如果上线springCloud服务的话,我们希望一个机房内的服务优先调用同一个机房内的服务,当同一个机房的服务不可用的时候,再去调用其它机房的服务,以达到减少延时的作用。
eureka提供了region和zone两个概念来进行分区,这两个概念均来自于亚马逊的AWS(Amazon Web Services ):
参考的内容:https://segmentfault.com/a/1190000014107639
我准备了两个eureka server组成一个高可用的服务注册中心, 两个service provider, 一个consumer
配置:
|
配置:
|
说明:Eureka Server-1和Eureka Server-2的region都配置的shanghai, zone配置的分别是zone1和zone2
服务启动(同一个项目可以使用不同的配置文件启动多次):
|
配置:
|
配置:
|
Service-1和Service-2 服务提供的代码都如下:
|
配置:
|
服务调用的代码:
|
当一个服务(作为一个eureka client)向注册中心(eureka server)注册的时候,会根据eureka.client下的配置来进行注册。
思考:有多个注册中心的情况下,服务会注册到哪个注册中心,并且和哪个注册中心来维持心跳检测?
preferSameZoneEureka: 表明客户端是否使用处在同一个zone中的eureka server,因为延迟或者其他原因
理想状态下,eureka客户端配置成和同一个zone中的server连接
1. 如果prefer-same-zone-eureka为false,按照service-url下的 list取第一个注册中心来注册,并和其维持心跳检测。不会再向list内的其它的注册中心注册和维持心跳。只有在第一个注册失败的情况下,才会依次向其它的注册中心注册,总共重试3次,如果3个service-url都没有注册成功,则注册失败。每隔一个心跳时间,会再次尝试。
(总结:false表示按照service-url中的顺序来注册)
2. 如果prefer-same-zone-eureka为true,先通过region取availability-zones内的第一个zone,然后通过这个zone取service-url下的list,并向list内的第一个注册中心进行注册和维持心跳,不会再向list内的其它的注册中心注册和维持心跳。只有在第一个注册失败的情况下,才会依次向其它的注册中心注册,总共重试3次,如果3个service-url都没有注册成功,则注册失败。每隔一个心跳时间,会再次尝试。
(总结:true, 如果每个zone下都只有一个实例,当向同zone下的服务注册中心注册失败时,还是会向其他zone中的服务注册中心注册,不会影响服务的调用)
所以说,为了保证服务注册到同一个zone的注册中心,一定要注意availability-zones的顺序,必须把同一zone写在前面。即availability-zones中的第一个值和eureka.instance.metadata-map.zone配置的值保持一样
eureka.instance.metadata-map.zone=zone2 指定服务提供者或者消费者是属于哪个zone
服务消费者会先通过ribbon去注册中心拉取一份服务提供者的列表,然后通过eureka.instance.metadata-map.zone指定的zone进行过滤,过滤之后如果同一个zone内的服务提供者有多个实例,则会轮流调用。只有在同一个zone内的所有服务提供者都不可用时,才会调用其它zone内的服务提供者。
两个服务注册中心的zone都设置成zone1, 让后调用服务消费者的接口,会发现Service-1和Service-2交替调用
条件:两个服务注册中心的zone分别设置成zone1和zone2, Service-1的zone配置成zone1, Service-2的zone配置成zone2, Consumer-1的配置成zone1
会发现Consumer一致调用的是 Service-1。如果把 Service-1停掉,立即调用消费者的接口,会发现报错,过一段事件后,再调用消费者的接口,会发现调用的是 Service-2
思考:为什么停掉 Service-1后理解调用消费者接口报错呢?
也就是说,当一个服务异常down掉后,90s之后注册中心才会知道这个服务不可用了。在此期间,依旧会把这个服务当成正常服务。ribbon调用仍会把请求转发到这个服务上。为了避免这段期间出现无法提供服务的情况,要开启ribbon的重试功能,去进行其它服务提供者的重试。
eureka.instance.lease-renewal-interval-in-seconds: 30
服务和注册中心的心跳间隔时间,默认为30s
eureka.instance.lease-expiration-duration-in-seconds: 90
服务和注册中心的心跳超时时间,默认为90s
Spring Cloud Ribbon使用服务名调用服务提供者异常 java.net.UnknownHostException
https://blog.csdn.net/makyan/article/details/88601507