如何画时序图:完整教程 | text2diagram

时序图的 5 个核心元素、5 种消息箭头、10 条画法规范,以及怎么用 text2diagram 从一段文字生成 Mermaid 时序图。

发布于 ·13 分钟阅读
sequence-diagramtutorialapi-designmermaid

一、什么是时序图

时序图(sequence diagram)画的是一组参与者随时间来回发消息的过程:谁在什么时候调用谁,得到什么响应 —— 就这些。流程图关心一个过程的形状,时序图关心几个角色之间的对话。

它来自 UML,到今天仍然是 API 集成、认证握手、分布式系统交互,以及任何 跟顺序有关 的场景的标准画法。时间从上往下流;参与者从左往右排,每人一条竖直的 生命线;每条消息是一条带标签的箭头,在某个高度连接两条生命线。

有两点是时序图独有的:

- 每条箭头都有发送方、接收方和内容。 没法含糊地说「然后后端做点事」—— 图逼你把谁发给谁、发的是什么写清楚。 - 纵向位置就是时间。 同一条生命线上,下面那条箭头一定发生在上面那条之后。这是流程图表达不了的东西。

二、为什么要画时序图

3 个理由,每个都对应 API 和分布式系统开发里的一个具体麻烦:

  • 每一步都得指名道姓。 每条箭头必须写清源和目标。模糊的交接 —— 比如「系统发出一个事件」—— 一旦写成 OrderService -->> Kafka: OrderPlaced,就再也含糊不了。
  • 顺序上的假设会暴露。 竞态、漏掉的 ack、悄悄的重试,画到图上就藏不住。同一条生命线上两条箭头之间没有 return?那你可能把一个同步调用当异步画了。
  • 设计评审和 code review 能对上。 一张时序图三分钟就能在会上讲完;等价的 200 行 Python 走一遍要半小时。图成了 review 的锚点 —— 代码对不对,以图为准。

除了这 3 个理由,一张画好的时序图还有 3 个直接好处:

同步还是异步不用再争。 实线箭头是同步,虚线是返回,开口三角是异步。语法本身就把问题解决了,评审时不会再有人问「等等,这个调用会阻塞吗」。

漏掉的失败路径会提前露出来。 没配 return 的箭头、没写 else 的 alt、没有终止条件的 loop,在画面上一眼可见。在图里改比在生产环境改便宜得多。

接入方上手更快。 外部团队读你的 OAuth 或 webhook 文档,看一张时序图比读一堆文字快得多。Stripe、Auth0、Google 的 API 文档都配时序图,就是这个原因。

三、5 个核心元素

时序图的词汇很少。掌握下面 5 个,95% 的时序图你都能读懂:

参与者(Participant)图最上面的方框,代表一个角色 —— 用户、服务、队列、数据库、外部 API。每个参与者下面挂一条竖直的 生命线。名字太长就起别名,图会紧凑很多:participant db as PostgreSQL
生命线(Lifeline)每个参与者下面那条竖虚线。时间顺着它往下走,同一条线上靠下的事件发生得更晚。
消息(Message)两条生命线之间的一条带标签箭头。标签写清楚发的是什么 —— 方法调用、HTTP 请求、队列消息。箭头样式一共 5 种,见第 4 节。
激活条(Activation)生命线上那个窄窄的矩形,表示这个参与者 正在干活。可以用 activate X / deactivate X 显式画,也可以用 + / - 前缀自动生成(A ->> +B 开启,B -->> -A 关闭)。可选,但建议画 —— 谁在同时忙,一眼就看出来了。
注释与片段(Note / Fragment)注释 是挂在一条或几条生命线上的说明(Note right of A: retry on 5xx)。片段 把几条消息包在一个控制流规则里 —— alt / elseoptloopparcriticalbreak。时序图的表达力主要来自片段,见第 7 节。

视觉词汇就这些。任何时序图 —— 从两行的「用户登录」到 50 条消息的 saga 编排 —— 都是这 5 个东西的组合。

四、5 种消息(箭头)类型

Mermaid 支持 5 种箭头,每种意思都不一样。箭头选对了,图就对了八成:

