1.1 单体服务的特点

当今世界,人们很难脱离他人的服务而完全自给自足地生活。回想一下,我们在超市购物,在餐饮店吃饭,都是在接受别人的服务,在公司解决客户的问题,则是在为别人提供服务。

那么服务是什么?简单来说,服务就是为满足他人需求所做的事情。一项服务就是一个独立的功能单元,比如上菜服务、结账服务、泊车服务等。

从软件开发的角度来看,服务就是进程外的组件,与其他组件以明确的接口进行交互,类似于进程内的函数调用。从可读性、可理解性、可维护性的角度出发,我们倾向于写小函数,避免写过于复杂的大函数,因为大函数容易出错,难于维护和修改。若干功能明确、职责单一的小函数有利于重用,提高开发效率。

同理,服务也一样。比如,我们起初开一个小饭馆,老板除了兼任厨师和服务员之外,可能还负责采购、收银以及打扫店面卫生,这是一个典型的单体服务。单体服务并非不好,店小利薄,提供的菜品较为简单,一个人也应付得来。然而生意兴隆、规模扩大之后问题就来了,老板分身乏术,就需要雇用厨师、服务员、收银员、采购员、保洁员等来共同做好饭馆服务,于是一个单体服务便拆分为多个微服务。

软件服务与此类似,当服务单一、规模小、逻辑简单时,用一个单体服务就挺好。在服务多样化、规模增大、逻辑变复杂之后,单体服务就不再适合了,缺点一一呈现。

❑ 复杂程度高。维护成本越来越高,各个模块之间边界模糊,一个模块的改动可能导致整个服务出现问题,一点内存泄漏、一处指针错误就会让整个服务停机,牵一发而动全身,更不要说共用的底层模块改动可能导致上层的异常,出现一点改动就需要做全面的回归测试,不敢漏过一个测试用例。维护成本的剧增也导致交付速度越来越慢。

❑ 水平扩展困难。单机的容量有限,而且缺乏弹性,一荣俱荣,一损俱损。垂直扩展受到硬件约束,水平扩展也比较困难,因为单体服务内部多半都维护着多个状态,从一台扩展到多台,状态迁移就是一个麻烦事,要做诸多改动,要么都从网络同步,要么放到共享存储中,无法做到有的放矢的局部扩展,比如,一个聊天服务器可提供语音和文字聊天服务,但无法只扩展语音服务来提供更好的语音体验。

❑ 性能优化困难。单体服务支持着多个业务,多种逻辑可以交织在一起,互相影响,互相争用系统资源,在服务性能调优时很难取舍,要做出令人满意的平衡很难。就如上述的聊天服务器,进行文字聊天时CPU占用率低,对及时性要求高,不可以丢包;而语音聊天由于需要编解码和混音,CPU占用率高,但允许少量丢包,在做优化时二者需要区别对待。

❑ 可测试性变差。复杂性变高、模块众多、耦合在一起的代码必然造成测试困难。如果只针对所改动的模块做出测试,则无法预知其他模块是否受到影响,所以在产品发布前有成百上千条用例(case)需要测试,往往也难以覆盖各种路径。如果这些用例不是自动化的,就更让人束手无策了。

所以,我们要提倡微服务,就是一种微小的服务,崇尚小而美,避免大而全。