《微服务的灾难》是我于 2019 年五一期间写的系列文章,当时其实写了很多:
只发了一部分(另外一部分出于言辞激烈或影射太容易对号入座等原因没有发),最近又看到有老外写类似的东西,阅读后主要是说基础设施和稳定性的,和我当时发出来的六篇正好算是互补吧。
这里将当时的内容汇集一下,仅供参考,虽然都是故事,但也都是真实(这让人想到了最终幻想里的 This is a fantasy based on reality,哈哈)。
通用语言的灾难
在架构师们很喜欢的 Domain Driven Design,即 DDD 中,第一课就是教导团队形成自己独有的通用语言(Ubiquitous Language),作为业务概念沉淀下来。
作为非英语母语的国家,我们在日常交流中使用的是中文,在公司业务战略描述上使用的是中文,在高层进行任务拆分的时候使用的是中文,在领导安排工作的时候使用的是中文。唯独到了具体实现,即代码这一环节便变成了英文。当然这里我们不考虑有些公司会有汉语拼音这种尴尬的情况。
两种语言天生便有难以填平的鸿沟,在业务人员编写代码时,从中文到英文的转换,往往丢失一部分业务信息,产生一部分信息噪音,或者发生概念上的偏移。
英文语系的人对业务进行建模时,与业务方(领域专家)交流时,产生的概念和反馈可以直接落实到代码上,他们所使用的词汇不会发生变化。而其它语系的人就会在编写代码的时候发生概念偏移,比如我司是做打车业务,快车在不同的系统中会存在不同的翻译,有人称之为 fastcar,有人称之为 quickcar,有人甚至就直接是 kuaiche。甚至同一个系统中,对于同一个概念也会存在不同形式的自创翻译。即使以文档的形式记录了业务的标准翻译,但显然以国内业务叠代的速度,这种词汇上的统一是做不到的。即使在一个只有 7~8 个人的组中都做不到。并不是所有人的英文都可以达到可用的程度,有些代码中的词汇可能根本就是词不达意,是某些搜索引擎中给出的直译结果,与真实的含义相差十万八千里。
这样的代码会给后来人带来理解上的困惑。一旦在同一个系统中,针对同一个业务概念存在三种以上的不同词汇,就需要阅读者在这些“错误”的词汇上不停地进行上下文切换,以正确地理解错误词汇的涵义。
可能有些人会提出反驳意见,碰上这种情况我们只要对代码进行重构就可以了,并不需要被这种弱智的事情折磨啊。重构虽好,在很多情况下,词汇的重构是不可能的。打个比方,上面提到的 fastcar 出现在我们系统提供给别人所用的 api 的关键字段中,quickcar 出现在我们内部数据库的字段名中,kuaiche 出现在异步发送的消息中。这种时候修改任何一个单词,对于我们来说都是不可能的事情。api 和事件中的字段名是我们对于外部系统的承诺,这种承诺也是编程契约中的一部分,不能随便修改。即使我们想要修改,在当今大多数互联网公司的架构下,根本就没法知道到底是谁在使用你的哪一个字段。也就是说,我们没有办法获得粒度细到“字段”级别的外部使用信息,所以我们没有办法对契约本身进行重构。如果未来的微服务管理能对服务间的依赖进行标准化,并且能够对服务之间字段的依赖进行显式管理,那么契约就是可以进行变更的了(就像单模块的重构那样),不过这也就是个设想,显然不太可能。而数据库中的字段虽然有重命名方法,并且在 《Refactoring Databases》这本书中也给出了各种数据库重构的完善方案。但同样的,上了体量的互联网公司,想要动动数据库结构,是比登天还难的(等五年后应该好一些)。
所以当你接手到这样的系统时,读代码的时候肯定是会骂娘的,但是读完之后也确实没有什么办法。只要你负责维护,就持续地接受这种痛苦吧。
通用语言的问题不只是单模块中存在,跨模块时也存在。在微服务的架构下,很多需求是必然会跨越模块的。别说不可能,那些鼓吹中台的公司跨模块的需求更普遍。一个需求改 20 个模块都不奇怪。
模块间负责人探讨新功能的实现时,混乱的命名和词汇也很可能让两边的沟通变得驴头不对马嘴。在服务之间是接力棒式运作,没有中心服务时,这种情况特别普遍。相信你也遇得到。
遗憾的是,目前推崇的微服务架构是没有办法解决这样的问题的。在肉眼可见的将来,程序员依然会因为概念产生的歧义而不断地受苦。
这些苦痛最终都会体现到业务开发迭代的速度上。
技术栈的灾难
微服务的布道师们特别喜欢鼓吹一个观点:拆分微服务之后,我们可以随意地对小模块进行重构,选择最合适的技术栈,并且如果写失败了随时对这个模块拿其它语言进行重写。这一点被大多数布道师当作微服务的重点优势。
但是布道师们有意地把这样做所带来的问题忽略了,或者更恶意的是,他们明知道有问题,但是不说?啊哈哈。
一个公司业务上有多种语言的话,理论上可以吸引到各种“语言的人才”。这确实不假,并且可以提供给各种语言大佬一个互相掐架的优秀竞技场,只要干掉对手(其它语言的大佬)了,我就可以扩张团队,让团队把所有其它语言的模块用我们擅长的语言重写一遍,善哉善哉。小伙子们一年的事情就都安排上了。
但是显然这种说辞是有问题的。在现行的微服务架构下,除了业务本身的研发人力投入之外,在业务之外的支持系统的研发工作也有很大的工作量,比如典型的,服务发现,熔断,优雅重启,存储系统 client,消息队列 client,缓存 client,配置系统 client 等等。。各种周边系统版本依次叠代下来,那可能也是几百上千人一两年的工作。为什么会带来这么多的工作量?其中很大一部分就是因为语言和技术栈混乱造成的。比如一个公司的技术栈能够统一到 java 的话,那没什么说的,大家都用 Spring Cloud 全家桶或者 Dubbo 全家桶就可以了。但是你们既有 java 又有 Go 又有 PHP 又有 C 又有 NodeJS 又有 Rust,这样显然就很难在众多神仙中达成一致。比如你想要选用 java 的 Spring Cloud 生态,但是这里面的服务发现或者配置系统并没有打算对其它语言进行支持,即使支持可能也支持地不全面。一旦支持不全面,公司内的轮子党们一定会跳出来,强行给你找出几十个缺点,一杆子打回去,最终得到一定要自己造这些轮子的结论。
好家伙,五种语言八种框架,一个服务发现的 client 轮子造五遍都能算少的了。目前开源界的趋势是将那些和业务无关的非功能性需求从模块中剥离出来,比如 service mesh 就是很好的尝试,只不过现阶段用过的都说坑。说好的那都是不怀好意,拉人入坑。对于研发人员来说,一个轮子造五遍真的没什么意思,可能也就是熟悉了五种语言的语法,并且写出了五种风格各异的 bug。只不过满足了部分中层管理老板的人员扩张野心。
语言和框架太多,对于公司来说显然是灾难。比如常见的公司组织架构调整,业务技术部门进行重组,不同部门的系统一般会进行暴力交接。这里说的“暴力”的意思是,不管你能不能接得下来,反正我是给你了。之后的维护也别来找我,甚至连简单的问答可能原部门都不一定愿意做。虽然公司对程序员的要求是可以随意地在不同语言技术栈之间切换,但程序员一般都有自己执著的美学偏好。让 java 程序员写 Go,往往是会翻车的。大多数 java 程序员在语言内的生态足够好,能满足几乎所有需求时,没有任何意愿去学习一门新语言。这是天然的抗拒心理,可以理解。我们这里已经有无数的案例表明,java 程序员转 Go,在写不满三个月的时间内就会离职 2333。就算不说 java,国内的 php 专家们也是不愿意写 Go 的,那些 PHP 大佬们哪怕在 swoole 之类的框架中重新实现一套 goroutine,也不愿意直接去写更原生的 Go 语言,因为用别人的东西体现不出轮子哥的价值啊。
对于一个公司来说,不应该听信那些微服务布道师的胡言,任由公司内的技术栈随意分裂。最终在公司调整或者变化的时刻才发现积重难返。那些几千甚至上万研发的“大公司”,大多都没有做到技术栈的统一,哪怕是在线业务技术栈也是如此。
拆分与收敛的灾难
在之前写事故驱动开发的时候,提到过,在企业中的项目进行开发时,只要是自己方便,一个人可以用拆分和收敛同时作为自己的标准。所以大家都是双标狗。
目前业界的微服务方法论一般也没有固定的套路,比如在 《Building Microservice》 一书中,作者也讲到了服务之间协作的时候,可以选择编排(orchestration)和协同(choreography)这两种方式来对服务进行架构。所以在拆分阶段,就没有什么硬性的标准了,每个公司可能风格都有差别,并且都可以阐述出自己的条条以支持自己的架构是“正确”的。
显然,这件事情没有绝对正确的解法。无论哪种拆分方式,都会遇到业务边界的问题。在大企业中,顶着“架构师”头衔的这些架构师们根本就不会管任何实现上的细节。相对较大的业务需求,一般也是一线的 RD 商量怎么进行实现上的拆分。想要达到合适的职责划分,需要多个合作方的所有人都靠谱才行。这个要求实在是有点强人所难。比如在目前的公司内进行了三年多的开发,就和各种类型的人都打过交道。
兢兢业业的人比较多,但也不乏一些完全不负责任的人。有的素质奇差,只会逢迎甩锅,自己模块的职责都搞不清楚。本来自己该做的兜底不做,让所有下游系统给他擦屁股。从应聘要求上来讲,程序员应该是一个注重逻辑能力,编码能力的职业。然而你总发现有些人是没有办法讲道理的(可能是早期的一些能力一般的员工?),在与这些人讨论技术实现时,会陷入无穷无尽的无意义循环。而他能说(逼)服(迫)所有其它人就范的法宝,就是在两个小时的会议中不断地复读自己的观点,而完全不听取任何别人的观点。一旦这样的人在你的某个系统边界上待着,那你所面临的也是持续的痛苦。并且不断地在自己的系统中进行妥协,做那些职责上跟你的系统完全没什么关系的东西。
除去人的问题,业务部门的大多数一线领导是需要有业务上的业绩的。这种业绩怎么来?一般都是揽各种各样的活儿,能揽多少就揽多少。
从设计原则上来讲,逻辑上相同或者类似的代码应该放在一个地方来实现。这个稍微学过一点 SOLID 中的 SRP 原则就应该知道。这样可以避免逻辑本身过于分散,好处是:“一个类(模块)只会因为一个理由而发生变化”,其实就是相同的需求,尽量能够控制在单模块内完成。当然了这是一种理想状态,确实有些情况下达不到这种完美的平衡状态。微服务场景下这种业务边界往往划分都非常糟糕。即使 Domain Driven Design 的观点讲述了再多的上下文划分技巧,你在实际工作中会发现没有多少人把这些思想、原则当回事,一线 leader 在乎的就是揽活儿而已。他们在划分模块的职责时只考虑这么几点:
这件事情有没有业务收益,我能不能掺一脚 如果没有收益,这件事能不能甩给别人,我就不去做了 把业务上的影响全刨去,才会考虑架构上的事情。有些大佬会讲,系统是演化出来,而不是设计出来的,而这些“演化论”的大佬也是不参与一线开发的。你再看看实际的情况,只靠演化,可能演化出合理的系统么?
不可能的,对人的要求实在太高。而且是对所有开发人员要求都高。大多数企业的业务系统,都缺乏较为顶层的设计,有人管这个叫“战略设计”。相对复杂的业务逻辑,在企业的系统中根本没法合理解耦,很多时候实现一套业务流程的代码会随意地散落在多个模块,在你把所有模块的代码都看过之前,你是没法确定哪部分逻辑在哪个模块里的。可以称之为薛定谔的业务逻辑。
这样在模块发生负责人离职或者工作交接时,所有 RD 都会进入非常痛苦的阶段,我只看自己的模块,根本没法理清全局的业务逻辑!对于产品来说也一样,所有逻辑的分布都具有不确定性,在哪里控制我需要去问研发,而研发还要再问其他的研发,其他的研发如果是刚接手,又要去看大量的代码才能确定到底是怎么回事。如果代码写的烂(对于大多数业务系统来说,如果可以去掉),那可能连看都看不懂。就随便胡诌应付过去好了。现实就是如此荒诞。
在所有服务都在单体的时代,我们可以在合适的时间,参考《重构》书里的观点对我们的模块进行重构。重构对系统本身的要求其实也不多,只要测试覆盖率足够,然后是强类型语言,大多数 IDE 对重构支持都很不错了。但演化到微服务的时代,原来很简单的重构就没那么简单了。在 http://xargin.com/disaster-of-microservice-ul/ 中我们提到,开放的 API、消息、数据库中的字段名根本没有办法进行重构,为什么没有办法,因为我们的模块都被切开了,原本在代码中的强联系变成了分布式系统中的弱联系,薛定谔的联系。如果我们想要实现和单体服务一样的重构功能要怎么办?根本实现不了。你至少需要下面这些设施支持才能完成这样的伟业:
所有其它模块对你都有集成测试,并且有统一的 API 平台管理所有你们之间的“联系”。有全局所有模块的“同时重构”工具 上线时,能针对旧版和新版的流量自动进行识别,防止新流量访问到旧系统 这个显然不可能啊,目前业界提出的 API 版本管理,也只是缓解了这种情况,新功能我如果在新版 API 实现,把旧版 API deprecated 掉,这样就可以逼迫用户放弃对我原来版本的依赖,平滑迁移到新版 API 上来。但显然加上版本,也并没有从本质上来解决问题,API 的用户在没有迭代需求的前提下,因为依赖方进行了修改就不得不进行修改,这是额外的工作量。
你也看到了,拆分给我们带来的并不全是好事,当前中大规模公司的开发日常流程,可能最终还是会把系统整体引向一片混沌。
难以治理的依赖地狱
微服务模式下,我们的系统中往往需要集成进各种各样的 SDK,这些 SDK 部分来自于非功能性的业务需求,例如 bool 表达式解析,http router,日期时间解析;一部分来自于对公司内基础设施的绑定,如 MQ Client,配置下发 Client,其它服务的调用 SDK 等等。
一般的观点会认为公司内的 SDK 是较为可靠的,而开源库的稳定性不可控,所以人们在升级公司内部库时往往较为激进,开源库版本升级较为保守。具体到 Go 语言,公司内的库,我们可能会直接指定依赖的版本为 master(glide 中每次构建会使用 master 分支的代码)。
实际上真的如此么?业界有个名词叫 dependency hell,指的是软件系统因依赖过多,或依赖无法满足时会导致软件无法运行。导致依赖地狱的问题有:
依赖过多 一个软件包可能依赖于众多的库,因此安装一个软件包的同时要安装几个甚至几十个库包。多重依赖 指从所需软件包到最底层软件包之间的层级数过多。这会导致依赖性解析过于复杂,并且容易产生依赖冲突和环形依赖。依赖冲突 即两个软件包无法共存的情况。除两个软件包包含内容直接冲突外,也可能因为其依赖的低层软件包互相冲突。因此,两个看似毫无关联的软件包也可能因为依赖性冲突而无法安装。依赖循环 即依赖性关系形成一个闭合环路,最终导致:在安装A软件包之前,必须要安装A、B、C、D软件包,然而这是不可能的。我们编写的服务也属于软件系统的范畴,所以也难以摆脱依赖地狱的问题。在微服务场景下,因为本文开头所述的原因,我们必然会依赖一大堆外部 SDK。对于开发者来说,实际上真正有选择权力的就只有我可以使用什么样的开源库。公司内的 SDK 是没有自己造轮子的价值的。毕竟自己造的司内 SDK 也没有人会帮你修 bug,原生 SDK 至少有单独的团队维护。
在开发 lib 时,比较好的做法是尽量引入少的依赖,以避免上面提到的问题 1。实际上没有几个提供 SDK 的团队能做得到,想想当初 javascript 圈子的 leftpad 事件吧,即使是一行代码的库,被人删除就引起了无数大公司系统无法 build 的悲剧。对于目前 Go 编写的系统,实际上存在同样的风险,我们的依赖大多来自于 github,如果你们没有使用 vendor 方案把依赖缓存到自己的系统中,别人删库了你有辙么?
一般 star 数比较高的开源库作者还是比较有节操的,删库的人毕竟是少,所以我们先假装这个问题不存在。公司内的实际开发过程中,我们遇到的依赖地狱大多体现在依赖冲突上,这个比较好理解,比如:
A --> B --> D.v1 A --> C --> D.v2 A 模块依赖 B 和 C,而 B 和 C 分别依赖 D 的不同版本,如果 D.v1 和 D.v2 恰好进行了 API 不兼容的更新,且都是在 github.com/xxx/D 路径下,通过 tag 来区分版本。那么就会对我们造成很大的麻烦,B 和 C 如果又恰好是公司内的不同部门的不同团队,要求他们因为这种原因进行兼容性更新就像是去求大爷一样难。
印象中之前 Go 社区是没有办法解决这种问题的,为了解决这个问题,我司还专门对 glide 进行了定制,以在依赖冲突的时候提醒用户,阻止构建,防止之后出现问题。Go 的社区在这方面也做得确实不太好,gomod 我还没有试用,不清楚是否对依赖冲突有优雅的解决方案。之前社区里大多数人对 Go 的依赖管理也一直颇有微词,希望 gomod 能彻底解决这些问题吧。
除了语言本身的问题,我发现公司内的 library 研发们,根本没有任何开源界的节操,版本升级时根本不考虑向前兼容或者向后兼容的问题,并且出现问题的时候也不会做任何提示,连日志都基本不打印。经常会有配置管理 v1 和配置管理 v2 的 sdk 同时存在模块中时,发生同一个全局变量初始化两次,发生冲突,逻辑不能正常运行,结果启动阶段没有任何 warning,直到执行阶段才出现诡异错误,导致用户在线上埋下定时炸弹的问题。
实在是不知道该说什么好。
多模块之间的循环依赖就更不用说了,如果循环依赖出现在单机系统中,至少在 Go 语言中是没法编译通过的,而因为微服务的关系,循环依赖往往会存在那些没有合理划分业务边界的系统当中。据我观察,出现得还不少。
这时候你能重新划分职责,让循环依赖消失么?
显然是不行的。程序员在当前的微服务架构下,将持续地被外部的垃圾 SDK 和各种莫名其妙的依赖问题所困。
嘴上最终一致,实则最终不一致
现在的架构师总喜欢把最终一致挂在嘴上,好像最终一致是解决分布式场景下数据一致问题的金科玉律。事实上又怎么样呢?
事实上的这些人嘴里的最终一致,往往都是最终不一致。在多个系统之间进行数据传递时,无非通过 RPC 或者异步消息。RPC 能保证一致性么?当 B 系统需要 A 系统提供数据时,想要达到一致的效果,那么在 A call B 时发生失败,那么必须让 A 中的逻辑终止。这样才能够使 B 中的状态或数据与 A 中的完全一致。这样实际上需要让 A 和 B 成为生死共同体,B 挂了,那 A 也得挂。可能么?在中大型规模的互联网公司的业务系统中,其下游系统往往有几十个,因此实际的场景是 A call B -> A call C -> A call D .... -> A call Z。这种情况下,你想让所有系统中的状态都一致,那是不可能的。
有的架构师又拿出 saga pattern 来说事,我如果有写数据的逻辑,那么我自然会有一套回滚逻辑,只要在中间发生错误,那么我就对之前的所有调用执行回滚逻辑即可。然而回滚是需要开发量的。我所有下游系统那都得支持回滚才行啊,你觉得做得到么? saga pattern 的异常处理就更扯蛋了:回滚过程中发生失败的话,那需要人工介入,人肉处理。显然人肉是处理不过来的,机房网络抖动实在太正常了,可能一天两天的就会有一次,每次抖动都造成 bad case,研发人员不用干别的事情了,都去处理 bad case 好了。
当然上面这种情况比较极端,一般公司内有靠谱的 MQ 方案的话,会选用 MQ 对这种数据同步的场景进行解耦。之前我做的一些总结也都提到过,只要往 MQ 发一条消息,在字段上尽量满足下游系统,那么我就不用挨个儿去调用他们了,可以很好地进行解耦。从设计的角度上来讲,这确实是比较好的解耦形式。但是你要考虑,逻辑执行和消息发送这两步操作并不具备原子性,除非 MQ 支持事务消息,我才能完成两个操作同时成功或者失败,何况逻辑执行内部可能还有更多的子操作,这件事情远没有打打嘴炮那么简单。
也有的公司会将发送失败的消息进行落盘,比如落进 MySQL 或者写入到磁盘,在发送失败之后,由后台线程在合适的时间进行重发,以让消息能够最终发出。一些简单的场景,这样确实算是解决了问题。如果下游对于消息本身有顺序要求呢?比如订单的状态流转,如果顺序错了,那状态机最终的状态都错乱了。又是一个麻烦的问题。
在当前的开发环境下,想要达到最终一致的效果需要上下游同时进行很多工作,例如上面说的异步消息的场景,上游至少需要做失败落盘和后台发送。而下游需要在状态机的正常状态流转之外,处理各种麻烦的乱序问题。这种乱序处理基本和业务是强相关的,并没有通用方案。即使是同一套状态机,针对不同的业务场景可能还需要定制不相同的业务逻辑。
除了网络抖动,数据不一致的问题可能还会因为模块上线导致。有些公司(比如我司)为了简单 MQ 的消费逻辑,提供了一套由 MQ 平台消费,然后通过 http post 来将消息发送给业务系统的逻辑,降低了业务系统的消息消费开发成本(这样就不用使用 MQ 的 client)了。这种情况下如果模块发生上线的话,即使在 MQ 平台侧有 post 重试,但在模块上线时,还是有概率发生消息丢失。如果有一些状态机流转强依赖于这些消息,那也会造成一部分 bad case。而且这种 bad case 查起来真是没什么意思。之后的数据修复也基本只能靠研发人员自行修复。
这种恶劣的场景下,也有一些人想到了一种方法,我在业务模块中插入多个桩,只要可以每过一段时间触发状态的全量更新,那么我就找一个其它模块来持续地刷新我系统中的数据状态。从而达到“最终一致”。只要这些最终一致的数据没有暴露给用户,没人看得见,那就是最终一致。倒确实是个可用的方案。但架构师们在吹牛逼的时候,对于这种恶心的逻辑一定是绝口不提的。
大多数公司的架构师嘴里的最终一致,依靠的都是人肉而非技术。
合作的灾难
架构师们常讲的设计定律之中最为重要的是康威定律,康威定律的定义:
Conway's law is an adage named after computer programmer Melvin Conway, who introduced the idea in 1967. It states that. organizations which design systems ... are constrained to produce designs which are copies of the communication structures of these organizations.
这里的 'are constrained to' 即是该定律的精髓所在。如果一个公司的组织架构已经基本成型了,那么基本上设计出的系统架构和其人员组织架构必然是一致的。
在微服务场景下,团队会按照其所负责的模块被依次切开成为一个 5-10 人小团队,然后再由更为顶层的架构少来按照组织架构设计相应的系统。但是这里面有一个先后关系,是先设计系统,再根据系统来形成对应的团队。但很多时候也并不一定是如此,因为某些公司招聘速度过快(笑),可能团队先形成了,然后才有系统设计,这时候,系统设计可能甚至会被团队架构所反作用(大概)。还是比较荒唐的。
即使是正常的设计流程,业务需求总是难以预测的。架构师们一般在设计完最初版本的系统架构之后,便会抽身到新的系统中继续挖坑。新的需求却在后续的实现过程中渐渐发现无法与最初的架构设计相匹配,具体体现在很难在当前架构上实现,或实现成本过于高昂,单模块几人天的事情,在当前架构上需要以月计的工时,这显然是不可接受的。
这时候该怎么办呢?没什么好办法。一套系统的架构一旦形成了,如果不是发生重大事件(例如迭代龟速导致公司在响应速度上跟不上竞争对手的步调;或者发生舆论事件,导致公司陷入风口浪尖,高层承诺短时间无法兑现),一般系统本身并不会有架构上的变动。一线的开发人员最能体会这时候的痛苦,但是痛苦也没有什么卵用,因为这时候没有人有动力去推进架构上的变动。试想,在风平浪静的平日,没有任何一个一线 RD 能有能力去推动一堆比他们高两三级的“专家”做事。而一线的 leader 也没有动力去做这种于自己于自己组完全无益的变动,哪怕明知道现行架构已完全无法满足业务需求,多一锅不如少一锅。Manager 们就更不用说了,多一事不如少一事,能堆人解决的问题就尽量不用技术去解决。
所以你也看到了,这种问题是无法解决的。曾经在和同事讨论的时候,同事提出,按照这种说法来看的话,小公司的架构可能比大公司还要靠谱?
这当然也不一定,小公司一般开不出优秀人才的价格,所以优秀的架构师基本上是不会去小公司的,这就意味着大多数小公司的架构,肯定更加不靠谱。除非他们能持续发展壮大,公司财务健康,在不进行服务治理没有办法继续做业务的困境时,招入了合适的架构师来做全局把控,完成一次大的整体重构,彻底偿还历史技术栈,才会慢慢有所好转。当然这也只是个空想,业务驱动的公司不可能把业务完全停下来支持这种技术上的整体重构,记得阿里的人说在 09 年的时候进行公司的服务化,让整个公司的业务停了半年?这种项目如果最后效果不好,那负责人肯定是要离职谢罪的。大多数技术老板也是一定没有这个魄力让业务半年没有进展的,这样搞不好直接就被 CEO 干掉了好吗。
从技术上来讲有解决方案的问题,如果把政治也考虑在内,可能就变成了无解的问题。大多数公司内的业务系统所要承受的这个痛苦的过程从公司发展的历程上来讲,是必然的。所以各位技术同学,就不要抱怨业务代码写得乱了。
技术人员所能发挥作用的范围被限制于自己的模块内,或者那些愿意接自己需求的其它支持系统间。除了前面说的组织架构的问题,还需要考虑 KPI 的问题。
之前和同事一起得到了一个在大公司内推进事情的靠谱结论,如果一件事情在一个部门内就可以解决,那可以开开心心地推动它解决。如果一件事情需要跨部门,那还需要本部门的大领导出面才能解决,哪怕这事情再小。如果一件事情需要跨两个部门,那就没治了,谁出面都不行。这种事情做不了的。而如果一件事情和你要跨的部门 KPI 有冲突,那就更别想了,把部门重组了才能解决,这是 CTO 才能干的事情。
如果想要在大公司得到较好的绩效,遵循 KPI 规则是必然的。没有办法。