大数跨境
0
0

「深度好文」从“披着微服务外衣的大单体”到云原生架构:微服务拆分七大原则与反例详解

「深度好文」从“披着微服务外衣的大单体”到云原生架构:微服务拆分七大原则与反例详解 码哥跳动
2025-12-08
0
导读:别再制造“微服务怪兽”!码哥带你复盘:微服务拆分的七大黄金法则与经典反例

大家好,我是《Redis 高手心法》畅销书作者码哥,可以叫我靓仔。

从单体应用架构发展为 SOA 架构,再到微服务架构,以及云原生时代的微服务架构、微服务服务治理架构演进、服务网格(Service Mesh)、无服务架构(Serverless),应用架构经历了多年不断地演进。

如今,云原生微服务架构被使用最多,但是我在工作中却发现,很多人并没有正确的使用,形成了一套披着微服务名字的分布式大单体。

软件架构并不是被发明出来的,而是持续演进的结果。

今天我分享一个从「分布式单体」演化为云原生微服务亲身经历的架构设计重新优化的历程。

单体应用架构

在说微服务云原生架构的时候,我们先说下单体应用架构。

我们要明白架构为何一直在演进?又分别解决了什么问题?只有知晓架构演进的来龙去脉,才能根据实际的业务场景,选择合理的架构。

大单体架构在很长的时间里成为软件架构的主流风格,也是我们大多数开发者学习和实践过的软件架构,哪怕是 2024 年,这个架构依然在在一些场景中比较适用,比如小型的工单提交系统。

需要注意的是,不能认为单体架构是一个落后的系统架构,对于一些小型系统,单体架构的应用比较容易部署、测试,各个功能、模块、方法的调用都是在进程内,不会发生进程间通信,所以效率比分布式系统更高。

注意,不要认为单体架构就是一个完全不可拆分、铁板一块。无论是单体还是微服务或者其他软件架构风格都会对代码结构纵向拆分为不同层

按照调用顺序,从上到下分为客户端层、表示层、业务逻辑层、DAO 数据访问层、DB 层。

  • 表示层:负责用户体验和展示;

  • 业务逻辑层:负责业务逻辑处理,整个应用的核心,图中有三个模块,这些模块都集成在一个系统中;

  • DAO 数据访问层:负责数据库的增删改查。

  • DB 层:数据库。

然而,随着业务的发展,所有的功能模块都部署在一个 WAR 包,每次提交代码都要经过长时间的编译和启动,研发效率非常低下,十几个人都提交到一个主干分支,每次 merge 都要面对代码冲突,耗费大量的沟通成本。

由于所有的代码共享一个进程空间,无法隔离,做不到单独停止、更新升级某个模块的代码,如果任何一部分代码出现 bug,过度消耗公共进程的资源,导致一个 bug 把整个系统干跨。

比如,有同事使用 POI 实现批量导入导出 excel 功能,因为导出数据量巨大,导致 JVM 内存溢出,整个系统都受到影响。

单体架构的潜在观点:系统的每个部件和代码都比较可靠,从而构建出一个可靠的、缺陷少的系统。

交付一个高质量可靠的单体系统本质就是一个伪命题,因为在大多数的软件开发中:代码和人,有一个能跑就行

原因就是,经过你一系列深思熟虑,不断优化重构,代码终于写的跟诗一样的。但是你的工期比别人多了 1/2,虽然 bug 少了,但是研发成本大增。

尽管你的代码十分优秀,但不出意外的,在绩效评定的时候,你只拿到了及格。

相反,另外一个能跑就行的同事,在每次线上出现问题的时候,都能及时化解,拿到了优秀。

所以代码垃圾才是我们每天面对的客观事实。

微服务架构

码楼:咋办呢?项目复杂度越来越高,构建部署时间长,某个应用 Bug,例如死循环、内存溢出等, 可能会导致整个应用的崩溃,可靠性差。

什么是为服务架构?很小的服务么?

微服务最早是由 Martin Fowler 与 James Lewis 于 2014 年共同提出,需要了解细节的读者可以阅览 https://martinfowler.com/articles/microservices.html。

