架构设计技巧——架构设计模板

一份实用、高效、覆盖核心要素的架构设计模板是确保设计质量、促进团队沟通和指导实施的关键。以下是一个经过提炼的架构设计文档核心模板框架,结合了业界最佳实践,并强调灵活裁剪:

架构设计文档模板 (核心框架)

文档标识

  • 项目/系统名称: [你的系统名称]
  • 文档版本: [v1.0]
  • 日期: [YYYY-MM-DD]
  • 设计负责人: [姓名/团队]
  • 主要受众: [开发团队、运维、产品经理、技术负责人等]
  • 文档状态: [草稿 | 评审中 | 已批准]

1. 背景与目标 (Why & What)

复制

下载

*   **业务驱动力/痛点:**
    *   `[清晰描述当前业务面临的问题、用户需求或现有系统的不足。例如:订单处理速度慢于2秒导致用户流失;现有单体架构无法支持新功能快速上线;数据库成本每月超预算30%]`
*   **设计目标 (必须可衡量!):**
    *   **功能性目标:** `[例如:实现用户在线实时聊天功能;支持每日100万次商品推荐请求]`
    *   **非功能性目标 (NFRs - 重中之重!):**
        *   `[性能]:` `[例如:订单创建API P99延迟 < 500ms;QPS >= 1000]`
        *   `[可用性]:` `[例如:系统整体SLA >= 99.95%]`
        *   `[可扩展性]:` `[例如:支持流量在5分钟内自动扩容50%]`
        *   `[可维护性]:` `[例如:新成员可在2天内理解核心模块并修改]`
        *   `[安全性]:` `[例如:通过OWASP Top 10安全扫描;用户数据加密存储]`
        *   `[成本]:` `[例如:月基础设施成本控制在 $X 以内]`
        *   `[(其他) 容错性、可观测性、合规性等]:` `[具体要求]`
*   **范围 (Scope):**
    *   **包含(In Scope):** `[明确本次设计覆盖的功能模块、服务、数据范围]`
    *   **不包含(Out of Scope):** `[明确排除的部分,避免范围蔓延]`
*   **关键约束 (Constraints):**
    *   `[例如:必须使用公司现有Kubernetes集群;必须兼容遗留订单系统API;预算上限$Y;必须在Q3前上线]`

2. 架构概览 (High-Level Blueprint)

复制

下载

*   **架构愿景图 (C4 Context 或 Container 图):**
    *   `[在此插入或链接清晰的架构概览图]`
    *   **核心展示:**
        *   系统边界和主要**用户角色**。
        *   关键**外部依赖系统**。
        *   核心**内部子系统/服务 (容器)** 及其**核心职责**(一句话描述)。
        *   主要**交互关系**和数据流向(箭头标注关键协议/技术,如HTTP, gRPC, Kafka)。
*   **核心架构风格与模式:**
    *   `[例如:微服务架构、事件驱动架构(EDA)、前端采用BFF模式、核心流程使用Saga管理分布式事务]`
    *   **选型理由:** `[简述为何此风格/模式最适合达成目标和满足约束]`
*   **技术栈选型 (Key Technologies):**
    *   | **类别**       | **技术选型**          | **主要理由/优势**                          | **备选考虑**       |
      | -------------- | --------------------- | ------------------------------------------ | ------------------ |
      | 编程语言       | `[Go/Java/Python等]` | `[高性能/团队熟悉/生态丰富]`             | `[备选方案]`     |
      | 主数据库       | `[PostgreSQL/MySQL]` | `[ACID需求/成熟稳定]`                    | `[MongoDB]`      |
      | 缓存           | `[Redis]`             | `[低延迟/数据结构丰富]`                  | `[Memcached]`    |
      | 消息队列       | `[Kafka]`             | `[高吞吐/持久化/流处理能力]`              | `[RabbitMQ]`     |
      | API网关        | `[Kong]`              | `[动态路由/插件生态/开源]`               | `[Nginx]`        |
      | 部署平台       | `[Kubernetes on AWS]` | `[弹性伸缩/公司标准/运维支持]`            | `[ECS]`          |
      | 监控追踪       | `[Prometheus+Jaeger]` | `[CNCF生态/集成性好]`                    | `[Datadog]`      |
      | *...(其他)*    | *...*                 | *...*                                      | *...*             |

3. 核心设计决策与权衡 (The Heart of Architecture)

复制

下载

*   **使用ADR (Architecture Decision Record) 格式记录关键决策:**
    *   **ADR 1:[决策标题,例如:选择事件驱动架构处理订单状态更新]**
        *   **状态:[提议 | 已批准]**
        *   **背景(Context):** `[描述遇到的问题或机会,例如:订单状态变更需触发多个下游动作(通知、积分、风控),同步调用导致耦合高、延迟大、故障扩散]`
        *   **考虑过的选项(Options):**
            *   `[选项A: 同步HTTP调用]` - *优点:简单直接。缺点:强耦合,一个下游故障拖垮整个链路,难以扩展。*
            *   `[选项B: 数据库轮询/触发器]` - *优点:利用现有DB。缺点:增加DB负担,实时性差,难以维护。*
            *   `[选项C: 事件驱动(发布/订阅)]` - *优点:解耦,异步,提高可用性和扩展性。缺点:引入消息中间件复杂度,最终一致性。*
        *   **决策(Decision):** `[选择选项C:事件驱动架构,使用Kafka作为消息中间件]`
        *   **理由(Rationale):** `[核心满足目标:解耦 -> 提高可维护性;异步 -> 降低延迟、提高吞吐量(QPS目标);故障隔离 -> 提高可用性(SLA目标)。接受最终一致性带来的短暂延迟风险(业务可接受)。]`
        *   **后果(Consequences):** `[需实现消息幂等性处理;需监控消息积压;需设计补偿机制应对业务失败。]`
    *   **ADR 2:[决策标题,例如:用户认证采用OAuth 2.0 + JWT]**
    *   **ADR 3:[决策标题,例如:核心交易数据存储使用关系型数据库而非NoSQL]**
    *   **... (其他关键决策)**
