microservice的anti-pattern

news/2024/7/23 8:39:01

本人主要关注的是领域驱动设计(ddd),一直觉得微服务算是另一个分野,没有特别地去关注。直到在油管上看到《领域驱动设计》的作者Eric Evans的这视频。(视频中Evans讲解了通过微服务,终于实现了可靠的Bounded Context的边界。)
这里写图片描述
才开始觉得这两个技术其实有一定关联。而在《building microservies》一书中作者也明确提出Bounded Context是一个很好的概念来帮助划分微服务。
这里写图片描述
工作上其实其他的部门也有过微服务的项目,不过由于当时经验不足(此处略去270个字…)。再加上最近看到的微服务的失败案例,正好分享一下这个话题。

API hell (API无限连击)

这是一个叫Jimmy Bogard的工程师在某个视频里分享的概念。其中的案例是说他为大电脑生产商Bell公司(你懂的)做一个耗时超过1年的网购服务时遇到的问题。
Bell公司的这个项目,工程师可能打了鸡血,一下子搞了200个服务!当然人家Netflix据说是有500多个微服务,与之相比差的还很远。
microservices
可能Bell公司是看了这张图给了他们启发。不过Jimmy Bogard对这个概念图的解释是这样的
gateway pattern
有某一个入口的服务gateway,它来调用内部的各种微服务,然后把结果合并返回。
而Bell公司的设计是相面这样的。
hell
服务A调用了服务B,结果服务B为了处理A的请求还要去调用服务C,服务C又要调用服务D,服务D可能又要去调服务A。虽然在例图里,大家都调用了4个服务,感觉没什么区别。问题是当A去调B时,A并没有意识到B要去掉其他的服务,A认为只是一个简单的请求,所以当A还要去调用另一个服务E,A根本不认为这是个很大的成本,结果可能就是在调E时,又去调了F,G两个服务。
hell
最极端的一个例子,一个请求如果要经过200个服务,每个子请求150ms,结果在理想的情况下,一个从画面过来的请求就要话30秒的时间。显然这是完全不能被接受的。再加上请求还有可能失败。假设一个请求成功率是99.9%,那经过两百次子请求的请求的成功率会是81%左右。也就是5次里有一次画面会报错。其结果就是Bell再发布这个网购服务时,没有能够跑起来。之后财大气粗的他们用了最好的硬件,尝试跑这个微服务时,第一个页面的响应据说用了超过30秒。
Bell公司的微服务项目建立在了如下的前提下。
- 网络是可靠的
- 网络的延迟是可以忽略的
- 带宽是正无限的
当然没有一个前提是成立的。(反之,在设计微服务时必须考虑微服务连不上和延迟问题,好痛苦orz)
另外Bell公司的开发模式也很有趣。将近百人的开发团队,分成各个小组,小组分担几个微服务,发自己的微服务时,把需要依赖的外部微服务全部以mock来实现,直到开发的后期进入集成阶段才发现了这个问题。
因为忽略了调用外部服务的成本,所以产生了数以千计的歌中API调用——API hell。同时想引出下一个点。

微服务没有自主权(autonomy)

微服务的自治权是一个很重要的概念。某种程度上它定义了什么才能被称为一个“服务“。
而Bell公司的项目上他们更重视另一个原则–single source of truth。目的是要避免数据一致性的破坏。
我个人觉得遵不遵循ssot是个比较有争议的话题。因为当你把一个服务的数据拷贝到另一个服务,无疑是增加了服务的复杂度,你必须考虑不同服务内的数据如何保持同步之类的。不过当你禁止服务间数据的拷贝,带来的问题便是各个服务之间会发生数据依赖性。
假设有一个Product服务,Order服务,Delivery服务。产品的信息只允许产品服务拥有。其他的服务要调用产品服务获取产品的信息。个人觉得当假设Order服务需要提供订单列表这个功能。每次显示订单列表都要调用Product服务。这显然称不上自主。
autonomy
如果你对的例子存有疑问,认为具体允不允许数据拷贝要根据实际的需求来决定。另外本人还经历过更悲惨的项目。居然把分层的概念于微服务混同。他们把分成了view service, logic service, data persistence service。
autonomy2
当然从自主权方面来讲根本完全不满足。一个单独的view service是没有任何意义,只是一个二传手而已。。。
autonomy3
这样的服务分割法,当然也继承了分层设计的优良传统–级联修改。假设Product需要增加一个字段,那我们必须修改3个服务,自主权何其的低啊。。。