->> 同步请求实线加实心箭头。调用方阻塞等响应。用于 HTTP 请求、RPC、方法调用。大多数时候选它,不确定就用这个。
-->> 返回虚线加实心箭头。上一条同步请求的响应。每个 ->> 都应该配一个 -->>,除非你是故意画一个发完就不管的调用。
-) 异步发送实线加开口箭头。调用方不等结果 —— 消息进队列、事件总线、WebSocket。Kafka publish、WebSocket 推送、SNS 通知都用它。
-> 自调用任意箭头样式,但源和目标是同一个参与者。表示这个参与者内部在处理 —— 服务调用自己的一个方法。渲染成生命线上的一个小回环。少用;自调用一多,通常说明该拆出一个新的参与者了。
x 丢失 / 找到消息实线末端画一个 X(->x 是丢失,x-> 是找到)。表示消息没送达,或者来自未知的源。用来标错误路径、断连、自发事件。进阶用法,一般用不上。

五、Mermaid 时序图语法速通

Mermaid 时序图的语法小到一张明信片就写得下。第一行必须是 sequenceDiagram,后面每行要么声明参与者,要么是一条消息,要么是一个片段标记:

sequenceDiagram
    autonumber
    actor User
    participant Web as Web App
    participant API as API Server
    participant DB as PostgreSQL

    User->>Web: click Login
    Web->>+API: POST /auth (email, password)
    API->>+DB: SELECT user WHERE email=?
    DB-->>-API: user row + password_hash
    API->>API: bcrypt.compare(pw, hash)
    alt password matches
        API-->>-Web: 200 OK + JWT
        Web-->>User: redirect to /dashboard
    else password wrong
        API-->>Web: 401 Unauthorized
        Web-->>User: show error banner
    end
    Note right of API: rate-limit: 5 attempts / min

代码里有 6 个细节值得注意:

- autonumber 会给每条消息前面加上 1. 2. 3.。正文里要引用「第 4 步」的时候少不了它,而且没有代价,建议一直开着。 - actor 用于 (小人图标),participant 用于 系统(方框)。纯视觉区分,但读者会领情。 - as 起别名。显示名太长把生命线撑宽时用它(participant DB as PostgreSQL)。 - 箭头后面的 + / -(比如 ->>+API)会在目标开始处理时自动激活、响应时自动解除。比手写 activate / deactivate 干净。 - alt / else / end 是分支片段,完整的片段列表见第 7 节。 - Note right of X 把注释挂在某条生命线右边,Note over X, Y 可以横跨几条生命线。

六、10 条让时序图易读的规范

下面 10 条来自看过的几百张线上时序图,决定了读者是 30 秒看懂还是 5 分钟看懂:

  • 一张图讲一件事。 每张时序图只回答一个问题:登录怎么走,支付失败怎么处理。发现自己在一张图里画两个场景,拆开。
  • 参与者最多 5-9 个。 再多图就成蜘蛛网了。相关的组件合并(用「数据层」代替 Redis + Postgres + Elasticsearch),或者拆成几张图。
  • 参与者按调用频次从左往右排。 发起调用最多的放最左边,这样横跨整张图的长箭头最少,画面更干净。
  • 每个 ->> 都配一个 -->> 缺 return 箭头是读者困惑的头号原因 —— 调用方是在等,还是继续往下走了?画出来就没有歧义。真的是发完不管才省掉,那种情况用 -)
  • 箭头标签写动作和数据,别只写方法名。 ->> API: POST /login (email, pw) 好过 ->> API: login()。读者想知道发过去的是什么,不是函数叫什么。
  • 打开 autonumber 白送的编号,让每条消息都能在文档、工单、代码注释里被引用(「登录时序第 3 步」)。没有副作用。
  • 真干活才画激活条,纯转发不画。 参与者从收到消息到响应之间有实际工作(查库、打外部接口),画激活条。只是转发的路由或网关,不画。
  • 时间只往下走。 不要画向上的箭头。A 响应 B,就把响应画在 B 那条请求的 下面。向上的箭头看着像时间倒流,谁都读不懂。
  • 每个 alt 都要有 else。 写了 alt success,就该给读者一个 else failure。只有一个分支的 alt 是个信号:真的是可选路径就用 opt
  • 箭头讲 做了什么,注释讲 为什么 一句到位的注释(Note right of API: 5xx 时重试 3 次)能省掉把上下文塞进箭头标签的努力。箭头短,上下文放注释里。

