极客时间已完结课程限时免费阅读

22 | 微服务架构:微服务化后系统架构要如何改造?

22 | 微服务架构:微服务化后系统架构要如何改造?-极客时间

22 | 微服务架构:微服务化后系统架构要如何改造?

讲述:唐扬

时长13:07大小12.01M

你好,我是唐扬。
上一节课,我带你了解了单体架构向微服务化架构演进的原因,你应该了解到当系统依赖资源的扩展性出现问题,或者是一体化架构带来的研发成本、部署成本变得难以接受时,我们会考虑对整体系统做微服务化拆分。
微服务化之后垂直电商系统的架构将会变成下面这样:
在这个架构中,我们将用户、订单和商品相关的逻辑抽取成服务独立的部署,原本的 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 包的方式依赖,这样每个子工程代码量减少可以减少打包时间;并且子工程代码内部可以做到高内聚低耦合,一定程度上减少研发的成本,也不失为一个不错的保守策略。

一课一思

结合你在实际微服务改造中的经验,可以和我说说你在微服务拆分后都遇到了哪些问题吗?你是如何解决的呢?欢迎在留言区与我分享你的经验。
最后,感谢你的阅读,如果这篇文章让你有所收获,也欢迎你将它分享给更多的朋友。
分享给需要的人,Ta购买本课程,你将得18
生成海报并分享

赞 19

提建议

上一篇
21 | 系统架构:每秒1万次请求的系统要做服务化拆分吗?
下一篇
23 | RPC框架:10万QPS下如何实现毫秒级的服务调用?
 写留言

