服务治理:Eureka
服务治理
主要用来实现各个微服务实例的自动化注册与发现
- 服务注册:
- 服务治理框架中,通常会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,将主机与端口号,版本号,通信协议等一些附加信息告知注册中心,注册中心按服务名分类组织服务清单。
- 服务注册中心需要以心跳的方式去监测清单中的服务是否可用,若不可用需要从服务清单中剔除,达到排除故障服务的效果。
- 服务发现:服务间的调用通过服务名发起请求调用实现。注册中心会将服务的位置清单返回给请求方,当请求方要发起调用的时候,从该清单中以某种轮询策略取出一个位置来进行服务调用。
Netflix Eureka
使用Netflix eureka实现服务注册与发现,包含服务端组件,客户端组件,主要适用于通过java实现的分布式系统。
- Eureka服务端(服务注册中心):支持高可用配置,如果以集群模式部署,当集群中有分片出现故障时,eureka转入自我保护模式。允许在分片故障期间继续提供服务的发现和注册。当故障分片恢复运行时,集群中的其他分片会把他们的状态再次同步回来。
- Eureka客户端:主要处理服务的注册和发现。通过注解和参数配置的方式嵌入在客户端程序代码中,在应用程序运行时,客户端向注册中心注册自身提供的服务并周期性地发送心跳来更新它的服务租约。也能从服务端查询当前注册的服务信息并把他们缓存到本地周期性的刷新服务状态。
搭建服务注册中心
-
@EnableEurekaServer注解:启动一个服务注册中心提供给其他应用进行对话
-
该服务注册中心也会将自己作为客户端来尝试注册自己,要禁用它的客户端注册行为(配置文件)
- eureka.client.register-with-eureka:false代表不向注册中心注册自己
- eureka.client.fetch-registry:注册中心不需要去检索服务,设置为false
注册服务提供者
- 注入DiscoveryClient对象
- @EnableDiscoveryClient注解,激活Eureka中DiscoveryClient实现
- 配置文件中,spring.application.name属性为服务命名
- 配置文件,eureka.client.serviceUrl.defaultZone属性来指定服务注册中心的地址
高可用注册中心
- Eureka高可用实际就是将自己作为服务向其他服务注册中心注册自己,这样形成了一组互相注册的服务注册中心,实现服务清单的互相同步,达到高可用效果。
- 不想使用主机名来定义注册中心的地址,也可以使用ip地址的形式,但需要在配置文件中增加配置参数,eureka.instance.prefer-ip-address=true 该值默认为false
服务发现与消费
消费者:发现服务和消费服务
- 服务发现的任务由Eureka客户端完成
- 服务消费的任务由Ribbon完成
- Ribbon基于HTTP和TCP的客户端负载均衡器,可以通过客户端中配置的ribbonServerList服务端列表去轮询访问达到均衡负载的作用
- Ribbon与Eureka联合使用时,Ribbon的服务实例清单RibbonServerList会被DiscoveryEnabledNIWSServerList重写,扩展成从Eureka注册中心获取服务端列表。
- 使用NIWSDiscoveryPing来取代IPing,将职责委托给Eureka来确定服务端是否已经启动。
Eureka详解
核心角色:服务注册中心,服务提供者,服务消费者
基础架构
核心三要素
- 服务注册中心
- 服务提供者
- 服务消费者
服务治理机制
例如:
- 服务注册中心1,和服务注册中心2,互相注册组成了高可用集群
- 服务提供者启动了两个实例,一个注册到服务注册中心1上,另一个注册到服务注册中心2上
- 还有两个服务消费者,他们都分别只指向了一个注册中心
重要通信行为
-
服务提供者
-
服务注册
- 服务提供者在启动的时候会通过发送rest请求将自己注册到eureka server,同时带些元数据信息
- server接到请求后,将元数据信息存储在一个双层结构map中
- 第一层key是服务名,第二层key是具体服务实例名
- 注册配置:eureka.client.register-with-eureka=true
-
服务同步
- 信息被两个注册中心维护
- 由于服务注册中心之间因为互相注册为服务,当服务提供者发送注册请求到一个服务注册中心,会将请求转发给集群中相连的其他注册中心,从而实现注册中心之间的服务同步
-
服务续约
-
注册完服务之后,服务提供者会维护一个心跳用来持续告诉Eureka server还活着,以防剔除任务将该服务实例从服务列表中排除出去,称为服务续约
//用于定义服务续约任务的调用间隔时间,默认30s eureka.instance.lease-renewal-interval-in-seconds=30 //用于定义服务失效时间,默认90s eureka.instance.lease-expiration-duration-in-seconds=90
-
-
-
服务消费者
- 获取服务
- 此时,服务中心已经注册了一个服务,该服务有两个实例
- 启动服务消费者,他会发送一个rest请求给服务注册中心获取注册的服务清单
- eureka会维护一份只读的服务清单来返回给客户端,同时该缓存清单会每隔30秒更新一次
- 获取服务配置:eureka.client.fetch-registry=true
- 修改缓存清单更新时间:eureka.client.registry-fetch-interval-seconds=30
- 服务调用
- 服务消费者通过服务名获得具体提供服务的实例名和该实例的元数据信息
- 在Ribbon中默认采用轮询的方式进行调用,实现客户端的负载均衡
- 一个region中可以包含多个zone,每个服务客户端需被注册到一个zone中,每个客户端对应一个region和一个zone
- 服务调用时,优先访问同处一个zone的服务提供方,若访问不到就访问其他的zone
- 服务下线
- 当服务实例进行正常关闭操作时,会触发一个服务下线rest请求给eureka server,告诉服务注册中心
- 服务端收到请求后,将改服务状态置为下线down,并把该下线事件传播出去。
- 获取服务
-
服务注册中心
- 失效剔除
- 服务非正常下线时,服务注册中心并未收到服务下线请求,需要从服务列表中将这些实例剔除
- 在启动时创建一个定时任务,默认每隔一段时间将当前清单中超时没续约的服务剔除
- 自我保护
- eureka server在运行期间,会统计心跳失败的比例在15min之内是否低于85%,如果出现低于的情况,eureka server会将当前的实例注册信息保护起来,让这些实例不会过期,尽可能保护这些注册信息。
- eureka.server.enable-self.preservation=false参数来关闭保护机制,已确保注册中心可以将不可用的实例正确剔除
- 失效剔除
源码分析
将普通的springboot应用注册到eureka server或是从eureka server中获取服务列表时,主要做两件事
-
在应用主类中配置了@EnableDiscoveryClient注解
-
主要用来开启DiscoveryClient实例
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import({EnableDiscoveryClientImportSelector.class}) public @interface EnableDiscoveryClient { boolean autoRegister() default true; }
-
DiscoveryClient相关接口,DiscoveryClient,EurekaDiscoveryClient,EurekaClient,LookupService
- DiscoveryClient:springcloud接口,定义了用来发现服务的常用抽象方法,通过该接口可以有效地屏蔽服务治理的实现细节,可以方便的切换不同服务治理框架
- EurekaDiscoveryClient:实现的是对Eureka发现服务的封装,所以依赖了EurekaClient接口,
- EurekaClient:继承了LookupService接口,主要定义了针对Eureka发现服务的抽象方法,而真正实现发现服务的是DiscoveryClient类。
-
DiscoveryClient类:
- 用于帮助与Eureka Server互相协作
- Eureka Server负责任务:
- 向Eureka server注册服务实例
- 向Eureka Server服务租约
- 当服务关闭期间,向Eureka Server取消租约
- 查询Eureka Server中的服务实例列表
- EndpointUtils类中getServiceUrlsMapFromConfig函数
- Region Zone
- 通过getRegion函数从配置中读取一个region返回,所以一个微服务应用只可以属于一个region,不特别配置为default,eureka.client.region属性定义
- getAvailabilityZones函数,没有特别为region配置zone的时候默认defaultZone,可以通过eureka.client.availablity-zones属性设置,zone可以设置多个,逗号分隔开
- region和zone是一对多的关系
- serviceUrls
- 获取region和zone之后才开始加载Eureka server的具体地址,根据传入的参数按一定算法确定加载位于哪个zone配置的serviceUrls
- defaultZone属性可以配置多个并且需要通过逗号分隔
- 当微服务使用ribbon实现服务调用时,对于zone的设置可以在负载均衡时,实现区域亲和特性,ribbon默认策略会优先访问同客户端处于一个zone中的服务端实例,如果没有可用服务实例才会访问其他zone中的实例。所以通过zone属性定义,配合实际部署的物理结构,可以有效设计出对区域性故障容错集群。
- 服务注册:
- DiscoveryClient类initScheduledTasks中 if(clientConifg.shouldRegisterWithEureka()),分支内,创建了一个InstanceInfoReplicator类的实例,会执行一个定时任务
- 定时任务 discoveryClient.register真正触发调用注册的地方。注册操作通过rest请求方式进行。发起注册请求时参数有InstanceInfo对象(是注册时客户端的服务的元数据)
- 服务获取与服务续约:initScheduledTasks中的其他两个定时任务
- 服务注册和服务续约在同一个if逻辑中,防止被剔除
- 服务续约相关时间控制参数:
- eureka.instance.lease-renewal-interval-in-seconds=30
- eureka.instance.lease-expiration-duration-in-seconds=90
- 服务续约:直接以rest请求方式进行续约
- 服务获取是在独立的一个if判断中,判断依据是eureka.client.fetch-registry=true参数,默认为true。不止限于服务启动,是一个定时执行任务。
- 会根据是否是第一次获取发起不同的rest请求和相应的处理。
- 服务注册和服务续约在同一个if逻辑中,防止被剔除
- 服务注册中心处理
- 所有交互都是通过REST请求发起的,服务注册中心对各类rest请求的定义都位于eureka.resources包下
- eg:服务注册请求
- 对注册信息进行一堆校验之后,会调用InstanceRegistry对象中的register函数进行服务注册
- register函数注册
- 先调用publishEvent函数,将该新服务注册的事件传播出去
- 调用AbstractInstanceRegistry父类中的InstanceInfo中的元数据信息存储在一个concurrentHashMap对象中
- 注册中心存储了两层map
- 第一层,key存储服务名,InstanceInfo中的appName
- 第二层,key存储实例名,InstanceInfo中的instanceId属性
- 注册中心存储了两层map
-
-
在application.properties中用eureka.client.serviceUrl.defaultZone参数指定了服务注册中心的位置
配置详解
- 服务端为服务注册中心
- 客户端为各个提供接口的微服务应用
Eureka客户端的配置主要分为:
- 服务注册相关的配置信息:包括服务注册中心的地址,服务获取的间隔时间,可用区域等
- 服务实例相关的配置信息:包括服务实例的名称,ip地址,端口号,健康检查路径等
服务注册类配置
这些配置信息都以eureka.client为前缀。常用配置为:
- 指定注册中心
- 参数:eureka.client.serviceUrl
- 配置值存储在hashmap中,设置有一组默认值,key:defaultzone value:ip:port/eureka(多个使用逗号分隔开)
- 为了服务注册中心的安全考虑,会为服务注册中心加入安全校验,配置serviceUrl时,需要在value值url中加入相应的安全校验信息
- 参数:eureka.client.serviceUrl
- 其他配置
服务实例类配置
配置信息以eureka.instance为前缀
-
元数据
-
eureka客户端在向服务注册中心发送注册请求时,用来描述自身服务信息的对象,其中包含一些标准化的元数据,比如服务名称,实例名称,实例端口等用于服务治理的重要信息,以及负载均衡策略或是其他特殊用途的自定义元数据信息
-
真正服务注册的时候,会包装成InstanceInfo对象发送给Eureka服务端。
-
Map<String,String> metadata 是自定义元数据信息,其他成员变量则是标准化的元数据信息
-
可以通过eureka.instance.=格式对标准化元数据直接进行配置
eureka.instance.metadataMap.zone=shanghai
-
-
实例名配置
- InstanceInfo中的instanceId参数是区分同一服务中不同实例的唯一标识。
- 实例名采用主机名作为默认值,使得在同一主机上无法启动多个相同的服务实例。针对同一主机多个服务实例的情况,对实例的默认命名做了更为合理的扩展。
- eg:本地负载均衡调试时,统一服务多个实例,命名:利用应用名加随机数的方式来区分不同的实例,从而实现在统一主机上,不指定端口就能轻松启动多个实例的效果
-
端点配置
- 状态页和健康检查的url在eureka中默认使用了actuator模块提供的/info端点和/health端点
- 为应用设置了context-path或为安全考虑修改了路径,需要做些特殊配置
- 相对路径配置
- eureka.instance.statusPageUrlPath
- eureka.instance.healthCheckUrlPath
- 绝对路径配置(eg以https方式暴露服务和监控端点时)
- eureka.instance.statusPageUrl
- eureka.instance.healthCheckUrl
- eureka.instance.homePageUrl
- 相对路径配置
-
健康检测
- 默认情况下,是依靠客户端心跳的方式来保持服务实例的存活,但有些情况不能判断
- 通过配置,把eureka客户端的健康检测交给spring-boot-actuator模块/health端点,实现更加全面的健康状态维护
- 步骤
- 在pom.xml中引入spring-boot-starter-actuator模块的依赖
- 在application.properties中增加增加配置eureka.client.healthcheck.enable=true
- 如果客户端的/health端点路径做了一些特殊处理。
-
其他配置
- 默认前缀eureka.instance
跨平台支持
参考:《spring cloud 微服务实战》