七、进阶:片段(alt / opt / loop / par / critical / break)

片段让时序图能表达分支、循环、并行,又不失去时序图的样子。6 种片段够用了:

alt / elseif / else。 流程按条件分岔时用,各分支互斥,用 end 收尾。可以嵌套,但别超过两层 —— 嵌套一深就没人读得懂了。
opt可选块。 用于「可能发生也可能不发生」,没有 else。常见的有缓存查找、日志这类副作用、可选的重试。
loop重复。 循环体执行 0 到 N 次。终止条件要写进 loop 的标签里(loop until success 好过一个光秃秃的 loop),否则读者会以为它停不下来。
par / and并行。 两条或多条分支同时进行,用于并发的 API 调用、Promise.all、扇出。分支之间用 and 隔开,并发关系在图上一眼可见。
critical / option关键段加备选。 用于「这段必须做完,做不完走备选」。常见于错误处理:先试主路径,不行走缓存,再不行返回错误页。语法比较新,不是所有渲染器都支持,Mermaid 支持。
break提前终止。 表示从更长的序列里跳出去 —— 比如「认证失败就不往下走了」。渲染成一个明显的框。

八、3 个实战示例

看几个真实系统的例子最容易上手。下面 3 个是经典模式,都附 Mermaid 源码 —— 点代码块下面的「在 text2diagram 里试试」可以从一段文字描述重新生成。

示例 1 —— OAuth 2.0 授权码流程

三方交互的经典例子:用户、客户端应用、授权服务器。注意有两次要绕经用户的浏览器 —— OAuth 用文字讲不清楚,就是卡在这里。

sequenceDiagram
    autonumber
    actor U as User
    participant C as Client App
    participant AS as Auth Server
    participant RS as Resource API

    U->>C: click "Log in with X"
    C->>U: 302 redirect to AS/authorize
    U->>AS: GET /authorize (client_id, scope)
    AS-->>U: login form
    U->>AS: submit credentials + consent
    AS->>U: 302 redirect back with code
    U->>C: GET /callback?code=abc
    C->>+AS: POST /token (code, client_secret)
    AS-->>-C: access_token + refresh_token
    C->>+RS: GET /user (Bearer access_token)
    RS-->>-C: user profile JSON
    C-->>U: render dashboard

示例 2 —— 分布式 saga 与补偿

saga 模式让微服务在没有 2PC 协调者的情况下完成一个事务:每一步发一个事件,每次失败触发一个补偿动作。事件的先后顺序就是这个模式的全部,时序图在这里最合适。

sequenceDiagram
    autonumber
    participant O as OrderService
    participant P as PaymentService
    participant I as InventoryService
    participant B as EventBus

    O->>+B: publish OrderCreated
    B-)P: OrderCreated
    P->>P: charge card
    alt charge succeeds
        P-)B: publish PaymentCharged
        B-)I: PaymentCharged
        I->>I: reserve stock
        alt stock available
            I-)B: publish StockReserved
            B-)O: StockReserved
            O->>-O: mark order Confirmed
        else stock unavailable
            I-)B: publish StockFailed
            B-)P: StockFailed
            P->>P: refund card (compensation)
            P-)B: publish PaymentRefunded
            B-)O: PaymentRefunded
            O->>O: mark order Cancelled
        end
    else charge fails
        P-)B: publish PaymentFailed
        B-)O: PaymentFailed
        O->>O: mark order Cancelled
    end

示例 3 —— WebSocket 聊天 + 已读回执

实时通信是异步消息(-))真正派上用场的地方。客户端 A 发消息,服务器落库,服务器推给客户端 B,B 回一个已读回执。没有一次调用是阻塞的,每条箭头都是打开的 socket 上发完就走的消息。

sequenceDiagram
    autonumber
    participant A as Client A
    participant S as Server
    participant DB as MessageDB
    participant B as Client B

    A-)S: WS send: hello
    S->>+DB: INSERT message (from=A, to=B)
    DB-->>-S: message_id
    S-)B: WS push: new message
    B->>B: render + auto-mark-read
    B-)S: WS ack: read_receipt(message_id)
    S->>+DB: UPDATE message SET status=read
    DB-->>-S: ok
    S-)A: WS push: read_receipt

