你好,我是唐扬。

上一节课,我带你了解了单体架构向微服务化架构演进的原因,你应该了解到当系统依赖资源的扩展性出现问题,或者是一体化架构带来的研发成本、部署成本变得难以接受时,我们会考虑对整体系统做微服务化拆分。

微服务化之后垂直电商系统的架构将会变成下面这样:

在这个架构中,我们将用户、订单和商品相关的逻辑抽取成服务独立的部署,原本的Web工程和队列处理程序将不再直接依赖缓存和数据库,而是通过调用服务接口查询存储中的信息。

有了构思和期望之后,为了将服务化拆分尽快落地,你们决定抽调主力研发同学共同制定拆分计划。但是仔细讨论后你们发现,虽然对服务拆分有了大致的方向可还是有很多疑问,比如:

当然,你也许想知道微服务拆分的具体操作过程和步骤是怎样的,但是这部分内容涉及的知识点比较多,不太可能在一次课程中把全部内容涵盖到。而且《DDD实战课》中已经侧重讲解了微服务化拆分的具体过程,你可以借鉴。

而上面这三点内容会影响服务化拆分的效果,但在实际的项目中经常被大部分人忽略,所以是我们本节课的重点内容。我希望你能把本节课的内容和自身的业务结合起来体会,思考业务服务化拆分的方式和方法。

微服务拆分的原则

之前你维护的一体化架构就像是一个大的蜘蛛网,不同功能模块错综复杂地交织在一起,方法之间调用关系非常的复杂,导致你修复了一个Bug可能会引起另外多个Bug,整体的维护成本非常高。同时,数据库较弱的扩展性也限制了服务的扩展能力

出于上述考虑,你要对架构做拆分。但拆分并不像听上去那么简单,这其实就是将整体工程重构甚至重写的过程。你需要将代码拆分到若干个子工程里面,再将这些子工程通过一些通信方式组装起来,这对架构是很大的调整,需要跨多个团队协作完成。

所以在开始拆分之前你需要明确几个拆分的原则,否则就会事倍功半甚至对整体项目产生不利的影响。

原则一,做到单一服务内部功能的高内聚和低耦合。也就是说每个服务只完成自己职责之内的任务,对于不是自己职责的功能交给其它服务来完成。说起来你可能觉得理所当然对这一点不屑一顾,但很多人在实际开发中,经常会出现一些问题。

比如,我之前的项目中有用户服务和内容服务,用户信息中有“是否为认证用户”字段。组内有个同学在内容服务里有这么一段逻辑:如果用户认证字段等于1,代表是认证用户,那么就把内容权重提升。问题是判断用户是否为认证用户的逻辑应该内聚在用户服务内部,而不应该由内容服务判断,否则认证的逻辑一旦变更内容服务也需要一同跟着变更,这就不满足高内聚、低耦合的要求了。所幸我们在Review代码时及时发现了这个问题,并在服务上线之前修复了它。

原则二,你需要关注服务拆分的粒度,先粗略拆分再逐渐细化。在服务拆分的初期,你其实很难确定服务究竟要拆分成什么样。但是从“微服务”这几个字来看,服务的粒度貌似应该足够小,甚至有“一方法一服务”的说法。不过服务多了也会带来问题,像是服务个数的增加会增加运维的成本。再比如原本一次请求只需要调用进程内的多个方法,现在则需要跨网络调用多个RPC服务,在性能上肯定会有所下降。

所以我推荐的做法是:拆分初期可以把服务粒度拆得粗一些,后面随着团队对于业务和微服务理解的加深,再考虑把服务粒度细化。比如对于一个社区系统来说,你可以先把和用户关系相关的业务逻辑,都拆分到用户关系服务中,之后,再把比如黑名单的逻辑独立成黑名单服务。

原则三,拆分的过程,要尽量避免影响产品的日常功能迭代。也就是说,要一边做产品功能迭代,一边完成服务化拆分。

还是拿我之前维护的一个项目举例。我曾经在竞品对手快速发展的时期做了服务的拆分,拆分的方式是停掉所有业务开发全盘推翻重构,结果错失了产品发展的最佳机会,最终败给了竞争对手。因此,我们的拆分只能在现有一体化系统的基础上不断剥离业务独立部署,剥离的顺序你可以参考以下几点:

1.优先剥离比较独立的边界服务(比如短信服务、地理位置服务),从非核心的服务出发减少拆分对现有业务的影响,也给团队一个练习、试错的机会;

2.当两个服务存在依赖关系时优先拆分被依赖的服务。比如内容服务依赖于用户服务获取用户的基本信息,那么如果先把内容服务拆分出来,内容服务就会依赖于一体化架构中的用户模块,这样还是无法保证内容服务的快速部署能力。

所以正确的做法是理清服务之间的调用关系,比如内容服务会依赖用户服务获取用户信息,互动服务会依赖内容服务,所以要按照先用户服务再内容服务,最后互动服务的顺序来进行拆分。

原则四,服务接口的定义要具备可扩展性。服务拆分之后,由于服务是以独立进程的方式部署,所以服务之间通信就不再是进程内部的方法调用而是跨进程的网络通信了。在这种通信模型下服务接口的定义要具备可扩展性,否则在服务变更时会造成意想不到的错误。