微服务是一种通过多个小型服务的组合,来构建单个应用的架构风格,这些服务会围绕业务能力而非特定的技术标准来构建。

各个服务可以采用不同的编程语言、不同的数据存储技术、运行在不同的进程之中。

服务会采取轻量级的通讯机制和自动化的部署机制,来实现通讯与运维。

每个微服务拥有独立的代码库和数据库,通常由一个小规模的技术团队全权负责,这个团队聚集了产品、技术、架构等人员,通常采用 Scrum 敏捷开发流程做快速迭代。微服务具备了独立演进和部署的能力。

如下图通过 Spring Cloud 构建的微服务。

其中有一些常用的组件以及概念如下:

  • 服务注册与发现:服务提供方将服务的调用地址、端口等信息注册到注册中心。服务调用者可以通过注册中心找到自己需要的服务地址。

  • 负载均衡:服务提供者一般多个实例组成集群实现高可用,负载均衡可以让服务调用者从中选择一个节点调用。

  • 配置中心:一个应用多个集群会有很多配置,需要将这些配置统一注册到配置中心,实现程序包在开发、测试、生产环境中的无差别性,方便程序包的迁移。

  • 服务网关:服务网关是外部服务调用本系统的唯一入口,可以在网关实现用户健全、灰度发布、流量整形等。

  • 调用链追踪:记录完成一次请求的先后衔接和调用关系,并将这种串行或并行的调用关系展示出来。在系统出错时,可以方便地找到出错点。

Chaya:说了这么多,微服务有什么优点?

  1. 应用拆分为多个模块,变得很容易开发、理解和维护。
  2. 每个服务都可以由专门开发团队来开发。不同团队的开发者可以自由选择开发技术,提供 API 服务。
  3. 微服务架构模式中每个微服务独立都是部署的。理想情况下,开发者不需要协调其他服务部署对本服务的影响。这种改变可以加快部署速度。UI 团队可以采用 AB 测试,快速地部署变化。

微服务虽然有很多吸引人的地方,但它并不是免费的午餐,使用它是有代价的。使用微服务架构面临的挑战。

  • 运维要求较高:更多的服务意味着更多的运维投入。在单体架构中,只需要保证一个应用的正常运行。而在微服务中,需要保证几十甚至几百个服务服务的正常运行与协作,这给运维带来了很大的挑战。

  • 分布式固有的复杂性:使用微服务构建的是分布式系统。对于一个分布式系统,系统容错、网络延迟、分布式事务等都会带来巨大的挑战。

  • 接口调整成本高:微服务之间通过接口进行通信。如果修改某一个微服务的 API,可能所有使用了该接口的微服务都需要做调整。

从微服务到云原生

余弦:码哥,你骗人,渣男。自从系统微服务化后,各个业务模块经过拆分变得更加细化,系统的部署、运维、监控等都比单体应用架构更加复杂。

听我说完,我窦娥冤。拆分了微服务后,需要将大部分的工作自动化。如果没有合适的支撑平台或工具,微服务架构就无法发挥它最大的功效。

当我们把大单体拆分成为多个微服务后,对于团队的开发人员、设计人员、架构人员来说,并没有感觉到工作变得轻松。

因为配置中心、服务发现、网关、熔断、负载均衡等等,就够一名新手学习好长一段时间。

那是因为我们在微服务架构中,选择在应用层而不是基础层去解决这些分布式问题,完全是因为硬件构成的基础设施,跟不上。

比如系统集群要解决负载均衡问题,微服务架构通常会在软件层面使用一种恰当的负载均衡算法分流。

人们选择在软件的代码层面而不是硬件的基础设施层面去解决这些分布式问题,很大程度上是因为由硬件构成的基础设施,跟不上由软件构成的应用服务的灵活性的无奈之举。

为了解决这些的问题,微服务迎来了云原生时代。

云原生应用有以下三大特征。

  • 容器化封装:以容器为基础,提高整体开发水平,形成代码和组件重用,简化云原生应用程序的维护。在容器中运行应用程序和进程,并作为应用程序部署的独立单元,实现高水平资源隔离。

  • 动态管理: 通过集中式的编排调度系统来动态管理和调度。

  • 面向微服务:明确服务间的依赖,互相解耦。