九、常见错误

看过几百次 code review 之后,反复出现的就这 6 种:

  • 画成了戴帽子的流程图。 你的「时序图」里出现了菱形判断和绕回自己的分支 —— 那你想画的其实是流程图。时序图画的是参与者之间的交互,不是某一个参与者内部的控制流。
  • 漏掉 return 箭头。 ->> API: query 后面没有 -->> Caller: result,读者不知道调用方是在等、超时了,还是继续往下走了。补一行 return 就没有歧义了。
  • alt 没有 else。 alt success ... end 不画失败分支,等于在说「失败了什么都不做」,而线上几乎不可能这样。真的可选就用 opt,否则把 else 写出来。
  • 参与者太多。 顶上排 10 个参与者,潜在的箭头就有 50 条。超过 9 个就合并(用「数据层」合掉 Redis + Postgres),或者拆两张(一张正常路径,一张错误路径)。
  • 向上的箭头。 永远不要把响应画在触发它的请求 上面。时间往下流,向上的箭头看着像时间倒流,会毁掉读者刚建立起来的理解。
  • 异步消息画成了实线。 ->> 隐含着调用方在阻塞等待。实际是发完就不管(Kafka publish、WebSocket 推送、后台任务入队),就用 -) —— 以后排查竞态问题的人会感谢你。

十、用 text2diagram 画时序图

语法掌握之后手写也不难,但 text2diagram 存在的意义,是把「用一句话描述一次 API 交互 → 拿到 Mermaid 时序图」压成一步。背后做了这几件事:

- 自动判断图型 —— 描述里出现「然后」「发送」「响应」「调用」这类词就走时序图,不用特意说「帮我画时序图」。 - 语义校验 在出图前把第 6 节那 10 条规范过一遍(漏 return、alt 没 else、向上的箭头、参与者超过 9 个)。 - 自动重试 治 Mermaid 语法错误 —— 模型生成了画不出来的图,就把解析器的报错回灌给模型重来一次。线上语法错误率低于 1%。 - 对话模式 遇到说不清的地方会先问 1-3 个问题。时序图常问的是:谁发起这次调用?这次调用同步还是异步?失败了走哪条路?

两种模式:

  • 快速模式 —— 一句话出图。适合描述已经足够精确的场景:参与者 5 个以内、顺序清楚、没有分支。
  • 对话模式 —— 出图前先问 1-3 个问题。适合 OAuth 这种复杂度的交互、saga 模式、任何带错误分支的场景。AI 问的问题经常会挖出你自己都没意识到的假设。

十一、可复用的 Prompt 模板

交互一复杂,就别用一整段自由发挥的文字,用下面这个模板:

Sequence diagram for [scenario name].

Participants:
- [name] ([role], e.g. user / service / queue / db])
- [name] ([role])
- ...

Happy path:
1. [source] -> [target]: [message + payload]
2. [source] -> [target]: [message]
3. ...

Failure paths:
- If [condition]: [alternative sequence]
- If [condition]: [alternative sequence]

Async / concurrent:
- [message A] and [message B] happen in parallel
- [message C] is fire-and-forget (no return expected)

Notes / constraints:
- [participant]: [rate limit / retry policy / timeout]

把方括号里的占位填掉,粘进 text2diagram(参与者超过 6 个建议用对话模式),你会拿到一张一次就满足第 6 节全部 10 条规范的图,或者一张问你歧义的确认卡。

十二、小结

顺序敏感、多个参与者来回传消息 的场景就是时序图的主场:API 集成、认证握手、分布式系统的流转、WebSocket 和流式模式,以及任何你发现自己一直在说「然后、然后、然后」的时候。

掌握 5 个核心元素(第 3 节),挑对箭头(第 4 节),控制住参与者数量(第 6 节第 2 条),别忘了画失败路径(第 9 节第 3 条)。剩下的都是打磨。

比白板草图复杂的场景,交给 text2diagram 的对话模式去问 —— 那几个问题本身,经常在一条箭头都还没画的时候就改进了设计。

常见问题

接着读

去试试 text2diagram

打开工具
← 回到全部教程