想象你是一家街角小餐馆的老板:一个人身兼切菜、炒菜、收银、传菜,所有菜谱和食材都在你脑子里。生意越来越好,你发现就算加班到凌晨也忙不过来——因为改一道菜谱,要重新排练整个后厨流程;想多接几桌客人,却发现灶台、冰箱、收银台全都绑在一起,动弹不得。于是你决定拆分:前台专门点单,蒸菜、炒菜、甜品各设一个档口,每个档口有自己的厨师和食材。软件系统的成长过程一模一样:业务变大后,一个“单体应用”装不下所有功能,就要按业务边界拆成多个独立服务,这就是系统拆分。 但拆,远不是把代码分几个文件夹那么简单。拆完之后:档口之间怎么传菜单(服务通信)?各档口的食材账本怎么对得上(数据一致性)?新档口开张客人怎么知道(服务发现)?某个档口着火怎么不殃及邻居(故障隔离)?这篇文章就用这家小餐馆的故事,带你完整走一遍系统拆分的思考路径——从“为什么要拆”,到“按什么拆”,再到“拆完怎么管”。

岗位关联

系统拆分是分布式系统设计的核心能力。学会它,你在面试中可以把“微服务”讲出真正的设计深度,而不是停留在背概念、数组件。

目录
  1. 可视化演示
  2. 单体架构困境
  3. 拆分维度选择
  4. 数据一致性保障
  5. 服务间通信协议
  6. 运维复杂度管理
  7. 拆分评估与演进
  8. 电商案例实战
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示单体架构到微服务的拆分过程,通过滑块控制服务拆分粒度,实时观察耦合度、通信开销与一致性指标变化。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统拆分权衡 单体:高耦合低开销 服务化:解耦但通信增加 协调节点保障一致性 耦合度 = 100 - 拆分粒度×10 按业务边界拆分是核心策略

单体架构困境

小餐馆最初只有一位全能厨师:所有的菜谱、食材、收银都在他脑子里。顾客再多也只能一桌一桌做——不是因为他能力不行,而是所有事情缠在一起,任何一步都依赖其他步骤。软件世界的“全能厨师”就是单体架构(Monolith):一个应用打包了所有业务模块,启动时全量加载,模块之间互相调用没有边界,专业说法叫“高耦合”。

单体的问题可以归结为三件事。第一,代码耦合:改一个支付模块,可能会影响到下单流程,回归测试必须全量跑一遍。第二,发布风险:任何小改动都要把整个系统重新打包上线,就像为了换一口锅要把整个厨房停业一天。第三,资源扩缩:只有下单功能流量大,却必须把整个系统一起扩容,白白浪费计算资源。

更本质的是,模块越多,彼此之间潜在的影响关系呈平方级增长。这个规律可以用沟通渠道数来量化:

就像餐馆老板每加一道新菜,都要重新核对它与所有旧菜的搭配禁忌——工作量逐年膨胀,直到某一天,你不得不按下“拆分”的开关。这就是单体架构在增长中必然走向拆分的根本原因。

C=n(n−1)2\displaystyle C = \frac{n(n-1)}{2}
模块越多,两两之间的影响组合爆炸式增长,这正是单体架构在规模变大后“动哪都疼”的数学根源。
  1. n 个模块n 代表系统里的模块或团队数量,n 越大,系统越庞大。
    nn
  2. 每个模块要面对 n-1 个伙伴每个模块都可能与其他模块发生调用或影响,所以要乘以 n-1。
    n−1n - 1
  3. 两两配对去重模块 A 和 B 之间的相互影响只算一对,除以 2 去除重复计数。
    n(n−1)2\frac{n(n-1)}{2}

拆分维度选择

餐馆扩店后,老板面临两个组织方案:按工种分(切菜组、炒菜组、甜品组),或者按菜系分(川菜档口、粤菜档口)。按工种分,人力可以复用,但一道菜从切配到上桌要跨组协作,沟通成本极高;按菜系分,一个团队能完整负责一道菜的备料、烹饪和出品,边界清晰。