云原生是释放云计算价值的最短路径,以容器技术为代表,云原生提供了强大的调度,弹性等能力,极大的降低了上云的成本。

这一阶段我们关注的目标主要是业务进行云原生化改造,随着 kubernates 作为容器编排市场的事实标准,我们需要把业务从原来的的虚拟机部署方式改造成容器化方式,部署并运行在 k8s 之上,最大限度享受到云原生带来的技术红利。

微服务拆分反例

很多人看完微服务架构的描述后,觉得很简单呀,不就是拆分服务嘛。无服务拆分是否合理,里面的门道多了去了…..

我先给大家举一个反例,在我工作中遇到很多的错误微服务架构带来的灾难……

这是码哥亲身经历的一个事情,当时我作为架构师角色将公司原 saas 团队的供应链金融系统重新打造成一个标准化应用,基于插件机制,业务开发只需要专注于功能,实现快速得到一个客户想要的软件。

也有人认为这是一个「平台」思想,比如在一些旅游公司电商系统,会有各种各样的业务线:酒店、票务游玩、机票、包车保险业务,但是都需要一个订单系统支撑。

这个订单系统就是一个『平台』!将功能和业务内聚到平台中,业务线只需要关注特定场景问题,订单的事情交给订单系统来解决。

订单平台本身也是一个需要高并发高可用的系统架构来支撑,内部还可以拆分微服务……

看着负责人放着 PPT 跟我讲解系统架构,该系统的团队负责人带着 IT 人的傲娇对我说这是一个微服务架构系统……

看着我以为好像真的是一套合理设计的微服务架构。

打开项目代码发现这是一个披着微服务外衣的大单体巨石服务,还是一个四不像的大单体。

该团队的开发人员把这个系统拆分成了十几个「微服务」,可实际上业务功能系统压力全集中在一个所谓的平台服务(platform-xxx-service)上。

其余被拆分出来的微服务只是一层接收请求转化参数的的接口,里面基本没有业务逻辑,也没有对应的数据库,你可以理解为这是一个 Web 接口服务,但是没有数据库读写的能力。

接着,就把请求透传到 platform-xxx-service,导致这个巨型 service 业务逻辑繁重,里面的接口是巨大的、功能是耦合在一起的。

就这样,按照所谓的业务功能拆出了十几个微服务,但是这些微服务没有了解数据库能力,只能都调用 platform-xxx-service,导致了不必要的分布式事务问题,而且压力也全都集中在 platform-xxx-service。

一开始架构设计的目的是尽可能不改变 platform-xxx-service 的代码。最后却一直要修改!!!

负责人说这样可以避免改动 platform-xxx-service,调用 platform-xxx-service 服务的接口得到各种数据或者业务逻辑处理,只需要改动上层的应用代码做适配。

然而结果却是因为上层只是转发逻辑,服务拆分也就没了意义,所有的核心功能到被写到 platform-xxx-service 服务,一个接口干了很多事情,功能和模块之间互相耦合,失去了 DDD 领域启动设计中的拆分领域和子域的理念。

platform-xxx-service 成了一个巨型大单体应用!

单一职责不是这样理解的,微服务拆分也不是拆了多个服务就是为服务了,大兄弟。真的是「黛玉骑鬼火,该强的强,该弱的弱」。

余弦:“码哥,微服务架构设计那些原则可以指导我们正确的设计?避免设计出「依托答辩」的架构。”

好问题,一共有 7 大原则可以帮助我们设计一个好的微服务架构。

最终,我基于以下的微服务拆分原则,设计了如下图的架构,省略了一些服务,重点关注拆分思想:

单一职责

简单的就是最好的

每个微服务都只负责一个单一的业务,并确保做好这个业务,保证微服务职责单一性、功能完整性拆分, 这样,就便于维护、测试和部署。

另外,每个微服务都有自己的数据库来存储数据,避免一个微服务与其他微服务共享数据库,在数据层解耦,以确保可扩展性和可靠性。

基于可靠性拆分

Dora:这个我懂,不能让一颗老鼠屎搞坏一锅汤。

你这么理解没毛病。

