技术专家
陈珙
轻阅网络科技系统架构师
阿里云开发者社区专家博主
【作者介绍】深耕.Net领域10年,专注微服务架构、分布式系统设计、高并发方案与高效工程化。拥有多年的开发团队管理与系统架构设计经验。广州.Net俱乐部成员,博客园同名作者。
写在前面:
大家曾经有没有遇过日常技术交流的时候,会讨论某某技术之间的关系是什么,某些技术是否应该用到微服务。我相信热爱技术交流的您,就算不是在微服务这个领域里,曾经或多或少也跟其他同行 做过一些争议性话题的探讨吧,而且我敢肯定讨论的热火朝天。
今天我想从微服务的4个比较火,又值得拿出来交流的话题进行出发,与大家分享我对微服务的一些个人见解,结合这4个话题展开与你聊一聊:微服务来带的新问题、微服务与SOA、微服务与DDD、是否有必要引入聚合层。
这里部分话题,在业界会存在一定的争议性,例如DDD的引入、微服务与SOA的关系。
一千个人眼中有一千个哈姆雷特,不同的人以不同的视角去看待这些问题,都会拥有不同观点与答案。
观点上,咱们求同存异,不盲目遵从别人的想法,也不自以为是的把自己当成标准。
当然了,我并不会为了输出个人见解而故意制造观点与话题,更不会把我所有的观点当成一个“所谓的”标准。因此在该篇文章,我会把多处理收集的资料梳理好放到文内,有理有据地结合自己的见解与大家分享一二。
这些内容可能是大家日常容易混淆的,也可能是大家多次纠结不知道如何做出选择,因此我希望通过我该篇文章的分享,能给大家带来新的观点上的碰撞,或是见解上的共鸣。
本篇内容较长,如果你对其中的某个话题,格外感兴趣,可以直接跳到对应的部分,选择性阅读。好了,话不多说,我们正文部分见。
责编 | 韩楠
约 7796 字 | 15 分钟阅读
以下,Enjoy~
01⎪ 微服务带来的新问题
做技术选型就如网上购物一样,即使知道了它的优点,还得看看它的差评。我们得多方面评估,事先知道团队与业务是否能抵御与承受“坑”的风险。
既然任何一样技术都无法成为软件工程的银弹,那么必然解决了某种问题的同时,也会带来一些新问题,微服务也不例外。我回顾了下当时我实施时的难点确实有不少。不过,我个人认为微服务给我们带来核心的问题主要有三点,两文化与一思维:
• 自动化文化
• 可观测性文化
• 分布式系统思维
上面这三个问题,每个的内容单独拿来出讲的篇幅,都足以出一篇完整的文章甚至是书,基于此我会挑一些重点和大家分享。当然,几乎每部分的文末,我会放入相关内容的文章外链,如果大家有兴趣可以自行扩展阅读。
▶︎ 自动化:避免重复的人力劳动
任何的架构模式,也是需要同等的开发模式与之配合的,随着应用的拆分后服务的数量由量变引起质变,因而需要接受自动化来代替从前的“人工处理”,包括服务的部署、服务注册发现等等。自动化,是软件工程的其中一种处理手段,允许团队采用主流的工具、流程形成一套自动化机制,从而减少重复性工作、减少人力干预的不确定性因素。
这里说说我对软件工程的理解:通过多人协作、有目标、有步骤、有计划的,并使用科学方法论指导开发与维护程序的这个过程,也可以换成一条公式表达:软件工程 = 工具 + 流程 + 模式。
无论是我们讨论的这些“事”、技术工具,还是流程制度都是需要人(团队组织)的参与,人的延申就是团队与文化,就如上述所说的软件工程是多人协作的工作,只有当然团队目标一致,共同负责承担团队的项目,共同接受同一种文化才能很好的实施自动化。
我简单举个例子:如同多匹马拉车一样,只有它们都有共同的目标的时候,才能快速拉车到目的,如果它们一匹向东一匹西,只会让马车无法前行甚至四分五裂。
说到这里有些人觉得疑惑,自动化这种能给团队省时省力、减少重复工作量、增加幸福度的技术难道还有人或者团队会抗拒的?是的,还真的有。
我曾经就接触过几个团队的Leader,他们的团队都是推行自动化失败了,失败的原因就在于成员不配合,成员拒绝配合的理由是:没有自己亲手去做总觉得机器不靠谱。我们先忽略他们的想法是对是错,回到团队与文化,从前面可知,统一团队的目标一致性是有必要的,作为技术推行优良的方案,是我们的职责之一。
如果团队成员无法配合,导致推行受阻,我们一共有三种应对策略:激励、考核和逐步试行。
如果有条件的公司可以设置奖金激励,如果有绩效考核的,可以将自动化的实施纳入考核目标,如果这俩都没有,那就选取团队里愿意改变的同事牵头试行,假如使用过后都说好,那么会更有说服力。
这部分就说到这里吧,基于此话题的内容比较多,广而泛,篇幅有限,这里分享一篇我自己曾分享过的内容《.Net微服务实战之DevOps篇》(https://www.cnblogs.com/skychen1218/p/13096784.html),如有感兴趣的朋友,可看完整篇文章后自行有选择性、针对性地扩展阅读。
▶︎ 可观测性:提高团队对系统运作的信息量
如果说自动化是给团队带来稳定性,减轻工作量的,那么可观测性就是提高团队对系统运作的信息量。建立可观测性的这项工作,虽然无法直接给系统带来健壮性,但能够使我们通过这些信息充分地了解到系统正在运作的情况,以至于大程度地做出合适的定位、判断与决策。
在单体应用的场景下,我们也是需要可观测性的,但是单体的架构相对简单,项目调试也更加便捷,无论是从复杂度和规模的角度来看,单体跟微服务相比都要低不少,也因此单体对可观测性的需要,相比于微服务显得没那么重要。
而我们只要进行了微服务实施后,因为应用被拆分成了细粒度,从而导致了架构从量变引起质变,这个时候可观测性的作用在微服务场景下被“无限放大”,也因此我们利用"可观测性",给与我们提供应用与服务器的监控、快速跟踪与问题定位的功能。
可观测性——可以由系统的外部输出推断其内部状态的程度,在软件系统中,可观察性是指能够收集有关程序执行、模块内部状态以及组件之间通信的数据
而可观测性三个维度组成:日志(logging)、跟踪( tracing)、指标(Metrics)。
• 日志(logging)的定义特征是它记录离散事件,目的是通过这些记录后分析出程序的行为。
• 跟踪( tracing)的定义特征是它处理请求范围内的信息,目的是排查故障。
• 指标(Metrics)的定义特征是它们是可聚合的,目的是监控和预警。
因此基于上述总结,有日志记录才能清楚知道当前系统的运行状况和具体问题;指标是给与后续做优化和定位偶发性问题的一些参考,没指标参考就没标准;我们平常做得多的调试、查看调用栈也是跟踪的一种,但是在分布式时代,更多考量的是跨进程通信的调用链路。
日常有小伙伴曾问过我,如果出了那些很奇怪的问题,应该怎么定位、排查,本质上其实还是得提高我们对这个问题或系统的信息量。
例如:是哪个模块、接口出问题?具体服务器表现的情况是怎样?CPU还是IOPS高?具体报错是什么?
你看,说白了还是可观测性的三个要素日志、跟踪、指标。我们在工作中只有灵活结合这三者,才能提高我们对系统运行情况的信息量,信息量越高思考的越是更加全面,才能尽可能地减少“不知道问题出在哪”的状况,所以当我们不清楚具体发生问题的原因时,建议你侧重做一件事:就是尽可能想办法,提高我们对问题与系统的信息量。
▶︎ 新思维:不可避免的分布式系统问题
上文的自动化和可观测性,主要偏向于运维层面,而作为后端开发人员更加关注的是数据与应用服务的层面,特别是服务的拆分后,数据的一致性该如何处理。我相信这问题困扰着不少的后端开发,接下来我将从幂等性、数据的一致性的读与写三个方向,跟大家分享一二。
幂等性
幂等性的定义——相同的参数在同一个方法里,无论执行一次还是多次都会响应相同的结果
对于查询和删除的场景都有天然的幂等性,那么我们考虑幂等性的处理,更多是关注于新增数据与更新数据。
新增数据缺乏幂等性,则会因为网络抖动导致请求重试或者是客户端重复点击,而引发的数据写入重复,其解决方案也相对简单,只要从客户端生成主键传给后端API 就可以解决,在这里得注意一点,只有请求成功或者主动刷新才会重新生成主键。
更新数据缺乏幂等性,主要会造成两种情况,数值错误自增和ABA问题。首先,数值错误自增,可以结合事务凭据与新增幂等性的方式解决。
而ABA问题,解决方案相对简单,可以在更新操作时带上版本号判断进行解决。
ABA问题
对某条记录先更新了A数据,紧接着又更新了B数据,理应是B是新的,但是因为其他客观原因使接口Retry或者别的问题,导致A数据再次请求覆盖了B。
数据一致性(读)
数据一致性读,其实说白了就是做数据关联,从我过往用的解决方案来看共有三种,应用层的接口聚合数据、把更新频率低的字段冗余存储、把数据库同步到一台服务器进行SQL联表处理,每种方式各有优缺点,我结合切身体会和过往经验,以表格方式整理呈现出来,你可以根据业务场景自行选择解决方案。
数据一致性(写)
写的数据一致性,其实就是分布式事务,主流的方案有TCC、本地消息表(基于消息可靠的终一致性)、异步请求/回调。其他的多数是基于以上几种方法的变种,例如RocketMQ的消息事务,就是TCC+本地消息表的变种。篇幅有限,这里分享一篇我曾经编写的文章《.Net微服务实战之必须得面对的分布式问题》(https://www.cnblogs.com/skychen1218/p/14346459.html)。
分布式事务方案,用文字描述起来相对比较吃力,因此我通过流程图代替文字描述展示给大家。下方表格是我对这三种方案的优缺点与使用场景的总结。
▶︎ 小结
从"拆"的层面来看,微服务的思想与优势给与开发人员带来了更多的便捷性,如技术细节的隐藏与认知负担的降低,使得开发人员更加关注自己负责业务代码的迭代。
但是,拆了后还得重新整合,如果拆了无法整合,那么这样的设计是没有意义的。从"整合"的层面,微服务大大提高了对架构师技能的要求,从通信交互到分布式问题,从自动化工程到可观测性,每一项对于架构师都是一种新的挑战。
02⎪ 聊到微服务总绕不过SOA
无论是过去还是现在,只要聊到微服务总有个绕不过去的坎,那就是SOA。接下来,我会结合《Microservices》原文(https://martinfowler.com/articles/microservices.html#MicroservicesAndSoa)的[Smart endpoints and dumb pipes]模块,简单聊聊微服务与SOA的之间的关系,有助于大家更好地了解为何微服务比SOA更容易实施与盛行。
在《Microservices》原文里有一个模块的标题是Smart endpoints and dumb pipes,如果用翻译直译成中文是[智能端点和哑管道],我结合了自己的经验并查阅了多种资料,终认为[强服务和弱通信]作为它的翻译才更加合适。
微服务的[强服务和弱通信],与SOA的[弱服务和强通信]是与之对应的,而SOA架构的核心是ESB,ESB主要功能有消息路由、协议转换、消息转换和应用业务规则的复杂。仔细观察下来,ESB基本上把微服务的基础设施该做的都做了,如此高的技术复杂度,也是导致SOA在过去那些年无法普及跟实施的根本原因。
ESB的大而全的特点,在文中描述到SOA的通信是“聪明的”(smart pipes),而我也因为ESB的特点给SOA的翻译是“强通信”。此外,我翻译的微服务的“弱通信”,并不代表通信的功能有限,而真正的意义是弱化协议差异,使用统一、轻量级的通信协议,减少差异化、降低技术难度。
总地来说,基于两者在通信层面的对比,我们因此能总结出它们本质上的差异:SOA基于配置,微服务则基于约定。
▶︎ 关系的分类
既然上面已经谈到了微服务与SOA之间的对比,接下来,又不得不提SOA与微服务的关系了。我从多个方面的资料梳理后,总结出有这样两种看法:
• 微服务是SOA的佳实践(出自维基百科)—— 微服务是SOA的演进版,粒度更细,基础设施去中心化。
• 微服务拒绝SOA的标签(出自马丁福勒的《Microservices》原文)——微服务与SOA,是两个完全不同的架构风格,只不过刚好长得像。
▶︎ 场景与目的
我相信,不少朋友看到SOA与微服务关系方面的话题时,肯定时间在想,无论是维基百科还是百度,都是写着微服务是SOA的演化这还要说?是的,人云亦云的,大家听多了看多了,不妨从新的观点与角度看待一下问题,这样或许会有新的碰撞与共鸣。
大家认为,微服务是SOA的演进版的理由也比较简单,因为微服务基础设施只不过是把SOA的ESB拆了,而微服务粒度更细,SOA更粗。从表面看起来的确是这么一回事,但是我认为,要看清楚它们关系的本质,就得从两者各自的处理场景目的出发。
首先从微服务角度出发,因为单体应用的大而全的高耦合与臃肿,随着业务的发展迭代的时间越久,就会有各种各样的问题,因此通过把应用拆成了多个"小"的服务,这里使用到了化繁为简、分而治之的思想,解决原有单体的“痛点”和“难点”。
接着从SOA的角度出发,在当时那个年代,希望把各种异构的系统给整合起来,这些异构的系统,有可能是从其他地方买的,也有可能是研发的,也有可能是找外包做的,因缘由存在各种不确定性,所以希望通过一个"聪明的"ESB解决所有的"愚蠢的"问题,也是因此ESB的高复杂度,导致当年SOA那会只是炒得火热,却无法普遍落实。
总地来说,微服务以拆与约定作为出发点,而SOA是以整合与配置作为出发点,所以我个人认为从它们的出发点与目的的角度出发,直接决定了它们在本质上是完全不同的架构风格。所以我更加倾向于《Microservices》原文提到的看法,微服务的倡导者拒绝SOA这个标签的。
03⎪ 微服务与DDD的争议
要是说SOA是微服务理不清的过去,那么DDD更是微服务的一块拦路石。拦住的不是技术人员对它们的热情,而是它们之间扯不清的“捆绑销售”。
《Microservices》原文、《微服务设计》、《领域驱动设计精粹》,都有涉及到DDD对微服务划分的参考。DDD战略上的化繁为简,与微服务的分而治之,这两者的思想,可以说是不谋而合。成就了彼此,但是它们是否必须密不可分,又或者说无DDD就无微服务的说法呢?我是这么认为的。
▶︎ DDD(领域驱动设计)的过去
2004年,埃里克•埃文斯(Eric Evans)发表了《领域驱动设计》一书简称DDD(domain-driven design ),自DDD提出后在软件开发领域一直处于SOA相似的尴尬地位——宣传得火热,真正用的少。
随着微服务架构的兴起,DDD才迎来了自己的时代。在上文提到三本书(《Microservices》原文、《微服务设计》、《领域驱动设计精粹》),都有涉及到使用DDD战略来识别边界上下文,从而划分服务。总地来说,DDD并不是什么新颖的技术,当年许多布道者也没有把它带火,如今却靠着微服务流行起来而加速了DDD的盛行。
但是,DDD的盛行并非它所有的一切,我总结了DDD的一些核心概念,希望在我详细叙述前,可以帮助到你快速了解DDD,以便于对后续内容有个更好的理解:
领域驱动设计的本质是,打破业务与开发之间沟通壁垒,建立通用语言;从而在战略设计与战-术设计两个维度上使用化繁为简手段将通用语言逐步拆解成领域模型;从而确定业务与应用的边界,保证业务模型与代码模型的一致性。
战略设计,从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的界限上下文,界限上下文可以作为微服务设计的参考边界。
战-术设计,从技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地,例如我们常讨论的聚合根、实体、值对象、领域服务等代码逻辑的设计与实现。
就算对DDD的概念再熟悉不过,要是对领域业务缺乏了解,结果也是无法正确建模的,那跟用不用DDD其实已经没什么区别了,所以我认为这种做法与结果是本末倒置的。
PS:[业务与团队]这个模块是先前发表的文章《重新理解微服务之温故》里的内容,两文之间没有特别的前后关联性,有兴趣的朋友,可以阅读完本文后可自行进行扩展阅读。
【维基百科】
领域驱动的局限性,为了帮助保证模型能作为一个单纯并有用的语言结构,团队通常必须在领域模型中实现大量的隔离和封装。因此,基于领域驱动设计的系统可能会花费相对较高的成本。
虽然域驱动设计提供了许多技术优势,如可维护性,但 Microsoft 建议仅将它应用于复杂领域中,在这些复杂领域中,通过模型和语言处理能够在复杂信息中提供交流便利性的,并且能够该领域达成共识。
文中我也多次声明软件工程是没有“技术银弹”的,结合上面领域驱动的局限性(维基百科)的叙述,DDD同样也有它的适用场景与局限性,像军工、金融等这些领域,就比较适合使用DDD,因为它们都具备了这两个特征:
• 生命周期长(投入产出比)
• 逻辑足够复杂(使用复杂对抗复杂)
而互联网系统拥有需求高频变动性、快速试错和定制性,DDD是否需要引入,大家见仁见智。
▶︎ DDD战略的重要性
首先,DDD的战略设计是可以使用指导划分微服务,这点是毋庸置疑的,因为微服务的分而治之的思想,与DDD的化繁为简,的确是不谋而合。
因此我实施微服务的时候,终还只是以战略设计引入到微服务设计里,在期间我尝试使用了事件风暴与其他同事合作,帮助识别我业务边界从而确定界限上下文,而DDD战-术则根据日后业务稳定,并且边界清晰后,再考虑选择性引入重构。
事件风暴,是一个从拆解到整合的过程,过程中需要领域专家(需求提出者)和技术实践者协作完成领域建模。
一般采用场景分析和用例分析,尽可能分解出领域对象(实体、命令、事件),可以从交流的过程中提取出领域专家(需求提出者)口中的名词、动作、触发事件等,这是一个拆解的过程。
在软件工程里没有银弹,微服务不是,DDD也不是,我们所做的永远提供的是解决方案,同一个问题能有很多种解决手段,但是同一种解决手段并不能解决所有问题。
04⎪ 微服务需引入聚合服务层吗?
这是咱们这篇文章的后一个话题了,也是我在微服务实践时被同行问得多的一个问题。
回顾我身边,不同的时间,不同的人,实施同一种微服务架构风格,都问过我同一个问题:他们网上找了很多资料,聚合服务层有的说引入,有的说不需要,那么,究竟微服务是否真的需要引入聚合服务层呢?
说句实话,这个问题也曾经困扰过我。经不断的积累,我想我可以给这个问题一个明确的答案了:根据系统的复杂度与场景进行取舍,接下来,我将详细叙述下我的思考结果。
▶︎ API网关的类型
API网关可以作为多个微服务提供统一入口,它的思想类似面向对象设计的外观模式,API 网关也称为“面向前端的后端(BFF)”,因为它是为了满足客户端的接口整合需求而出现的。在《Microservices-Architecture-for-Containerized-NET-Applications》一书里有这样个观点,认为API网关主要分两种类型:
• 基础设施型API网关
• 自定义业务API网关
基础设施型API网关比较容易理解,我举个例子估计你就秒懂了,Kong 和 APISIX。这种类型的API网关主要把服务具有相同的功能抽象到上游服务,像鉴权、日志、限流、熔断等通用功能,可以避免每个服务都需要去重新的实现这些相同的功能。
自定义业务API网关,其实就是把多个微服务,适配成方便客户端使用的API服务,避免客户端组装过多的API导致过于臃肿,也就是咱们上面说的聚合服务层,也有称之为面向前端的后端(BFF)。
有些朋友一看,既然能减少客户端的工作量和复杂度那这个不是好事么,还需要思考是否要引入?大家可别忘记了,在上文,我们多次提到了微服务的核心点——自治性。
如果在原有的基础上加多了一层新的微服务,那么上游微服务就会受到下游微服务的更新而有所影响,如果下游服务接口有更新,上游服务也可能跟着一起更新,因此丢失了自治性。除此之外增加了调用链长度,也会降低系统整体的可用性。
无论用和不用好像都有问题,该怎么办?取舍!
作为一个架构师,大部分的工作就是根据问题场景选择合适的处理方案,因为大部分的方案是无法做到“完美的”,因此我们很多时候还得做出一定程度的取舍。
从个人的角度认为,如果多个业务需要客户端组合大于等于3个接口,我们可以舍弃部分自治性而选择引入BFF,以此来降低客户端对接的复杂度。如果客户端的业务相对比较简单,BFF则不需要引入,那可以大程度的保证自治性。
因此在开始设计微服务的时候,在不明确的情况可以暂时不需要考虑,随着业务系统的迭代,系统复杂度越来越高,我们再考虑BFF的引入。
基础设施型API网关与BFF两者的存在不是非此即彼的,因为基础设施型API网关不影响微服务的自治性,因此可以独立于BFF的选择,是否引入也取决于团队的运维支撑程度如何,运维能力足够则使用中间件支撑,运维能力有限,则在微服务的代码里实现。
▶︎ 小结
总上文可见,方案是没有“完美的”,设计只有"合适"与"不合适”,没有“对"与"错",架构师永远是在"取"与"舍"之间扎挣,而促使我们能做出那一次又一次的合适的选择,只有积累与经验。
05⎪ 结语
该篇文章到这里就差不多结束了,我始终认为一图胜千言,因此我在文中基于上述分享的知识点,给大家做了个脑图,以便于帮助你回顾与总结。
全文思维框架导图(可点击图片放大查看)
微服务与DDD的见解,我也是再三思考了才打算写出来,我清楚地知道有不少的DDD热衷者认为这门技术是多么的“神圣”,当然曾经我也是。
但是,随着自己使用、接触的技术越多,越是发现技术并不是那么的、神圣,促使我们能在这条道路走得更高更远的,是咱们的思维,是我们对这些技术做出适当的选择与使用。而不是想着“一招鲜吃遍天”,我们是技术的创造者与使用者,而不是概念名词的搬运工。
该类型的话题与见解,每个人接触后的解读可能都不一样,我的并非是标准,仅是我自己观点上的分享,希望能与你产生见解上的碰撞与共鸣。
微服务是否需要引入聚合服务层这个例子,可以作为架构师日常工作——取舍与选择的缩影,通过这个例子我分享了API网关的类型,并且结合我的架构设计方法论,从取舍与选择回答了这个问题,咱们可以根据自己问题的场景选择合适的方案进行应对。
好了,到这里我们就要结束了,非常感谢你耐心的阅读,我们在后面的分享再见。同时期待后续的某几个时段里,我与你能够有更多思想上的交流、碰撞。如果愿意分享,这一讲也欢迎转发给你的朋友,和他一起讨论。