查看原文
其他

微服务架构师的道、法、术(文字版)

技术琐话 2021-08-08

The following article is from 码码的土狼 Author 土狼


2020年10月,李鑫老师在2020 CSDI SUMMIT中国软件研发管理行业技术峰会上做了《微服务架构师的道、法、术》的分享。以下是李兄事后整理的文字版本,扩散之。


主要内容


本次分享主要聚焦于微服务架构师的能力图谱,包含了道、法、术三个层次。

“道”这部分内容里,主要讨论宏观层面的一些能力构建,包括微服务的场景选择及治理能力构建。

“法”这部分内容里,主要讨论使用微服务架构的一些方法论,包括领域划分、架构分层及DevOps能力构建等等。

“术”这部分内容中,我会介绍一些常用的微服务的度量及管控手段。



我们首先介绍一些宏观层面上的选型策略。

什么是微服务?


微服务是什么



微服务的核心就在这个“微”字,衡量是否“微”,需要有一个参照物。

现实中,我们经常拿微服务和单体系统做比较。

以我们公司的基金销售系统为例,在早期的建设中,其实没有什么服务化和微服务的概念。当时的常用做法是有什么类型的业务,就针对这种业务单独开发一套独立的销售及清结算系统。由于业务量普遍不大,这些系统往往采用单体架构模式,不考虑横向扩展性。这些功能常年累月堆积下来,就成了一个巨无霸系统,代码量巨多、各种功能耦合在一起,由于要初始化很多东西系统启动很慢,维护也很麻烦。一大堆人改一个系统,修改某个功能稍有不慎,就会对其它功能产生影响,导致后面没人敢下手修改代码。而且单体系统还有一个无法克服的问题,就是“一死全死”,一个功能发生致命性问题,会导致整个系统崩溃。

如果把这么一个巨无霸系统按功能拆分成一个个小系统并独立部署的话,那么单个系统的功能简单了、体量变小了、维护的人少了,启动变快了,开发也快了。一个系统出问题,其它系统仍能提供服务,影响也可控了,还能有足够的弹性应对流量的波动。

当我们按这种模式将一个单体系统拆的足够小,我们就可以将这些拆分出来的小应用称为“微”服务了,如图2。微服务的优势就是“小”、“快”、“弹性好”、开发快、上线快、灵活伸缩。


微服务适用场景




图3 企业业务及IT架构

既然微服务架构的特点是“小”而“快”,那么判断什么样的业务场景适合上微服务架构,自然也要从这两个特点出发。

我们来看看企业的业务架构,从下往上可以看到,企业的业务包含了企业运营、生产制造、市场运营、客户触达四大领域。

其中企业运营是维持企业正常运作的基础,支撑它的业务系统包括了企业的组织、流程、人事、办公等,一般由内部IT负责。企业的组织架构和运作流程相对稳定,调整一般以“年”为单位,我们很少看到一个企业三天两头调整组织和流程。相对应的系统也强调稳定,一般不追求快速迭代及变更。

再上一层是生产制造,支撑这层业务的系统包含了企业的产品生命周期管理、生产计划排班管理、上下游供应链管理等。这些系统很多都是采购的商业系统,相互之间要花大力气进行集成和打通,求稳不求快!随着市场变化越来越快,精益制造和柔性制造理念的普及,订单批量日渐趋于1,个性化制造及效率的考量在生产领域越来越受重视,快的因素也在增长。 

第三层市场运营,包括客户、竞品、市场、渠道等。这就需要较高的敏捷性了,要求企业能够保持足够的市场灵敏度,随时感应市场变化,有效进行资源的调配和重组。因此支持这一层业务运作的系统要具有较高的灵活性,能根据市场需求随时进行新功能的开发和部署,以支持一线销售的需要。

最顶层的业务是与市场主体客户的直接触达,也就是终端消费。这是一个“喜新厌旧”、追求热点的时代,客户群体的口味时刻在变,一个热度只能保持3天。我们做的APP,营销活动从规划到上线基本以周为单位来计算,这就要求这一层的支撑系统具有极高的敏捷形态,能够快速开发、部署,并能应对流量的大幅波动。

总的来说,最底层的企业运营的迭代周期以年为单位,追求稳定,是一种稳态IT模式。越往顶层迭代周期越短,追求的是一种敏捷性,可以称之为“敏态IT”模式。可见企业IT架构中存在两种IT形态,微服务 “小、快、弹性好”,刚好适合靠近市场的顶层业务领域。因此,市场运营和终端消费业务领域最适合采用微服务架构,变化越快,微服务架构的优势越明显。当企业面对激烈的市场竞争和多样的终端渠道时,微服务架构是一个非常好的选择。 


微服务的问题




微服务的问题


但是,也不要以为采用了微服务就能走上研发的康庄大道,从此过上幸福生活。这个世界没有银弹,上帝给你开了一扇窗,就会关上一扇门。

我们确实能利用微服务架构解决单体系统的很多问题,但同时也带来了一系列新的研发、运维及协同问题。

服务化的本质是一个“拆”字!原来的单体应用被拆成了大大小小的应用集群和服务集群,分散到网络的各个节点,由不同的团队负责,每个团队各管一段。

在这个离散化的大背景下,运维和研发都会遭遇一系列新的问题和挑战,例如集群环境中故障的定界定位:你如何快速定位问题出在哪?整个集群的调用关系是什么样的,如何梳理?是否合理?整个集群的性能瓶颈在哪?这些都是运维要直面的问题。