*   **`[注:强烈建议将每个ADR作为独立Markdown文件管理,在此章节汇总链接和核心结论即可]`**

4. 关键领域设计详解 (How - Focus on Critical Areas)

复制

下载

*   **按需选择并深入描述以下1-N个核心领域的设计:**
    *   **A. 服务/模块设计:**
        *   **`[服务名称]` 服务:**
            *   **职责:** `[明确该服务负责的核心业务能力]`
            *   **接口(API/Event):** `[关键API定义 (方法、路径、请求/响应示例) 或 发布/订阅的事件契约]`
            *   **内部组件图 (可选 C4 Component图):** `[展示服务内部关键组件及交互]`
            *   **关键技术点:** `[例如:使用本地缓存减少DB访问;采用XX算法进行推荐计算]`
    *   **B. 数据设计:**
        *   **核心数据模型:** `[ER图 或 核心实体/值对象描述,突出关键关系和字段]`
        *   **存储策略:**
            *   **主存储:** `[选型理由,分片策略(如按用户ID Hash),索引策略]`
            *   **缓存:** `[缓存策略(如Cache-Aside),缓存Key设计,TTL设置,失效机制]`
            *   **搜索引擎:** `[如使用Elasticsearch,描述索引Mapping和查询策略]`
        *   **数据流与一致性:**
            *   `[描述关键数据如何流动,例如:订单创建 -> 发Kafka事件 -> 扣库存服务消费 -> 更新DB]`
            *   **一致性保障:** `[例如:订单创建与库存预扣采用本地事务(强一致);库存实际扣减与积分增加通过事件最终一致]`
    *   **C. 关键流程设计:**
        *   **`[流程名称,例如:用户下单流程]`**
            *   **流程图 或 序列图:** `[清晰展示参与组件、调用顺序、协议、关键数据]`
            *   **异常处理:** `[关键失败场景(如库存不足、支付失败)的处理逻辑和补偿机制]`
    *   **D. 非功能性需求 (NFR) 设计落实 (重点!):**
        *   **`[性能]`:** `[具体措施:API网关层限流配置;数据库读写分离;热点数据缓存;异步化处理耗时操作]`
        *   **`[可用性]`:** `[具体措施:服务K8s多副本部署 + Pod反亲和;数据库主从+异地灾备;关键依赖熔断降级配置(Hystrix/Sentinel阈值)]`
        *   **`[可扩展性]`:** `[具体措施:服务无状态设计;K8s HPA基于CPU/自定义指标自动扩缩容;消息队列分区(Partition)支持水平扩展消费者]`
        *   **`[安全性]`:** `[具体措施:API网关统一JWT认证鉴权;数据库字段加密;输入参数严格校验;定期漏洞扫描]`
        *   **`[可观测性]`:** `[具体措施:Prometheus采集核心指标(Grafana看板);ELK集中日志收集;Jaeger集成实现全链路追踪;定义关键业务告警规则]`
        *   **`[成本]`:** `[具体措施:使用Spot实例运行非核心服务;设置自动伸缩下限避免闲置资源;监控并优化大查询/存储]`

5. 部署与运维视图 (Where & How to Run)

复制

下载

*   **部署架构图:**
    *   `[在此插入或链接清晰的部署图]`
    *   **核心展示:**
        *   服务/组件在**基础设施**上的映射(如K8s Namespace/Deployment, EC2实例)。
        *   **网络拓扑:** VPC/子网、负载均衡器(ALB/NLB)、安全组规则。
        *   **存储:** 数据库实例位置、存储卷配置。
        *   **跨区/可用区部署策略**。
*   **关键基础设施与配置:**
    *   `[例如:Kubernetes集群版本及配置;数据库实例规格和存储类型;缓存集群大小和配置]`
*   **发布与运维策略:**
    *   **部署方式:** `[蓝绿部署 | 金丝雀发布 | 滚动更新]`
    *   **配置管理:** `[使用ConfigMap/Secret | Spring Cloud Config | Consul]`
    *   **监控告警:** `[核心监控指标列表及告警阈值(如CPU>80%持续5分钟, API错误率>1%)]`
    *   **灾难恢复(DR)计划:** `[简要说明,如:数据库定时备份与跨区复制;关键服务在备区有冷备方案]`

6. 演进与风险 (Looking Ahead)

复制

下载

*   **初步演进路线图 (可选):**
    *   **MVP (V1.0):** `[核心功能清单,满足最基本目标]`
    *   **Phase 1 (V1.x):** `[扩展功能A, 优化性能点B]`
    *   **Phase 2 (V2.0):** `[引入功能C, 重构模块D]`
*   **已知风险与应对:**
    *   | **风险描述**                     | **可能性** | **影响** | **缓解措施**                          | **负责人** |
      | -------------------------------- | ---------- | -------- | ------------------------------------- | ---------- |
      | `[Kafka消息积压导致延迟]`        | 中         | 高       | `[设置消费者Lag监控告警;优化消费者并行度;预留紧急扩容预案]` | `[张三]` |
      | `[新选型的XX框架学习曲线陡峭]`   | 高         | 中       | `[安排提前培训;准备详细示例文档;初期安排专家支持]`       | `[李四]` |
      | `[第三方支付接口不稳定]`         | 低         | 高       | `[实现支付状态查询补偿Job;接入备用支付渠道]`          | `[王五]` |