不过你可能觉得如果我们不是那么傻,把层级都分成服务,就按业务分成各个服务(就是这节开头的分法),不是也会发生同样的级联修改的问题吗?
autonomy
当你的product的数据构造(比如加了一个字段)变化了,会影响到Order服务。
面对这种情况个人觉得应该先弄清需求。Product服务与Order服务所需要的product是同样的东西吗?关于product,Product与Order关注的点是否相同。这少很多情况下两者关注的东西是不同的。比如假设Product服务需要不仅需要商品的名称,型号,价格,还要关注发售日期,生产厂家等等信息。而Order服务则可能只需要商品的名称,型号,价格。
这里写图片描述
这里借鉴了ddd的Bounded Context这个概念。在不同的Context中,我们有不同的描述产品(product)的模型。在Order服务中,product模型可能只是隶属于order集合(aggregate)的一个value object。
在这个前提下,只要是Order服务不关注的对product的更改,比如说新增了一个产品原料这类的信息,这些更改理论上都不会影响Order服务。
然后再把Order服务需要的product数据都通过某种方式同步给Order服务,结果Order也有product的数据的拷贝。具体数据同步的方式,根据需求的不同,实现也不同。这个例子里,我们甚至可以把product的信息当作orders表的一个字段,然后在Order生成,对orders插入数据时,一起将product的信息也保存。
这里写图片描述
如此情况下,Order服务在实现”显示订单信息”功能时不用再询问Product服务了。
autonomy4

总结

这次讲了microservice的两种反面教材。
一种是忽略了通讯的成本和风险。这就好像你是用云服务,然后指望云上的虚拟机用不宕机一样。人家云服务供应商已经明确说明了云上的资源可能是不稳定(把责任规避得一干二净),需要考虑到这个风险设计能够应对它的服务。(还信誓旦旦地出了个cloud design pattern供你参考)
另一种是微服务分割的不合理。ddd的理论在服务分割时会有帮助。
通过这两种方式可以让你的微服务项目迅速变成一个天坑,(希望大家慎用! ^ ^)
另外其实还想谈谈开发团队,组织方面的问题,等到下次再写吧。


http://www.niftyadmin.cn/n/4428405.html

相关文章

Java 线程池 四种创建方式

Java通过Executors提供四种线程池,分别为: newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 newFixedThreadPool 创建一个定长线程池&am…

谷歌云服务架构师的考点整理: VPC network

GCP的VPC networknetworkregion, zone通信子网创建模式防火墙规则(firewall rules)最近参加谷歌的云服务架构师的考试,趁现在把知识整理一下。VPC network的话对于用过云服务的人来说应该是个很基础的概念。它就是物理网络的虚拟版(virtual private clou…

MongoDB安装、启动与关闭

安装包下载地址:mongodb-win32-x86_64-2.6.6 1.解压到当前文件夹,进到目录mongodb-win32-x86_64-2.6.6,在这里新建data文件夹, 2.进到data目录下,在这里新建db文件夹, 3.命令行进到bin目录下,输…

Mysql慢查询日志的使用 和 Mysql的优化

一、生成实验数据 原理:sql 蠕虫复制(这种生成数据方式同样适用于数据表中有主键的情况)。 insert into comic (name,pen_name,cover) select name,pen_name,cover from comic二、慢查询日志设置 当语句执行时间较长时,通过日志的…

tdd(测试驱动开发)的概述

最近的工作的项目,使用了tdd(test-driven development测试驱动开发)的开发模式。 这两几年大概听说了无数种xxx-dd, ddd, tdd, atdd, bdd, fdd, udd各种名词眼花缭乱,当然很多dd其实也有相互借鉴(抄袭)的部…

取文件的MD5值

之前找了好多取文件MD5的都不能用 最后找到这个可以用 记录一下 MD5.h #include <Windows.h>#include <stdio.h>#ifndef _LGY_MD5_H#define _LGY_MD5_H/* MD5 Class. */class MD5_CTX {public: MD5_CTX(); virtual ~MD5_CTX(); bool GetFileMd5(char *pMd…

嵌套对象

嵌套对象&#xff0c;就是相当于一个封装&#xff0c;内存空间里留下一条连续内存

接口 、final

接口&#xff1a; interface 接口名{变量&#xff1b;方法&#xff1b;}/*** author 司*** 功能&#xff1a;接口的使用*/package com.InterFace;public class Demo1 {public static void main(String[] args) {// TODO Auto-generated method stubWuKong wknew WuKong()…