大多数系统崩溃并非因为缺少工具,而是因为工具过多。

以下五种架构实践改变了我对系统设计的看法。
一、仅在必要时拆分系统
我们曾从一开始就采用微服务架构构建了一个支付系统,初衷是打造一个从上线第一天起就具备可扩展性的系统。

即使流量很低,每次支付请求也会触发多次网络调用。任何服务一旦出现故障,整个支付流程都会失败。
除此之外,调试也让人头疼,它需要翻阅大量日志以及协调各个团队。
此外,即便是简单的更改,也需要在相关服务中进行部署。
让我们来简化一下这个架构。

通过保持逻辑边界的同时消除物理隔离,我们大幅减少了潜在的故障模式。
现在部署更容易了,调试速度也更快了。
当需要扩展时,我们可以将使用频率最高的服务之一拆分出来,使其成为独立的微服务。
旨在包罗万象的架构,通常什么都处理不好。
二、避免过度抽象
我们接手了一个比较难维护的订单管理系统,第一反应是通过添加抽象层来对其进行整理。
为了“标准化”行为,我们引入了泛化服务、共享仓库以及各种基类。
结果却比之前更糟。

理解订单流程需要在各个接口和基类之间来回切换。业务规则分散在各个层级,即使是简单的修改,成本也很高。
抽象消除了重复,但也抹去了含义。
于是我们改变了策略。

我们没有采用通用的抽象层,而是明确定义了工作流程。订单创建、定价和履约都体现在具体的代码中。
虽然存在少量代码重复,但这都是有意为之且清晰可见的。这样一来,变更的影响范围得以本地化,故障的追踪也变得更加容易。
简单的系统,出了故障会一目了然;复杂的系统,一旦故障则难以捉摸。
三、 架构设计应以简洁为主,而非花哨
在一个结账系统中,我们曾对整个流程采用了事件驱动架构,每一步骤都负责发布和消费事件。
这套设计在白板上看起来优雅无比,在生产环境中却令人痛苦不堪。

先等一下,你觉得这种架构存在什么明显的缺陷?
大多数讨论都围绕事件排序、重试和消费者延迟展开。没有哪个服务明确负责订单的最终结果。一旦发生故障,订单就会处于部分处理状态,需要人工干预。
这个架构以牺牲清晰度为代价,换取了所谓的灵活性。
我们用一套简洁的同步结账服务,取代了原先完全事件驱动的流程。

整个结账流程由一个服务负责:它验证购物车、处理支付,并最终返回明确的成功或失败结果。
事件机制虽仍被采用,但仅用于分析和通知等辅助功能。核心业务流程依然保持线性,易于理解。
现在,任何故障都能即刻显现。订单要么完成,要么明确失败,绝不会处于模棱两可的状态。
当一种架构需要冗长的讲解才能说明其工作原理时,那么它可能设计得太复杂了。
四、你不需要它 (YAGNI)
这个产品的适用范围很窄。
用户提交反馈意见,管理员进行审核。
但实际的架构远比这复杂得多。

理论上,这套设计是为未来扩展做准备。但实际上,它优化的却是一个永远不会到来的问题,这也造成了诸多复杂性:
单个服务中的多个架构层
基于事件驱动的反馈提交流程
反馈、状态和审计日志分别用单独的表格记录
功能标志用于保护未完成或未使用的功能
这些复杂性都集中在一个服务中,它并没有解决任何实际问题,仅仅是对未来的一种臆测。
简化后的架构大致如下:

通过移除那些无用的抽象层、数据表与基础设施,系统变得不言自明。
大多数系统都会因为应对从未出现的问题而做出的决策不堪重负,最终崩溃。
五、配置过多并非总是好事
某个内部系统过度追求灵活性,几乎所有行为都由配置驱动。
功能标志、环境变量和多个配置文件都影响着核心逻辑。绝大多数生产事故并非由代码缺陷导致,而是源于错误的配置。

理论上,这看起来很灵活,但实际上却有很多隐藏的复杂性:
运行时加载多个配置源
控制关键执行路径的功能标志
环境特定行为遍布整个代码库
要了解系统的运行方式,必须先了解它的部署方式。
现在,让我们来看一下简化后的架构:

清除代码中直接定义的默认值
功能标志仅用于临时发布
配置仅限于实际环境差异
减少活动部件之后,部署变得更加安全,系统行为变得可预测,代码本身也变得易于理解。
当灵活性成为首要目标时,清晰度通常是第一个被牺牲的。
简洁的架构并不意味着设计薄弱。它意味着剔除不必要的复杂性,使系统在持续演进中依然易于理解。
稳固的系统始于简洁,它们的复杂性,是在时间的沉淀中逐步积累而来的。
如果字段的最大可能长度超过255字节,那么长度值可能…
只能说作者太用心了,优秀
感谢详解
一般干个7-8年(即30岁左右),能做到年入40w-50w;有…
230721