在单体应用中,一个组件的故障可能导致整个系统的崩溃。

订单系统是一个核心系统,需要确保高并发、高可用、高性能,低耦合可拓展。

通过微服务架构,我们可以将系统拆分为多个独立的服务,从而将故障隔离在单个服务内,避免故障扩散到整个系统。

上文说的例子,想要抽象一个平台订单的系统,思想上是正确的,但是执行中除了问题,因为拆分处理的微服务并没有数据库读写能力,业务逻辑全部集中在 一个服务内, platform-xxx-service 出问题,整个订单系统崩溃!!

将可靠性要求高的核心服务和可靠性要求低的非核心服务拆分开来,然后重点保证核心服务的高可用。

当重要策划高难度较低的服务发生故障时,不会影响核心模块的服务。

比如将账号信息、登录信息、服务中心等重要度最高的要害模块单独拆分在一个服务颗粒上(因为这类模块不可用之后,整个系统基本完全瘫痪),再做成服务集群,来保障它的高可用。

DDD 领域驱动原则

微服务架构设计其实非常采用 DDD。因为每个微服务本就可以设计成特定领域的实现。

基于领域模型拆分,围绕业务领域按职责单一性、功能完整性拆分。

  • 战略设计主要从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。

  • 战术设计则从技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地,包括:聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。

使用 DDD(领域驱动建模) 进行业务建模,从业务中获取抽象的模型(例如订单、用户),根据模型的关系进行划分限界上下文。

从 DDD 的限界上下文往微服务转化,并得到系统架构、API 列表、集成方式等产出。

限界上下文可以视为逻辑上的微服务,或者单体应用中的一个组件。

Dora:“码哥,如何找到系统的边界呢?爱情起码还能根据生理上的喜欢来辅助判断。”

DDD 边界上下文可以通过事件风暴来找到,把系统状态做出改变的事件作为关键点,从系统事件的角度触发,提取能反应系统运作的业务模型。

再进一步识别模型之间的关系,划分出限界上下文,可以看做逻辑上的微服务。

例如系统管理员可以创建商品、上架商品,对应的系统状态的改变是商品已创建、商品已经上架;

相应的顾客创建订单、支付,对应的系统状态改变是订单已创建、订单已支付。

商家发货:选择快递公司、顾客填写收货地址……

按照业务稳定性拆分

这个很容易理解,需要区分系统中变与不变的部分,不变的部分一般是成熟的、通用的服务功能。

变的部分一般是改动比较多的需求、满足业务迭代扩展性需要的功能,我们可以将不变的部分拆分出来,作为共用的服务,将变的部分独立出来满足个性化扩展需要。

根据二八原则,系统中经常变动的部分大约 20%,80% 是很少变动的,这种拆分方式还能避免 80% 那部分服务的频繁发布

比如一个电商系统,用户信息、商品信息等管理模块一般是比较稳定的;而运营类的活动和页面是经常变化的。

还有电商系统中,订单创建、支付、退款这些模块都是比较稳定的。

基于吞吐量拆分

这是一个拓展原则,是针对特定场景的微服务拆分,简单的说就是访问量特别大,访问频率特别高的业务,又要保证高效的响应能力,这些业务对性能的要求特别高

比如积分竞拍、低价秒杀、限量抢购。

在我们的场景中,创建订单的正向流程尽可能把这部分业务拆分出来,既能保证高性能的要求,又能保证业务的独立性。

如果这种访问量巨大的业务如果与其他通用业务放一块,很容易因为某个链路阻塞,导致雪崩效应影响其他业务。

演进式原则

微服务拆分并不是一步到位的,应当根据实际情况逐步展开。

如果一开始不知道应该划分多细,完全可以先粗粒度划分,然后随着需要,适当将粒度划分更细拆分

Chaya:如果拆分粒度太细会增加运维复杂度,粒度过大又起不到效果,那么改造过程中如何平衡拆分粒度呢?

从两个方面做权衡,一是业务发展的复杂度,二是团队人员规模。

比如一个电商一开始索性可以拆分为商品服务和交易服务,一个负责展示商品,一个负责购买支付。

随后随着交易服务越来越复杂,就可以逐步的拆分成订单服务和支付服务、库存服务、价格服务、物流服务等等。