*   **待办事项 (TODOs):**
    *   `[需要进一步验证的技术点,如:XX库在压测下的表现]`
    *   `[需要细化的设计部分,如:风控模块详细规则]`

7. 附录 (Reference)

复制

下载

*   **术语表 (Glossary):** `[领域专有名词解释]`
*   **参考资料 (References):** `[相关文档、规范、RFC链接]`
*   **详细图表索引:** `[指向序列图、状态图、更细粒度组件图等链接]`
*   **ADR 目录:** `[所有架构决策记录的列表和链接]`

核心使用原则与建议:

  1. 受众驱动,灵活裁剪:
    • 开发团队看:重点在 3. 核心设计决策与权衡, 4. 关键领域设计详解 (特别是接口、流程、数据), 5. 部署与运维视图
    • 技术负责人/CTO看:重点在 1. 背景与目标, 2. 架构概览, 3. 核心设计决策与权衡 (特别是权衡理由), 6. 演进与风险, 4D. NFR设计落实
    • 运维看:重点在 5. 部署与运维视图, 4D. NFR设计落实 (可观测性、可用性、扩展性), 6. 风险
    • 小型项目: 聚焦 1. 背景与目标, 2. 架构概览 (图!), 3. 关键决策 (选1-2个最重要的ADR), 4. 最核心领域设计 (如主流程), 5. 基础部署
  1. 图是灵魂: 架构图、流程图、序列图、部署图 必须清晰、规范、一致 (遵循之前讨论的绘图技巧)。一图胜千言。
  2. NFRs 是试金石: 架构的优劣很大程度上体现在如何满足非功能性需求。4D. 非功能性需求 (NFR) 设计落实 章节是展示架构师功力的核心,务必具体、可落地、可验证。
  3. 决策与权衡是精髓: 3. 核心设计决策与权衡 (使用ADR) 是文档最有价值的部分。它记录了思考过程、评估了备选方案、明确了取舍理由,是未来维护和复盘的关键依据。务必投入精力写好ADR!
  4. 版本控制与活文档:
    • 将文档(尤其是 Markdown 格式的文本和 PlantUML/Mermaid 代码)纳入 Git 等版本控制系统。
    • 将架构图源文件 (如 .drawio) 也纳入版本控制。
    • 架构变更时,同步更新设计文档和相关ADR。将文档更新作为上线流程的一个环节。
  1. 工具推荐:
    • 文档编写 & 协作: Markdown (GitHub/GitLab Wiki, VS Code), Confluence, Google Docs, Notion
    • 绘图: Diagrams.net (draw.io) (首选,免费强大), Mermaid (文本绘图,适合版本控制), PlantUML (文本UML)。
    • ADR 管理: Markdown文件 (推荐),专用ADR工具,Confluence模板。
    • 图表版本控制:draw.io 图保存为 .drawio 文本格式 (本质是XML) 或 Mermaid/PlantUML 源码放入Git。

记住: 模板是工具,不是束缚。核心目标是清晰传达设计意图、关键决策、以及如何满足需求(尤其是NFRs)。好的架构设计文档是团队协作的基石,也是系统长期健康演进的指南针。开始为你的系统填写这个模板吧!

一、架构设计步骤

一个大型网站的技术架构主要会从高性能、可扩展性、高可用、安全、成本、伸缩性等方面进行考虑,之后可以通过以下几步进行详细的架构设计:

1.架构设计第 1 步:识别复杂度

架构设计由需求所驱动,本质目的是为了解决软件系统的复杂性。为此,我们在进行架构设计时,需要以理解需求为前提,首要进行系统复杂性的分析。

(1)复杂性分析步骤

①构建复杂度的来源清单——高性能、可用性、扩展性、安全、低成本、规模等。

②结合需求、技术、团队、资源等对上述复杂度逐一分析是否需要,是否关键

  • “高性能”主要从软件系统未来的TPS、响应时间、服务器资源利用率等客观指标,也可以从用户的主观感受方面去考虑。
  • “可用性”主要从服务不中断等质量属性,符合行业政策、国家法规等方面去考虑。
  • “扩展性”则主要从功能需求的未来变更幅度等方面去考虑。

③将需要解决的复杂度影响因素按照优先级排序,越是排在前面的复杂度,就越关键,就越优先解决。

需要特别注意的是:随着所处的业务阶段不同、外部的技术条件和环境的不同,得到的复杂度问题的优先级排序就会有所不同。一切皆变化。


 

(2)识别复杂度案例演示

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

用户发一条微博后,微博子系统需要通知审核子系统进行审核,然后通知统计子系统进行统计,再通知广告子系统进行广告预测,接着通知消息子系统进行消息推送……一条微博有十几个通知,目前都是系统间通过接口调用的。每通知一个新系统,微博子系统就要设计接口、进行测试,效率很低,问题定位很麻烦,经常和其他子系统的技术人员产生分岐,微博子系统的开发人员不胜其烦。

用户等级达到 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 已经比较高了,因此高性能读取是复杂度之一。注意,这里的设计目标设定为峰值的 4 倍是根据业务发展速度来预估的,不是固定为 4 倍,不同的业务可以是 2 倍,也可以是 8 倍,但一般不要设定在 10 倍以上,更不要一上来就按照 100 倍预估。

②这个消息队列是否需要高可用性

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

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

③这个消息队列是否需要高可扩展性

消息队列的功能很明确,基本无须扩展,因此可扩展性不是这个消息队列的复杂度关键。为了方便理解,这里我只排查“高性能”“高可用”“扩展性”这 3 个复杂度,在实际应用中,不同的公司或者团队,可能还有一些其他方面的复杂度分析。例如,金融系统可能需要考虑安全性,有的公司会考虑成本等。