开发要面对的问题也不少。团队被拆分了,团队之间如何高效的协作?系统被拆分了,功能被分散到远程节点,如何做调试?分布式架构下的数据一致性如何保障?

。。。。。。

所以,架构的变更带来的影响绝不仅仅只是应用系统,它会对整个研发体系,包括开发、运维、团队组织、协同带来全方位的冲击!你必须构建起一整套从线下到线上的新的能力体系来支撑这套新的架构。很多团队用了微服务,但没有构建起这套能力体系,直接在服务化的“反作用”下,倒在了路上,看不到服务化带来的“曙光”。

那么,这套新的能力体系是什么呢?

我大概总结了一下,它包括了服务治理、架构分层、统一监控、APM、稳定性建设、敏捷协同、精益度量、DevOps、分布式开发调测、契约测试等等。


微服务的度量



微服务的度量



微服务的度量要解决的就是微服务的“看”的问题,这是对微服务进行管控管理的前提和基础。只有全面了解微服务集群的线上运行状况及线下的开发效率、开发质量问题,才能“有的放矢”的提出针对整个微服务体系的问题。这些问题不仅针对架构,同时还针对开发、运维、研发组织及团队协同等方面。根据问题进而制定改进措施和治理策略,并最终落地。 

所以,只有先解决微服务的“度量”问题,才能解决微服务的“管理”问题

如何构建微服务的度量体系呢?本质上还是要从数据入手,准确的说是能够衡量微服务状态研发等领域状态的指标数据。这些数据分为线上数据和线下数据两大类。

在线上,可以通过服务注册中心获取到服务的注册信息及服务的管控指令信息,通过各个微服务主机节点上的主机日志、应用及服务日志、APM监控的调用链日志,可以获取到相关的性能及异常指标信息。

线下的指标就更多了。通过需求管理系统,可以采集到UserStory及各类需求的基本信息;通过项目管理系统可以采集到开发人员、开发团队、开发任务的基础信息;通过测试相关的管理系统可以采集到测试用例及测试bug的相关定义信息及过程指标信息;通过源码仓库及软件版本仓库可以采集服务之间的调用关系,还可以通过代码扫描获取到服务之间的静态调用关系,以及最终研发产出物的基本信息。

软件研发是一个强调协同的群体性行为,产品、开发、测试、运维需要紧密合作。为了达到更高效的配合,我们经常会采用一些协作模式,比如针对开发和测试之间的配合,采用持续集成(CI);针对产品、开发、测试的协作,采用敏捷协作模式;另外,我们可能还会使用一些DevOps的Pipeline。不管采用何种协作模式,都可以从其相关的过程管理系统中抽取出相关的过程指标事件,比如一个任务什么时候完成设计、什么时候开始进入开发、什么时候完成开发等等,这也是一大类很重要的治理度量指标。

将这些线上线下指标采集后统一汇总到数据仓库,进行进一步的深度度量和分析。

这些原始指标以ODS(操作型数据)的格式汇总到数据仓库后,通过数据模型抽取出主数据(MDM),主数据包括了服务、需求、任务、人员、团队等。在此基础上,通过不同的数据主题构建起一个多层的“数据集市”,这些数据主题包括了异常、性能、资源、调用关系、容量、系统、测试、开发、运维协同效率等。基于这个数据集市,就可以进行各类数据分析,包括性能分析、容量分析、健康度分析、团队以及个人的质量报告、质量趋势、动态调用链及静态调用链的深度梳理以及各维度的汇总报表

根据这些分析报告,由治理委员会进行深度的分析并制定出各类的治理决策,通过人为或自动化的机制发出各类管控指令。

所以,治理决策管控指令就是微服务度量及分析体系的最终产出物。我们通过治理决策和管控指令对微服务的线上及线下体系进行治理。


微服务治理架构




6  服务治理的整体架构图


图6是服务治理的整体架构图,它展示了度量、管控、管理三位一体的治理格局其核心观点是:微服务治理既要进行线上的治理,也要进行线下的治理


前面介绍的线上、线下两大维度的治理指标被统一汇总到数据仓库中。这些度量指标中,有相当一部分线上的性能及异常指标会被直接转化为运维事件,一旦触发预先设置的阈值,就会被进一步转化成治理“管控指令”,通过调度中心下发,驱动云上的资源编排和调度能力,进行服务的弹性伸缩、扩容缩容操作,以应对流量的波动;或者直接驱动服务进行服务内部的限流、降级、容错、路由调整等管控操作。

另一部分度量指标,特别是线下度量指标,例如架构、开发、测试、运维、过程协作效率等会通过治理委员会(泛指,治理成员的集合)进行人为的深入分析,制定出治理决策。这些治理决策包括技术决策、研发团队调整、过程优化、技术选型决策、线上资源调配、线上系统管控等等。治理决策会通过相关的管理措施进行落地。

这样,我们通过服务的度量、管控、管理三大举措,构建起一个三位一体、围绕服务治理的闭环体系。在这套体系中,针对服务的度量是进行服务管控和管理的前提和基础,只有看得到,才能管得到,必须先解决“看”的问题,才能解决“管”的问题。


微服务架构选型


图7 微服务框架的架构模式


7是目前微服务框架的两大架构流派,一种是“网关代理连接模式”,一种是“客户端直连模式”。这两种方式的服务提供者都会在服务启动时到服务的注册中心去注册。但在网关代理模式下,服务调用者只通过网关调用服务,不关心服务提供方的具体节点信息;直连架构的调用者会从服务的注册中心去拉取相应服务的节点列表。


