编辑
2026-05-09
记录知识
0

目录

前言
识别复杂度的重要性
复杂度识别的常见问题
正确的做法:逐个解决
识别复杂度实战:前浪微博消息队列
背景信息
使用排查法识别复杂度
排查法复杂度识别函数
总结
参考

前言

架构设计的第一步是什么?是画架构图?是选技术栈?还是写设计文档?都不是。架构设计的第一步是识别复杂度——只有正确分析出了系统的复杂性,后续的架构设计方案才不会偏离方向。如果对系统的复杂性判断错误,即使后续的架构设计方案再完美再先进,都是南辕北辙,做的越好,错的越多、越离谱。

识别复杂度的重要性

架构设计的本质目的是为了解决软件系统的复杂性。在我们设计架构时,首先就要分析系统的复杂性。只有正确分析出了系统的复杂性,后续的架构设计方案才不会偏离方向。

例如,如果一个系统的复杂度本来是业务逻辑太复杂、功能耦合严重,架构师却设计了一个 TPS 达到 50000/秒的高性能架构,即使这个架构最终的性能再优秀也没有任何意义,因为架构没有解决正确的复杂性问题。

架构的复杂度主要来源于"高性能""高可用""可扩展"等几个方面,但架构师在具体判断复杂性的时候,不能生搬硬套,认为任何时候架构都必须同时满足这三方面的要求。实际上大部分场景下,复杂度只是其中的某一个,少数情况下包含其中两个,如果真的出现同时需要解决三个或者三个以上的复杂度,要么说明这个系统之前设计的有问题,要么可能就是架构师的判断出现了失误

复杂度识别的常见问题

专栏前面提到过的"亿级用户平台"失败的案例很能说明问题。设计对标腾讯的 QQ,按照腾讯 QQ 的用户量级和功能复杂度进行设计,高性能、高可用、可扩展、安全等技术一应俱全,一开始就设计出了 40 多个子系统,然后投入大量人力开发了将近 1 年时间才跌跌撞撞地正式上线。

上线后发现之前的过度设计完全是多此一举,而且带来很多问题:

  • 系统复杂无比,运维效率低下,每次业务版本升级都需要十几个子系统同步升级,操作步骤复杂,容易出错,出错后回滚还可能带来二次问题
  • 每次版本开发和升级都需要十几个子系统配合,开发效率低下
  • 子系统数量太多,关系复杂,小问题不断,而且出问题后定位困难
  • 开始设计的号称 TPS 50000/秒的系统,实际 TPS 连 500 都不到

由于业务没有发展,最初的设计人员陆续离开,后来接手的团队无奈又花了 2 年时间将系统重构,合并成不到 20 个子系统,整个系统才逐步稳定下来。

这个案例的教训是:如果一开始没有正确识别复杂度,就会导致过度设计,而过度设计带来的复杂性反而会成为系统的负担。

正确的做法:逐个解决

对于按照复杂度优先级解决的方式,存在一个普遍的担忧:如果按照优先级来解决复杂度,可能会出现解决了优先级排在前面的复杂度后,解决后续复杂度的方案需要将已经落地的方案推倒重来。

这个担忧理论上是可能的,但现实中几乎是不可能出现的,原因在于软件系统的可塑性和易变性。对于同一个复杂度问题,软件系统的方案可以有多个,总是可以挑出综合来看性价比最高的方案。

即使架构师决定要推倒重来,这个新的方案也必须能够同时解决已经被解决的复杂度问题,一般来说能够达到这种理想状态的方案基本都是依靠新技术的引入。例如,Hadoop 能够将高可用、高性能、大容量三个大数据处理的复杂度问题同时解决。

正确的做法是将主要的复杂度问题列出来,然后根据业务、技术、团队等综合情况进行排序,优先解决当前面临的最主要的复杂度问题,不要幻想一次架构重构解决所有问题。

识别复杂度实战:前浪微博消息队列

我们假想一个创业公司,名称叫"前浪微博"。前浪微博的业务发展很快,系统也越来越多,系统间协作的效率很低:

  • 用户发一条微博后,微博子系统需要通知审核子系统进行审核,然后通知统计子系统进行统计,再通知广告子系统进行广告预测,接着通知消息子系统进行消息推送……一条微博有十几个通知,目前都是系统间通过接口调用的
  • 用户等级达到 VIP 后,等级子系统要通知福利子系统进行奖品发放,要通知客服子系统安排专属服务人员,要通知商品子系统进行商品打折处理……

