避免对微服务的误解


开篇声明:我是微服务的超级粉丝。

当下的时代,每隔一段时间就会出现一种新概念或新技术,它带来了希望、炒作的热点,貌似可以拯救全世界一样。但我认为,这确实有它正确的一面,新技术具备突破性,能够带来重大受益。然而,它也像生活中的其他任何事物一样,有它的能力适用范围。就好比我们不能把牛顿定律应用到亚原子粒子上面,或者尝试过把电视塞进袜子里。就好比我们不能忽略了合同最后的约束小条款,它的后果可能是灾难性的。
图片_3.png

照片源自Mollie Sivaram

拆分是可选之策

常规的Web应用程序通常只有一个进程接收和处理所有请求,也许出于负载均衡或高可用等因素考虑,我们会复制一份部署,但在任何单一场景下,都是将全部的实现逻辑,打包成一个包和软件在运行。而使用微服务的方式,我们可以设计拥有多个不同的进程,每个进程可以运行不同的包,而每个包只包含自己的代码,处理请求的子集数据。

是否为新事物

答案是否定的,事实上,微服务模式就像任何新兴技术一样,在热炒之前已经存在了好几年。它本质上就是面向对象编程在更高级层面的应用,即是应用程序架构。同时,情理之中的是微服务会经常使用HTTP API,尤其是RESTful。而RESTful API正是OOP原则在API设计的应用结果。

稍微扩展一下面向对象编程的过程。使用面向对象编程创建一个程序时,通常会将它分割成几个类,每个类表示一个组件,具有特定的角色,处理相对应的数据子集,这都是耳熟能详的内容。虽然在此讨论面向对象编程并不合适,但是本文内容会涉及到几个关键的方面。如果读者不太熟悉这些内容的话,我建议参考这方面的在线文档

两个原则——“好裁缝”

我们在设计类的时候,必须遵循2个主要原则:低耦合和高内聚。坦白来讲,我认为这不仅仅是面向对象项目的指导原则,而应该是每个软件项目的明星指导规则(可能存在歧义,不展开)。

高内聚:是指组件每部分都是高度一致的愿景,包括它的数据和方法都指向非常具体的某一领域、意义或角色。

低耦合:是指每个组件与其他组件之间具备交互次数尽可能少的状况。它隐藏了底层细节,能够独立运行,仅仅暴露必要的接口。

对于微服务来说也是如此,好的面向对象设计会带来最优的微服务设计,个人觉得这点如何强调都不为过。这应该也足以回答为什么以及什么时候考虑采用微服务的问题,接下来继续阐述。
图片_2.png

好的裁缝就是用松散的针法缝合高密度的衣料。图片来自jeff Wade

拆分的时机

这与面向对象编程的情况是完全一样。即当需要且能够遵循高内聚、低耦合原则的时候。如果你知道如何以正确的方式将代码拆分为类。那你何必要随意地拆分你的微服务呢?如果一个糟糕的类图由于设计不合理,导致功能代码散落到多个文件内,最终难以维护和管理的话,那我们可以想象一下随意拆分微服务应用,导致一个微应用中运行着完全不同进程时的窘境。

拆分应用的理由

想象两个对象交互有多么容易,只需要调用另一个对象的方法,实现起来也非常容易,而且所有的代码都在一个程序内。而与之相反的微服务架构,应用运行在不同的线程,甚至可能是在不同的机器上,基于网络,采用API进行请求通信。

这显然增加了复杂性。但这么做一定有着充分的理由,既不是为了潮流,也不是为了好玩。我可以保证,如果你随意地使用微服务,所带来的管理一点都不会好玩。

实际上,决定使用API访问方式,与使用微服务的理由是相同的。API能够隐藏接口背后的所有实现细节,这在某些情况下是非常重要的,它可以带来超过预计的好处。包括:

针对非均匀流量的可伸缩性

