在微服务架构中,帮助开发者快速构建应用的脚手架技术无疑是非常重要的。以Spring Boot为代表的基底技术在继承了Spring框架思想的同时将简洁便利、约定优于配置、开箱即用等特性进一步发扬光大。然而仅仅依靠Spring Boot还不足以支撑微服务架构应对服务高可用、服务动态配置、服务高可扩展、服务负载均衡、服务容错与隔离等非功能需求,我们还需要相关基础设施提供服务治理及管控能力。
Pivotal公司的Spring Cloud可以说是JVM平台上微服务治理框架的集大成者。本章我们将详细讲解服务注册中心、服务配置中心、微服务网关三个微服务运行时的关键支撑系统。另外,将介绍SpringCloud组件依赖Ribbon及Hystrix模块如何实现负载均衡和熔断管理等。
在云原生架构下,微服务需要具备极强的动态性及可扩展性,而服务注册与发现机制正是微服务可扩展性的基础。在微服务体系中,服务注册中心是微服务的核心模块,它是微服务架构中对服务的位置信息、心跳信息、元数据信息进行管理的重要基础设施。服务注册中心通过中心化、动态化的方式管理众多微服务实例。
在传统的应用系统中,基于硬编码方式对外提供服务地址的方式存在诸多问题,在服务扩展、服务高可用、服务升级方面,如果使用硬编码方式将使服务调用方与服务提供方产生紧耦合问题。
在早期的分布式系统架构中,使用DNS(Domain Name Server,域名服务器)协议作为服务发现机制,它是实现服务调用方与服务提供方解耦的一种简单有效的方式。DNS协议是一个“古老”的协议,也是最基本、最通用的协议之一。几乎所有操作系统底层都支持DNS协议,基于DNS协议的服务发现具备非常好的通用性,几乎使用所有编程语言都可以无缝接入。DNS可以让我们的一个服务名称与一组IP地址关联,或者与一个负载均衡器关联,实现多实例的分发。但是基于DNS协议的服务发现机制还是存在灵活性差、无法定制、端口及语言框架等问题。
在云原生范式的微服务架构中,服务的地址信息、服务的描述信息、服务的健康状态、服务的心跳信息、服务的版本、服务的元数据都是服务注册中心关心的初始数据。所以越来越多的基于“私有”协议的服务注册中心涌现出来。后面我们会详细介绍一些目前比较流行的微服务架构的服务注册中心。
下面我们首先来了解一下服务注册与服务发现两者之间的关系。
服务注册是生产者将自己的服务元信息上传到服务注册表中的过程,而服务发现是一个消费者通过服务注册表实时获取可用生产者服务信息的过程。
服务注册方式
● 自注册:顾名思义就是服务提供方在启动服务时自己把提供服务的IP和端口发送到注册中心,并通过心跳的方式维持健康状态;服务下线时,自己把相应的数据删除。典型的案例是使用Eureka客户端发布微服务。
● 第三方注册:存在一个第三方的系统负责在服务启动或停止时向注册中心增加或删除服务数据。Netflix Prana就是一款第三方注册系统,可与非JVM应用共同运行,并利用Eureka为该应用注册。
● 注册中心主动同步:是指将注册中心和调度或发布系统打通,注册中心主动同步最新的服务IP列表。在Kubernetes体系中,Core DNS订阅API Server数据就是采用这种方式实现的。
服务发现方式
● 客户端服务发现
在向某一服务发送请求时,客户端会通过查询服务注册表(Service Registry)获取该服务实例的位置,该注册表中包含所有服务的位置。下图展现了这种模式的结构。
客户端服务发现拥有以下优势:
○ 相较于服务端服务发现,活动部件与网络中转数量更少。
○ 客户端可以根据自己的策略实现负载均衡,有掌控优势。
客户端服务发现存在以下弊端:
○ 客户端与服务注册表耦合。○ 需要为应用程序中使用的每种编程语言或框架建立客户端服务发现逻辑。举例来说,Netflix Prana就为非JVM客户端提供了一套基于HTTP代理的服务发现方案。
● 服务端服务发现
在向某一服务发送请求时,客户端会通过在已知位置运行的路由器(或者负载均衡器)发送请求。路由器会查询服务注册表,并向可用的服务实例转发该请求。服务注册表也可能内建于路由器中。下图展现了这种模式的结构。
服务端服务发现拥有以下优势:
○ 相较于客户端服务发现,其客户端由于无须实现发现功能,对代码没有侵入,而且客户端只需要向路由器发送请求即可。
○ 可以解耦客户端与服务端的依赖关系。
服务端服务发现存在以下弊端:
○ 路由机制作为另一系统组件进行安装与配置,不仅需要具备一定的接入能力,还需要为其配置一定数量的副本。
○ 相较于客户端服务发现,服务端服务发现需要更多的网络跳转。
技术选型可以说是软件开发人员经常面临的问题,技术选型的首要原则是满足业务场景的需要,《人月神话》中提到软件的本质复杂性在于复杂的业务领域,而技术仅仅是辅助工具,技术选型解决的问题是将业务领域问题转换为软件实现。除此之外,从技术的层面上来说,我们还需要考虑如下关键因素。
● 软件的成熟度及软件架构的完整性。不要盲目追求新技术,要采用成熟稳定的技术,在生产环境中经过长时间运行检验的技术可减小我们的使用风险。
● 软件工程性方面。需要考虑公司人员擅长的技术,如果公司目前的业务系统大部分都使用Java相关技术,迁移到以Java为核心的服务框架或者技术工具会更加容易,且可以降低学习成本。
● 社区活跃度。软件在开源之后,社区越活跃说明有越多的人关注和使用该软件,你也可以在遇到技术问题后得到更多的帮助。
随着目前微服务架构越来越流行和成熟,作为微服务架构的核心模块,各种第三方注册中心如雨后春笋般涌现,很多开源产品都实现了服务注册与发现的功能,如何选择一个适合你的服务注册中心呢?
下面我们以三款主流的注册中心(ZooKeeper、Consul、Eureka)为例,开始我们的详细说明和分析。
注册中心与CAP
CAP理论告诉我们,一个分布式系统最多只能同时满足一致性( Consistency ) 、 可 用 性 ( Availability ) 和 分 区 容 错 性(Partition tolerance)这三项中的两项,如下图所示。
● 一致性:指所有节点在同一时刻的数据完全一致。
● 可用性:指服务一直可用,而且响应时间正常。例如,不管什么时候访问X节点和Y节点都可以正常获取数据值,而不会出现问题。
● 分区容错性:指在遇到某节点或网络分区故障时,仍然能够对外提供满足一致性和可用性的服务。例如X节点和Y节点出现故障,但是依然可以很好地对外提供服务。
CAP的取舍
● 满足CA舍弃P,也就是满足一致性和可用性,舍弃分区容错性。这也就意味着你的系统不是分布式的了,因为分布式就是把功能分开部署到不同的机器上。
● 满足CP舍弃A,也就是满足一致性和分区容错性,舍弃可用性。这也就意味着你的系统允许有一段时间访问失效等,不会出现数据不一致的情况。
● 满足AP舍弃C,也就是满足可用性和分区容错性,舍弃一致性。这也就意味着你的系统在并发访问的时候可能会出现数据不一致的情况。
事实证明,在分布式系统中,为了避免单点故障,分区容错是不可避免的,所以对于注册中心来说只能从CP(优先保证数据一致性)、AP(优先保证数据可用性)中根据你的业务场景选择一种。
对于服务注册与发现场景来说,针对同一个服务,即使注册中心的不同节点保存的服务提供者信息不尽相同,也不会造成灾难性的后果。对于服务的消费者来说,能消费才是最重要的,拿到可能不正确的服务实例信息可以通过重试的方法再次获取,总比因为无法获取实例信息而无法消费好。所以,对于服务发现而言,可用性比数据一致性更加重要,即AP胜过CP。Spring Cloud Netflix在设计Eureka时遵守的就是AP原则。
● Eureka:使用AP原则、无Master/Slave(主/备)节点之分,一个节点挂了,自动切换到其他节点,实现了去中心化,它优先保证了服务的可用性。Eureka使用P2P(Pear to Pear,点对点)的对等通信原则,每一个Peer都是对等的。在这种架构风格中,节点通过彼此互相注册来提高可用性,每个节点需要添加一个或多个有效的URL指向其他节点。每个节点都可被视为其他节点的副本。
● Consul:使用CP原则,采用分布式一致性协议实现健康检查、链值对存储。为了维护数据的一致性,通常需要选举出一个Leader(领导者)来进行协调,Consul的Raft协议要求必须过半数的节点都写入成功才认为注册成功。在Leader“挂掉”之后、重新选举出Leader之前Consul服务不可用。
● ZooKeeper:使用CP原则,它可以保证服务的一致性。搭建集群的时候,如果某个节点失效,则会进行Leader选举,或者半数以上节点不可用则无法提供服务,因此可用性没法满足。ZooKeeper使用Paxos算法保证数据的一致性。
服务侵入性差别
Eureka属于应用内的注册方式,对应用的侵入性比较强,而且目前主要支持基于Java语言方式的接入。不过Eureka使用HTTP方式接入,降低了异构系统接入Eureka的难度,如果需要使用Eureka注册中心,可以使用SideCar(边车)模式完成。
Consul属于应用外注册方式,本质上把应用当成一个黑盒,通过consul-template和envconsul等工具集成需要应用程序进行的更改,避免了对语言的侵入性。
ZooKeeper属于应用内的注册方式,对跨语言支持较弱,如果需要接入ZooKeeper,则需要使用对应语言的SDK方式支持,对语言的侵入性比较大。服务状态监听差别
Eureka使用客户端心跳的方式实现服务状态的监控。同时Eureka为了在网络不稳定情况下保护服务地址,可以通过配置开启自我保护功能。使用“/health”端点服务可以获得更多本地服务健康状态数据,同时Eureka也支持自身监控。
Consul提供了系统级和应用级健康检查。一种方式是服务主动向Consul告知健康状态,还有一种是服务被动方式,由Consul主动来检查服务端状态,可以查看服务状态、内存、硬盘等健康指标数据,Consul也支持自身监控。
ZooKeeper通过长连接和KeepAlive的方式检查服务进程和连接情况。同时ZooKeeper提供Watch机制监听对象变化事件,实现分布式的通知功能。当触发服务端的一些指定事件时,它就会向客户端发送通知,但是ZooKeeper本身不支持自身监控。
上述三服务注册中心都支持TTL(Time To Live,生存时间)机制。也就是说如果客户端在一定时间内没有向注册中心发送心跳,则服务注册中心会将这个客户端摘除。
易用性差别
Eureka和Consul都是专门设计作为注册中心使用的,所以在服务模型和API封装方面比较友好,同时在应用协议方面,Eureka的定位就是注册中心,支持HTTP协议,第三方接入也相对容易。Consul同时支持HTTP和DNS,在协议层面Consul更通用,Consul扩展了很多功能,除了注册中心,它也支持作为配置中心、键值对存储、丰富形式的健康检查、多数据中心、ACL控制、日志快照生成等功能。
ZooKeeper基于TCP上的私有协议完成服务注册与发现,ZooKeeper的客户端使用起来比较复杂,ZooKeeper自己的定位是分布式协调器,也扩展了很多的功能:分布式锁、Watcher通知机制、分布式下的队列管理、键值对存储,没有服务发现模型及想用的API封装,所以ZooKeeper作为单纯的服务注册中心的易用性比较差。
编程语言实现差别
Eureka与ZooKeeper都是基于Java实现的,定制开发需要使用Java,与Spring Cloud的生态圈有先天的良好的适配性。Consul使用GO开发,开发定制需要使用GO,Spring Cloud封装了GO的客户端实现。
多数据中心支持
ZooKeeper和Eureka都不支持在多数据中心场景下的服务注册与发现,而Consul支持多数据中心。Consul通过WAN的Gossip协议完成跨数据中心的同步,而且其他的系统需要额外的开发工作来实现。
总结,对于技术选型而言,使用者选择一款开源软件产品,最重要的是从外部特征或者主要的功能特性考虑是否满足业务场景的需求。对于服务注册中心而言,上面总结的是ZooKeeper、Eureka、Consul的一些根本性差别,当然,这些注册中心在编程语言技术栈、数据存储模型、使用的一致性算法、访问协议等内部原理机制方面也都存在很大的不同。
Eureka来自古希腊词语,含义是“我找到了!我发现了!”相传阿基米德发现浮力原理后说出了这个词。Eureka的官方介绍如下:
Eureka项目最早是由Netflix OSS开源的一个基于REST的服务发现组件,用来定位运行在AWS[1]中的后端服务。Eureka客户端是一个Java客户端,它可以用来简化与服务器的交互,作为轮询负载均衡器提供服务的故障切换支持。
Spring Cloud Eureka基于Eureka进行二次封装,增加了更人性化的UI,使用更方便。Eureka包括Eureka Server和Eureka Client两个模块。一般Eureka Server由多个Eureka Server实例组成,可避免单点问题,而Eureka Client又分为服务的生产者和服务的消费者。
Eureka Server采用点对点对等通信实现去中心化架构,每个Server节点需要添加有效的URL注册给其他Server节点,实现注册信息互相备份,达到高可用的效果。
如下图所示是Eureka的整体高可用架构图,从CAP理论看,Eureka是一个AP系统,优先保证可用性(A)和分区容错性(P),不保证强一致性(C),只保证最终一致性。
Eureka Server接入步骤
1.添加Eureka Server的依赖
2.添加配置文件
3.配置启动类
Eureka Client的接入步骤
1.添加Eureka Client的依赖
2.配置application.yml文件
3.配置Eureka Client启动类
Eureka的主要配置参数
Eureka配置参数众多,它的很多功能都是通过配置参数来实现的,了解这些参数的含义有助于我们更好地应用Eureka的各种功能。
● Eureka Instance配置
○ instance-id:表示实例在注册中心注册的唯一ID。
○ prefer-ip-address:“true”实例以IP地址的形式注册,“false”实例以机器HostName的形式注册。
○
lease-expiration-duration-in-seconds :表 示 EurekaServer在接收到上一个心跳之后等待下一个心跳的秒数(默认为90s)。
○
lease-renewal-interval-in-seconds :表 示 EurekaClient向Eureka Server发送心跳的频率(默认为30s)。
● Eureka Server配置
○ enable-self-preservation:表示注册中心是否开启服务的自我保护能力,默认为true。○ renewal-percent-threshold:表示Eureka Server开启自我保护的系数,默认为0.85。
○
eviction-interval-timer-in-ms:表示Eureka Server清理无效节点的频率,默认为60s。
● Eureka Client配置
○ register-with-eureka:表示此实例是否注册到EurekaServer以供其他实例发现。
○ fetch-registry:表示客户端是否从Eureka Server获取实例注册信息。
○ serviceUrl.defaultZone:表示客户端需要注册的EurekaServer的地址。
Eureka的架构设计
Eureka的架构主要包含两大部分:Eureka Server Cluster和Eureka Client Cluster,架构图如下。
Eureka使用P2P(点对点)的心跳机制保证Eureka Client启动后向Eureka Server发送心跳,默认周期是30s,如果Eureka Server在多个心跳周期内没有接收到心跳,将会从服务注册中心剔除相应节点。其中LeaseManager是Eureka的核心模块,代码如下。
Eureka心跳机制主要有以下几个操作方法。
● Register:用于实现服务注册实例信息接口。
● Cancel:用于删除服务实例信息接口。
● Renew:用于服务实例与Eureka Sever完成远程心跳,维持续约接口。
● Evict:是服务端的一个方法,用来剔除过期的服务实例的接口。
Eureka的数据模型
如下图所示是Eureka Server保存注册实例信息的数据结构,可以看到它是一个双层Map结构。
●第一层ConcurrentHashMap,Key值是spring.application.name,Value值是一个Map数据结构。
● 第二层ConcurrentHashMap,Key值是InstanceId,Value值是Lease对象,Lease的实现代码如下:
Eureka工程中的包层级关系
如下图所示,eureka-core和eureka-client是Netflix用来实现服务注册与发现功能的核心包,所有服务的注册与发现都是在包中实现的。
Eureka使用Jersey和XStream第三方库组件配合JSON作为Server和Client 之 间 的 通 信 协 议 。
spring-cloud-netflix-eureka-server 和spring-cloud-netflix-eureka-client是Spring Cloud借助自动配置管理机制,将Eureka资源加载到Spring IoC容器,实现的对Eureka功能的封装和管理。可见,Spring Cloud就像一个“八爪鱼”一样,可以轻松集成其他组件。
Eureka的心跳流程图
Eureka使用心跳机制维持与Server的连接,主要包含注册、续约、退出、剔除等服务生命周期相关操作。Server节点复制的流程图如下所示。
Eureka 通 过 REST 接 口 实 现 对 外 服 务 暴 露 , 所 有 接 口 在ApplicationResource中,ApplicationResource会调用PeerAwareInstanceRegistry的接口。Eureka注册中心的核心生命周期管 理 入 口 正 是 InstanceRegistry , 它 继 承 了 LookupService 、LeaseManager 接 口 , 提 供 应 用 实 例 的 注 册 与 发 现 服 务 。
AbstractInstanceRegistry是应用对象注册表的抽象实现,其中定义了注册、续约、退出、剔除等方法,贯穿于服务实例在注册中心Server中的整个生命周期管理,而PeerAwareInstanceRegistry提供Eureka Server集群内注册信息的同步服务接口。PeerAwareInstanceRegistryImpl继承了抽象注册表类AbstractInstanceRegistry,新增了peer相关处理和RenewalThreshold的更新。下面是核心代码示例:
Eureka Server 存 在 三 个 缓 存 变 量 :registry 、readWriteCacheMap和readOnlyCacheMap,用于保存服务注册信息,默认 情 况 下 定 时 任 务 每 30s 将 readWriteCacheMap 同 步 至readOnlyCacheMap一次,每60s清理一次超过90s未续约的节点,Eureka Client每30s从readOnlyCacheMap更新服务注册信息一次,前端则从registry更新服务注册信息。如下图所示是和Eureka缓存机制相关的服务和数据结构的关系。
Eureka Server三级缓存组件如下表所示。
Eureka Server缓存相关配置如下表所示。
对于Eureka Client来说,它有两种角色:服务生产者和服务消费者,一般会配合Ribbon或Feign使用。在启动后,Eureka Client作为服务生产者会立即向Server注册,暴露服务地址供消费者使用,默认情况下每30s续约(renew)一次;作为服务消费者它立即向Server全量更新服务注册信息,默认情况下每30s增量更新服务注册信息一次;
Ribbon延时1s向Client获取使用的服务注册信息,默认每30s更新一次使用的服务注册信息,只保存状态为UP的服务。
Eureka Client二级缓存组件如下表所示。
Eureka Client缓存相关配置如下表所示。
总结,因为Eureka基于AP原则,服务端和客户端的实例状态信息可能会有不一致的情况,我们需要了解Eureka的缓存机制,以便于了解服务状态同步异常问题的原因,也可以通过修改相应配置参数缓解缓存不一致问题。对于需要保证强一致性的服务场景,建议使用ZooKeeper。
配置优化
为了避免Eureka Server和Client缓存问题,一般会配置多节点集群,这样单个节点服务上线的状态更新滞后并没有什么影响。
下面总结一些配置优化,用来改善数据一致性和状态感知滞后引起的状态不一致问题。
● 服务端:缩短readOnlyCacheMap更新周期,可减少滞后时间。
● 服务端:关闭readOnlyCacheMap缓存,让Eureka Client直接使用readWriteCacheMap更新服务注册信息。
● 客户端:缩短服务消费者更新周期。Eureka Client和Ribbon二级缓存影响状态更新,缩短这两个定时任务的周期可减少滞后时间,例如配置定制化开发:
Eureka Server作为服务注册中心可能有不一样的定制化需求,这个时候需要在Eureka Server开源工程的基础上完成定制化开发。
在默认情况下,Eureka会展示Eureka Server集群中所有的注册服务实例,没有对用户做权限控制,我们可以根据不同用户的权限在Admin管理端做定制化开发,展示该用户所属权限下的服务实例状态数据,示例代码如下:
另外,在具备定制化权限管理的基础上,我们可以通过覆盖Eureka自带的上下线操作方法,实现对服务实例的下线或者上线操作,它可以完成服务实例的灰度发布,示例代码如下:
本文给大家讲解的内容是微服务的核心模块——服务注册中心