新来的架构师在梳理这些问题时,结合自己的经验,敏锐地发现了这些问题背后的根源在于架构上各业务子系统强耦合,而消息队列系统正好可以完成子系统的解耦。

背景信息

  • 中间件团队规模不大,大约 6 人左右
  • 中间件团队熟悉 Java 语言,但有一个新同事 C/C++ 很牛
  • 开发平台是 Linux,数据库是 MySQL
  • 目前整个业务系统是单机房部署,没有双机房

使用排查法识别复杂度

第一步:排查是否需要高性能

假设前浪微博系统用户每天发送 1000 万条微博,那么微博子系统一天会产生 1000 万条消息。假设平均一条消息有 10 个子系统读取,那么其他子系统读取的消息大约是 1 亿次。

1000 万和 1 亿看起来很吓人,但对于架构师来说,关注的不是一天的数据,而是 1 秒的数据,即 TPS 和 QPS。一天内平均每秒写入消息数为 115 条,每秒读取的消息数是 1150 条;再考虑系统的读写并不是完全平均的,设计的目标应该以峰值来计算。峰值一般取平均值的 3 倍,那么消息队列系统的 TPS 是 345,QPS 是 3450。

虽然根据当前业务规模计算的性能要求并不高,但业务会增长,因此系统设计需要考虑一定的性能余量。由于现在的基数较低,为了预留一定的系统容量应对后续业务的发展,我们将设计目标设定为峰值的 4 倍,因此最终的性能要求是:TPS 为 1380,QPS 为 13800

TPS 为 1380 并不高,但 QPS 为 13800 已经比较高了,因此高性能读取是复杂度之一。

第二步:排查是否需要高可用性

对于微博子系统来说,如果消息丢了,导致没有审核,然后触犯了国家法律法规,则是非常严重的事情;对于等级子系统来说,如果用户达到相应等级后,系统没有给他奖品和专属服务,则 VIP 用户会很不满意,导致用户流失从而损失收入。

综合来看,消息队列需要高可用性,包括消息写入、消息存储、消息读取都需要保证高可用性。

第三步:排查是否需要高可扩展性

消息队列的功能很明确,基本无须扩展,因此可扩展性不是这个消息队列的复杂度关键。

排查法复杂度识别函数

以下 Python 实现了一个通用的排查法复杂度识别函数:

python
def analyze_complexity(system_name, features, peak_factor=3, design_factor=4): """ 使用排查法识别系统复杂度 参数: system_name: 系统名称 features: 功能列表,每个功能包含 (名称, 每日量, 读取倍数) peak_factor: 峰值倍数(默认3倍) design_factor: 设计余量倍数(默认4倍) """ print(f"=== {system_name} 复杂度分析 ===") print("=" * 60) complexities = [] total_daily_writes = sum(f[1] for f in features) # 计算平均 TPS/QPS avg_tps = total_daily_writes / (24 * 60 * 60) peak_tps = avg_tps * peak_factor design_tps = peak_tps * design_factor print(f"\n【步骤1: 排查高性能需求】") print(f"每日总写入: {total_daily_writes:,} 条") print(f"平均 TPS: {avg_tps:.1f}") print(f"峰值 TPS (×{peak_factor}): {peak_tps:.1f}") print(f"设计目标 TPS (×{design_factor}): {design_tps:.0f}") # 判断高性能复杂度 if design_tps > 10000: complexities.append(("高性能写入", "高", f"TPS {design_tps:.0f} > 10000")) print(f"✓ 高性能写入是复杂度: TPS {design_tps:.0f} 较高") else: complexities.append(("高性能写入", "低", f"TPS {design_tps:.0f} 不高")) print(f"△ 高性能写入要求不高: TPS {design_tps:.0f}") # 计算总读取量 total_daily_reads = sum(f[1] * f[2] for f in features) avg_qps = total_daily_reads / (24 * 60 * 60) peak_qps = avg_qps * peak_factor design_qps = peak_qps * design_factor print(f"\n每日总读取: {total_daily_reads:,} 次") print(f"平均 QPS: {avg_qps:.1f}") print(f"峰值 QPS (×{peak_factor}): {peak_qps:.1f}") print(f"设计目标 QPS (×{design_factor}): {design_qps:.0f}") # 判断高性能读取复杂度 if design_qps > 10000: complexities.append(("高性能读取", "高", f"QPS {design_qps:.0f} > 10000")) print(f"✓ 高性能读取是复杂度: QPS {design_qps:.0f} 较高") else: complexities.append(("高性能读取", "低", f"QPS {design_qps:.0f} 不高")) print(f"△ 高性能读取要求不高: QPS {design_qps:.0f}") print(f"\n【步骤2: 排查高可用需求】") for name, daily_writes, read_multiplier in features: if "审核" in name or "支付" in name or "订单" in name: complexities.append(("高可用写入", "高", f"{name} 消息丢失不可接受")) print(f"✓ 高可用写入是复杂度: {name} 消息丢失不可接受") break else: print("△ 高可用写入要求一般") complexities.append(("高可用存储", "高", "消息存储不能丢失")) print(f"✓ 高可用存储是复杂度: 消息存储不能丢失") print(f"\n【步骤3: 排查可扩展性需求】") print("△ 可扩展性要求不高: 功能明确,无特殊扩展需求") # 按优先级排序 print(f"\n【复杂度排序结果】") print("=" * 60) # 高复杂度排在前面 complexity_order = [] for name, level, reason in complexities: if level == "高": complexity_order.append((name, reason)) for i, (name, reason) in enumerate(complexity_order, 1): print(f"{i}. {name}: {reason}") return complexity_order # 前浪微博消息队列案例 features = [ ("微博发布", 10_000_000, 10), # 每日1000万条,10个子系统读取 ("等级变化", 500_000, 5), # 每日50万条,5个子系统读取 ] result = analyze_complexity("前浪微博消息队列系统", features)