软件拆分也有这两条主线:按功能拆和按领域拆(即 DDD 中的“限界上下文”)。按功能拆,就像把所有“切菜的”放在一起——代码结构好看了,业务上却是撕裂的,改一个订单功能要同时改好几个服务。按领域拆则是让订单域、支付域、库存域各自闭环,一个域内的修改不扩散到其他域。

业界更推荐按领域拆,因为领域边界就是业务边界。识别边界有一个很实用的量化手段——看“变更耦合度”:如果两个模块总是被同时修改,说明它们其实是同一件事,不该被拆开。

这里还藏着一个深刻的规律:康威定律——组织的沟通结构决定了系统的架构形状。就像餐馆老板如果把川菜和粤菜分给两个团队管,后厨自然会长出两个菜系档口。所以,拆分维度不是纯技术问题,先回答“团队怎么组织”,再回答“系统怎么拆”。

CC=CcoCtotal\displaystyle CC = \frac{C_{\text{co}}}{C_{\text{total}}}
变更耦合度衡量两个模块“同生共死”的程度,是判断业务边界的照妖镜。
  1. 共同变更次数一段时间内,两个模块在同一次提交中被一起修改的次数,记为 C_co。
    CcoC_{\text{co}}
  2. 总变更次数同一时间段内所有提交的总数,记为 C_total。
    CtotalC_{\text{total}}
  3. 比值接近 1 就是一家人CC 越接近 1,说明它们应该留在同一个服务里;CC 很低,才适合拆到不同服务。
    CcoCtotal\frac{C_{\text{co}}}{C_{\text{total}}}

数据一致性保障

拆成多个档口后,最头疼的是食材账本。以前一个账本大家共用,谁都看得见;现在每个档口都有自己的小库房。如果川菜档口取走了冰箱里最后一块牛肉,粤菜档口不知道,就可能在同一天把同一块牛肉卖给两桌客人——这就是“数据不一致”。

软件里对应的是分布式事务难题:下单、扣库存、支付分属三个服务,原来单体里的本地数据库事务失效了。有人会想到“两阶段提交”(2PC)来强制一致,但它的代价就像要求所有档口同时举手确认才能开炒——只要一个档口网络抖动,整桌菜就要停摆,可用性大打折扣。

所以大多数业务场景改用最终一致性方案,最经典的是“本地消息表 + 消息队列 + 幂等消费”:订单服务把业务操作和消息写进同一个本地事务,然后异步把消息搬给库存、支付服务,发送失败就反复重试,消费端对同一个订单号去重。为什么我们要尽量避免跨服务的强一致?看这个连乘公式就明白了:

回到餐馆:账本不需要实时同步,每晚对一次总账完全可以接受——高峰期“库存先扣、支付稍后确认”,偶尔短时间对不上没关系。用“最终一致”换“高可用”,这才是分布式时代的账房哲学。

P(success)=∏i=1npi\displaystyle P(\text{success}) = \prod_{i=1}^{n} p_i
分布式事务的成功概率是各环节成功率的连乘,环节越多,整体越脆弱。
  1. 单个环节成功率 p_i比如库存扣减、支付、通知,每个局部事务单次成功的概率。
    pip_i
  2. 连乘全部环节所有环节都必须成功,整体事务才算成功,所以用连乘符号 Π。
    ∏i=1npi\prod_{i=1}^{n} p_i
  3. 环节越多整体越低每个 p_i 都小于 1,乘数越多乘积越小,所以强一致的跨服务操作要尽量少。
    ∏i=1npi<min⁡(p1,p2,…,pn)\prod_{i=1}^{n} p_i < \min(p_1, p_2, \dots, p_n)

服务间通信协议

档口之间怎么协作?有两条路:让服务员跑腿传单,当面等对方回复——这叫同步调用,稳但慢;或者把菜单往传菜口一放,后厨自己来取——这叫异步消息,快但没法当场确认结果。两种方式各有各的用武之地。