这两种模式最大的区别是路由、容错和负载均衡策略的实施主体不同。代理网关模式进行服务调用、服务治理的主体是服务代理网关,客户端不需要关注这些治理动作,甚至不需要知道服务有多少个节点,所以客户端可以做得很轻巧,除了连接协议之外,对客户端的开发语言没有什么约束,嵌入的SDK很轻巧甚至不需要SDK。直连模式由于没有代理网关,所以服务的路由、容错、负载均衡策略的实施有很大的比重都需要在客户端进行,客户端需要拉取对应服务全量的服务节点信息,并时刻关注节点的上下线情况,所以嵌入的SDK很重,与服务框架会形成强耦合的关系。 

但是,由于代理网关模式在网络上多了一跳,为了兼容多样的客户端调用,网络在服务调用方和代理网关之间通常采用HTTP这种通用的短连接协议。而直连模式由于是客户端直接调用的服务提供方,可以采用更高效的RPC长连接模式,比如基于netty的NIO长连接协议,所以在处理效率上P2P直连模式往往要高于代理网关模式。对于很多大型互联网公司,由于线上调用量大,RPC方式可以有效提高处理效率,因此这些公司通常会采用P2P直连模式,这样可以有效的节省服务器资源。比如国内公司用的很普遍的dubbo或者蚂蚁金服提供的sofa等,都是采用RPC直连模式。SpringCloud同时提供了两种框架,既可以使用Ribbon提供的P2P直连模式,也可以基于zuul,现在叫SpringCloud Gateway,来实现代理模式。由于SpringCloud的生态比较全,而且又有Privotal这样的公司在持续维护,目前市场占有率越来越高。

但从服务治理的角度看,代理网关模式是要优于直连模式的。由于所有的调用都要集中通过代理网关,所以对整个微服务集群的监控指标采集,路由、负载、容错等管控措施都可以在网关集中实施,不分散,可控性也比较强。而直连模式的治理主体包含了各个服务调用方,指标采集的难点,管控的难度都呈指数上升。集群规模越大,这种难点差异越明显。

所以,究竟选择那种微服务框架,最终还是要基于业务规模、研发资源投入、人员水平来综合考虑。另外,我个人建议,如果要在微服务方面做到可持续发展的话,前期就需要考虑服务治理的问题。



根据康威定义,整个组织的协同沟通模式及行事风格,要和你所采用的架构体系相匹配,否则一定会出问题。

因此,一旦确定引入微服务架构,接下来就是很现实的适配问题,包括微服务架构与业务及研发体系的贴合度问题。如何利用微服务架构顺畅的落地业务需求,如何让研发团队在这种架构模式下高效的设计、开发、运维,并协同配合。

接下来,我们就介绍一些采用微服务架构的方法论。


微服务的规划




微服务规划的三个问题


微服务的规划是一个很大的话题,但总结一下,无非是回答3个问题。

第一:要不要上微服务?

这个问题我们在前面讨论“企业的哪些业务领域适合上微服务”时已经有了结论,越贴近市场的业务,迭代变更速度越快的业务越适合上微服务。前提是需要有一整套支撑微服务架构的能力体系,包括服务治理,强大的运维能力等等。对于一个创业团队,尤其是精益创业团队,很难在短期内构建这套能力,因此创业团队不要太刻意追求微服务。早期功能不多的时候,单体架构对技术能力要求低,维护成本也低,是很合适的选择,功能复杂了再考虑往微服务架构迁移也来得及。

研发团队经常犯的另一个问题是,喜欢基于技术潮流来选择技术,而不是基于业务来选择技术。我们经常会说“别人都在上微服务了,我们也要上”!这就本末倒置了!技术是为业务服务的,基于业务的需求来考虑适合的技术,才是技术选择的初心。

我想强调的是,不要纠结于具体的技术选择,毕竟唯一不变的是变化。一种技术不能包打天下,单体架构不行,微服务架构也不行。业务不断发展,对应的系统也要不断的进化,不断根据业务的需求及环境进行升级和重构。任何一种架构也只是适用于当下,在未来的某个时刻,当业务发展到新的阶段,它也必须随之改变。因此,我们最应该做的是保持进化的心态,构建起一套持之以恒的架构优化体系,通过持续的架构优化,不断对每个阶段的架构体系进行监控和调整,让它能够适配业务发展的要求,这才是架构的可持续之道。 

第二:如何定义微服务

我在很多场合都被问到一个问题:怎么把当前的业务或者当前的一个单体系统拆成微服务?我的回答是:按业务领域拆!业务域本身也是分层的,比如对基金交易业务来说,可以分为用户域、交易域、资产域等等。用户域又可细分为客户域和账户域。再细分,还有认证域和授权域。当你一层一层的细分下来,直到业务上已经无法再细拆的时候,就是微服务了。每个服务还可以包含若干个API,比如登陆服务,基于账号密码登陆是一个API,基于微信或支付宝的第三方认证登陆是一个API,静默登陆可以是另一个API。这样,一个微服务的定义就明确了。从一个单体系统逐步的拆分成微服务架构也可以按这种模式来,一步步、一点点的把功能逐步迁移过去。Martin Fowler将这种策略称为绞杀,还是比较形象的。这也符合系统是进化而来的规律,旧的单体系统慢慢消亡,微服务应用在逐步成长。

