作者介绍
张真,宜信技术研发中心高级架构师,负责基础系统架构演进与优化、服务治理、监控平台、微服务建设、DevOps平台、自动化测试框架及电子签约、短信、邮件等应用系统。早年就职于IBM中国研发中心,负责IBM WebSphere应用服务器的设计与开发。目前主要关注微服务架构实施,微智能设计思想应用,虚拟化技术应用,共识计算研究。
上文我们已经详细讲到了一些经典微服务架构的特点及问题,微服务计算平台的设计思想与抽象模型,今天就接着打造微服务计算的基础三件事这一话题,说说服务情景感知与监控和服务调用的自适应机制。
高效的服务运维需要实现对服务的有效监控。微服务计算平台在设计上考虑了两个方面:
服务运行的监控
服务上下文的监控
1服务运行的监控
服务运行的监控是针对服务处理能力的监控,常规的指标包括吞吐量(QPS),响应时间,错误,响应码等等。
我们采用了自动发现+自动捕获的方式:
服务能力实现框架中Http服务框架除了可以被画像以外,也天然集成了指标计数器。
在服务接口运行的过程中,各种指标计数器被更新。
基础服务能力“服务监控捕获”定期从Http服务框架采集各个服务接口的指标数据并打包成监控数据。
服务监控捕获会将监控数据通过消息队列发送到具备服务能力“服务监控存储”的计算节点。
当然实际上基于JAVA的微服务计算节点,还会采集很多JVM的指标,它们包括Heap各个区的使用状态,GC次数&累计时间,Class加载情况,进程CPU占用率,线程使用状况等。
监控数据采用JSON作为传输格式
2服务上下文的监控
服务上下文是服务计算节点运行环境,主要是系统层面的监控:CPU占用率,内存使用状况,连接数量,相关端口的进出流量(KB),磁盘使用状况等等。服务上下文的状况会直接影响服务运行的性能以及健壮性。
微服务计算中的情节感知
首先,每个服务计算节点通过服务能力“运行环境感知”调用各种系统功能来获取监控数据。这些系统功能一般包括三类:
系统命令:操作系统内置的命令,常用的有netstat,top,du,df。通过netstat可以获得当前进程所有监听端口以及连接数;通过top可以获得当前进程的CPU占用率和内存消耗。通过df命令可以获得各个挂载的磁盘消耗,du命令可以获取节点进程占用的相关目录的磁盘消耗。
系统API:获取进程各个端口的进出流量,需要通过Raw Socket来截取。使用C或Python编程调用Raw Socket API来实现。
系统目录:Linux系统所有系统行为基本上都有一组文件对应。比如为了实现服务计算节点的自动值守或自重启,通过readlink读取/proc/<进程id>/XX下的信息,通过这些信息可以构建任意进程的启动命令。
通常来说,优先使用系统命令,因为原生,执行代价小;系统API是应对较为底层的监控数据获取;系统目录需要深入操作系统了解运行机理,代价最大,但是也最为准确。需要根据实际情况而定。
然后,运行环境感知会将捕获到的监控数据通过心跳客户端发送给心跳服务端,再由心跳服务端交由服务能力“运行环境监控存储”实现持久化。情景感知数据是另一种心跳数据,之所以采用心跳系统来传输的原因是:
与服务注册接口数据保持高度的时间同步性,因为微服务计算平台期望每个服务计算节点根据上下文监控数据进行自适应调整,这个话题也会在以后的分享专题中进行说明。
上下文监控数据无需“高频”(间隔30秒~1分钟)采集且指标数量固定,这意味着实际Payload不大,这符合Http携带数据的特性,也能保证其随多级心跳上行。
那么为什么服务监控数据是通过消息队列传输,而不是使用心跳上行?主要是以下原因:
监控数据的使用方式不同:服务监控数据指导自动适应调节是依靠一段时间内的指标数据的聚集结果(比如平均值,求和值等),这也可以消除系统误差,而非依靠“瞬时”数据,而且这个过程需要服务监控数查询提供运算支持;上下文监控数据是客观反映服务计算节点的环境状况,瞬时数据保证精确性,可以直接使用,反而一段时间的聚集结果会丢失精确性。
服务监控数据的数据量会远远大于上下文监控数据: 服务监控数据量=服务接口数量*指标个数*采集频率。因为它采用频率更高(间隔5秒~15秒),服务接口数量会远远大于服务计算节点数量,除了固定的一些指标外,可以包含业务指标,随之指标数量会增加许多。心跳系统不适合“重,多”的场景,而消息队列适合数据Payload大的场景,且一定程度保证数据有序。此外,消息队列会持久化数据,防止由于接收端宕机导致的数据丢失。
服务调用过程中往往会遇到各种异常。单体架构的年代,由于基本都是内存调用,几乎很少遇到这类问题,只有在跨系统的时候才会出现;到了SOA的时代,逐渐出现了服务调用问题,不过主要是服务与服务总线(Service Bus)之间,服务连接到服务总线也缺少容错机制。微服务架构出现以后,服务之间的依赖变得直接而且更多元化,面对庞大的服务接口如何使其更“平顺,聪明,可靠”是一个必须解决的问题。
“服务熔断”是微服务架构的热词之一,这本是个电力工程的术语,原意大致是当输入链路电力过载时,为了保护下游链路以及设备,自动“熔断”电力链路。最常见的就是保险丝机制。
那么服务熔断又是什么含义呢?主要体现以下三个方面:
失效自动切换(Failover)
失效隔离(Isolation)
自动高效重试(Retry)
如果再加上服务调用的负载均衡(Load Balance),其本质体现的是服务调用的自适应。
服务调用自适应基本模型
自适应机制的体现实际也是服务计算节点/服务接口的资源可用性的状态转化过程:
可用的计算节点/服务接口是能够被负载均衡机制发现的;
服务调用中出现异常,调用方会切换好的资源,而异常的资源可能被隔离起来;
需要通过重试来确定异常的资源是否恢复,是否解除隔离;
重试成功的资源会解除隔离,变成可用状态而被负载均衡机制发现;
多次重试都失败的资源,可能被更”强”的隔离起来。
下面会分别解读它们是如何实现的。
1负载均衡与失效自动切换
在微服务计算平台中,负载均衡与失效切换是密不可分的,这里会放在一起说明。
负载均衡分为两种模式:
代理模式:通过前置代理,代理向后对接服务计算节点,比如Ngnix,HAProxy。经典NetFlix微服务架构中的“服务网关”实际上也是一个代理,它对外是代理网关,对内则类似服务总线。
客户端模式:由调用客户端决定使用何种策略来访问目标服务。像Dubbo,Dubbox等就是使用Zookeeper客户端获取服务地址列表,然后使用负载均衡策略来访问服务。
两种模式没有“绝对”的优劣之分,主要看适用场景。在微服务架构下,客户端模式应该更加适合。
微服务计算平台中的负载均衡是由服务能力实现框架提供的一种通用型组件。无论是何种通信协议(HTTP,RPC,RMI等)实际都可以统一成一种通用型组件。负载均衡的实质是对调用目标的策略,关于负责均衡的策略很多,也有很多实现算法,本节不做讨论。
它的处理机制是:
每个服务接口注册以后,都可以通过服务注册中心的API(对外暴露的服务,人工调用)来设置这些服务接口的负载均衡策略。
在服务发现的过程,调用方业务服务能力X除了可以获得服务接口地址列表,还同时获得了服务接口的负载均衡策略,此外还有失效切换策略。这么做也可以实现策略的动态更新。
在负责轮转的过程中,如果出现调用异常,则需要进行失效切换的操作。服务调用异常可分为四类:
服务接口地址无法连接
服务调用超时异常: 目标服务计算节点可能进程存活,但由于某种原因无法响应或hang住。这中场景通常棘手,不容易第一时间发现,危害比服务计算节点挂掉更大,会拖延整个调用链路恢复的时间
业务处理异常(系统性,可恢复):由于目标服务的处理过程以及上下文环境造成的异常,这种异常的特点是换一个同类型的服务计算节点异常可能就解决了。
举个例子,电子签章服务在处理文件签章时,需要一个临时目录来做暂存,如果因为某种原因使得该服务所在的机器上磁盘满了,导致临时目录无法写入,这时签章调用就会失败。但如果切换到另外一个磁盘未满的节点上,调用就成功了。
业务处理异常(业务数据引起,难以恢复):目标服务的处理过程是“完好正确”的,但由于业务数据的问题(数据格式,数据内容缺失,数据一致性等等)造成调用失败。请注意,这类异常通常不能进行切换。
所以失效切换实质是对前三类异常进行切换,我们把切换分为两类:
非通异常切换:针对连接不可用异常和调用超时异常。
业务异常切换:针对系统性的业务处理异常。注意,这是客户端模式独有的切换方式,因为在代理模式下无法识别业务异常类型,甚至无法识别业务异常(例如业务上在响应报文中提示异常,但HTTP响应码仍然是200)。
前文在说明服务注册与发现时提到了服务接口失效的快速反馈,它为失效服务接口地址提交提供了通道,使得其他可能的调用方能够不再去尝试失效服务接口地址,也为失效资源隔离提供了全局视图。
微服务计算平台还提供了一项“黑科技”,是基于动态服务编排的切换机制。这种切换机制可以不依赖与人工设置,它能够自主的根据目标服务能力需求,全网同类型服务能力计算节点的运行状况来做出决策,决定由哪个或哪些服务计算节点来完成计算任务。这会在以后的专题分享中进行解读。
2自动高效重试
重试是服务接口失效后的补偿操作,它的目的是为了确认失效资源是否“Back Online”。
微服务计算平台中的重试也是由服务能力实现框架提供的能力,也是通用型组件的一部分。我们把它设计成与负载均衡,失效切换一体化的机制:
首先,服务调用重试是由第一个发生对该服务接口进行失效切换的节点来尝试,这个节点会缓存这个服务接口地址
在这个节点进行负载均衡的某次请求中,会再次返回失效的服务接口地址,当然是以保证只有这次请求的线程获得,而其他并发请求的线程是按照正常的负载均衡策略进行的。之所以这样做是因为:
1)无需引入额外资源来进行重试,没有维护额外资源的代价
2)用尽可能小的代价进行重试,仅仅是某次请求的线程
在业务服务能力类型很多,且关联复杂时,实际很难实现一个合适的重试机制。因为往往微服务之间的编排不像做一个网络探测一样容易
如果这次重试成功,调用方节点立即解除隔离,并将这个“好消息”上传到服务注册中心服务注册中心则可以“通知”所有可能的调用方。
如果经过多次重试(失效切换策略决定)后仍然不成功,也将这个“坏消息”上传到服务注册中心,服务注册中心会根据隔离策略进行相关的动作。
3失效隔离
失效隔离是有前提的,不一定所有调用失败的服务接口都能隔离。这个前提是被隔离的服务接口不会对业务或数据一致性产生影响。比如如果某个服务计算节点的某服务能力是作为分片持久化数据的一个节点,如果该节点被隔离可能造成分片错误,这时则不能隔离该节点。
失效隔离可以分为:
失效软隔离(调用隔离):在服务的可访问性上进行隔离。这种隔离是从调用方角度来考虑,而对失效额服务计算节点本身不会施加影响。比如让某个服务接口地址从服务地址列表中消失。软隔离应该是隔离场景中最常见的,也使用最多。
失效强隔离(资源隔离):在服务使用的资源上进行隔离。这种隔离不仅仅从服务可访问性上,也会直接对失效的服务接口施加操作。比如如果因为磁盘写错误问题造成某个服务计算节点上的某个服务能力出问题,则直接停止该服务能力,注意只是停止了一个服务能力,而不是服务计算节点,除非该节点上所有服务能力都不可用,则应停止该节点。
微服务计算平台以失效软隔离为主,也支持失效强隔离。它的处理机制是:
在服务注册中心收到失效反馈后,将服务注册缓存中对应的服务接口信息置为不可用,这样在其他服务计算节点通过心跳下行刷新地址列表时,该服务接口的地址将不可见。当然这中间会因为延迟,出现“二次失效反馈”的现象,但由于每个服务计算节点会使用相同的失效切换策略,从业务上是保障正确的。
对于某些多次重试都失败的服务计算节点,服务注册中心会采用强隔离。由服务注册中心直接调用该服务计算节点的管控接口(心跳也会注册这个接口),提交停止服务能力命令
该节点的心跳客户端收到命令后,调用服务能力生命周期管理API来停止服务能力。当然有时候失效隔离策略也是“挽救”策略。例如某个证书申请服务接口失效了,从经验上讲可能只要重启一下就可以解决问题,那么在设置隔离策略时,将隔离操作的命令改为停止并启动其对应的服务能力,那么最终该证书申请服务接口的服务能力是被重启了,当它被再次重试时,就会从强隔离中解除。
其实服务隔离除了失效隔离,还有一种是防御隔离,也是“熔断”的一部分,它不是因为服务失效,恰恰服务运行是正常的,而因为业务上或安全上的某个需求,主动进行隔离的方式,比如某些服务计算节点受到请求轰炸,为了避免业务链条瘫痪,将收到轰炸的服务计算节点停服。关于这方面的内容会与以后分享专题中的软件定义服务集群(SDSC)一起介绍。
本文从经典的微服务治理系统的特点和问题出发,结合“微智能”思想,“拟社会化”分布式设计来考虑微服务计算平台的节点抽象模型。并在该模型基础上就微服务计算的三项基础:服务注册与发现,服务情景感知与监控,服务调用的自适应分别进行说明,如何落地微服务节点抽象模型。
当然微服务计算平台的建设还包括一些话题:
外部如何访问(集成问题),API网关的实现
服务之间的信任以及外部访问授权
服务SLA如何量化,如何让系统聪明的使用
动态服务集成与编排
动态计算编排
…
希望能在以后的分享专题中与大家分享。
如果字段的最大可能长度超过255字节,那么长度值可能…
只能说作者太用心了,优秀
感谢详解
一般干个7-8年(即30岁左右),能做到年入40w-50w;有…
230721