软件里有三类主流通信方式。REST/HTTP + JSON:可读性好、跨语言通用,适合对外部开放的 API。RPC(如 gRPC):走二进制序列化 + 长连接,体积小、性能高,适合内部服务间高频调用。消息队列(如 Kafka、RocketMQ):生产者把消息扔进队列就走,消费者异步处理,适合削峰填谷和跨服务解耦。

选择的关键是理解同步与异步的权衡:同步链路实时性强,但延迟会逐跳叠加,适合查询类操作;异步吞吐高、不阻塞,但结果滞后、排查困难,适合事件通知和跨服务的写操作。调用链越长,越应该考虑异步化。网络延迟的叠加规律可以用一个简单的公式描述:

就像高峰期客人催菜,服务员跑腿传单的人越多,走廊越拥挤——这时候不如把点单往传菜口一放,后厨按优先级自己消化掉,前台也不用干等。异步消息牺牲一点实时性,换回的是整个系统的吞吐和平滑。

T=m×RTT+DB\displaystyle T = m \times RTT + \frac{D}{B}
串行调用链的总延迟等于每跳网络往返时间之和,再加上报文传输时间。
  1. m 次串行往返请求在调用链上经过 m 跳,每一跳都要经历一次网络往返 RTT。
    m×RTTm \times RTT
  2. 传输数据耗时报文体积 D 除以网络带宽 B,得到物理传输花费的时间。
    DB\frac{D}{B}
  3. 加总得到总延迟链路越长、报文越大,总延迟越明显,所以长链路要么改异步、要么压缩数据。
    m×RTT+DBm \times RTT + \frac{D}{B}

运维复杂度管理

档口多起来之后,老板发现管理成本反而涨了:新档口开张,客人怎么知道?今日菜单改了,怎么让所有档口同步知道?一道菜从下单到上桌,出了问题怎么知道是哪一步的锅?这三个问题,对应微服务的三大基础设施。

第一个问题靠服务注册与发现:新档口开张时,把自己的招牌挂到公告栏(注册中心),客人点餐时先看公告栏找到档口位置,档口歇业了自动摘牌。第二个问题靠配置中心:菜单统一由总厨下发,档口不用改火候设置、不用重启,配置热更新即时生效。第三个问题靠链路追踪:一张订单从下单到上桌,每个环节都盖一个带 traceId 的章,出了问题顺着章就能查到是哪一步。

此外还有故障隔离:一个档口着火了,要保证其他档口还能正常营业。实现手段是熔断、限流和舱壁隔离,调用某服务失败率达到阈值就熔断,不让故障像多米诺骨牌一样传遍全链路。为什么要这么较真?因为整体可用性是连乘的:

档口多了,老板必须把“挂招牌、发菜单、查流程”变成制度化工具,否则拆完的系统比单体还难伺候。这也是为什么成熟的微服务实践都强调:先建好可观测性和容灾能力,再动手拆分。

Atotal=∏i=1nAi\displaystyle A_{\text{total}} = \prod_{i=1}^{n} A_i
服务越多,整体可用性下降得越快,必须用可观测性和快速恢复能力来对冲。
  1. 单个服务可用性 A_i比如 99.9%,意味着一年只允许约 8.76 小时不可用。
    AiA_i
  2. 连乘得到链路可用性一次请求要经过 n 个服务,所有服务都可用请求才成功。
    ∏i=1nAi\prod_{i=1}^{n} A_i
  3. 服务越多越难保证10 个 99.9% 的服务连乘后只剩约 99%,相当于一年要增加 4 天多的不可用时间。
    0.99910≈0.990.999^{10} \approx 0.99

拆分评估与演进

是不是所有餐馆都该拆成十个档口?当然不是。一家只有三张桌子的小店,拆了反而亏钱——多出来的传菜流程和档口间的扯皮,比省下的那点人力还贵。老板要算一笔账:拆了之后,上菜速度真的快了吗?交付一道新菜的成本真的降了吗?