这两年领域驱动设计(DDD)很火,关键是大家发现它的一些设计思想包括子域、限界上下文、映射、聚合等等与微服务架构天然能对应上。但它本质上还是面向对象的设计,只是在对象之上增加了域的隔离和交互等能力,它的有些概念比较拗口,对于是否需要深入研究领域驱动设计大家不用太纠结。我个人的经验是,只要坚持从业务域划分面向对象设计,你的微服务设计一般不会太差。学习一些DDD的好处是在做复杂业务的微服务设计时,条理性更好,同时通过DDD这套领域设计语言,实现需求结构化,让业务人员和开发人员能够更顺畅地对话。

另外,不管多么精心设计出来的微服务架构也不能包打天下。必须承认我们的认知是有限的,只能基于当前的业务状态和对未来演变的有限预测来制定相对合适的拆分方案。任何方案不管多完美,都只能保证在当下是相对合适的,要时刻做好在未来的某个时刻会变得不合时宜需要再次调整的准备。所以,变化是永恒的,我们要时刻关注微服务的拆分粒度是否合适,如果不能满足需求就要继续拆分它。

在实际操作中,我们每一期的迭代开发,除了业务需求还有架构优化需求,就是用来应对这些架构技术层面自我进化重构的任务。

第三:多小才是“小”

我们在讨论按业务领域拆分并定义微服务时曾说过,当你感觉拆无可拆的时候,它就是微服务了。那么,是否每个微服务都要独立部署呢?其实不然,逻辑划分实际部署是两回事。理论上,每个微服务都可以独立部署,但是以资源无限为前提的。实际应用中,我们往往将微服务合设进行部署。一个一级业务域或者二级业务域下的微服务会统一成一个应用部署,这样可以节省硬件资源和降低维护成本。从这个角度看,实际的微服务应用粒度可以很大,不用太纠结“多小才是小”这个问题。开发和运维觉得合适就可以了,反正重构是常态,不合适继续拆,或者合并,就行了。

微服务及调用关系示例


我们基于前面介绍的规划方法进行微服务拆分。 结果拆出了一大堆微服务,密密麻麻的调用关系如图9所示。

这张图给我们第一直观感受是什么?

多! 

,就意味着复杂。图9中服务之间的关系已经超出了人眼可以辨识的范畴。所以,需要对这些微服务进行组织和管理,让它整体更清晰。

服务分层是降低服务化架构体系整体复杂度的一种有效方式。


微服务架构分层


10 两层服务分层示意图



为了便于理解,这里以我们的基金交易平台的服务化作为例子。

在系统进行服务化改造的早期,我们主要是将各系统中通用的能力,如用户账户、交易、支付、资产、结算、权益、投顾等功能抽取出来,改造成服务后以服务集群的形式独立部署,统一对上层应用提供通用服务。这就是我们内部俗称的8大服务中心,它们共同构成了后台的通用服务层。原来的业务系统拆分后变得更轻了,更多的充当了一个交易大厅的角色,我们称它为前台服务层。 

我们的网关层共提供了两套网关,一套是SLB,另外一套是移动网关。用户的交易请求通过网关层被统一接入进来,先在前台服务层进行协议解析、安全校验和简单的业务逻辑处理后,通过调用后台通用服务层的服务进行基金的相关交易。

由于早期前端的业务渠道不多,业务也比较单一,这样的两层服务化的拆分已经够用了,前台服务层的粒度足以支撑正常的业务迭代速度

11 三层服务分层示意图

随着业务进入快速发展阶段,大量的终端渠道接入,业务迭代的速度也明显加快。举个例子,我们有一个叫“目标赢”的定投产品,在很多终端渠道上进行销售,每个渠道对这个产品都或多或少有一些个性化的诉求,有在客户校验上增加特殊要求的,有要求在产品销售上叠加一些额外的运营规则的。综合来看,这些终端渠道对“目标赢”的功能诉求中,有80%是一致的,有20%的个性化诉求。如果按早期的两层服务划分的方式,我们需要为每个服务都开发一个重复度很高的功能,这明显不划算,太重了。怎么办呢?继续拆!将“目标赢”里与渠道无关的80%的通用功能拆出来继续下沉,形成了一个新的服务分层,我们称它为业务服务层。这样还不够,继续将前台服务层中剩下的20%个性化能力中与业务无关的部分,例如协议适配、拆包、安全校验等功能拆出来,与网关层合并形成了安全网关这个新的扩展。这样,前台服务层只剩下了10%左右的业务功能,里面大部分的逻辑是对业务服务层及通用服务层的能力进行组装聚合。前台服务层已经被拆的很轻了,我们可以改称它为聚合服务层。通过这种继续拆分的方式,将早期的两层服务化分层架构,演进成包含聚合服务层、业务服务层和通用服务层的三层服务化分层架构。

17年之后,“业务中台”这个概念大火,我们发现三层服务化分层架构中的“业务服务层”与业务中台定义高度匹配,把它定位为基金销售业务的业务中台也是完全OK的。因此,聚合服务层、业务服务层和通用服务层这样的三层分层定义换个说法就是业务前台、业务中台和通用后台。这样的分层架构并非我们刻意为之,而是基于业务的驱动,在服务化的架构下自然形成的。

12 架构演变


将我们线上系统的架构变迁的整个历史拉通来看,可以看到明显经历了3个阶段。


