Hints for Microservice design - 如何进行微服务的划分
作者简介:姚钢强,具有丰富的技术架构和业务架构经验;2013 - 2019 在知乎工作,完整经历了知乎从内部注册到上千万 DAU 的过程;2019 年加入猿辅导,担任斑马事业部首席架构师的职务,主要负责工程实践的改善与架构的优化;参与重要业务与技术架构方案的评审;目前微服务架构趋向合理,数量大幅削减,依赖逐渐清晰;也逐步建立了以 Metrics 驱动的 DevOps 文化。
本文是我在 2021 GIAC 上的一次分享《Hints for Microservice design》的精简过后的讲稿。
前言
本次分享的内容主要是关于业务架构的讨论,业务架构不像技术架构一样有明确的好坏衡量标准,例如评价数据库的技术架构好不好,就可以在限定的资源限制下去 benchmark 进行评判,但是业务架构不能 benchmark。你现在不能证明某个业务架构是好的,未来也不能。所以我把我分享的内容称为 hint,而不是 Guide,Principle;这些只是写个人经验的简单总结而已,所以「Don't Belive Me!」
本次分享总共分为两大部分
提出问题
如何衡量是否解决了问题和可能的解决方法
即「期待解决的问题,目标,现状」和「如何设计架构,有哪些维度,好的标准是什么」
期待解决的问题,目标,现状
我们不从微服务的定义开始进行讨论,而是从我们期望解决的问题开始进行讨论。
期望微服务解决什么问题?
首先需要回答一个问题是「研发团队做得好坏的标准是什么」?我个人喜欢用 four key metrics进行衡量。即
Deployment Frequency—How often an organization successfully releases to production.
Lead Time for Changes—The amount of time it takes a commit to get into production.
Change Failure Rate—The percentage of deployments causing a failure in production.
Time to Restore Service—How long it takes an organization to recover from a failure in production.
这四个指标从研发速度和研发质量两个维度进行了衡量。
架构设计成什么样子有利于提高研发的速度和质量呢?其实是尽可能的自治,不互相依赖,自己搞自己的;我在公司的技术群里发起过一个投票「期望微服务解决什么问题?」,也一定程度印证了这个观点;
现状
现状是怎样的呢?大部分业务不太乐观。
目标
理想的情况又是怎样的呢?如果用一张图表示,我希望理想的业务系统可以像乐高一样,每一类乐高组件是一个子系统(业务能力),可以利用不同类的组件基础能力组合成复杂的业务能力(系统)。
那我们来看看大部分公司的真实拆分微服务的历程,最后有没有达到像乐高一样各组件尽可能的自治,又能拼装出来复杂业务系统的目的。即「满足一定规范的尽可能的自治,又能自由组合」。接下来我们来看拆分案例
案例
最开始业务模型比较简单,要满足的需求比较少,只有一个系统。
后来发现供应链很重要,出现问题就是重大故障。所以需要把供应链系统单独拆出来单独维护。
后来大家发现课时 CRUD 模块是公用模块也拆出来吧
拆着,拆着就总结出了如下几条规律
最后把系统拆成了这个样子,一个十分复杂的系统
根据我们总结的规律最后拆分出来的系统,做到「在一定的规范下尽可能的自治」了吗?解决了所谓的耦合问题了吗?貌似没有解决,而且出现了下面的新问题。
那么如何衡量是否做到了「在一定的规范下尽可能的自治」,标准是什么?又如何做到呢?我们在第二部分进行讨论
如何设计架构,有哪些维度,好的标准是什么
从哪些维度设计架构?
1995 年有一篇相关的论文 Architectural Blueprints—The “4+1” View Model of Software Architecture 即围绕业务场景从四个视角设计和观测架构。借鉴这个思路,但是我们简化一下重点讨论两个维度
Logical view: 业务架构,业务模块的设计与划分
Process view:物理运行时架构,运行时进程组的设计与划分
其实也就是要达到的目的「自治要达到业务模块和物理模块(物理层面的弹性与隔离)的自治」
Logical view 可以通过部署与 Process view 进行对应
那接下来我们开始讨论 Logical view 和 Process view
Logical view(业务架构)
提起业务业务架构我们一般会想到哪些建模方法和指导原则?更重要的是衡量业务架构的好的标准是什么?因为只要把好的标准搞清楚了,才能真正的讨论问题,
这些我们经常提到的方法和原则实际能帮到我们吗?确实有一定的指导意义。但是它们还是太笼统了,不够具体。不够具体造成的问题
不同的人理解差距太大,不能达成共识
不能真正的实施,不能通过工具以数据的方式体现
极难进行知识的传达
那么好的标准到底是什么呢?接下来我们尝试通过一些现象来总结一下
分层
下图是我们写代码时最常见的结构,将代码分成了这几层。对应到微服务上很容易地变成按照层次拆分成四类(DAO Service Application View)服务的实践。
下图是我在 知乎的技术架构是什么样的?中的一张关于基础架构的一张架构图。也是明显的分层架构。
从业务架构视角来看,与上图的分层最大的区别是什么呢?
最大的区别是第一个分层很多时候并没有达到分层的目的,很多时候一个新需求或者修改一个已有的需求都需要从头(view)改到尾(DAO),层与层之间的接口是不稳定的;第二张图里层与层之间的接口是稳定的,一般一个新需求只改一层就可以了,层与层是不知道彼此的存在的。
什么样的分层架构是好的?如何进行衡量呢?
层与层之间接口是否稳定,层与层之间是否必须知道彼此的存在也许是分层架构一个好的衡量标准
问答社区
再看下经过简化过后的问答社区的业务架构,这个架构看清来比较清晰。没有我们第一部分遇到的问题,看起来也达到了自治的目的。
这个架构为什么比较清晰?
因为模块除了 feed 之外模块之间没有任何数据和逻辑共享,彼此之前不互相影响。
从业务架构角度还有哪些可以优化的地方吗?
也许由 Feed 定义接口其他的系统(文章,问答,广告,知识市场)来实现会更好。这样更能保证接口的稳定。即业务架构上将 Feed 系统放到下边;下边电商的例子也会提到
电商(订单,价格)
订单
电商领域一般被认为是比较复杂的,电商团队一般也是加班最多,最繁忙的;那我们一起来分析下单的这个业务。即用户点击「提交订单」,相关信息(收货人信息,支付方式,送货清单,发表信息,优惠券等)被保存,然后开始履约。
对于这个业务场景,最直观地业务架构设计是将所有的信息存储到订单系统里,然后各方履约的时候从订单系统里获取(getOrderByID)。这样的设计十分容易理解。如果部分履约系统发生了发生了业务变化是怎样的;例如买了免费商品不需要付费了怎么办?新增了某种支付方式或者开发票的信息怎么办?都需要通知订单系统进行修改,进行接口的变动,跨团队的沟通。是不够自治,十分影响开发效率的。
可以换一种思路来设计,最极端情况下订单是不是可以只一个 orderID,然后各个履约方存储自己关心的信息(不让订单系统代为存储),并且与 orderID 关联。这样各个履约方如果发生业务迭代的时候只需要改动自己的系统和存储就可以,与其他系统业务相对隔离,保持足够的自治。
价格
再看下图中右下角的价格,价格理论上是根据多方的信息计算出来的。例如商品的原始价格,优惠券,支付方式,配送方式等;实现价格系统时我当然可以把所有的信息都存储在一起,并且把所有价格相关的计算逻辑也写到一个模块里;这样维护价格的团队可能是全公司最忙的了,因为每个业务系统都想改变价格,频繁调整价格来强调自己业务的重要性<(* ̄▽ ̄*)/。
价格计算有两种计算逻辑。一种是类似 Chain-of-responsibility pattern,价格计算按照一定顺序交给各个系统轮流处理,报价系统按照特定顺序进行调用即可。另一类稍微复杂一些,必须根据各方系统的信息综合判断才能计算出价格,例如用户是会员,持有特定优惠券,并且选择特定支付方式的前提下才能计算出最后的价格,这种情况下报价系统从各方系统获取必要信息,进行小部分的计算的集成算出最后的价格
这里需要特别注意的是:接口一定是由报价系统定义,然后由其他系统(优惠券,会员,团购,支付等)来实现的。而不是直接调用其他系统暴露出来的 getCouponByID 类似的接口,这样才能更好的保持在价格这个业务逻辑里接口的稳定,从而保证足够的自治。这也正是 Dependency inversion principle 在业务架构里的重要体现。
数仓
在很多公司做业务数仓的工程师和与业务数仓合作的业务研发工程师都很痛苦,因为面临如下问题
MySQL schema 的变化导致数仓同学改一个月,如果没有通知到数仓的同学直接导致数仓的故障
业务研发想把 MySQL 换成其他的存储如 Redis,HBase;但是数仓同学不同意,因为不能像 MySQL 一样监听 binlog 进行数据统计
数仓的数据错了,debug 超级困难
造成这些问题的根本原因是什么呢?MySQL 即为业务服务又为数仓的数据服务,双方都强耦合了 MySQL 的 schema 的这个接口,但是这个接口却不稳定。可以有其他的解决方案吗?
也许我们可以像维护 RPC 接口一样去维护数仓数据的接口,而不是依赖业务 MySQL schema。也就是说让每个业务团队定义好自己对外提供数据的接口,并将其实现,同时将接口注册到类似微服务注册中心的数据接口注册中心。从组织架构上直接上业务研发团队具备数据处理能力,统一的业务数仓团队从各个业务研发获取数据,做数据的互联互通,而不是把所有的细节的业务数据处理放到统一的数仓团队。这也是最近比较火的 Data Mesh 的核心思想。
总结
从上文的各个例子中,能否总结出一个业务架构好的标准是什么?如何才能做到在一定的规范下尽量自治,从而提高开发效率和质量。也许是这样的(按照顺序从上到下,越上边越好)
不同模块间完全没有影响
问答社区例子,能做这样是最好的。这种情况最理想,但是很多业务不会这么简单。
只共享 immutable data
电商订单的例子中不同的系统间只共享了 orderID,是不可变数据。不同系统间的影响是可控的
共享 mutable data,但是 interface 少,而且几乎不变
interface 变化次数 / implementation 变化次数比例小
电商报价系统与业务数仓的例子,系统间共享的数据是会变化的,但是系统间的接口是稳定变化的的;哪怕会变化,接口的变化次数也是大大小于实现的变动次数的。
再看一下这张图,为什么我们在第一部分总结的「拆得越细越好」不对呢?乐高可以使用小组件组合成不同的模型是最核心原因是因为「拆得细」吗?其实并不是,而是「乐高小组件形状是不变的,即组件的接口是稳定不变的」
稍微上升一下高度,如果只用一个词来概念系统面临的问题,就是 Complexity。Complexity的核心来源又是什么?是「不同模块间的相互影响」。我们前边总结的业务架构好的标准和设计方法不就是在想办法衡量复杂度,控制复杂度吗?
那么有哪些具体的 hints 可以帮助我们达到上文提到的好的标准呢,如下图琐事。具体在这里就不详细介绍了,后再单独另开其他的 topic 进行展开。到此 Logical veiw 告一段落。
Process view(物理运行时架构)
首先要问为什么要做物理运行时隔离?目的其实就是达成物理层面的自治。
这是物理运行时自治带来的好处?那又有哪些负面影响呢?一方面是 Fallacies of distributed computing,另一方面是违背了 Law of Demeter。
物理运行时的隔离面临这么多问题,那有什么好的解决方案吗?希望在做到物理层次的隔离的同时,又尽可能不受分布式的影响,并能尽量少的暴露 API ,不违背 Law of Demeter。
此图表示的是一个业务模块,但是在运行时有不同的 SLA 和弹性边界的要求,需要把 HTTP,RPC,Consumer 进行物理运行时的隔离。那么我们可以把整个业务模块的代码写到一个 git repo 里,同一个 git repo 跑起来后所有的通信都是进程内通信,把有物理隔离诉求的服务编译成不同的可运行可运行的包,然后分别部署到不同的进程组就可以了。如上图可以把 HTTP,module A,module B,backend 和 RPC ,module B,module C,backend 和 Consumer ,module B,backend 分别编译之后部署到不同的进程组,这几个进程组一定要一起部署一起回滚,从而保证版本分化的风险;这样的物理运行时设计就做到了物理层面的自治的目标了。
到这里大家也看到了,也是需要重点强调的是业务架构模块与物理运行是模块并不是一一对应的,这也是大家普遍对于微服务的误解。千万不要把两者混为一谈,而是要先设计业务架构再设计物理运行时架构。
总结
简单回顾一下,整个分享我们先看了我们面临的问题,现状和要达到的目标。然后第二部分通过一些现象总结了业务架构好的标准,并且给出了如何做到业务架构自治的标准与物理自治的一些 hints。整个分享内容很多,如果只记住两个 hint 的话,我希望是这两点。
后记
回头看起来这个分享有以下几个问题,需要进行改善。
想要表达的东西太多,造成了重点不是十分明确;
目录划分不好,逻辑不够清晰,有些头重脚轻;
笼统的现象举得太多,造成讲得都不够透彻,不如只讲一两个具体的案例。
此外需要声明的是文中的主要思想都是来源于
@陶文
的 业务逻辑拆分模式 和 Udi Dahan,在此郑重感谢两位老师的指导。
希望大家多多评论或者以私信的方式提出批评和质疑,碰到好的问题我会给到私人微信进行深入讨论。
参考
业务逻辑拆分模式
Udi Dahan
Complexity Has to Live Somewhere
Monolith First
Don’t start with a monolith
我们到底要微服务还是业务能力?
多“微”才算微服务
Modular Monolith
On the Criteria To Be Used in Decomposing Systems into Modules
Hints for Computer System Design
技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。