综合分析下来,消息队列的复杂性主要体现在这几个方面:高性能消息读取、高可用消息写入、高可用消息存储、高可用消息读取。


 

2.架构设计第 2 步:设计备选方案

虽然软件技术经过几十年的发展,新技术层出不穷,但是经过时间考验,已经被各种场景验证过的成熟技术其实更多。例如,高可用的主备方案、集群方案,高性能的负载均衡、多路复用,可扩展的分层、插件化等技术,绝大部分时候我们有了明确的目标后,按图索骥就能够找到可选的解决方案。只有当这种方式完全无法满足需求的时候,才会考虑进行方案的创新,而事实上方案的创新绝大部分情况下也都是基于已有的成熟技术。

(1)几种常见的架构设计误区

①设计最优秀的方案。不要面向“简历”进行架构设计,而是要根据“合适”、“简单”、“演进”的架构设计原则,决策出与需求、团队、技术能力相匹配的合适方案。

②只做一个方案。一个方案容易陷入思考问题片面、自我坚持的认知陷阱。

(2)备选方案设计的注意事项

①备选方案不要过于详细。备选阶段解决的是技术选型问题,而不是技术细节。

②备选方案的数量以 3~5个为最佳。

③备选方案的技术差异要明显。

例如,主备方案和集群方案差异就很明显,或者同样是主备方案,用 ZooKeeper 做主备决策和用 Keepalived 做主备决策的差异也很明显。但是都用 ZooKeeper 做主备决策,一个检测周期是 1 分钟,一个检测周期是 5 分钟,这就不是架构上的差异,而是细节上的差异了,不适合做成两个方案。

④备选方案不要只局限于已经熟悉的技术。

(3)设计备选方案实战

通过“排查法”已经识别了消息队列的复杂性主要体现在:高性能消息读取、高可用消息写入、高可用消息存储、高可用消息读取。接下来进行第 2 步,设计备选方案。

①备选方案 1:采用开源的 Kafka

Kafka 是成熟的开源消息队列方案,功能强大,性能非常高,而且已经比较成熟,很多大公司都在使用。

②备选方案 2:集群 + MySQL 存储

首先考虑单服务器高性能。高性能消息读取属于“计算高可用”的范畴,单服务器高性能备选方案有很多种。考虑到团队的开发语言是 Java,虽然有人觉得 C/C++ 语言更加适合写高性能的中间件系统,但架构师综合来看,认为无须为了语言的性能优势而让整个团队切换语言,消息队列系统继续用 Java 开发。由于 Netty 是 Java 领域成熟的高性能网络库,因此架构师选择基于 Netty 开发消息队列系统。

由于系统设计的 QPS 是 13800,即使单机采用 Netty 来构建高性能系统,单台服务器支撑这么高的 QPS 还是有很大风险的,因此架构师选择采取集群方式来满足高性能消息读取,集群的负载均衡算法采用简单的轮询即可。

同理,“高可用写入”和“高性能读取”一样,可以采取集群的方式来满足。因为消息只要写入集群中一台服务器就算成功写入,因此“高可用写入”的集群分配算法和“高性能读取”也一样采用轮询,即正常情况下,客户端将消息依次写入不同的服务器;某台服务器异常的情况下,客户端直接将消息写入下一台正常的服务器即可。

整个系统中最复杂的是“高可用存储”和“高可用读取”,“高可用存储”要求已经写入的消息在单台服务器宕机的情况下不丢失;“高可用读取”要求已经写入的消息在单台服务器宕机的情况下可以继续读取。架构师第一时间想到的就是可以利用 MySQL 的主备复制功能来达到“高可用存储“的目的,通过服务器的主备方案来达到“高可用读取”的目的。


 

简单描述一下方案:

采用数据分散集群的架构,集群中的服务器进行分组,每个分组存储一部分消息数据。每个分组包含一台主 MySQL 和一台备 MySQL,分组内主备数据复制,分组间数据不同步。

正常情况下,分组内的主服务器对外提供消息写入和消息读取服务,备服务器不对外提供服务;主服务器宕机的情况下,备服务器对外提供消息读取的服务。客户端采取轮询的策略写入和读取消息。

③备选方案 3:集群 + 自研存储方案

在备选方案 2 的基础上,将 MySQL 存储替换为自研实现存储方案,因为 MySQL 的关系型数据库的特点并不是很契合消息队列的数据特点,参考 Kafka 的做法,可以自己实现一套文件存储和复制方案(此处省略具体的方案描述,实际设计时需要给出方案)。

可以看出,高性能消息读取单机系统设计这部分时并没有多个备选方案可选,备选方案 2 和备选方案 3 都采取基于 Netty 的网络库,用 Java 语言开发,原因就在于团队的 Java 背景约束了备选的范围。通常情况下,成熟的团队不会轻易改变技术栈,反而是新成立的技术团队更加倾向于采用新技术。

3.架构设计第 3 步:评估和选择备选方案

(1)如果评估和选择方法

列出我们需要关注的质量属性点,然后分别从这些质量属性的维度去评估每个方案,再综合挑选适合当时情况的最优方案。

常见的方案质量属性点有:性能、可用性、硬件成本、项目投入、复杂度、安全性、可扩展性等。在评估这些质量属性时,需要遵循架构设计原则 1“合适原则”和原则 2“简单原则”,避免贪大求全,基本上某个质量属性能够满足一定时期内业务发展就可以了。

假如我们做一个购物网站,现在的 TPS 是 1000,如果我们预期 1 年内能够发展到 TPS 2000(业务一年翻倍已经是很好的情况了),在评估方案的性能时,只要能超过 2000 的都是合适的方案,而不是说淘宝的网站 TPS 是每秒 10 万,我们的购物网站就要按照淘宝的标准也实现 TPS 10 万。