在之前的项目中,某一个微服务的接口有三个参数,在一次业务需求开发中,组内的一个同学将这个接口的参数调整为了四个,接口被调用的地方也做了修改,结果上线这个服务后却不断报错,无奈只能回滚。

这是因为这个接口先上线后参数变更成了四个,但是调用方还未变更还是在调用三个参数的接口,那就肯定会报错了。所以服务接口的参数类型最好是封装类,这样如果增加参数就不必变更接口的签名,而只需要在类中添加字段就可以了。

微服务化带来的问题和解决思路

那么依据这些原则将系统做微服务拆分之后,是不是就可以一劳永逸解决所有问题了呢?当然不是。

微服务化只是一种架构手段,有效拆分后可以帮助实现服务的敏捷开发和部署。但是由于将原本一体化架构的应用拆分成了多个通过网络通信的分布式服务,为了在分布式环境下协调多个服务正常运行,就必然引入一定的复杂度,这些复杂度主要体现在以下几个方面:

1.服务接口的调用不再是同一进程内的方法调用而是跨进程的网络调用,这会增加接口响应时间的增加。此时我们就要选择高效的服务调用框架,同时接口调用方需要知道服务部署在哪些机器的哪个端口上,这些信息需要存储在一个分布式一致性的存储中,于是就需要引入服务注册中心,这一点,是我在24讲会提到的内容。不过在这里我想强调的是,注册中心管理的是服务完整的生命周期,包括对于服务存活状态的检测。

2.多个服务之间有着错综复杂的依赖关系。一个服务会依赖多个其它服务也会被多个服务所依赖,那么一旦被依赖的服务的性能出现问题产生大量的慢请求,就会导致依赖服务的工作线程池中的线程被占满,依赖的服务也会出现性能问题。接下来问题就会沿着依赖网逐步向上蔓延,直到整个系统出现故障为止。

为了避免发生这种情况,我们需要引入服务治理体系针对出问题的服务采用熔断、降级、限流、超时控制的方法,使问题被限制在单一服务中,保护服务网络中的其它服务不受影响。

3.服务拆分到多个进程后,一条请求的调用链路上涉及多个服务,那么一旦这个请求的响应时间增长或者是出现错误,我们就很难知道是哪一个服务出现的问题。另外,整体系统一旦出现故障,很可能外在的表现是所有服务在同一时间都出现了问题,你在问题定位时很难确认哪一个服务是源头,这就需要引入分布式追踪工具,以及更细致的服务端监控报表。

我在25讲和30讲会详细地剖析这个内容,在这里我想强调的是,监控报表关注的是依赖服务和资源的宏观性能表现;分布式追踪关注的是单一慢请求中的性能瓶颈分析,两者需要结合起来帮助你来排查问题。

以上这些微服务化后在开发方面引入的问题,就是接下来“分布式服务篇”和“维护篇”的主要讨论内容。

总的来说,微服务化是一个很大的话题,在微服务开发和维护时,你也许会在很短时间就把微服务拆分完成,但是你可能会花相当长的时间来完善服务治理体系。接下来的内容会涉及一些常用微服务中间件的原理和使用方式,你可以使用以下的方式更好地理解后面的内容:

课程小结

本节课,为了能够指导你更好地进行服务化的拆分,我带你了解了微服务化拆分的原则,内容比较清晰。在这里我想延伸一些内容:

1.“康威定律”提到设计系统的组织其产生的设计等同于组织间的沟通结构。通俗一点说,就是你的团队组织结构是什么样的你的架构就会长成什么样。

如果你的团队分为服务端开发团队、DBA团队、运维团队、测试团队,那么你的架构就是一体化的,所有的团队共同为一个大系统负责,团队内成员众多,沟通成本就会很高;而如果你想实现微服务化的架构,那么你的团队也要按照业务边界拆分,每一个模块由一个自治的小团队负责,这个小团队里面有开发、测试、运维和DBA,这样沟通就只发生在这个小团队内部,沟通的成本就会明显降低。

2.微服务化的一个目标是减少研发的成本,其中也包括沟通的成本,所以小团队内部成员不宜过多。

按照亚马逊CEO贝佐斯的“两个披萨”的理论,如果两个披萨不够你的团队吃,那么你的团队就太大了需要拆分,所以一个小团队包括开发、运维、测试以6~8个人为最佳;

3.如果你的团队人数不多还没有做好微服务化的准备,而你又感觉到研发和部署的成本确实比较高,那么一个折中的方案是你可以优先做工程的拆分。

比如你使用的是Java语言,你可以依据业务的边界将代码拆分到不同的子工程中,然后子工程之间以jar包的方式依赖,这样每个子工程代码量减少可以减少打包时间;并且子工程代码内部可以做到高内聚低耦合,一定程度上减少研发的成本,也不失为一个不错的保守策略。

一课一思

结合你在实际微服务改造中的经验,可以和我说说你在微服务拆分后都遇到了哪些问题吗?你是如何解决的呢?欢迎在留言区与我分享你的经验。

最后,感谢你的阅读,如果这篇文章让你有所收获,也欢迎你将它分享给更多的朋友。

评论