Thread Reader
Steam & Magic

Steam & Magic
@xuwenhao

Nov 18, 2022
54 tweets
Tweet

也不知道过去几年这股奇怪的“微服务”风潮是怎么起来的。在我看来,微服务的适用场景非常有限。 许多巨头互联网公司的核心业务逐渐微服务化,的确是在特定的历史时期和场景下的解决方案。但是使用微服务从长期来看,会带来一些更糟糕的问题👇#微服务 #Microservices

在我看来,大型系统的开发,核心的挑战其实只有一个,就是“控制复杂性”。 即使是工程师,也不要自己被自己骗了。无论是“高并发”还是“大数据”,并不是大部分互联网公司遇到的核心技术挑战。事实上,过去几年这些问题,其实根本不是拿着高薪资的软件工程师们解决的。大部分是靠硬件解决的。👇
持久层存储从机械硬盘变成SSD,以及I/O走PCI/E通道,解决了IOPS和吞吐量问题 硬件的持续降价,使得大家用得起几百GB级别的Redis Cache 数据中心内部的网络结构从优化南北向,到考虑东西向,加上带宽从100MB级别变成100GB级别,让网络也不再成为瓶颈 👇
软件和系统上,从BigTable到Spanner其实都是Google解决的。Google让跨数据中心,跨州的分布式系统的一致性问题,得到了解决。 而其他的大部分互联网巨头的基础设施团队,都是在学习模仿抄袭。 而这些巨头们内部在解决这些问题的人数也只是很小的一部分,大部分也只是利用这些基础设施去写业务系统👇
那当我们回到业务系统之后,其实面对的核心技术问题,就是“如何管理复杂性”。 大部分的软件工程、系统架构设计,乃至于团队内的管理机制都是为了解决这个问题。 软件工程和管理机制上,带来了统一的代码规范、自动的持续系统和灰度发布,各种在线协作工作和项目管理工具,以及OKR等等 👇
而“微服务”,就是过去一段时间,很多公司采用的“控制复杂性”的解决办法。 要理解“微服务”是怎么来的,以及我为什么认为这对99%的公司不适用。我们就来看看我们过去是怎么一步步“管理复杂性”的。 在系统开发上,控制复杂性的方式,可以用三个关键词来描述,那就是“抽象”、“封装”和“复用” 👇
软件开发从一开始,就是围绕着三个关键词进行的。因为这三个关键词,我们有了函数、过程、类、设计模式等等一系列的概念。 我们通过抽象和复用,减少重复的代码。我们通过抽象和封装,区做领域建模,理清系统之间的依赖关系。也是因为封装和复用,使得软件开发的分工成为可能。👇
一个人自己和自己分工的时候(时分复用),有函数、类、设计模式就够了。 少数几个人协作分工,特别是不是在一个团队内协作分工,于是就出现了library。 类似需求的项目需要反复实现,就出现了各种开发框架,最常见的就是各种Web开发框架。 一直到这里为止,还没有微服务什么事儿。
到上面这里,我们看到的还是孤立的软件系统,比如一个在线的电商网站。这个时候,就是所谓的单体应用(Monolith)。 接着,问题来了。一个电商网站,并不是只有网站就够了。我们可能还需要一系列用户看不见的管理功能,去管理采购、库存、发货等一系列问题。
这个时候我们有两个选择,一个是继续在这个单体应用上更改。另一个是单独写一个应用,和当前的应用系统做系统集成。 往往我们会选择第二个,一方面,这样这个系统我们不用内部开发,可以直接外部采购。另一方面,当我们内部有资源和能力开发,往往意味着我们本身的业务量已经比较大了,团队人数膨胀了
而且,两个应用需要考虑的领域问题完全不同,所以最合适的方式就是有两个独立的团队开发两个系统。然后两个系统之间进行集成。 一开始,大家尝试了各种各样的方式。简单粗暴的共用数据库通信,离线上传数据到FTP等等
这些方式,其实就是在不同的系统直接定义了一组“协议”,通过协议来通信。 最终,也就变成了现在我们常说的“服务化”,常见大家采用的协议,往往也是Restful的HTTP API,或者gRpc/Thrift这样的RPC协议。 进一步的,这个服务化,也随着团队和业务规模的膨胀,进入到了一个系统的内部。
比如说,前面的电商网站,也进一步拆分成了订单、用户、商品这样的服务。 这样的拆分,对于大型互联网平台也是合理的。一方面,业务本身的复杂度,使得原本单体应用内的功能越来越多,代码愈发膨胀,需要进行合理的拆分。 另一方面,大型互联网平台也有足够的经济回报,去用一个团队来支撑一个个服务
服务化的理念,某种程度上可以认为来自于亚马逊。并且被Stevey Yegge自己批判老东家Google的文章 Stevey's Google Platforms Rant 所为人熟知。 在我看来,Amazon服务化做得比Google早和好是有原因的。因为自营电商天然有巨大的业务复杂性,不得不进行服务化的拆分,来管理这样的复杂性。
而尽管Google的分布式系统上远强于亚马逊,但是天然地,Google不需要那么地服务化,一个搜索引擎需要的服务数量并不会很多,特别是早期的Google的核心挑战也不在这里。
当我们的系统变成服务化的时候,一切都还没有什么问题。系统逐步从一个单体应用,因为业务的扩张和需要,逐步变成了服务化的应用。 从组织上也非常合理,单个团队,关注自己内聚的相关业务。抽象、封装、复用,在这个时候仍然得到了一个合理的解决。 不过,在这个合理性上,还是有一个小小的挑战。
第一个挑战,是“服务”并不是无状态的,“服务”也绑定了数据。还是以我们说的电商业务为例。我们的电商商城会生成订单,比在服务内持久化下来。然后,这个订单会发给到订单履约系统,也会持久化下来。然后,两边都有可能触发订单状态的变更,商城用户可能取消订单,履约系统可能因为商品缺货也取消订单。
于是,两边服务,都需要有对应的接口和实现,去完成这样的状态同步。这个过程中,就容易引入数据不一致的问题。只不过,这个不一致不是我们通常说的分布式共识问题。而是业务带来的潜在数据不一致的风险。
不过,这个问题在服务化的系统下,还不是个大问题,因为服务的边界是清晰的。只要各个团队自己有合理的日志,排查问题并不困难,排查问题的责任划分也非常清晰。 不过这个问题,在微服务下,会变得复杂或者并没有那么容易处理。
第二个挑战,因为是不同的服务,就会面临一个“向前兼容”的问题,不同的系统并不是完全同步迭代的。 而已经发布的服务,意味着对外有了明确的协议承诺。在服务发布新版本的时候,必须要确保向前兼容。 这个在服务化下也还好,因为服务之前的调用链路短,依赖关系少,但是在微服务下会严重地被放大。
第三个挑战,就是因为服务化划分了明确的边界,系统更容易变成异构的,更容易引入更多的技术栈。并且,有些功能,会在两个不同的语言、框架下各实现一遍。 而这个“各自实现”一遍,也容易进一步放大之前所说的业务数据不一致的第一个挑战。
不过,这些问题虽然让人觉得“麻烦”,但其实并没有什么太好的办法完全避免。优秀的基础设施和团队工程管理能力,可以解决一部分问题。但是这些类似的小问题仍然会频繁出现,并且需要频繁修复。
那么,我们能不能进一步地拆分服务,用“微服务”来解决上面的这些挑战呢? 比如,我们单独将订单的状态处理变成一个服务,无论是电商前端的交易,还是后端的履约,都和这个服务交互,那么只需要维护一份数据,似乎这个问题就消失了。
再比如,对于多个系统里都要实现的小功能,也抽象成一个微服务。而不是在两个系统里面各实现一遍,那么好像也解决了之前的问题。 极端情况下,我们完全可以把正则表达式匹配变成一个微服务,来避免不同语言正则表达式引擎支持的差异。
相信到这里,很多人的Bullshit Detector应该被触发了。为什么我们做一次正则表达式匹配,要去调用一次RPC? 这也是微服务最大的一个问题,单个微服务本身,很有可能没有业务含义,或者业务含义很弱,已经退化到一个功能代码而非业务代码。 而这个退化,带来了新的系统的“阻抗不匹配”问题。
这样弱化的,缺少业务含义的微服务带来了两个新的问题。 第一,这个微服务可以由谁来维护? 第二,这个微服务可以由谁来调用? 通常大家会给一个偷懒的结果。我们选择由之前的两个团队之一负责维护(电商商城或者订单履约)或者干脆再找一组人来维护这样的“基础服务” 第二个则是开放出来谁都能调用
缺少业务含义,只能由某一个团队维护,最终非常容易导致这些服务的业务含义进一步退化,变成一个简单的crud入口。 而谁都可以调用,则会导致为了向前兼容难以去迭代单个服务。
crud入口性质的服务,会让业务逻辑重新退化到各个业务服务内部,这个时候,微服务化导致两个服务直接在数据层面产生了耦合。 向前兼容,则非常容易导致不得不部署多个版本的服务在线上,甚至重新写一个差异很小的新服务。
微服务的拥戴者会告诉你开发新功能很容易,却不会告诉你背后带来的是维护灾难。 要记住,软件开发的80%的成本来自于维护。
从服务化进一步拆分到微服务之后,系统之间的职责和依赖关系,非常容易大规模膨胀。 整个服务变成一张巨大的网,系统的圈复杂度往往会提升一个数量级。 不要忘了,一开始我所说的,大型系统开发的最大挑战是“控制复杂性”,而微服务化,导致的是复杂性的进一步上升。
因为“控制复杂性”的核心,并不只是要复用代码。而是要明确职责划分。 一个好的大型系统的设计,一定是当需要做迭代和改动的时候,很容易判断和知道应该在哪一个环节做修改。 这个也是服务化带来的价值,而进一步微服务化,往往非常容易破坏这一点。
而因为微服务这个概念是如此动听,很多根本不应该以服务形式提供的功能也变成了微服务。 应该通过一次离线批处理的任务逻辑,很多也变成了每条记录调用一次微服务rpc。 如果说前面的例子太过极端,那么下面我来讲一个最近看到的真实案例,看看无脑微服务的坏处。
一个电商平台有这样一个商品价格模块,商品售价是自动根据商品的成本价和一系列规则计算出来的。 这些规则可能是根据售卖的国家不同,或者类目不同,或者认为选择了一系列的商品打上了特定的标签。 根据这些条件组合会有不同的价格在,这些规则会由运营修改,商品的成本价格是通过其他系统更新的
乍一看,这样“内聚”的需求,非常适合封装成一个价格服务对不对?找一个人专门负责这个服务。 然后每次有商品在前端要呈现价格的时候,我们就调用这个服务来计算一次就好了。 于是,这个服务就这么被部署上线了。
但是,这个服务因为支持的规则比较复杂,所以每次计算都需要花的时间偏多,比如50ms。这样,对于电商商城的展示来说就有点慢了。 于是,一个很直观的想法就来了,那就是给计算结果加上缓存。 那么缓存应该加在哪里呢?一个选择是在这个计算服务身后。那外部的应用不需要改变,就有了显著的性能提升。
于是,外部需要计算价格的时候,就向这个服务发送一个商品id,这个服务会发起一个rpc去调用商品服务,拿到采集到的成本价格,然后计算价格返回给调用方,并缓存下来。 而当商品成本价格更新的时候,以及价格规则发生变化的时候,也需要rpc调用这个服务,让缓存失效。下一次请求的时候重新计算。
继续昨天没说完的。 但是,前端的App并不是直接请求价格服务的,而是直接请求商品服务,商品服务再发起请求给到价格服务。 而商品服务本身,也有其他一系列的计算逻辑,或者再调用其他服务,最后将各个数据包组合在一起,返回给App。为了性能,商品服务本身也有一层组合后的结果数据的Cache。
好了,这下问题来了。之前,当价格规则发生变更的时候,不仅对应的价格服务应该失效缓存,它还应该告知商品服务,也让它失效缓存。 不然,我们就可能在系统中出现潜在的业务数据不一致性。 这个时候,被调用的价格服务,必须要知道它的调用方有谁会缓存数据。这显然带来了糟糕的耦合性。
那么,我们可不可以不要在价格层面增加缓存,只是在商品层自己增加缓存呢?我们也不通过价格服务来主动失效缓存,而是在商品侧根据过期时间自动失效。 可是,我们的价格服务还有其他的调用方。如果其他的调用方也等过期时间自动失效。那个那个服务和商品服务的价格数据中就出现了业务上的不一致性。
这只是一个被简化了的例子,但是很容易看到。因为“服务”本身通常是有数据和状态的。大量的微服务很容易引入这一类的业务数据不一致的问题。
事实上,过去几年“微服务”流行起来的原因,是因为不少互联网“大厂”在尝试快速扩张批量制造“即抛型软件”。 很多业务要快速扩张,于是快速招聘,招聘标准放得很低,然后堆很多人。先把功能做出来,质量不重要,好不好维护也不重要。反正不是造火箭,出了bug也没关系,加班修复就是饿了。
那么怎么把一群菜鸟组合早一起做出功能呢?用“微服务”呗,每个人都可以独立工作,不需要考虑长期维护性。 业务跑得出来再投入资源重写,业务跑不出来就把代码和系统扔掉就好了。 不过,这么知道业务跑不出来是不是因为这样开销太大而且系统太难维护导致很多事情做不好呢?这个问题没法有答案。
当然,我相信无论我举什么样的例子,都可以有人跑过来说,我举的确例子是“坏的微服务”,是设计的不好,而不是“微服务”本身有问题。 可是,如果一个东西特别容易被滥用,那是不是这样的基础技术决策本身有问题? 就像C语言里面的goto一样,所有人都会建议你,不要使用goto。
你当然可以说,那是使用不当的问题,是你写了坏的goto,而不是goto有问题。而在我看来,这就是一个语言设计上的错误决策。 回到微服务,在我看来,他并没有解决软件开发的“控制复杂性”这个核心问题。而只是在尝试不停地“转移复杂性”,并且在这个过程中,非常容易“增加复杂性”。
把任何的系统业务逻辑通过“微服务”拆分,并不会减少复杂业务和系统的逻辑关系,只是增加了一层rpc调用而已。 各种服务治理措施,让你部署多个版本的服务来解决向前兼容,增加了你需要在线上维护的版本以及兼容性考虑。而不是让你尽早减少版本并且对你的系统设计深思熟虑。
“微服务”架构天然地“容易”把系统滑坡,往一个更难维护的状态变化。你在短期内,特别是项目早期阶段,看起来容易加功能,但是越往后越容易有巨大的难以偿还的债务。
而大量良莠不齐的工程师进入各种“大厂”,再因为业务被干掉带着“大厂光环”一知半解地把自己在大厂看到的在新公司实现一遍。 而各种没有去大厂的也为了“面向简历编程”在不适用的场景下做一遍微服务改造。
微服务的确有一整套生态链来让自己容易被“治理”。但是你要理解这其实是针对不得不将服务拆分的情况下,需要额外承受的overhead。 因为有大量的微服务,所以我们不得不做更多的dtrace,因为有大量的版本,所以要做服务侧网关的自动流量分发,因为不知道调用方可能来自哪里要做更复杂的熔断处理。
更多无效的时间被花在线上运维,解决数据一致性上。这对于巨型业务团队来说是ok的,因为更细致的分工带来的一些单点优化可以放大到几亿的用户。 但是对于大部分公司,包括一些大公司,都是overhead高于能够获得的收益。
真正适合用微服务的场景非常少,要么就是巨头的核心业务,要堆上几百几千人研发团队的情况。 那样,单个小的业务模块就很复杂,几个微服务也都有一个小团队负责。 要么,就是你是个个人开发者,把一些自己不熟悉的领域工作完全“外包”出去开发部署,然后通过微服务来调用。
而大部分团队,单体应用+服务化就足够了。 对于那些十几个人的团队,搞上几十个微服务,或者几十个人的团队,搞上两三百个微服务。多半会遇到需要不停招聘团队,但是产出却没有进展的情况。因为在这个时候,你的系统的复杂度已经完全失控了。
其实,对于技术的决策我们先不用探讨那么多繁复的细节。还是要回归一个本质,就是这符合常识吗? 20个人的团队,有100个repo,200个线上微服务,修改一个功能要穿梭过5个微服务,这应该立刻触发你的bullshit detector。就像Elon Musk听说twitter首页timeline需要200个rpc调用的反应一样,its bullshit
记住,微服务不会帮你减少业务层面的复杂性,它只会转移复杂性 只有当你有一个足够复杂的商品定价系统,并且它能够体现为给几亿人服务所以多赚很多钱,所以值得投入30个人专门干这个的时候,拆解成为独立的服务是合理的 如果你只有39个人负责整个电商前端的交易App,那么单体应用或者服务化一定够了
暂时就先到这里吧。
Missing some tweets in this thread? Or failed to load images or videos? You can try to .