微服务之间的最佳调用方式( 四 )


它的解决办法就是使用一个支持向后兼容的RPC协议,现在最好的就是Protobuf gRPC,尤其是在向后兼容上 。
它给每个服务定义了一个接口,这个接口是与编程语言无关的中性接口,然后你可以用工具生成各个语言的实现代码,供不同语言使用 。函数定义的变量都有编号,变量可以是可选类型的,这样就比较好地解决了函数兼容的问题 。
就用上面的例子,当你要增加一个可选参数时,你就定义一个新的可选变量 。由于它是可选的,原来的客户端不需要提供这个参数,因此不需要修改程序 。
而新的客户端可以提供这个参数 。你只要在服务端能同时处理这两种情况就行了 。这样服务端并没有增加新的函数,但用户的新需求满足了,而且还是向后兼容的 。
微服务的数量有没有上限?总的来说微服务的数量不要太多,不然会有比较重的运维负担 。有一点需要明确的是微服务的流行不是因为技术上的创新,而是为了满足管理上的需要 。单体程序大了之后,各个模块的部署时间要求不同,对服务器的优化要求也不同,而且团队人数众多,很难协调管理 。
把程序拆分成微服务之后,每个团队负责几个服务,就容易管理了,而且每个团队也可以按照自己的节奏进行创新,但它给运维带来了巨大的麻烦 。所以在微服务刚出来时,我一直觉得它是一个退步,弊大于利 。但由于管理上的问题没有其他解决方案,只有硬着头皮上了 。
值得庆幸的是微服务带来的麻烦都是可解的 。直到后来,微服务建立了全套的自动化体系,从程序集成到部署,从全链路跟踪到日志,以及服务检测,服务发现和注册,这样才把微服务的工作量降了下来 。
虽然微服务在技术上一无是处,但它的流行还是大大推动了容器技术,服务网格(Service Mesh)和全链路跟踪等新技术的发展 。不过它本身在技术上还是没有发现任何优势 。
直到有一天,我意识到单体程序其实性能调试是很困难的(很难分离出瓶颈点),而微服务配置了全链路跟踪之后,能很快找到症结所在 。看来微服务从技术来讲也不全是缺点,总算也有好的地方 。但微服务的颗粒度不宜过细,否则工作量还是太大 。
一般规模的公司十几个或几十个微服务都是可以承受的,但如果有几百个甚至上千个,那么绝不是一般公司可以管理的 。尽管现有的工具已经很齐全了,而且与微服务有关的整个流程也已经基本上全部自动化了,但它还是会增加很多工作 。
Martin Fowler几年以前建议先从单体程序开始(详见 MonolithFirst),然后再逐步把功能拆分出去,变成一个个的微服务 。但是后来有人反对这个建议,他也有些松口了 。
如果单体程序不是太大,这是个好主意 。可以用数据额库表的数量来衡量程序的大小,我见过大的单体程序有几百张表,这就太多了,很难管理 。正常情况下,一个微服务可以有两、三张表到五、六张表,一般不超过十张表 。但如果要减少微服务数量的话,可以把这个标准放宽到不要超过二十张表 。
用这个做为大致的指标来创建微程序,如果使用一段时间之后还是觉得太大了,那么再逐渐拆分 。当然,按照这个标准建立的服务更像是服务组合,而不是单个的微服务 。不过它会为你减少工作量 。只要不影响业务部门的创新进度,这是一个不错的方案 。
到底应不应该选择微服务呢?如果单体程序已经没法管理了,那么你别无选择 。如果没有管理上的问题,那么微服务带给你的只有问题和麻烦 。其实,一般公司都没有太多选择,只能采用微服务,不过你可以选择建立比较少的微服务 。如果还是没法决定,有一个折中的方案,“内部微服务设计” 。
内部微服务设计这种设计表面上看起来是一个单体程序,它只有一个源代码存储仓库,一个数据库,一个部署,但在程序内部可以按照微服务的思想来进行设计 。它可以分成多个模块,每个模块是一个微服务,可以由不同的团队管理 。

微服务之间的最佳调用方式

文章插图
 
用这张图做例子 。这个图里的每个圆角方块大致是一个微服务,但我们可以把它作为一个单体程序来设计,内部有五个微服务 。
每个模块都有自己的数据库表,它们都在一个数据库中,但模块之间不能跨数据库访问(不要建立模块之间数据库表的外键) 。
“User”(在Conference Management模块中)是一个共享的类,但在不同的模块中的名字不同,含义和用法也不同,成员也不一样(例如,在“Customer Service”里叫“Customer”) 。


推荐阅读