让一个团队画出他们的微服务,你经常会得到同一张图:三十个方框,每个都和其他所有方框相连,箭头上没有标签。它很准确,却回答不了任何人关心的问题。工程师们管这叫“死星图”。
一张有用的微服务架构图,是一系列关于“展示什么”的决定。本文以一个有五个服务的在线商店为例,讲解让图保持可读的七条规则。无论你有五个服务还是五十个,这些规则都同样适用。
图需要回答哪些问题
动手画之前,先把问题写下来。一张好的微服务图能让人一眼看出这五个问题的答案:
- 请求从哪里进入? 客户端、API 网关、BFF(Backends for Frontends)。
- 哪个服务拥有哪些数据? 每个数据库都应该有且只有一个所有者。
- 谁同步调用谁? 这些调用会共担延迟和故障。
- 哪些事情异步发生? 事件和队列让服务在时间上解耦。
- 哪些不在我们的控制范围内? 支付服务商、邮件服务、合作伙伴的 API。
如果一个方框或一条箭头无助于回答其中任何一个问题,它多半应该放到另一张图上。
示例:一个有五个服务的在线商店
这家商店有 Web 和移动客户端、一个 API 网关和五个服务:
- Catalog 从自己的 PostgreSQL 数据库提供商品数据。
- Cart 把购物车保存在 Redis 中。
- Orders 在自己的 PostgreSQL 数据库中创建订单,并向 Kafka 中的
orders主题发布OrderPlaced。 - Payments 消费
OrderPlaced,通过 Stripe 扣款,保存结果,并向payments主题发布PaymentSucceeded或PaymentFailed。 - Notifications 消费
payments主题,通过邮件服务商发送收据,或者发送支付失败的通知。
Orders 也消费 payments 主题,以便把订单标记为已支付。没有任何服务直接调用 Payments,结账流程是一条事件链。这个设计选择,正是图必须让人看见的:
图表内容
- Web and mobile clients
- API gateway
- Catalog
- Cart
- Orders
- Catalog DB
- Cart store
- orders topic
- Orders DB
- Payments
- Stripe
- Payments DB
- payments topic
- Notifications
- Email provider
- HTTPS
- GET /products
- /cart
- POST /orders
- SQL
- RESP
- OrderPlaced
- group: payments
- PaymentSucceeded / PaymentFailed
- group: orders
- group: notifications
- SMTP
从左往右读:网关是唯一的入口,实线箭头是同步调用,虚线箭头是经过具名主题的事件,每个数据库都只挂在一个服务下面。
让微服务图保持可读的七条规则
1. 从入口画起
先在左侧画出客户端和 API 网关。它们确定了阅读方向,也表明到底有哪些服务对外暴露。从网关出发没有路径可达的服务就是内部服务,这一点值得让人看见。
2. 把每个数据库连到它的所有者
把每个数据存储放在拥有它的那个服务旁边,并且只连接这个服务。如果目前有两个服务共用一个数据库,也照实画出来。这是一个众所周知的坏味道,把它藏起来的图会让下一次迁移更难规划。
3. 区分同步调用和事件
使用两种线型,并在图例中说明:实线表示请求和响应(REST、gRPC),虚线表示经过消息代理的消息。把主题或队列画成节点。如果只画一个“事件总线”方框、把所有服务都连上去,就看不出谁生产了什么、谁消费了什么。
4. 给每条连线标上流经的内容
GET /products、POST /orders、OrderPlaced、PaymentFailed。标签把箭头变成契约,也是大家在处理故障时会在图上搜索的东西。
5. 按领域分组,而不是按技术
如果需要框,就按业务领域或负责团队给服务分组:购物、结账、客户沟通。“Node.js 服务”或“Java 服务”这样的框,回答的是一个没人会问的问题。
6. 把基础设施放到单独的视图里
Pod、sidecar、服务网格和仪表盘都很重要,但不该出现在逻辑视图里。把部署画在另一张图上,比如一张 Kubernetes 架构图,再把两张图互相链接起来。
7. 展示一条关键流程
挑出撑起营收的那条流程,这里是结账,让它容易跟读:在连线上给步骤编号,或者突出它的路径。一张图只放一条流程;第二条流程就单独复制一份视图来画。
系统大到一张图放不下时
超过二十个左右的方框,就该拆分了。一种行之有效的做法:
- 一张全景图,以领域作为框,只画出领域之间最重要的连接。
- 每个领域一张图,包含该领域的服务、数据存储和主题,并按上面的规则来画。
- 流程图,只为少数几个关键旅程而画,比如结账或注册。
这与 C4 模型 能很好地对应:全景图接近系统上下文视图,每张领域图则是一张容器视图,其中服务及其数据库都是容器。
常见错误
- 箭头没有标签。 读者只能猜,而且每个人猜得都不一样。
- 网关直接连到数据库。 如果它真的读取数据库,那是一个需要讨论的设计决策,而不是画图时可以走的捷径。
- 混用视图。 把 Pod、AWS 服务和业务能力放在同一张画布上,结果谁也看不懂其中任何一部分。
- 隐藏消息代理。 Kafka 不是一根管道;主题有名称、所有者和消费者。
- 一张从不更新的快照。 上一次架构重构时画的图只是一份历史文档。要么标上日期,要么让它保持实时更新。
在 Archboard 中构建
Archboard 为这张图的每个部分都提供了节点包,所以服务、存储和主题都是带端口的类型化节点,而不是长得都差不多的方框:
- 使用 Google 或 GitHub 登录,并新建一张图。
- 从基础节点包中添加一个 方框 表示客户端,再从云节点包中添加一个 API 网关。
- 用 C4 模型节点包中的 容器 节点添加各个服务,并在“技术”字段中填写语言或框架。
- 从数据节点包中添加 Postgres 和 Redis,放在各自的所有者旁边,再为
orders和payments添加 主题 节点。 - 从 C4 模型节点包中添加 外部系统 节点,分别表示 Stripe 和邮件服务商。
- 连接端口。同步调用保持实线,把事件改为虚线,然后给每条连线加上标签。
- 把实时链接分享给团队;如果使用 Pro 方案,也可以把它嵌入你的文档。
上面的示例有 15 个图表节点,在 Free 方案的限制之内。相关示例可以打开 /examples/kubernetes-microservices 查看部署层面,打开 /examples/api-gateway-infographic 查看网关模式的信息图。
常见问题
一张图应该展示多少个服务? 以不用缩放就能看清为限,通常最多二十个左右的方框。超过这个数量,就按领域拆分,并在上层保留一张全景视图。
微服务应该用 C4 模型吗? 很合适。服务及其数据库在容器视图中都是容器。有些团队会把每个独立归属的服务建模为单独的软件系统;选定一种约定,并把它写下来。
Kubernetes、服务网格和监控该怎么表示? 画在单独的部署图上。逻辑视图回答谁和谁通信;部署视图回答它运行在哪里。
消费者是主动拉取的,那事件箭头是不是应该从消费者指向主题? 按数据流动的方向画:从生产者到主题,再到消费者。如果这一点对讨论有影响,再注明消费者是拉取模式。
怎样让图保持最新? 给图指定一个负责人,在包含改动的同一个 pull request 中更新它,并在文档中链接实时的图,而不是粘贴截图。