通常情况下,如果某个质量属性评估和业务发展有关系(例如,性能、硬件成本等),需要评估未来业务发展的规模时,一种简单的方式是将当前的业务规模乘以 2 ~4 即可,如果现在的基数较低,可以乘以 4;如果现在基数较高,可以乘以 2。例如,现在的 TPS 是 1000,则按照 TPS 4000 来设计方案;如果现在 TPS 是 10000,则按照 TPS 20000 来设计方案。

没有哪个方案是完美的,极少出现某个方案在所有对比维度上都是最优的。例如:引入开源方案工作量小,但是可运维性和可扩展性差;自研工作量大,但是可运维和可维护性好;使用 C 语言开发性能高,但是目前团队 C 语言技术积累少;使用 Java 技术积累多,但是性能没有 C 语言开发高,成本会高一些。

面临这种选择上的困难,正确的做法是按优先级选择,即架构师综合当前的业务发展情况、团队人员规模和技能、业务发展预测等因素,将质量属性按照优先级排序,首先挑选满足第一优先级的,如果方案都满足,那就再看第二优先级……以此类推。那会不会出现两个或者多个方案,每个质量属性的优缺点都一样的情况呢?理论上是可能的,但实际上是不可能的。前面我提到,在做备选方案设计时,不同的备选方案之间的差异要比较明显,差异明显的备选方案不可能所有的优缺点都是一样的。

(2)评估和选择备选方案实战

①备选方案 1:采用开源 Kafka 方案

业务主管倾向于采用 Kafka 方案,因为 Kafka 已经比较成熟,各个业务团队或多或少都了解过 Kafka。

中间件团队部分研发人员也支持使用 Kafka,因为使用 Kafka 能节省大量的开发投入;但部分人员认为 Kafka 可能并不适合我们的业务场景,因为 Kafka 的设计目的是为了支撑大容量的日志消息传输,而我们的消息队列是为了业务数据的可靠传输。

运维代表提出了强烈的反对意见:首先,Kafka 是 Scala 语言编写的,运维团队没有维护 Scala 语言开发的系统的经验,出问题后很难快速处理;其次,目前运维团队已经有一套成熟的运维体系,包括部署、监控、应急等,使用 Kafka 无法融入这套体系,需要单独投入运维人力。

测试代表也倾向于引入 Kafka,因为 Kafka 比较成熟,无须太多测试投入。

② 备选方案 2:集群 + MySQL 存储

中间件团队的研发人员认为这个方案比较简单,但部分研发人员对于这个方案的性能持怀疑态度,毕竟使用 MySQL 来存储消息数据,性能肯定不如使用文件系统;并且有的研发人员担心做这样的方案是否会影响中间件团队的技术声誉,毕竟用 MySQL 来做消息队列,看起来比较“土”、比较另类。

运维代表赞同这个方案,因为这个方案可以融入到现有的运维体系中,而且使用 MySQL 存储数据,可靠性有保证,运维团队也有丰富的 MySQL 运维经验;但运维团队认为这个方案的成本比较高,一个数据分组就需要 4 台机器(2 台服务器 + 2 台数据库)。

测试代表认为这个方案测试人力投入较大,包括功能测试、性能测试、可靠性测试等都需要大量地投入人力。

业务主管对这个方案既不肯定也不否定,因为反正都不是业务团队来投入人力来开发,系统维护也是中间件团队负责,对业务团队来说,只要保证消息队列系统稳定和可靠即可。

③备选方案 3:集群 + 自研存储系统

中间件团队部分研发人员认为这是一个很好的方案,既能够展现中间件团队的技术实力,性能上相比 MySQL 也要高;但另外的研发人员认为这个方案复杂度太高,按照目前的团队人力和技术实力,要做到稳定可靠的存储系统,需要耗时较长的迭代,这个过程中消息队列系统可能因为存储出现严重问题,例如文件损坏导致丢失大量数据。

运维代表不太赞成这个方案,因为运维之前遇到过几次类似的存储系统故障导致数据丢失的问题,损失惨重。例如,MongoDB 丢数据、Tokyo Tyrant 丢数据无法恢复等。运维团队并不相信目前的中间件团队的技术实力足以支撑自己研发一个存储系统(这让中间件团队的人员感觉有点不爽)。

测试代表赞同运维代表的意见,并且自研存储系统的测试难度也很高,投入也很大。

业务主管对自研存储系统也持保留意见,因为从历史经验来看,新系统上线肯定有 bug,而存储系统出 bug 是最严重的,一旦出 bug 导致大量消息丢失,对系统的影响会严重。

针对 3 个备选方案的讨论初步完成后,架构师列出了 3 个方案的 360 度环评表:


 

列出这个表格后,无法一眼看出具体哪个方案更合适,于是大家都把目光投向架构师,决策的压力现在集中在架构师身上了。架构师经过思考后,给出了最终选择备选方案 2,原因有:

排除备选方案 1 的主要原因是可运维性,因为再成熟的系统,上线后都可能出问题,如果出问题无法快速解决,则无法满足业务的需求;并且 Kafka 的主要设计目标是高性能日志传输,而我们的消息队列设计的主要目标是业务消息的可靠传输。

排除备选方案 3 的主要原因是复杂度,目前团队技术实力和人员规模(总共 6 人,还有其他中间件系统需要开发和维护)无法支撑自研存储系统(参考架构设计原则 2:简单原则)。

备选方案 2 的优点就是复杂度不高,也可以很好地融入现有运维体系,可靠性也有保障。

针对备选方案 2 的缺点,架构师解释是:

