Segment 微服务反水案的一点思考
2015 年底,Segment 博客刊登了一篇文章,Why Microservices Work For Us(下文简称为《Work》),三年多,又来了一篇更具话题性的新作:Goodbye Microservices: From 100s of problem children to 1 superstar(下文简称为《Bye》),两相比较,感觉还是有一定的代表性的,这里做一点整理和记录。
微服务的动机
《Work》中提到,主要是因为原有架构中,故障处理不力,需要有更快的诊断和处理方法,这里提到了两个途径:
- 问题的快速定位:借助更细致方便的监控指标,为突发事件提供明确指导。
- 问题的快速解决:在定位问题之后,能够更好的从源码中获得支持并解决问题。
要针对某个功能加入监控指标,在原有单体架构中可能会造成不必要的影响,范围不易控制;而源码方面,越小的服务,通常也代表着相对易读的代码;这两个主要需求都指向了同样的解决方案:微服务。
微服务带来的好处
将原有单体应用拆分为微服务之后,不但解决了监控和排错的问题,还带来了一些额外的好处:
- 隔离的消息队列,不同订阅互不干涉
- 隔离系统资源,如 CPU 和内存。
- 隔离网络,如 ELB。
这里只是原文中提到的好处,更多的照本宣科内容这里就不赘述了。
改造的隐忧
《Work》一文中提到了两次,新建微服务应该如何如何。作者似乎认为,微服务有必要更快的创建起来。但按照我的理解,微服务仅是通过进程隔离的强制手段使得模块之间的边界更加清晰,事实上,因为缺乏单体应用强大的上下文支持,同一系统内的不同微服务,往往会因为上下文问题,导致更加复杂的开发过程。
《Bye》一文中补充的拆分过程
单代码库阶段
因为缺乏有效的时间说明,这一操作让人很迷惑,不知道是不是在《Work》发表的时候,各个服务还是在共用同一套代码。也在共用同一套测试方案。《Bye》中提到,一个失败的提交会导致整体测试失败,因此我们大致可以说,这一阶段里,CI/CD 过程也没能完成分割,个人认为,这种情况不太应该算作微服务。
多代码库阶段
为微服务独立创建各自的代码库,并享用各自的测试组件。
共享库阶段
我认为这一阶段呼应了前面的隐忧,在微服务落地之处,为了更快的建立微服务,开始出现了跨服务的共享代码库。
败局的开始
《Bye》的共享代码库一节,罗列了不少遇到的问题:
- 共享库版本出现碎片:因为工程需要,并不敢冒险同时更新的多个服务的共享代码,造成部分服务的共享代码滞后。
- 伸缩能力:微服务架构经常鼓吹的能力,在这里似乎被狠狠抽了一巴掌。
个人的一点分析
我眼中的微服务,务虚的角度上来说,有两个关键字:妥协,怀疑。
这里所说的妥协指的是,我们的系统是存活于一个非“理想状态”中的,不管是“拥抱变化”,还是“面向故障”,都是对不完美世界的具体应对方式。
而怀疑的设计态度,最简单的证据就是对隔离的强调,系统资源、数据的隔离都如此强调,我想,代码的隔离是不言自明的。
反观 Segement 的重构之路,(可能)在宣称微服务的时候,各个服务还躺在同一个仓库里,还在共享同样的测试过程。
在进行代码分离之后,又出现了两个不太容易理解的纰漏:
- 暧昧的代码:前面提到,怀疑是微服务的基本态度,从《Bye》文中可以看到,他们在几十个服务之间共享了需要进行频繁重构的代码,抛开微服务和单体之争不谈,这个行为实在是无法理喻。
- 暧昧的负载:在拆分之后,按照《Work》的说法,每个微服务都有了独立的队列和监控,由此推论,各个微服务的负载模型应该是比较容易做个描述的,很可惜的,这里称之为一种艺术。
回到单体之后
此处内容给人一种感觉:“今天是个好日子”。给我的感觉是,一些原本应该在微服务阶段完成的工作,甚至是在微服务阶段之前的单体阶段完成的工作,终于完成了——例如改进的测试方案,这能算是半渡而击么?
牢骚
流水账写到这里,有两个深刻的印象就是,这个团队从来没想过这些微服务是彼此独立的,也从来没把每个服务作为一个单独的交付物进行完善。从这个印象出发,回到单体形态,可以说是——得其所哉。