输出示例:

=== 前浪微博消息队列系统 复杂度分析 === ============================================================ 【步骤1: 排查高性能需求】 每日总写入: 10,500,000 条 平均 TPS: 121.5 峰值 TPS (×3): 364.5 设计目标 TPS (×4): 1458 ✓ 高性能写入是复杂度: TPS 1458 较高 每日总读取: 107,500,000 次 平均 QPS: 1244.2 峰值 QPS (×3): 3732.7 设计目标 QPS (×4): 14931 ✓ 高性能读取是复杂度: QPS 14931 较高 【步骤2: 排查高可用需求】 ✓ 高可用写入是复杂度: 微博发布 消息丢失不可接受 ✓ 高可用存储是复杂度: 消息存储不能丢失 【步骤3: 排查可扩展性需求】 △ 可扩展性要求不高: 功能明确,无特殊扩展需求 【复杂度排序结果】 ============================================================ 1. 高性能读取: QPS 14931 较高 2. 高可用写入: 微博发布 消息丢失不可接受 3. 高可用存储: 消息存储不能丢失

这个示例展示了如何系统地使用排查法识别复杂度,最终确定前浪微博消息队列系统的复杂性主要体现在四个方面:高性能消息读取、高可用消息写入、高可用消息存储、高可用消息读取。

总结

  • 架构设计第1步是识别复杂度,只有正确分析复杂度,后续方案才不会偏离方向;如果对复杂性判断错误,方案越完美越离谱
  • 复杂度可能只有一个,也可能是两个,极少三个以上;如果同时需要解决三个以上的复杂度,要么说明系统之前设计有问题,要么架构师判断失误
  • "亿级用户平台"过度设计教训:40+子系统→TPS 500都不到→重构为20个子系统;根源是一开始没有正确识别复杂度
  • 正确的做法是将主要的复杂度问题列出来,按优先级排序,一个一个解决,不要幻想一次解决所有问题
  • 软件系统的可塑性和易变性使得推倒重来几乎不可能发生,因为总有性价比更高的方案可以选择
  • 前浪微博消息队列案例使用排查法分析:TPS=1380(不高),QPS=13800(较高),高可用(消息丢失不可接受)
  • 复杂度排序结果:高性能读取 > 高可用写入 > 高可用存储 > 高可用读取
  • 设计目标不一定要固定为峰值的4倍,不同业务可以是2倍或8倍,但一般不要设定在10倍以上,更不要一上来就按照100倍预估
  • 新技术引入可以同时解决多个复杂度问题(如Hadoop解决高可用、高性能、大容量),但这是可遇不可求的
  • 识别复杂度对架构师来说是一项挑战,有经验的架构师一看需求就知道复杂度大概在哪里,如果经验不足,只能采取排查法从不同角度逐一分析

参考

  • 软件架构基础