评估拆分的核心指标是交付周期(CT):一个需求从提出到上线要多久。如果单体里发布一次要全量回归两周,CT 就极长;拆成独立服务后,每个团队只对自己的模块负责,CT 大幅缩短。这里有一个通用的定量工具——Little's Law:

具体怎么拆?业界推荐绞杀者模式:在旧系统旁边建一条新走廊,把单体的一条条业务按“领域走廊”逐步迁出,每迁出一个域就灰度验证,验证不过随时回滚。反过来,两个经典反模式要避开:一是“一步到位的大爆炸重构”,好比把厨房整个拆了重建,期间只能停业;二是“为了拆而拆”,把服务拆到十几个微服务,每个却只有几百行代码,纯粹给自己增加运维负担。

WIP=λ×CT\displaystyle WIP = \lambda \times CT
在制品数量等于交付速率乘以交付周期,用它可以定位系统瓶颈在哪个环节。
  1. 在制品 WIP同时进行中的需求/任务数量,越多说明积压越严重。
    WIPWIP
  2. 交付速率 λ单位时间内完成并上线的需求数。
    λ\lambda
  3. 交付周期 CT一个需求从提出到上线所花的平均时间。
    CTCT
  4. 定位瓶颈如果 WIP 不断上涨而 λ 不变,CT 必然被拉长,说明团队或系统被耦合拖住了,这才值得拆。
    CT=WIPλCT = \frac{WIP}{\lambda}

电商案例实战

把餐厅搬上线,就是电商系统。用户下单就像客人点菜:先看菜单(商品校验),再锁定食材(扣库存),最后结账(支付)。在单体时代,这一步用一个数据库事务就搞定了;拆成订单、库存、支付三个服务后,同一个流程要在网络上跑好几趟,任何一环失败都会造成“订单下了但没扣库存”或“库存扣了但没付钱”的尴尬。

拆分后的典型分工是这样的:订单服务创建订单,把“待扣库存”消息写进本地消息表;库存服务消费消息扣减库存,扣减失败就回滚订单并发起补偿;支付服务收到支付回调后幂等入账,同一个订单号重复回调只记一次。失败的流程用 SAGA 补偿事务处理:按逆序执行补偿操作,比如先取消订单、再释放库存,让系统回到一致状态。

很多人担心“重试会不会永远失败”?不会。只要单次调用的成功率稳定,重试足够多次后,最终成功率以指数级速度逼近 100%,这就是补偿和重试的底气和数学依据:

拆分前后的对比很直观:拆分前,大促时单体 CPU 打满,为了撑住流量只能把整个系统翻倍扩容,一次发布要全量停服半小时;拆分后,大促只需单独扩容订单和库存服务,发布互不干扰,某个服务出故障也有熔断和降级兜底。从“一个账本包打天下”到“本地消息表 + 重试 + 幂等 + 补偿”四件套兜底,看似绕了远路,实则每个服务都轻装上阵,系统在高并发下依然稳如老狗。

Pok=1−(1−p)k\displaystyle P_{\text{ok}} = 1 - (1 - p)^k
重试 k 次的最终成功率等于 1 减去全部失败的概率。单次成功率越低,需要的重试次数越多,但最终仍可收敛。
  1. 单次成功率 p一次支付或库存扣减调用成功的概率,比如 0.9。
    pp
  2. k 次全部失败单次失败概率是 1-p,k 次独立尝试全部失败的概率是它的 k 次方。
    (1−p)k(1-p)^k
  3. 至少一次成功用 1 减去全部失败的情况,就得到重试 k 次后至少成功一次的概率。
    1−(1−p)k1 - (1-p)^k

AI 应用场景

电商订单-库存一致性保障

阶段:系统设计阶段(服务边界划分与数据一致性方案设计)

解决的问题:单体时代扣库存和下单在同一个事务里;拆成订单服务、库存服务后,本地事务失效。传统做法是硬编码同步调用 + 超时重试,导致超卖和丢单频发。AI 平台中订单系统要对接推荐、搜索、营销等多个上游服务,同样需要这种跨服务的一致性保障。