早期是典型的单体+竖井架构,复用性及可扩展性都差。 

在服务化的初期,将系统进行了两层服务化的拆分:前台业务服务层+后台通用服务层。当业务发展比较平稳,前端业务渠道不多的时候,两层服务化分层足以支撑业务的正常迭代。

随着业务的快速发展及多端适配需求增多,两层架构下的前台业务服务层还是太重,无法支撑业务的快速变更。这时需要继续拆分,将业务域的通用服务继续下沉出新的业务中台层,这就形成了现在的三层服务化分层架构。

在目前的三层服务分层架构下,通用服务被不断下沉,因此越底层的服务抽象度越高,越通用也更静态,不会经常改变;越靠近前端的服务越贴近业务也越不稳定,会随着业务的快速变化而不断改变,客观上必须保持更轻的体态。

业务中台的存在解决了两层服务化架构下前台业务服务和后台通用服务之间的适配问题。可以将前台服务的粒度拆分的更细,让前台服务不用通过大量的代码来处理业务逻辑,只需要少量的粘合剂代码来对中台专属业务领域的通用服务和后台跨业务领域的通用服务进行快速组装和聚合,从根本上降低了前台服务开发的工作量和成本。

“快”是很重要的一种能力!

业务变化越快,多端适配的需求越多,中台建设的收益越大。如果业务比较平稳,没那么多的业务渠道,两层服务化架构就能工作得很好,服务分层和拆分的需求就不那么强烈,进行中台建设的意义也就不大。因此企业应该根据自身的业务特征来判断是否需要建设业务中台,顺其自然。


架构治理


13 线上服务集群调用关系



在传统企业级开发中,一般遵循先架构设计再开发的协同联动原则。但在服务化架构下,由于原来的单体系统被拆的很散,传统架构师的职责更多的被分散到一线研发团队,由开发人员来承担,架构师的职责貌似被减弱了。而且由于服务的粒度比一般应用小,架构的工作也好像没有以前那么重要了。

恰恰相反,在离散化体系中更要加强对架构的管控,以防止整体架构劣化。与传统的架构先行的模式不同,服务化下的相当一部分架构工作被“后置”了,成了一种事后行为,也就是说架构度量和优化的工作会大幅度上升。这里我们主要讨论如何通过服务之间的调用关系来针对服务化的整体架构进行度量和治理。

图13是线上服务集群服务间的调用关系总图。这个图可以通过动态调用链的汇总来获取,目前大部分公司都是这么干的。但使用动态调用链有个很大的缺陷,调用链只有在运行时才能生效,而且必须有埋点并实际发生的调用才能被监控和采集数据。对于一个复杂的平台或大系统而言,通常有大量的冗余分支异常处理逻辑,这些分支及逻辑,往往需要在特定场景下才会被触发,甚至有可能永远都不会被触发。所以通过动态调用链抓取的这个服务之间的调用关系是不完整的。我们除了使用动态调用链,还开发了静态代码扫描技术,通过代码之间的调用关系来生成调用关系图,这种方式可以有效弥补调用链的不足。基于微服务间的整体调用关系,我们可以采用图论的相关算法对微服务的调用质量进行深入的分析。

服务是分层的,好的服务调用关系也是分层的,层层往下推进,最终形成一个有向无环图(DAG)。因此,可以对调用关系图进行闭环检测。如果检测到如图G点到B点的回环调用,说明调用关系是有问题,需要进行优化。这种回环调用现在也许无感,但难保未来哪天就会由于一条旁路逻辑导致死循环。

可以对整个调用网络进行遍历计算,找出所有调用深度最深的调用链,如图13红色标注出来的调用链。对跨网络的调用访问,涉及到的网络节点越多,稳定性越差。可以将所有调用链路最深的链路找出来,按调用深度进行topN排序,重点分析排在头部的调用链的必要性和合理性,看是否能对调用深度进行缩减和优化。

还可以找出整个网络中被调用最多的服务节点,比如图13的F节点。从调用关系上来说,它是被依赖最多的节点,自然是最重要的节点,作为枢纽节点,在运维等级上需要重点保障。实际应用中,我们还会加上调用量这个权重来综合判定服务节点的重要性

随着架构的不断演进,可能有些服务节点再不会有调用关系了,比如图13中绿色的L节点。这些节点再不会去调用别的服务节点,别的服务节点也不会来调用它。这类“孤零零”的节点被找出来,可以考虑对它进行下线处理,以释放资源。

如果有了服务之间的完整调用关系,一旦某个服务需要修改,我们就可以循着调用链路逆流而上,找到它可能影响到的前台业务。这样,不用等到服务上线,在修改之前就能评估到它的变更影响,从而降低服务变更的风险。

以上所有的度量和治理都是在调用关系图的基础上进行的,所用的算法也是图计算(图论)中的常用算法,包括BFS、DFS、PageRank等等。大家如果嫌麻烦,可以找个图数据库(比如neo4j),这些算法已经集成在它的基本查询能力中。


DevOps体系




图14 DevOps体系示例
我们在前面讨论了企业引入微服务架构最主要的目的就是要“快”,要实现价值快速交付

微服务架构通过“”的方式解决了单体系统的解耦问题,但也导致运维工作量直线上升。拆分后的微服务的编译、打包、部署及背后的环境维护工作是原来单体系统的几倍到几十倍。如果没有自动化工具和平台的支持,研发和运维人员都将寸步难行。 采用微服务架构之后,由于工程数量多了,上线频度也快了,产品、开发、测试、运维几个团队的协同配合难度也将升高。