虽然业务复杂度已经满足了,如果公司此时没有足够的人力(招聘不及时或员工异动比较多),服务最好也不要拆分,拆分会因为人力的不足导致更多的问题,如研发效率大幅下降(一个开发负责与其不匹配数量的服务)。

Chaya:恋爱是两个人的事,3+ 以上就大乱了,所以一个微服务究竟需要几个开发维护是比较合理呢?

三个!

系统规模

系统规模来讲,3 个人负责开发一个系统,系统的复杂度刚好达到每个人都能全面理解整个系统,又能够进行分工的粒度。

如果是 2 个人开发一个系统,系统的复杂度不够,开发人员可能觉得无法体现自己的技术实力。

团队管理

从团队管理来说,3 个人可以形成一个稳定的备份,即使 1 个人休假或者调配到其他系统,剩余 2 个人还可以支撑。

如果是 2 个人,抽调 1 个后剩余的 1 个人压力很大。

一个人更不用说了,如果他去大保健被抓了,系统出问题就没人维护了。

技术提升

从技术提升的角度来讲,3 个人的技术小组既能够形成有效的讨论,又能够快速达成一致意见。

如果是 2 个人,可能会出现互相坚持自己的意见,或者 2 个人经验都不足导致设计缺陷。

一个人的话,没人进行技术讨论,容易陷入思维盲区。

避免环形、双向依赖

微服务拆分还有一个重要原则,就是避免避免环形、双向依赖。

服务之间的环形/双向依赖会使得服务间耦合加重,在服务升级的时候会比较头疼,不知道应该先升级哪个,后升级哪个,难以维护。

产生这种情况大多数是因为服务之间的调用可没有约束导致,为了方便获取或者更新某个表的数据,服务之间任意调用。

也说明我们的功能划分不够清楚或者通用功能没有下沉下来。

消除环形依赖的方法

要解决循环依赖,必须要在微服务之间建立一些原则来约束微服务之间的通信,定期通过这些原则来审视我们的系统,找到问题并进行重构,这些原则应该包括:

  • 定义服务上下游关系,上游服务可以直接依赖下游服务,反之则不可。

  • 上游服务的变更对下游服务产生影响需要通过领域事件(异步)的方式来实现。

  • 服务之间要通过数据 Id(或类 Id,能够唯一代表数据且不变的属性)来进行关联,尽量不做过多的数据冗余。

  • 一旦需要上游服务调用下游服务才能完成业务时,要考虑是否上游服务缺少业务概念

  • 为满足前端逻辑而导致的服务间交互逻辑要放到 BFF(Backend for frontend)中来编排,而不是增加服务间的调用。

最后介绍我人生的第一本书《Redis 高手心法》本书基于 Redis 7.0 版本,复杂的概念与实际案例相结合,以简洁、诙谐、幽默的方式揭示了Redis的精髓。

本书完美契合你对一个具体技术学习的期望: Redis 核心原理、关键细节、应用场景以及如何取舍......

从 Redis 的第一人称视角出发,拟人故事化方式和诙谐幽默的言语与各路“神仙”对话,配合 158 张图,由浅入深循序渐进的讲解 Redis 的数据结构实现原理、开发技巧、运维技术和高阶使用,让人轻松愉快地学习。


另外,我组建了一个公众号粉丝读者群,大家可以在群里谈天说地,跟全国各地后端开发者建立连接。加我微信:MageByte1024。备注吗,粉丝加群。
图片

另外,我作为 InfoQ 签约作者,腾讯云架构师技术同盟成员,欢迎技术大佬和架构师一起加入腾讯云架构联盟,与全国各地的优秀架构师一起成长。
可扫码申请加入,推荐人写我(码哥跳动@李健青)即可。
图片
图片



【声明】内容源于网络
0
0
码哥跳动
《Redis 高手心法》作者,后端架构师,宗旨是拥抱技术和对象,面向人民币编程。
内容 510
粉丝 0
码哥跳动 《Redis 高手心法》作者,后端架构师,宗旨是拥抱技术和对象,面向人民币编程。
总阅读154
粉丝0
内容510