假设一个超市应用程序,包含一个库存微服务,它只显示库存文章的数量;一个视图微服务,它使用GPU来检测文章中包含的图片。如果某一时间段,应用收到数千个库存应用的API请求,而只是少数视图微服务的API请求,那就只需要复制多个的库存微服务就可以应对API请求处理,而不需要去成倍增加GPU资源。

而换一种情况来考虑,将所有代码实现都集中在一台机或一个应用程序,且仍然希望通过扩展资源来匹配全部的访问请求,那么就必须整体复制部署,导致明显的非必须资源浪费。

容错弹性

假设有一个银行应用程序的转账微服务总是崩溃,原因可能是偶然的错误,或者更糟一些,软件版本未经测试,存在一个新bug。那是否因为这个原因,将整个银行应用程序都关闭呢?有些顾客并不关心这个业务,他们只是想看看自己资金动向或是用卡而已。

单独部署

按照前面所述的场景,如果要增加一个新功能或需要修复某个bug,微服务可以让我们只部署更新所对应的那个微服务即可,构建和部署时间也相应少很多。此外,还可以把微服务部署到不同的机器上或不同的位置。

完全隔离

对于需要在服务之间实现完全隔离的需求。通过微服务这种方式可以采用不同的数据库(SQL和NoSQL)或完全不同的实现技术。具备最大限度的设计自由。当然,容错弹性和请求处理等其他方面,它也同样存在严格的关联要求。

差异化需求

业务应用是否有重度密集计算的需求,例如机器学习,需要GPU资源,或需要调用大量的算法模型库等。或许部分需求用Python实现会更好,而其他部分更适合采用Java开发。又或者同语言的库可能会产生部分冲突,迫切需要两个特定的软件版本来处理两个不同的任务。甚至最终需要使用两种不同的云产品服务来承载2个不同的组件。造成上述等等现象的原因有很多,其中很多都是常见的情况。

在这些情况下,进行应用程序拆分可能是更容易的解决问题方法。但是我们必须谨记要遵守“两个原则”,成为一个“好裁缝”,否则将会经常面临艰难时刻,包括在微服务之间共享数据等方面。
图片_1.png

再高的巨石也是由石块或原子组成。图片来源Zoltan Tasi

上述内容不适用你的情况,就像巨石

如果你的系统是紧耦合,采用单体应用程序方式,那可称为巨石应用。你具有清晰的依赖关系管理,不需要复杂的编排或分布式系统来跟踪错误、共享数据、收集日志、同步调用、安全的网络交互等等。那是否面向对象编程就不适合你的应用程序架构,RESTful设计和微服务也不适用呢?

如果应用程序需要采用很长的API请求队列,其中一个API还需要调用前面API的返回结果,那么这种设计并不是一种理想的方式。它肯定会存在网络延迟等问题。

而如果应用程序是需要用户交互的单一过程呢,那么这句话本身就意味着它是一个不可拆分的程序。

如果必须在调用之间共享一个状态,每个组件都不是独立的,序列中的一个错误会完全阻塞整个请求序列。那分割应用程序几乎没有好处。

巨石应用并不等于混乱

混乱也有不同的理解,有人认为巨石应用是一堆错综复杂的代码。他们声称微服务模式是应用拆分、有序构建的唯一方式。

我想表达的是:这个观点并不正确。因为代码设计并不依赖于线程划分,面向对象编程也是如此。如果能够正确地拆分代码,在一个清晰的层次结构中管理好依赖关系,以及代码包文件,就可以实现同级别层面的内聚和解耦。

还有一个秘诀,如果把API划分成独立的包/模块/控制器/蓝图/甚至代码段等,就可以实现在代码构建时或运行时的阶段,自由决定是采用单一进程运行它们,还是拆分成微服务运行,没有任何限制。

结论

就如开篇所说的,我是微服务的超级粉丝。

如果从正确的环境,出于正确的原因考虑,微服务可以解决问题、提升性能或节约成本。但是,也请大家不要将它们理解为救世主,它们也可能是万恶之源。

原文链接:Stop this Microservices Madness(翻译:易理林)

0 个评论

要回复文章请先登录注册