精选留言(37)

  • Stalary
    2019-11-13
    微服务化之后一些读库操作变成了远程调用,很多多次读的操作都要修改为批量读取来减少网络耗时,这里的改动还是很大的,甚至一些要求响应时间很高的,还需要做本地缓存/

    作者回复: 是的

    共 2 条评论
    26
  • 2019-11-15
    老师好,我们在做微服务项目的时候。碰到最难受的问题就是:比如一个流程要调用三四个服务,前两个调用成功了,第三个失败了。类似的这种情况,该怎么处理呢?

    作者回复: 这个只能保证最终一致性,比如如果有失败的,想办法把数据修复

    共 5 条评论
    19
  • 小喵喵
    2019-11-13
    微服务拆分的原则: 原则一,做到单一服务内部功能的高内聚,和低耦合 原则二,你需要关注服务拆分的粒度,先粗略拆分,再逐渐细化。 原则三,拆分的过程,要尽量避免影响产品的日常功能迭代,也就是说,要一边做产品功能迭代,一边完成服务化拆分。 原则四,服务接口的定义要具备可扩展性。
    展开
    共 1 条评论
    14
  • Hwan
    2019-11-13
    然后想问下老师,就是我们的用户服务也是单独分出来了,有的内容放在了缓存里面了,但是目前除了去调用用户服务的接口获得数据外,有的时候是直接在其他服务里面调用用户的redis里面的数据的,感觉这样比较快,比调用接口快,然后想问下,按照微服务的话,这种放在缓存供其他服务调用是合理的吗?是不是最好还是缓存也分开,然后每次都得调用接口,然后接口里面调用缓存会比较好啊,希望老师解答,谢谢老师

    作者回复: 这样不太好,服务依赖的资源最好不要和其他服务共享,否则一旦这个资源变更了,我们很可能会漏掉其他服务;再有就是如果其他服务对这个资源有不当的使用,那么也会影响到你的服务

    共 4 条评论
    8
  • 吃饭饭
    2019-11-13
    服务接口修改版本号问题;服务间的循环依赖问题;传输协议的选型很重要,因为牵扯到序列化问题;链路追踪能及时发现报错日志;点太多啦:)
    共 2 条评论
    5
  • mickey
    2019-11-13
    微服务拆分以后最大的问题是故障的排查与定位问题。

    作者回复: 是的

    4
  • AngryApe
    2020-02-23
    唐老师,请教个问题,在微服务化落地以后会有基础服务和业务服务的区分,这时候会出现两个问题: 1. 基础服务的改动会影响到很多上层业务服务,从而带来很大量的回归测试任务。 2. 随着业务的发展,经常需要基础服务跟着改动,对基础服务团队来说被业务团队牵着鼻子走。以订单服务为例,经常会有各种各样的枚举值或者数据库字段需要添加进来。 综上,当需求来临时,怎么以更小的成本去实现?
    展开
    共 1 条评论
    4
  • 吕超
    2020-01-05
    两个披萨就够我一个人吃。老外一个个人高马大的,胃口咋那么小。

    作者回复: 这个有时间要问问老贝了:)

    共 3 条评论
    4
  • 2020-04-25
    微服务化基础设施不全会很痛苦,尤其是出现服务问题时,怎么拆最关键的?具体需要看公司大环境,以及组内业务情况,不过设计原则基本是老师所说的吧!目标是提高人效节省成本,使公司业务更好的开展赚更多的钱😍

    作者回复: 能不碰就不碰吧 尤其是业务前景不明的情况下,就别添乱了

    2
  • 芳菲菲兮满堂
    2020-03-18
    这个微服务的编译依赖其他的微服务的 版本管理很是难受

    作者回复: 是的

    共 3 条评论
    1
  • 空知
    2020-01-15
    老师,问下文中说的 用户服务 和 内容服务 那里,对于内容服务内容的获取鉴权的过程,每次请求都先去用户服务那边判断是否有权限,有权限再走内容服务 没权限直接返回,而 内容服务只是获取内容 不做任何鉴权 这样做可以吗?

    作者回复: 我觉得是可以的

    1
  • 大鸡腿🍗
    2019-12-08
    反驳一点,用户相关的数据有时需要冗余到本服务,跨项目有时查点数据很蛋疼,或者说加点接口,用户服务要排期,如果是冗余到自己的服务,随便加接口。 其次改接口成4个参数的时候,应该加个过期注解,保留旧的接口兼容旧的调用,让他们慢慢迁移,然后新增新的接口来实现新的逻辑。其次还有maven的快照版本跟正式版本来解决,虽然我没有去了解过,哈哈
    共 5 条评论
    1
  • Luciano李鑫
    2019-11-20
    感觉微服务的拆分像极了面向对象中的类的划分。
    共 1 条评论
    1
  • 郑继强
    2022-04-21
    我不太同意老师对认证用户判断的说法,接口设计应该面向数据,而不是面向行为,如果说用户服务去提供一个是否认证用户的接口,那之后又用别的类似的需求,是不是都要不断去加接口。因为这里只是其中一个字段,并不是说需要有逻辑判断。所以我觉得用户服务应该只暴露数据,至于认证用户推内容这种,属于内容服务的逻辑。
  • 温旭升
    2022-04-01
    老师好,请教两个问题: 1、把一个内聚的功能逻辑,是应该设计成微服务,还是设计成类(或函数)在进程内调用问题。我理解,微服务是跨进程的调用,从性能角度,是比不过进程内的过程调用的。微服务的好处服务扩展性较强。那么,对数据处理吞吐量要求较高的场景,按微服务拆分业务处理逻辑,适用吗? 2、外部系统调用服务,可先接入服务网关,通过应用服务网关做安全鉴权,保障请求的合法性,但在系统内部的微服务之间的服务调用,还需不需要做接口调用方面的安全处理?老师在实际项目应用中,是怎么考虑及处理这个问题的?
    展开
  • 亚林
    2021-11-25
    没钱的小老板 能不微服务就不要微服务
  • 2020-10-28
    微服务拆分也有加剧服务不可信的问题,为了避免服务调用异常导致的这身服务异常,我每次都要try catch一下调用服务,保证自身服务的可用性。由其他服务抛出来的异常,不加已处理的就抛到用户端,出了问题都不好排查,拿接口返回的message搜索自身项目都搜索不到的
  • 互金从业者X
    2020-07-29
    其实面向对象做不好,就很难去界定单一职责,比如注册这个功能是内聚到用户中心还是给api呢,之前有在推一个服务化的项目,结果最后……一不留心有些微服务就直接给我写成远程DAO的。现在估计很多人的微服务也是这么个情况…… 最后只能折中,做取舍
  • xiaochao321
    2020-06-24
    服务的循环依赖?调用a、b、c三个服务,服务a b 调用成功,调用c失败怎么处理?
  • 木木
    2020-04-14
    我们的微服务,缺少运维啊。都是一个人当两三个人用,开发测试运维都做了。导致各种基建没做好,也没有运维,就是定定位问题就太痛苦了

    作者回复: 那是痛苦啊

    共 2 条评论