要解决这些问题,需要我们去构建完善的DevOps体系。

DevOps的理论体系这里就不多说了,下面重点介绍我们是怎么玩的。

   1、业务需求分解为UserStory后,被逐项整理为Jira的工作项,进入backlog中。

    2、迭代开始后,在Jira中创建迭代对应的Sprint,并从backlog中捞一批工作项,指定开发人员,进行需求下发,同时配置管理员在Git中创建新的开发版本分支。

    3、开发人员利用开发机进行编码开发。

    4、提交的代码会触发流水线的构建服务,调用Maven进行编译,生成构件。

    5、构件生成后会通过自动化的单元测试和集成测试对它进行校验,只有通过测试验证的构件才会被提交到构件仓库;如果没有通过测试验证,则终止发布活动。

    6、手动或者自动进行部署。部署过程直接从云上申请资源,我们用的是蚂蚁金融云,这个过程会从构件仓库下载指定构件。环境部署过程中,会通过预定义的各种验证机制进行环境可用性的验证。

    7、一旦部署通过就可以测试了,这个阶段包含手工测试和自动化测试。自动化测试环节会调用预先定义的大量自动化脚本进行API接口测试,或者调用Selenium、Appium这类自动化测试工具进行功能验证、性能测试等等。测试通过,测试团队会发出测试准出指令。如果测试不通过,就会在jira里生成指定bug工作项,让研发人员进行缺陷修复,直到所有缺陷都被修复,测试准出。

如图14,在整个流水线流转过程中,我们会用到3个看板:

u  管理看板:监控需求流转状况及效率;

u  精益过程看板:监控、分析整个协同流水线的运行效能;

u  运维看板:主要涉及到环境的运营监控。

以上这些看板都需要数据的支持。所以,在流水线流转过程中,一方面研发人员要基于自身任务的完成状况及时更新Jira中工作项的状态;另一方面,也可以基于工具的执行结果自动采集或推送需求状态。这样,Jira中的需求看板就能实时展示当前任务的完成情况。采集流水线各个环节的执行过程指标,包括任务完成状态、开始时间、任务耗时等,汇总计算后再通过精益看板对研发过程进行度量和分析。至于运营监控,则是采用运维监控看板展示各个环境中的相关日志、告警信息等。

图15 服务化架构下的研发痛点 

在服务化的过程中,研发遇到的第一个困难,一定是调试。原来单体应用中的服务被拆分到不同团队,部署在不同的服务器上,而本地只有一个服务接口。这时候要做调试,要么做P2P直连,要么做MOCK。采用传统的MOCk手段,要写一堆的MOCK语句,比如用mockito,就要写一堆的when…..thenReturn….的语句,耦合度非常的高。

我们是利用分布式服务框架提供的过滤器机制,开发了一个Mock过滤器,通过Mock数据文件来详细定义要被mock的服务的名称、入参及出参。当请求过来的时候,将服务名及入参和mock数据中的定义进行比对,结果吻合就将mock数据文件中的出参反序列化后作为服务的调用结果直接返回,同时远程调用的所有后续操作被终止。这样,就通过mock数据模拟了一个真实的远程服务。

通过这种方式来构建服务的mock能力,就不需要写一堆的mock代码了,而且整个过程对业务逻辑来说完全无感知,把mock能力完全下沉到底层的服务框架。

另外,为了有效降低制作mock文件的成本,我们开发了一些辅助工具,可以基于服务接口定义直接生成mock文件的框架。我们还基于服务框架过滤器机制开发了“在线数据抓取过滤器”,它可以将指定的服务请求的入参和返回结果都抓取下来,直接写成mock数据文件。通过抓取方式获得的mock数据文件,往往有更好的数据质量,毕竟反映的是更加真实的业务场景。不过,这有合规性的问题,一定要做好数据的脱敏处理。我们目前只在测试环境中进行数据抓取操作。 

我们的综合调测能力是综合P2P直连和Mock两种方式来共同构建的。综合调测能力的构建可以有效改善服务化架构下团队的开发效率,而且团队规模越大,效果越明显。



谓术,就是一些微服务的监控、度量和管控的具体方法。


度量之术



16 度量基础


我们做服务度量的具体方法,就是由点到线,再到面,最后构成一个监控及度量的立方体。

一个最简单的跨网络的请求,如图16的左边。如果我们把每个请求对服务的调用耗时和它的调用状态都记录下来,所谓状态就是成功或者失败,如果失败还要额外记录失败信息和失败码。同时也可以记录服务对外部服务,包括数据库、缓存、消息队列的调用耗时和状态;记录操作的动作,比如对数据库的查询插入删除等,以及所操作的是哪个表等等信息。 所有这些信息里面,最核心的就是两个指标,调用延时调用状态

有了单次请求的调用指标,进一步汇总一分钟之内所有请求的这些指标,叠加后就得到了图16右边的示意图。可以看到在这一分钟之内,各服务的调用系的调用情况,包括调用次数、成功和失败次数、总耗时等详细信息。这就是单个度量时间段的基础汇总指标。同样的,对其它服务和资源的汇总统计也按这个模式来。当然,对资源的度量会更细一些,我们会计算update操作发生了多少次,select操作进行了多少次,维度也会更多一些。

