架构设计的第一步是什么?是画架构图?是选技术栈?还是写设计文档?都不是。架构设计的第一步是识别复杂度——只有正确分析出了系统的复杂性,后续的架构设计方案才不会偏离方向。如果对系统的复杂性判断错误,即使后续的架构设计方案再完美再先进,都是南辕北辙,做的越好,错的越多、越离谱。
架构设计的本质目的是为了解决软件系统的复杂性。在我们设计架构时,首先就要分析系统的复杂性。只有正确分析出了系统的复杂性,后续的架构设计方案才不会偏离方向。
例如,如果一个系统的复杂度本来是业务逻辑太复杂、功能耦合严重,架构师却设计了一个 TPS 达到 50000/秒的高性能架构,即使这个架构最终的性能再优秀也没有任何意义,因为架构没有解决正确的复杂性问题。
架构的复杂度主要来源于"高性能""高可用""可扩展"等几个方面,但架构师在具体判断复杂性的时候,不能生搬硬套,认为任何时候架构都必须同时满足这三方面的要求。实际上大部分场景下,复杂度只是其中的某一个,少数情况下包含其中两个,如果真的出现同时需要解决三个或者三个以上的复杂度,要么说明这个系统之前设计的有问题,要么可能就是架构师的判断出现了失误。
专栏前面提到过的"亿级用户平台"失败的案例很能说明问题。设计对标腾讯的 QQ,按照腾讯 QQ 的用户量级和功能复杂度进行设计,高性能、高可用、可扩展、安全等技术一应俱全,一开始就设计出了 40 多个子系统,然后投入大量人力开发了将近 1 年时间才跌跌撞撞地正式上线。
上线后发现之前的过度设计完全是多此一举,而且带来很多问题:
由于业务没有发展,最初的设计人员陆续离开,后来接手的团队无奈又花了 2 年时间将系统重构,合并成不到 20 个子系统,整个系统才逐步稳定下来。
这个案例的教训是:如果一开始没有正确识别复杂度,就会导致过度设计,而过度设计带来的复杂性反而会成为系统的负担。
对于按照复杂度优先级解决的方式,存在一个普遍的担忧:如果按照优先级来解决复杂度,可能会出现解决了优先级排在前面的复杂度后,解决后续复杂度的方案需要将已经落地的方案推倒重来。
这个担忧理论上是可能的,但现实中几乎是不可能出现的,原因在于软件系统的可塑性和易变性。对于同一个复杂度问题,软件系统的方案可以有多个,总是可以挑出综合来看性价比最高的方案。
即使架构师决定要推倒重来,这个新的方案也必须能够同时解决已经被解决的复杂度问题,一般来说能够达到这种理想状态的方案基本都是依靠新技术的引入。例如,Hadoop 能够将高可用、高性能、大容量三个大数据处理的复杂度问题同时解决。
正确的做法是将主要的复杂度问题列出来,然后根据业务、技术、团队等综合情况进行排序,优先解决当前面临的最主要的复杂度问题,不要幻想一次架构重构解决所有问题。
我们假想一个创业公司,名称叫"前浪微博"。前浪微博的业务发展很快,系统也越来越多,系统间协作的效率很低:
新来的架构师在梳理这些问题时,结合自己的经验,敏锐地发现了这些问题背后的根源在于架构上各业务子系统强耦合,而消息队列系统正好可以完成子系统的解耦。
第一步:排查是否需要高性能
假设前浪微博系统用户每天发送 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 实现了一个通用的排查法复杂度识别函数:
pythondef 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. 高可用存储: 消息存储不能丢失
这个示例展示了如何系统地使用排查法识别复杂度,最终确定前浪微博消息队列系统的复杂性主要体现在四个方面:高性能消息读取、高可用消息写入、高可用消息存储、高可用消息读取。