【摘要】近年来越来越多的企业开始实践微服务,本文分为上下两篇介绍微服务框架ServiceComb
如何帮助企业应用进行微服务化,实现快速交付,并可靠地运行在云端。上篇介绍ServiceComb
的服务管理中心设计。
近年来越来越多的企业开始实践微服务,而微服务在企业应用落地的过程,面临着微服务开发框架的选型,无论是自研还是选择第三方框架都不得不考虑的问题包括:微服务框架是否具备高可靠性,任何时间不能中断业务;微服务框架是否能够实现高速通信性能,保证业务从单体架构向微服务架构切换时,性能下降不会太多。本文从服务管理中心、通信处理两个模块来介绍华为开源微服务框架SeviceComb如何帮助企业应用快速具备高性能的通信能力以及高可靠的服务管理能力。上篇先介绍微服务的服务管理中心。
从服务注册中心到服务管理中心
1 整体介绍
图1 ServiceCenter整体介绍
ServiceCenter是一个具有微服务实例注册/发现能力的微服务组件,提供一套标准的RESTful API对微服务元数据进行管理。ServiceComb的微服务注册及动态发现能力也是依赖其实现的。
除了以上描述的微服务动态发现外,ServiceCenter帮助应用具备以下能力:
- 支持微服务实例隔离管理
- 支持微服务黑白名单管理
- 支持长连接**微服务实例状态变化
- 支持OpenAPI规范微服务契约管理
- 支持微服务依赖关系管理
- 提供Web portal展示微服务管理界面
- 高可用的故障处理模型(自我保护机制)
- 高性能接口和缓存数据设计
2 管理的需要
作为微服务系统中非常重要的组件。当前可选来做微服务注册中心的组件很多,例如Zookeeper、Consul、Eureka、ETCD。然而这些服务注册中心组件的重点在于解决服务的注册和发现,也就是一个动态路由的问题。而在一个大型的微服务架构的系统中,除了这些动态路由信息外,还有一些微服务自身的元数据同样需要进行管理,所以我们定义出微服务静态元数据。通过ServiceCenter提供的接口可以很方便的检索微服务信息。
除了微服务本身信息属于静态元数据外,ServiceCenter还扩展了微服务依赖关系、黑白名单、Tag标签和契约信息等元数据,他们之间的关系如下图所示:
图2 微服务相关静态信息之间的关系
这些微服务元数据不仅把实例信息分组管理起来,同时也满足了用户对微服务管理的需要。比如说微服务的黑白名单,业界的服务注册中心大多仅实现了consumer服务依赖的服务实例发现,但没法对一些安全要求高的服务设置白名单访问控制,ServiceCenter支持给这类provider服务设置黑白名单过滤规则,防止恶意服务发现并DDoS攻击。
3 分布式系统的高可用性保障
微服务及支撑它运行所需要的系统,从广义上来看,是一个典型的分布式系统。而分布式系统就一定不能逃脱CAP魔咒:
Consistency(
一致性),
在分布式系统的各点同时保持数据的一致。 Availability(
可用性),
每个请求都能接受到一个响应,无论响应成功或失败。Partition tolerance(
分区容错性)
,当出现网络分区故障时系统的容错能力。C
和A
是每个系统都在努力追求的目标,但是P
又是不能完全避免的。想同时满足三者是不可能的任务。
此后应运而生的BASE理论便成了现代化,特别是互联网分布式系统设计的准则:BASE理论,Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性);
ServcieCenter本身作为实现微服务架构中服务注册发现的组件,对整体微服务系统起到至关重要的作用,并且其本身也是一个分布式的系统。因此后面我们通过对一个微服务系统中常见故障模式的剖析来介绍在CSE中对BASE的实现。
3.1 常见的故障模式
图3
常见的故障模式
这里列出了ServiceComb
架构中常见的故障以及对应的自保护机制,由于后端存储使用ETCD
,本身也有一定的故障恢复能力,具体可以访问官网了解;现在简单了解下ServiceComb SDK
(简称:SDK
)和ServiceCenter
是如何实现高可用的。
SDK
:
1. 与ServiceCenter网络分区:SDK提供实例缓存机制,保证业务继续可以用。
2. 业务实例不可达:SDK提供客户端负载均衡能力,快速将请求路由到可用的业务实例,同时也支持提供一系列的容错策略,控制流量继续流向异常的实例。
ServiceCenter
:
1. 进程异常:ServiceCenter提供**127.0.0.1的本地HTTP健康检查接口/health,当监控脚本调用该接口时,ServiceCenter内部会进行etcd可用性检查、当前管理资源容量超限检查和平均延时等检查。当以上某个指标发生异常时,接口将返回错误,便于监控系统及时感知。
2. 环境约束:一般有CPU负载高、内存不足和磁盘空间不足等因素,ServiceCenter针对环境不稳定或资源不足的场景,除了自动触发自保护机制以外,还有提供一些可定制策略尽量保证自身可服务在这类环境中。如:针对CPU负载高的环境,ServiceCenter支持配置请求读写超时,最大限度保证请求可以正常处理完毕,针对磁盘空间资源不够场景,ServiceCenter自身提供定时日志压缩转储能力,尽量减少磁盘空间占用。
3. 并发操作:针对并发向ServiceCenter发写请求场景,需要保证数据一致。为此ServiceCenter利用了etcd本身提供数据强一致性能力,保证每一次写操作是原子操作;对于复杂的分布式业务场景,ServiceCenter内部还提供了分布式锁组件,防止分布式并发写数据不一致的问题。
4. 网络分区:微服务依赖常见问题,一般是依赖的服务不可用和相互之间网络故障问题。针对前者,松耦合设计,ServiceComb SDK(简称:SDK)和ServiceCenter均有提供缓存机制,保证运行态下,SDK到ServiceCenter、ServiceCenter到etcd各段间互不干扰;针对后者,ServiceCenter除了提供常见的客户端重试外,还提供异步心跳和自我保护机制。
可见ServiceComb从设计上已经考虑了高可用,下面分别从SDK到ServiceCenter和ServiceCenter到etcd两个层面来说明ServiceComb如何实现的。
3.2 保护机制
3.2.1 从SDK到ServiceCenter
1. 实例缓存机制
基于SDK开发的微服务,会在第一次消费Provider微服务时,会进行一次实例发现操作,此时内部会请求ServiceCenter拉取Provider当前存活的实例集合,并保存到内存缓存当中,后续消费请求就依据该缓存实例集合,按照自定义的路由逻辑发送到Provider的一个实例服务中。
这样处理的好处是,已经运行态的SDK进程,始终保留一份实例缓存;虽然暂时无法感知实例变化及时刷新缓存,但当重新连上ServiceCenter后会触发一缓存刷新,保证实例缓存是最终有效的;在此过程中SDK保证了业务始终可用。
图4 微服务实例缓存机制
2. 心跳保活机制
ServiceCenter的微服务实例设计上是存在老化时间的,SDK通过进程上报实例心跳的方式保活,当Provider端与ServiceCenter之间心跳上报无法保持时,实例自动老化,此时ServiceCenter会通知所有Consumer实例下线通知,这样Consumer刷新本地缓存,后续请求就不会请求到该实例,也就微服务的实现动态发现能力。
这样做的好处是,解决了业务某个实例进程下线或异常时,依赖其业务的微服务实例,会在可容忍时间内,切换调用的目标实例,相关业务依然可用。
图5 微服务实例心跳保活机制
3.2.2 从ServiceCenter到etcd
1. 异步缓存机制
在ServiceCenter内部,因为本身不存储数据,如果设计上单纯的仅作为一个Proxy服务转发外部请求到etcd,这样的设计可以说是不可靠的,原因是因为一旦后端服务出现故障或网络访问故障,必将导致ServiceCenter服务不可用,从而引起客户端实例信息无法正确拉取和刷新的问题。所以在设计之初,ServiceCenter引入了缓存机制。
图6
异步缓存机制
1) 启动之初,ServiceCenter会与etcd建立长连接(watch),并实时**资源的变化。
2) 每次watch前,为防止建立连接时间窗内发生资源变化,ServiceCenter无法**到这些事件,会进行一次全量list查询资源操作。
3) 运行过程中,List & watch所得到的资源变化会与本地缓存比对,并刷新本地缓存。
4) 微服务的实例发现或静态数据查询均使用本地缓存优先的机制。
异步刷新缓存机制,可以让ServiceCenter与etcd的缓存同步是异步的,微服务与ServiceCenter间的读请求,基本上是不会因为etcd不可用而阻塞的;虽然在资源刷新到ServiceCenter watch到事件这段时间内,会存在一定的对外呈现资源数据更新延时,但这是可容忍范围内的,且最终呈现数据一致;这样的设计即大大提升了ServiceCenter的吞吐量,同时保证了自身高可用。
2. 异步心跳机制
中心化设计,另一个难题是,性能问题,基于心跳保活机制,随着实例数量增加,ServiceCenter处理心跳的压力就会越大;要优化心跳处理流程,首先了解下ServiceCenter怎么实现实例心跳的。
实例心跳,主要是为了让ServiceCenter延长实例的下线时间,在ServiceCenter内部,利用了etcd的KeyValue的lease机制来计算实例是否过期。lease机制的运作方式是,需保证在定义的TTL时间内,发送keepalive请求进行保持存储的数据不被etcd删除。基于这个机制,ServiceCenter会给每个实例数据设置下线时间,外部通过调用心跳接口出发ServiceCenter对etcd的keepalive操作,保持实例不下线。
可以知道,这样设计会导致上报心跳方因etcd延时大而连接阻塞,上报频率越高,ServiceCenter等待处理连接队列越长,最终导致ServiceCenter拒绝服务。
图7 异步心跳机制
在这里ServiceCenter做了一点优化,异步心跳机制。ServiceCenter会根据上报实例信息中的心跳频率和可容忍失败次数来推算出最终实例下线时间,比如:某实例定义了30s一次心跳频率,最大可容忍失败是3次,在ServiceCenter侧会将实例定义为120s后下线。另外,在实例上报第一次心跳之后,ServiceCenter会马上产生一个长度为1的异步队列,进行请求etcd,后续上报的心跳请求都会在放进队列后马上返回,返回结果是最近一次请求etcd刷新lease的结果,而如果队列已有在执行的etcd请求,新加入的请求将会丢弃。
这样处理的好处是持续的实例心跳到ServiceCenter不会因etcd延时而阻塞,使得ServiceCenter可处理心跳的能力大幅度的提高;虽然在延时范围内返回的心跳结果不是最新,但最终会更新到一致的结果。
3. 自我保护机制
前面提到的缓存机制,保证了ServiceCenter在etcd出现网络分区故障时依然保持可读状态,ServiceCenter的自我保护(Self-preservation)机制保证了Provider端与ServiceCenter在出现网络分区故障时依然保持业务可用。
现在可以假设这样的场景:全网大部分的Provider与ServiceCenter之间网络由于某种原因出现分区,Provider心跳无法成功上报心跳。这样的情况下,在ServiceCenter中会出现大量的Provider实例信息老化下线消息,ServiceCenter将Provider实例下线事件推送到全网大部分的Consumer端,最终导致一个结果,用户业务瘫痪。可想而知对于ServiceCenter乃至于整个微服务框架是灾难性的。
为了解决这一问题,ServiceCenter需要有一个自我保护机制(Self-preservation):
图8 ServiceCenter
的自我保护机制
1) ServiceCenter在一个时间窗内**到etcd有80%的实例下线事件,会立即启动自我保护机制。
2) 保护期间内,所有下线事件均保存在待通知队列中。
3) 保护期间内,ServiceCenter收到队列中实例上报注册信息则将其从队列中移除,否则当实例租期到期,则推送实例下线通知事件到consumer服务。
4) 队列为空,则关闭自我保护机制。
有了自我保护机制后,即使etcd存储的数据全部丢失,这种极端场景下,SDK与ServiceCenter之间可在不影响业务的前提下,做到数据自动恢复。虽然这个恢复是有损的,但在这种灾难场景下还能保持业务基本可用。