备选方案 2 的第一个缺点是性能,业务目前需要的性能并不是非常高,方案 2 能够满足,即使后面性能需求增加,方案 2 的数据分组方案也能够平行扩展进行支撑(参考架构设计原则 3:演化原则)。

备选方案 2 的第二个缺点是成本,一个分组就需要 4 台机器,支撑目前的业务需求可能需要 12 台服务器,但实际上备机(包括服务器和数据库)主要用作备份,可以和其他系统并行部署在同一台机器上。

备选方案 2 的第三个缺点是技术上看起来并不很优越,但我们的设计目的不是为了证明自己(参考架构设计原则 1:合适原则),而是更快更好地满足业务需求。

最后,大家针对一些细节再次讨论后,确定了选择备选方案 2。


 

4.架构设计第 4 步:详细方案设计

(1)什么是详细方案设计

假如我们确定使用 Elasticsearch 来做全文搜索,那么就需要确定 Elasticsearch 的索引是按照业务划分,还是一个大索引就可以了;副本数量是 2 个、3 个还是 4 个,集群节点数量是 3 个还是 6 个等。

假如我们确定使用 MySQL 分库分表,那么就需要确定哪些表要分库分表,按照什么维度来分库分表,分库分表后联合查询怎么处理等。

假如我们确定引入 Nginx 来做负载均衡,那么 Nginx 的主备怎么做,Nginx 的负载均衡策略用哪个(权重分配?轮询?ip_hash?)等。

(2)详细方案与备选方案引发问题及解决

详细设计方案阶段可能遇到的一种极端情况就是在详细设计阶段发现备选方案不可行,一般情况下主要的原因是备选方案设计时遗漏了某个关键技术点或者关键的质量属性。例如,我曾经参与过一个项目,在备选方案阶段确定是可行的,但在详细方案设计阶段,发现由于细节点太多,方案非常庞大,整个项目可能要开发长达 1 年时间,最后只得废弃原来的备选方案,重新调整项目目标、计划和方案。这个项目的主要失误就是在备选方案评估时忽略了开发周期这个质量属性。幸运的是,这种情况可以通过下面方式有效地避免:

①架构师不但要进行备选方案设计和选型,还需要对备选方案的关键细节有较深入的理解。例如,架构师选择了 Elasticsearch 作为全文搜索解决方案,前提必须是架构师自己对 Elasticsearch 的设计原理有深入的理解,比如索引、副本、集群等技术点;而不能道听途说 Elasticsearch 很牛,所以选择它。

②通过分步骤、分阶段、分系统等方式,尽量降低方案复杂度,方案本身的复杂度越高,某个细节推翻整个方案的可能性就越高,适当降低复杂性,可以减少这种风险。

如果方案本身就很复杂,那就采取设计团队的方式来进行设计,博采众长,汇集大家的智慧和经验,防止只有 1~2 个架构师可能出现的思维盲点或者经验盲区。

详细方案设计实战

③细化设计点。

细化设计点 1:数据库表如何设计?

  • 数据库设计两类表,一类是日志表,用于消息写入时快速存储到 MySQL 中;另一类是消息表,每个消息队列一张表。
  • 业务系统发布消息时,首先写入到日志表,日志表写入成功就代表消息写入成功;后台线程再从日志表中读取消息写入记录,将消息内容写入到消息表中。
  • 业务系统读取消息时,从消息表中读取。
  • 日志表表名为 MQ_LOG,包含的字段:日志 ID、发布者信息、发布时间、队列名称、消息内容。
  • 消息表表名就是队列名称,包含的字段:消息 ID(递增生成)、消息内容、消息发布时间、消息发布者。
  • 日志表需要及时清除已经写入消息表的日志数据,消息表最多保存 30 天的消息数据。

细化设计点 2:数据如何复制?

直接采用 MySQL 主从复制即可,只复制消息存储表,不复制日志表。

细化设计点 3:主备服务器如何倒换?

  • 采用 ZooKeeper 来做主备决策,主备服务器都连接到 ZooKeeper 建立自己的节点,主服务器的路径规则为“/MQ/server/ 分区编号 /master”,备机为“/MQ/server/ 分区编号 /slave”,节点类型为 EPHEMERAL。
  • 备机监听主机的节点消息,当发现主服务器节点断连后,备服务器修改自己的状态,对外提供消息读取服务。

细化设计点 4:业务服务器如何写入消息?

  • 消息队列系统设计两个角色:生产者和消费者,每个角色都有唯一的名称。
  • 消息队列系统提供 SDK 供各业务系统调用,SDK 从配置中读取所有消息队列系统的服务器信息,SDK 采取轮询算法发起消息写入请求给主服务器。如果某个主服务器无响应或者返回错误,SDK 将发起请求发送到下一台服务器。

细化设计点 5:业务服务器如何读取消息?

  • 消息队列系统提供 SDK 供各业务系统调用,SDK 从配置中读取所有消息队列系统的服务器信息,轮流向所有服务器发起消息读取请求。
  • 消息队列服务器需要记录每个消费者的消费状态,即当前消费者已经读取到了哪条消息,当收到消息读取请求时,返回下一条未被读取的消息给消费者。

细化设计点 6:业务服务器和消息队列服务器之间的通信协议如何设计?

考虑到消息队列系统后续可能会对接多种不同编程语言编写的系统,为了提升兼容性,传输协议用 TCP,数据格式为 ProtocolBuffer。

细化设计点 7:发送端和消费端如何寻址

利用zookeeper做注册中心,把broker的地址注册到zk上,发送端和消费端只要配置注册中心的地址即可获取集群所以broker地址,当有broker下线时,发送端和消费端能及时更新broker地址。

细化设计点 8:发送端消息重试

当发送消息发生网络异常时(不包括超时异常),可以重新选择下一台broker来重试发送,重试策略可以自定义。