这样,单次请求的指标就构成一个,一分钟单个节点的汇总指标就构成线,再把一个服务下所有服务节点的信息进行汇总就构成了描述一个服务的完整的。再汇总所有服务及资源的调用状况就构成整个微服务集群度量的指标立方体了。

要注意的是,一分钟是基础汇总维度,在这个基础上还可以做小时、天、周、月的汇总度量。 这就是我们针对服务度量的最基础的方法。

17 性能度量


线上服务最核心的两个度量维度,一个是性能,另一个是异常。性能度量是微服务度量工作的基础,也是最重要的工作。

性能度量最核心的几个汇总计算指标是性能最差服务度量,总资源占用最多服务度量以及这两个指标的一些演化指标,包括历史同期的横比指标以及性能分布等等。在性能报表上,我们还会叠加调用量等指标,便于多维度的分析。如果一些服务调用耗时高,调用量又大,那么出现阻塞的风险就会很高,大概率存在一些隐患,需要具体进行分析。

另外,对调用耗时还可以看它的分布,查看一些长尾效应,考察有没有性能毛刺。如果有,尤其是有周期性出现的“毛刺”,它本质上和系统的“脆弱性”有关。在高并发/大负载等极限情况下,这种“脆弱性”将被放大,可能给系统造成严重影响。通过分析调用耗时这类性能分布图,很容易发现这类异常指标。

性能指标有时也要结合系统指标进行综合分析,低负载状态和高负载状态下,就算性能指标一样,系统的健康程度也是有巨大区别的。为了获得更客观的系统性能指标,有时还要综合一些系统及服务容器的指标进行综合分析。

18 异常度量


服务度量的另外一个维度是异常,异常的准确定位有时很复杂。因为异常是会传导的,它会从底层往顶层,从服务的被调用方向服务的调用方传导。 所以,对异常的度量往往要基于关系来进行分析。我们日常收集的日志是散的,这些线上日志往往描述的是单个点的状态。所以,我们尽量要通过其它的一些手段来对这些离散化的日志进行关联。

举个我们进行线上故障定位的例子。我们有个线上的接口监控报警,如图18-1。服务集群中的一个服务(A服务)周期性的出现操作异常率偏高的现象,监控数据显示大量的异常都是由于请求耗时延长,超出了API预定的最大延时阈值而被服务框架主动终止请求导致的。 这时,我们将正常时段的监控数据和异常时段的监控数据分别叠加在这个A服务的静态调用链路图上,如图18-2。可以看到,在这个异常时段A服务对大后台的一个服务(B服务)的接口的调用比例非常高,而且调用延时也从正常的几十毫秒飙升到1000多毫秒。我们高度怀疑可能是其它业务对B服务的调用挤占了资源,从而造成调用堵塞。基于这个假设,以B服务为起点,调看它的“被调用关系图”, 如图18-3。果然,除了A服务外,还有一个定时批处理任务也调用了B服务,而且在异常时段对B服务的调用量非常大。查看处理逻辑,发现这个批处理任务是用来遍历所有用户并进行用户积分变更操作的,这个过程中,它会调用服务B。由于用户数量特别多,它在并行分片处理时没有做限流,短时间内会发起大量的针对服务B的调用从而导致资源被挤占。服务B的调用延时虽然增长,却没有超出预定的最大延时,也就是说,服务B不会报错。但性能瓶颈传导到服务A后,直接导致了服务A的调用延时超长,超过微服务框架预设的最大阈值,最终导致请求被终止,引发请求失败故障。故障查明后,对这个批处理进行限流操作,同时对它的调用做了缓存处理,问题解决。

这就是典型的由一个问题导致另外一个问题的故障传导。传统监控中,监控数据在呈现模式上是“散”的,很难发现相互间直接的联系。我们通过引入静态调用链路图,通过调用链的关系来关联监控数据,让监控数据从“无序”变成了“有序”,从而为我们提供了快速定位问题根源的途径。

服务治理以及度量的过程本质上就是寻找数据关系的过程。所以在实际治理工作中,要通过各种手段来获取数据之间的关系,包括时间、空间、业务等各个维度。静态调用链路仅仅是其中的一种手段而已。只有获取了数据的关系,才能够顺藤摸瓜,找到问题症结及治理策略。

19 调用链跟踪


调用链跟踪算是分布式环境下故障定界定位最有效的工具了,是更高级的故障定界定位手段。相关产品也很多,开源的有Zipkin,SkyWalking、PinPoint、CAT,商用的有听云、AppDynamic或NewRelic等等。

调用链本质上也是基于日志,只不过它比常规的日志更重视日志之间的关系。在一个请求刚发起时,调用链会赋予它一个跟踪号(traceID),这个跟踪号会随着请求穿越不同的网络节点,随着日志落盘。日志被收集后,可以根据traceID来对日志做聚合,找到所有的关联日志,按顺序排序,这样就能构建出这个请求跨网络的调用链。

要更好的利用动态调用链需要和监控大盘相结合。我们的经验是,动态调用链跟踪体系构建得比较早,在监控大盘上有很多的点可以进入调用链。例如,我们有一个单位时间段内异常最多服务的TopN排序列表,点击列表上的任何一个服务,可以打开这个服务在这个时间段内所有异常的列表,点击列表上的每一个异常,就会打开这个异常所属调用链,进行故障分析。还可以利用监控大盘,监控大盘上有很多“毛刺”,这些都是系统的异常点。点击任何一个“毛刺”,会将“毛刺”所在时间段内的请求以“散点”的形式列出,“散点”的颜色代表了不同的状态,有的成功,有的失败。点击任何一个“散点”,就可以进入这个请求对应的调用链。针对核心服务的异常也有专门的一个监控表格,会列出最近发生的核心链路服务上的异常,点击这上面的任何一个异常,同样可以进入对应的调用链。

