Report of the Week [4] – Monolithic Microservice

/ 0评 / 0

Let's take a wild ride, shall we?

永远的全栈工程师

上一篇刚抱怨完无事可做,第二天可就来活了。当然,仍然是没有生产环境,还是要自己搞。

好玩的是,这个自己搞是真·自己搞,人员配置就三个:服务端、客户端和待定的测试。这比 GameJam 还缺人啊,美工呢?

这给出了一个明确的提示:当全栈只有零次和无数次的区别。所以,我们开始捯饬吧。

宏服务式的微服务

我们来谈一谈标题。所谓宏服务式的微服务,说白了就是仍然以宏服务的思路设计应用,但是模块之间的逻辑使用微服务的范式。换句话说,野路子。

这么做的唯一原因是为了控制服务个数。微服务中的一个诡异范式是成倍增长的服务数量,以及不合理的模块拆分。在后期,这种问题会变得更加明显。随着服务的增多,一些逻辑会要求多个服务之间协调运行,造成令人头疼的并发处理问题;服务过于细碎化,使得服务间通信占据了绝大部分的处理时间;模块与模块的负责人不同,相互甩锅,问题得不到解决;运维部署失误,关键服务无法运行,拖垮整个系统……

所以,我们需要反思一下:真的需要搞微服务么?我们搞微服务究竟是为了什么呢?

微服务究竟为了什么

微服务的提出是为了解决两个问题:部署和异构。

部署问题是当一个应用程序足够大的时候,部署这个应用程序并让它能用是非常困难的。尽管按道理,一个设计良好的宏服务应用应该就是一个 JAR 包或者一个可执行文件,但实际的部署过程要更复杂一些。某种意义上,部署过程是一个手术:把旧的程序下线、新的程序上线,同时服务还不能中断,难度可想而知。即使我们允许服务中断的话,情况也不会好很多,因为大型应用程序,尤其是包含非常多修改的程序,难免会包含错误。上一版本的 Bug 修好了,这一版本的 Bug 又一堆,你是上线还是不上?不上线回滚又是一大堆事情。出现了新的技术,你用还是不用?用的话,回滚事务会变得极端复杂;不用的话,当前技术栈迟早会被淹没在时代的洪流中。

微服务给出的答案是:别写那么大的程序,写小程序。当程序被分割得足够小的时候,其行为将可以被明确地定义,进而减少潜在的 Bug. 同时,部署小程序比部署大程序要来得更简单,因为小程序所包含的状态和依赖要更少。拆分程序也使得使用不同的技术栈成为可能。

宏服务

在系统的外部,不应该能够区分这个服务内部的具体实现。即任何系统的实现对于外部而言都是宏服务。当我们在做一个产品的时候,应该以整体的眼光去声明我们希望实现的功能,而不是因为我们需要做微服务,就先开始独立地设计每一个模块,再把每个模块凑到一起看看能出什么结果。

独立设计每一个模块能会有一个原因:希望能够复用模块到尽可能多的产品当中去。但实际上这么做有一点本末倒置的意味。模块的复用应该天然地发生在功能声明阶段:我们发现某些产品的部分功能是相似乃至一样的(比如日志功能和用户鉴权功能等等),那么我们就可以复用已经部署的、实现了这些功能的子系统,而非一开始就寄希望于设计一个通用日志或通用鉴权——毕竟,需求是会变化的,越通用的东西能提供的能力就越少;而能提供较多功能的一般其抽象程度都会极速增加,导致编写和维护困难。

*nix 哲学

一个程序,做一件事情,并把它做好。我想微服务也是应该反映这一点的。与其编写一个能够涵盖所有功能的程序,不如先从一个稳定的、易于测试的方法写起。大道至简,或许如此。

不可序列化的数据

尽管我们认为所有数据都是一等公民,但是在应用程序逻辑内,有许多数据是不可序列化的,是 volatile 的。如果我们将进程视作一个状态机,那么状态机的状态将是不可序列化的。这给我们的测试和调试工作带来了不小麻烦,以及对于一些需要动态重载的服务(可能随时会挂的 ReplicaSet 中的应用),状态不可重载就是大问题。

是什么导致了一个状态是不可保存的呢?

我们可以认为这是因为状态机与底层耦合的结果。不可序列化的数据通常表示一个底层状态——因为只有底层结构是 volatile 的,上层结构在合理设计的情况下可以是 immutable 的。任何 immutable 的数据均可以被序列化。当状态机的转移函数是在 ImmutableSet 上的代数结构时,状态机必然是可以序列化的。

理解了这个问题之后,我想对于实际应用中见到的各种奇怪问题,答案都在这里可以找到。

学数学的用处是什么?在菜市场买菜确实连一元二次方程都不需要,但是学会数学决定你之后可以在哪个菜市场买菜。

发表评论

邮箱地址不会被公开。 必填项已用*标注