细化设计点 9:消息消费采用pull还是push?

考虑push模式会更复杂,故放弃,采用pull模式,消费端主动去拉,为了达到与push模式相同的低延迟效果,可以采用长轮询的方式,消费端轮询拉取消息费,当有消费可消费时,返回消息,如果没有可消费的消息,挂起当前线程,直到超时或者有可消费的消息为止。

细化设计点 10:消息重复问题

消息中间件不解决消息重复的问题,有业务系统自己根据业务的唯一id去重。

细化设计点 11:顺序消息

发送端在发生顺序消息时,只发送到相同broker的相同队列,消费端消费时,顺序消息只能由同一个消费端消息。

细化设计点 12:定时消息

发送端指定消息延时多长时间消费,broker端定时扫描定时消息,达到延时时间的消息加入到消费队列。

细化设计点 13:事务消息

发送端分两步,先预发送消息,broker端只记录消息为预发送状态,再执行本地事务,然后再根据本地事务的成功或者失败发送确认消息(回滚还是提交),这步如果发生异常,broker启动定时任务,把未确认的消息发送给发送端回查事务状态(需要发送端提供回查接口)。


 

二、备选方案模板

1. 需求介绍

需求介绍主要描述需求的背景、目标、范围等

随着前浪微博业务的不断发展,业务上拆分的子系统越来越多,目前系统间的调用都是同步调用,由此带来几个明显的系统问题:

  • 性能问题:当用户发布了一条微博后,微博发布子系统需要同步调用“统计子系统”“审核子系统”“奖励子系统”等共 8 个子系统,性能很低。
  • 耦合问题:当新增一个子系统时,例如如果要增加“广告子系统”,那么广告子系统需要开发新的接口给微博发布子系统调用。
  • 效率问题:每个子系统提供的接口参数和实现都有一些细微的差别,导致每次都需要重新设计接口和联调接口,开发团队和测试团队花费了许多重复工作量。

基于以上背景,我们需要引入消息队列进行系统解耦,将目前的同步调用改为异步通知。

2. 需求分析

需求分析主要全方位地描述需求相关的信息

(1)5W
  • Who:需求利益干系人,包括开发者、使用者、购买者、决策者等。
  • When:需求使用时间,包括季节、时间、里程碑等。
  • What:需求的产出是什么,包括系统、数据、文件、开发库、平台等。
  • Where:需求的应用场景,包括国家、地点、环境等,例如测试平台只会在测试环境使用。
  • Why:需求需要解决的问题,通常和需求背景相关

消息队列的 5W 分析如下:

  • Who:消息队列系统主要是业务子系统来使用,子系统发送消息或者接收消息。
  • When:当子系统需要发送异步通知的时候,需要使用消息队列系统。
  • What:需要开发消息队列系统。
  • Where:开发环境、测试环境、生产环境都需要部署。
  • Why:消息队列系统将子系统解耦,将同步调用改为异步通知。
(2)1H

这里的 How 不是设计方案也不是架构方案,而是关键业务流程。消息队列系统这部分内容很简单,但有的业务系统 1H 就是具体的用例了,有兴趣的同学可以尝试写写 ATM 机取款的业务流程。如果是复杂的业务系统,这部分也可以独立成“用例文档”

消息队列有两大核心功能:

  • 业务子系统发送消息给消息队列。
  • 业务子系统从消息队列获取消息。
(3)8C

8C 指的是 8 个约束和限制,即 Constraints,包括性能 Performance、成本 Cost、时间 Time、可靠性 Reliability、安全性 Security、合规性 Compliance、技术性 Technology、兼容性 Compatibility

注:需求中涉及的性能、成本、可靠性等仅仅是利益关联方提出的诉求,不一定准确;如果经过分析有的约束没有必要,或成本太高、难度太大,这些约束是可以调整的。

性能:需要达到 Kafka 的性能水平。

成本:参考 XX 公司的设计方案,不超过 10 台服务器。

时间:期望 3 个月内上线第一个版本,在两个业务尝试使用。

可靠性:按照业务的要求,消息队列系统的可靠性需要达到 99.99%。

安全性:消息队列系统仅在生产环境内网使用,无需考虑网络安全;如消息中有敏感信息,消息发送方需要自行进行加密,消息队列系统本身不考虑通用的加密。

合规性:消息队列系统需要按照公司目前的 DevOps 规范进行开发。

技术性:目前团队主要研发人员是 Java,最好用 Java 开发。

兼容性:之前没有类似系统,无需考虑兼容性。

3. 复杂度分析

分析需求的复杂度,复杂度常见的有高可用、高性能、可扩展等,具体分析方法请参考专栏前面的内容

(1)高可用

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

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

(2)高性能

前浪微博系统用户每天发送 1000 万条微博,那么微博子系统一天会产生 1000 万条消 息,平均一条消息有 10 个子系统读取,那么其他子系统读取的消息大约是 1 亿次。将数据按照秒来计算,一天内平均每秒写入消息数为 115 条,每秒读取的消息数是 1150 条;再考虑系统的读写并不是完全平均的,设计的目标应该以峰值来计算。峰值一般取平均值的 3 倍,那么消息队列系统的 TPS 是 345,QPS 是 3450,考虑一定的性能余量。由于现在的基数较低,为了预留一定的系统容量应对后续业务的发展,我们将设计目标设定为峰值的 4 倍,因此最终的性能要求是:TPS 为 1380,QPS 为 13800。TPS 为 1380 并不高,但 QPS 为 13800 已经比较高了,因此高性能读取是复杂度之一。

(3)可扩展

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

4. 备选方案

备选方案设计,至少 3 个备选方案,每个备选方案需要描述关键的实现,无须描述具体的实现细节。