具体案例:某电商平台大促期间,订单服务将业务和消息同库写入本地消息表,异步投递到库存服务扣减;库存不足时反向补偿回滚订单,支付回调做幂等处理,最终一致地完成全部订单。高峰期系统可用性从 99.5% 提升到 99.99%。

微服务全链路监控与故障定位

阶段:上线运维与可观测性建设(监控、告警、链路追踪)

解决的问题:服务拆分后一次请求要跨 5-8 个服务,报错后不知道卡在哪一环。传统方式是挨个查日志,效率极低。借助注册中心做服务发现、traceId 穿透全链路做追踪、监控大盘统一告警,可以把定位时间从小时级压缩到分钟级。

具体案例:某推荐服务线上响应变慢,链路追踪发现日志集中在消息队列消费环节大量堆积,定位到消费者实例不足,扩容消费者组后秒级恢复。

存量单体渐进式拆分

阶段:架构演进与重构(绞杀者模式落地)

解决的问题:老系统不敢动、又不得不动。传统做法是大版本重写,风险极高;用绞杀者模式按领域逐条迁出,每个域独立灰度,出问题可回滚,业务几乎无感知。

具体案例:某金融平台把用户中心作为第一个拆分试点,拆出后独立发布、独立扩缩,验证收益后逐步拆出订单域、支付域,一年内完成 60% 流量的服务化迁移。

Python 演示

python42 行
import random # 模拟"本地消息表 + 定时重试"方案:# 业务操作与消息在同一个本地事务中写入, 发送失败后持续重试,# 配合消费端幂等, 最终实现跨服务的最终一致性。class Outbox:    def __init__(self, success_rate):        self.rows = []        self.success_rate = success_rate        self.delivered = set()     def add(self, msg_id, business):        # 业务与消息同事务写入本地消息表        self.rows.append({"id": msg_id, "biz": business, "status": "pending"})     def deliver_once(self):        # 一轮发送尝试: 网络失败的消息留在表里等下一轮        for row in self.rows:            if row["status"] == "pending" and random.random() < self.success_rate:                row["status"] = "delivered"                self.delivered.add(row["id"])     def done(self):        return len(self.delivered) == len(self.rows)  def simulate(order_count, success_rate):    outbox = Outbox(success_rate)    for order_id in range(order_count):        outbox.add(order_id, "deduct_stock_and_pay")    rounds = 0    while not outbox.done() and rounds < 10000:        outbox.deliver_once()        rounds += 1    return rounds, len(outbox.delivered)  if __name__ == "__main__":    print("单次成功率\t重试轮数\t最终投递数")    for rate in (0.3, 0.6, 0.9):        rounds, delivered = simulate(50, rate)        print(f"{rate:.0%}\t\t{rounds}\t\t{delivered} / 50")
目的:模拟“本地消息表 + 定时重试”如何在低成功率网络环境下,依然保证消息最终全部投递成功。
  1. outbox.add(order_id, "deduct_stock_and_pay")模拟订单创建时,在同一“事务”里同时写入业务数据和待发消息,这是本地消息表的精髓。
  2. def deliver_once(self)每一轮扫描消息表做发送尝试,失败的消息状态不变,留在表里等待下一轮。
  3. while not outbox.done() and rounds < 10000无限重试直到全部投递完成,用轮数上限做安全阀,体现最终一致性。
  4. for rate in (0.3, 0.6, 0.9)对比不同单次成功率,你会发现即使只有 30% 成功率,重试几十轮后 50 条消息依然全部送达。

常见误区

误区微服务嘛,当然拆得越细越灵活。最好一个功能一个服务,甚至一个类一个服务,这样每个服务都超小、超独立。

正解服务越多,网络延迟、数据一致性、监控联调的成本呈平方级增长,整体可用性反而下降。拆分的粒度应该跟随业务边界和团队规模:一个服务由一个团队长期负责,团队能舒适地维护它,就是合适的粒度。小餐馆拆出十个档口,客人没多,传菜员倒是先忙不过来。