以上就是基于动态调用链进行线上故障定界定位的常用模式。


管控之术


以上介绍了一些服务度量的方法,解决了看的问题,接下来开始管。对微服务进行管控又有哪些手段呢?


19 管控之术——限流


首先介绍服务限流。

服务限流是微服务集群自我保护的一种常用机制,我们对线上调用比较频繁和资源占用较大的服务都加上了相应的限流举措,并构建了单机限流及集群限流两套限流措施。

单机限流有多种限流算法可供选择,最主要的是两种,漏桶算法令牌桶算法。它们之间有什么区别呢?打个比方,疫情期间很多餐厅都限制客流,一种举措是出来一个顾客,才放进去一个顾客,保证餐厅中的顾客总数是固定的,人人都有座位,这就是漏桶算法——必须有“漏”出去的,才能有进来的。另外一种举措是不管有没有顾客出去,门口招待固定每隔5分钟就放一波客人进来,这和春运火车站的波段式限流非常类似,可以保证客流比较均匀。但这种策略的风险是,如果餐厅中的顾客离开得不够及时,餐厅中的顾客总数可能会升高,导致一部分顾客没有座位,这就是令牌桶算法。因此,如果要对线上并发总数进行严格限定化,漏桶算法更合适。这是单机限流机制。

集群限流的情况要更复杂一些。首先在各个微服务节点上都要有一个计数器,对单位时间片内的调用进行计数,计数值会定期汇总到日志中心,由统计分析器进行统一汇总,算出这个时间片的总调用量。集群限流分析器拿到总调用量后与预先定义的限流阈值进行比对,计算出限流比例。这个限流比例通过服务注册中心下发到各个服务节点上,服务节点基于限流比例各自算出当前节点对应的最终限流阈值,最后利用单机限流进行流控。

可以看到,这是一套环环相扣、各环节紧密协作配合的技术体系。单纯拎出一个点来看,实现技术都不麻烦,但要构建一套贯穿整个技术栈的技术体系,则需要有一套统一的技术标准。各个环节都要遵循这套标准,对不符合标准的应用要推动其进行改造,保证标准落地,才能构建起这套限流技术体系。因此构建服务限流能力的难点有两个,标准化体系化

体系化的构建是最难的,但也是最有威力的,大公司往往就是依靠体系的力量去构建起护城河,碾压没有体系能力的小公司。

限流一大原则是限流动作尽量前置,毕竟被限制的流量注定要被“抛弃”,越早处理越好,免得无谓的消耗资源。

20 管控之术——降级


服务降级和服务限流类似,也是微服务集群自我保护的机制。一般在线上动用服务降级手段的时候,都是比较危急的时候,生死存亡了,这时候留给你思考和反应的时间不多。所以在使用服务降级之前一定要做好预案,要提前梳理出核心业务链路和非核心业务链路,然后通过降级开关一键把部分或所有非核心链路降级,这样才能救命。

服务降级也有很多手段可以使用,包括:

u  容错降级

u  静态返回值降级

u  Mock降级

u  备用服务降级

我们常说的熔断,本质上也是容错降级策略的一种,只不过相对一般容错降级,它提供了更为丰富的容错托底策略,支持半开降级全开降级模式。

构建服务降级能力也和限流机制类似,同样需要坚持标准化和体系化。

21 管控之术——容错

 集群容错是微服务集群高可用的保障,它有很多策略可供选择,包括:

u  快速失败(failfast)

u  失败转移(Failover)

u  失败重试(Failback)

u  聚合调用(Forking)

u  广播调用(Broadcast)

很多容错策略都是靠重试来实现的,但不管你使用哪种集群容错策略,都要注意重试的次数,尤其是线上负载已经很高的时候。这时候如果重试次数过多,一方面会推高服务被调用方的负载及并发,另外一方面会导致服务调用方的调用延时增长,双重因素叠加之下,最终极可能引起“服务雪崩”,导致集群被“击穿”。

所以,在使用集群容错时,一定要设置最大重试次数。另外,有可能的话,尽量做“重试减速”,也就是让每次重试的间隔时间越来越长(可以考虑采用指数),这样可以将重试带来的压力尽量分散到一个足够长的时间段,避免阻塞在一起从而人为造成DDOS攻击。


总结


微服务架构的道、法、术囊括了从体系到方法论,再到具体算法的三个层面。其中 “道”是体系,主要讨论微服务应用场景,微服务带来什么问题,要解决这些问题需要构建什么体系,以及我们如何根据业务去选择微服务的模式。 “法”是方法论,包括微服务的规划,还有架构分层,如何更高效的协同。“术”是具体的方法和技术,包括微服务的度量,度量里面也包括性能,还有微服务的管控,包括限流、降级、容错等等。

这就是我今天分享的内容。谢谢各位!


最后——

我把十几年的经验总结起来,出了本书,叫做《微服务治理:体系、架构及实践》,大家如果感兴趣可以关注一下。




往期推荐:


技术琐话 



以分布式设计、架构、体系思想为基础,兼论研发相关的点点滴滴,不限于代码、质量体系和研发管理。

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存