备选方案 1:直接引入开源 Kafka

备选方案 2:集群 + MySQL 存储

备选方案 3:集群 + 自研存储

5. 备选方案评估

备选方案 360 度环评。注意备选方案评估的内容会根据评估会议的结果进行修改,也就是说架构师首先给出自己的备选方案评估,然后举行备选方案评估会议,再根据会议结论修改备选方案文档


 

三、架构设计模板

1. 总体方案

总体方案需要从整体上描述方案的结构,其核心内容就是架构图,以及针对架构图的描述,包括模块或者子系统的职责描述、核心流程

2. 架构总览

(1)架构图


 

(2)架构的描述

采用数据分散集群的架构,集群中的服务器进行分组,每个分组存储一部分消息数据。

每个分组包含一台主 MySQL 和一台备 MySQL,分组内主备数据复制,分组间数据不同步。

正常情况下,分组内的主服务器对外提供消息写入和消息读取服务,备服务器不对外提供服务;主服务器宕机的情况下,备服务器对外提供消息读取的服务。

客户端采取轮询的策略写入和读取消息。

3. 核心流程

消息发送流程

[此处省略流程描述]

消息读取流程

[此处省略流程描述]

4. 详细设计

详细设计需要描述具体的实现细节

(1)高可用设计

①消息发送可靠性

业务服务器中嵌入消息队列系统提供的 SDK,SDK 支持轮询发送消息,当某个分组的主服务器无法发送消息时,SDK 挑选下一个分组主服务器重发消息,依次尝试所有主服务器直到发送成功;如果全部主服务器都无法发送,SDK 可以缓存消息,也可以直接丢弃消息,具体策略可以在启动 SDK 的时候通过配置指定。

如果 SDK 缓存了一些消息未发送,此时恰好业务服务器又重启,则所有缓存的消息将永久丢失,这种情况 SDK 不做处理,业务方需要针对某些非常关键的消息自己实现永久存储的功能。

②消息存储可靠性

消息存储在 MySQL 中,每个分组有一主一备两台 MySQL 服务器,MySQL 服务器之间复制消息以保证消息存储高可用。如果主备间出现复制延迟,恰好此时 MySQL 主服务器宕机导致数据无法恢复,则部分消息会永久丢失,这种情况不做针对性设计,DBA 需要对主备间的复制延迟进行监控,当复制延迟超过 30 秒的时候需要及时告警并进行处理。

③消息读取可靠性

每个分组有一主一备两台服务器,主服务器支持发送和读取消息,备服务器只支持读取消息,当主服务器正常的时候备服务器不对外提供服务,只有备服务器判断主服务器故障的时候才对外提供消息读取服务。

主备服务器的角色和分组信息通过配置指定,通过 ZooKeeper 进行状态判断和决策。主备服务器启动的时候分别连接到 ZooKeeper,在 /MQ/Server/[group] 目录下建立 EPHEMERAL 节点,假设分组名称为 group1,则主服务器节点为 /MQ/Server/group1/master,备服务器的节点为 /MQ/Server/group1/slave。节点的超时时间可以配置,默认为 10 秒。

(2)高性能设计

[此处省略具体设计]

(3)可扩展设计

此处省略具体设计。如果方案不涉及,可以简单写上“无”,表示设计者有考虑但不需要设计;否则如果完全不写的话,方案评审的时候可能会被认为是遗漏了设计点

(4)安全设计

消息队列系统需要提供权限控制功能,权限控制包括两部分:身份识别和队列权限控制。

①身份识别

消息队列系统给业务子系统分配身份标识和接入 key,SDK 首先需要建立连接并进行身份校验,消息队列服务器会中断校验不通过的连接。因此,任何业务子系统如果想接入消息队列系统,都必须首先申请身份标识和接入 key,通过这种方式来防止恶意系统任意接入。

②队列权限

某些队列信息可能比较敏感,只允许部分子系统发送或者读取,消息队列系统将队列权 限保存在配置文件中,当收到发送或者读取消息的请求时,首先需要根据业务子系统的身份标识以及配置的权限信息来判断业务子系统是否有权限,如果没有权限则拒绝服务。

(5)其他设计

[其他设计包括上述以外的其他设计考虑点,例如指定开发语言、符合公司的某些标准等,如果篇幅较长,也可以独立进行描述]

消息队列系统需要接入公司已有的运维平台,通过运维平台发布和部署。

消息队列系统需要输出日志给公司已有的监控平台,通过监控平台监控消息队列系统的健康状态,包括发送消息的数量、发送消息的大小、积压消息的数量等,详细监控指标在后续设计方案中列出。

部署方案

[部署方案主要包括硬件要求、服务器部署方式、组网方式等]

消息队列系统的服务器和数据库服务器采取混布的方式部署,即:一台服务器上,部署同一分组的主服务器和主 MySQL,或者备服务器和备 MySQL。因为消息队列服务器主要是 CPU 密集型,而 MySQL 是磁盘密集型的,所以两者混布互相影响的几率不大。

硬件的基本要求:32 核 48G 内存 512G SSD 硬盘,考虑到消息队列系统动态扩容的需求不高,且对性能要求较高,因此需要使用物理服务器,不采用虚拟机。

5. 架构演进规划

通常情况下,规划和设计的需求比较完善,但如果一次性全部做完,项目周期可能会很长,因此可以采取分阶段实施,即:第一期做什么、第二期做什么,以此类推

整个消息队列系统分三期实现:

第一期:实现消息发送、权限控制功能,预计时间 3 个月。

第二期:实现消息读取功能,预计时间 1 个月。

第三期:实现主备基于 ZooKeeper 切换的功能,预计时间 2 周。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值