为何常见:看到微服务成功案例,误把“细”当“好”,忽略了成功案例背后的组织规模和组件成本。

误区业务跨了服务,就必须用两阶段提交(2PC)这类分布式事务来保证强一致,否则数据就错了。

正解2PC 加锁时间长、协调者单点风险高,可用性极低,基本只适合强一致要求极高的极少数场景。绝大多数业务用最终一致性:本地消息表 + 消息队列 + 幂等消费 + SAGA 补偿,短时间数据不一致可接受,最终一定收敛。就像各档口账本不用实时同步,每晚对一次总账就行。

为何常见:习惯了单机数据库的本地事务思维,不理解分布式环境下无法全局加锁,把“正确性”等同于“强一致”。

误区REST + JSON 最通用、最简单,内部服务之间也全用 REST,没必要引 RPC 那套复杂的东西。

正解REST 适合对外 API 和跨团队网关,可读性好;但内部高频低延迟调用用 gRPC 这类二进制 RPC 更合适——传输体积小一个量级、长连接免去反复握手。长调用链和削峰场景还要改异步消息,固定一种协议打天下是性能浪费。

为何常见:只对比了协议的可读性和上手难度,没评估序列化体积、连接复用与延迟差异在万亿次调用下的放大效应。

误区消息队列保证可靠投递,所以生产端把消息往 MQ 里一丢就完事,剩下的交给消费者。

正解MQ 的“可靠”不等于“不丢不重”,它通常只保证“至少一次投递”——可能重复、可能延迟。生产端要用本地消息表 + 定时补偿防止消息丢失,消费端必须做幂等防止重复处理,否则一个订单被重复扣两次库存。厨房里广播铃响了不代表菜就做好了,还要有人确认这道菜确实送到了客人桌上。

为何常见:把消息队列想象成“不会出错的数据库事务”,忽略了网络分区和消费者崩溃的真实场景。

误区反正要拆,干脆花几个月把单体一次性重写成微服务,一步到位最干净。

正解大爆炸重构风险极高——重构期间业务停滞、新旧系统难以对齐、出现问题无法回滚。更稳的是渐进式拆分(绞杀者模式):先搭新骨架,再按领域一条条把业务从单体里迁出,每迁一条就灰度验证、监控对比,出问题立即回滚。聪明的老板不会一夜之间拆掉厨房,而是先开一个甜品档口试水。

为何常见:想快速见效,低估了拆分期间双轨运行、数据迁移、接口对齐的复杂度和长期成本。

面试题

Q1. 单体架构最典型的问题是什么?

单选题 · easy

  • A.容器镜像体积偏大
  • B.模块紧密耦合相互影响← 正确答案
  • C.数据库单表数据量大
  • D.前端页面加载速度偏慢

答案:B。单体最核心的痛点是模块间紧密耦合,改一处要全量回归、发布互相牵制。就像小餐馆一道菜改动要重新排练整个后厨。镜像大、数据量大、加载慢都只是表象,不是本质。

Q2. 康威定律的核心观点是什么?

单选题 · easy

  • A.架构由组织沟通结构塑造← 正确答案
  • B.团队越大交付效率越高
  • C.架构必须由资深工程师拍板
  • D.数据库设计决定系统边界

答案:A。康威定律说:系统的架构会最终长得像设计它的组织的沟通结构。餐馆老板把川菜粤菜分给两个团队,后厨自然长出两个档口。所以拆分维度要先想清楚团队怎么组织,而不是反过来。

Q3. 订单服务和支付服务需要最终一致性,最推荐的做法是什么?

单选题 · mid

  • A.两阶段提交保证强一致性
  • B.同步调用并设置超时重试
  • C.本地消息表加异步消息加幂等← 正确答案
  • D.订单支付共享同一个数据库

答案:C。跨服务强一致用 2PC 会拖垮可用性;同步超时重试会造成重复扣款;共享数据库等于把拆开的服务又缝回去。本地消息表 + 异步消息 + 消费幂等,是兼顾可用性和一致性的标准答案,相当于各档口先记账后对账。

Q4. 服务注册与发现的核心作用是什么?

单选题 · easy

  • A.动态感知服务实例的地址← 正确答案
  • B.缓存热点数据以提升性能
  • C.对请求报文进行压缩处理
  • D.统一收集并归档应用日志

答案:A。服务实例的 IP 和端口是动态变化的,注册中心让服务之间能动态感知彼此地址,实例挂了自动摘除。就像新档口开张在公告栏挂招牌,客人照着招牌就能找到位置。

Q5. 为什么服务数量增加后,整体可用性会下降?

单选题 · mid

  • A.服务数量增加带宽消耗变大
  • B.存储与网络的成本随之上升
  • C.可用性是各服务可用性的乘积← 正确答案
  • D.容器镜像数量增多难以管理

答案:C。一次请求经过 n 个服务,所有服务都可用请求才成功,整体可用性是连乘关系。每个服务 99.9%,10 个连乘后就只有 99%。所以服务越多,越需要监控、容灾和快速恢复来对冲风险。

Q6. 本地消息表方案中,业务操作与消息写入必须满足什么条件?

单选题 · hard

  • A.在同一个本地事务中原子提交← 正确答案
  • B.先写业务库再异步同步消息
  • C.依赖消息队列回执确认投递
  • D.借助分布式锁保证消息顺序

答案:A。本地消息表的核心是“要么业务和消息一起成功,要么一起失败”,这要求二者在同一个本地事务里原子提交,绝不能先写业务再发消息——否则业务成功而消息丢失,就出现数据不一致。

Q7. 同步调用与异步消息的主要权衡是什么?

单选题 · mid

  • A.同步调用链路越长延迟越高
  • B.异步消息吞吐高且实时性强
  • C.同步模式下不存在任何故障风险
  • D.异步解耦但一致性滞后难排查← 正确答案

答案:D。同步调用实时性强,但延迟逐跳叠加;异步消息解耦、吞吐高,但消息落地有延迟,结果滞后且排查链路难。用哪类要看业务场景,长链路、削峰场景优先异步,查询类场景保持同步。

Q8. 以下哪些属于“按领域拆分”时的业务边界?

多选题 · mid

  • A.商品域与订单域之间的边界← 正确答案
  • B.页面展示层与接口层的边界
  • C.支付域与库存域之间的边界← 正确答案
  • D.数据库读写分离的存储边界

答案:AC。商品域、订单域、支付域、库存域都是业务领域,它们之间的边界是领域边界(限界上下文)。页面层与接口层是技术分层,读写分离是存储优化,都不属于按领域的业务边界。

Q9. 服务拆分后,运维需要哪些基础设施支撑?

多选题 · mid

  • A.注册中心实现服务发现← 正确答案
  • B.配置中心统一下发配置← 正确答案
  • C.链路追踪串联调用日志← 正确答案
  • D.物理服务器硬件的采购

答案:ABC。拆分后服务实例动态变化需要注册中心;配置频繁调整需要配置中心热下发;一次请求跨多服务需要链路追踪定位问题。这三件套是微服务运维的地基,而物理硬件采购不属于软件基础设施。

Q10. 关于 SAGA 补偿事务,下列说法正确的是?

多选题 · extreme

  • A.所有局部事务同步一次性提交
  • B.每个局部事务配有补偿事务← 正确答案
  • C.失败后按逆序执行补偿操作← 正确答案
  • D.补偿操作本身必须具备幂等← 正确答案

答案:BCD。SAGA 把长事务拆成多个局部事务,每个局部事务都配一个补偿操作;失败时按逆序(反向)逐个执行补偿来撤销已生效的操作,且补偿操作必须幂等,防止重试造成二次影响。A 是 2PC 的思路,不是 SAGA。