每次在文档里画架构图时,你是不是也经历过这样的痛苦?在绘图工具里拖拽半天调整箭头位置,好不容易对齐了所有元素,产品经理突然要求改个字段名称,又得从头再来。上周我就因为一个简单的微服务调用图反复修改了7次版本,浪费了整整一个下午。
其实有个更高效的方法——用代码画图。GraphViz的DOT语言能让你像写Markdown一样轻松生成专业图表。我们团队最近把所有技术文档中的图表都改成了DOT脚本,现在修改架构图就像改代码一样简单,还能用Git做版本管理。
传统绘图工具最大的问题是难以维护。当你在Visio或Draw.io中绘制复杂架构图时,每次修改都可能引发连锁反应——移动一个组件会导致连线错位,调整样式需要逐个元素操作。而DOT语言通过声明式语法描述图形关系,修改时只需调整几行代码。
这些场景特别适合用DOT替代手动绘图:
dot复制digraph 微服务架构 {
rankdir=LR
node [shape=box, style="rounded"]
gateway -> auth_service [label="JWT验证"]
gateway -> order_service [label="负载均衡"]
order_service -> payment_service [label="异步消息"]
order_service -> inventory_service [label="gRPC"]
}
上图的DOT脚本仅用6行代码就定义了一个完整的微服务调用关系,调整服务名称或连线类型只需修改对应参数。相比之下,手动绘制可能需要20分钟以上。
DOT语言的核心只有三种元素:图类型、节点和边。下面这个对照表帮你快速掌握基础语法:
| 元素类型 | 语法示例 | 关键说明 |
|---|---|---|
| 无向图 | graph { a -- b -- c } |
用graph声明,--表示连接 |
| 有向图 | digraph { a -> b -> c } |
用digraph声明,->表示方向 |
| 节点属性 | node [shape=circle] |
可统一设置或单独定义 |
| 边属性 | edge [color=red] |
控制连线样式 |
实际案例:我们需要绘制一个数据库ER图,包含用户、订单两个实体和一对多关系:
dot复制digraph ER图 {
node [shape=record]
用户 [label="{用户|id : INT\name : VARCHAR}"]
订单 [label="{订单|order_id : INT\user_id : FOREIGN KEY}"]
用户 -> 订单 [label="1:n", arrowhead=crow]
}
这个脚本中:
shape=record让节点显示为数据库表结构label定义表字段,用\n分隔不同行arrowhead=crow表示一对多关系符号用DOT描述部署流程比画流程图高效得多。下面是一个典型的GitOps流程:
dot复制digraph pipeline {
rankdir=TB
node [shape=cylinder]
代码提交 -> 代码扫描 -> 镜像构建 -> 部署测试环境 -> 安全审计 -> 生产发布
代码扫描 -> 失败处理 [label="未通过", color=red]
安全审计 -> 失败处理
失败处理 -> 代码提交 [label="修复后重新提交", style=dashed]
}
实用技巧:
rankdir=TB让图形从上到下排列(Top to Bottom)color=red突出异常路径style=dashed表示非主线流程对于分布式系统,子图(subgraph)功能可以清晰划分模块边界:
dot复制digraph 电商系统 {
compound=true
node [shape=box3d]
subgraph cluster_frontend {
label="前端服务"
网页端 -> 移动端 [style=invis]
}
subgraph cluster_backend {
label="后端服务"
商品服务 -> 订单服务 -> 支付服务
}
网页端 -> 商品服务 [ltail=cluster_frontend, lhead=cluster_backend]
}
关键参数说明:
compound=true允许集群间连线ltail/lhead指定连线起止于子图边界style=invis创建不可见边用于布局用DOT绘制状态机比UML工具更简洁:
dot复制digraph 订单状态 {
node [shape=circle, width=1.2]
待支付 -> 已支付 [label="支付成功"]
待支付 -> 已取消 [label="超时未支付"]
已支付 -> 配送中 [label="仓库发货"]
配送中 -> 已完成 [label="用户签收"]
配送中 -> 退货中 [label="申请退货", color=red]
已支付 [shape=doublecircle]
已完成 [shape=doublecircle]
}
专业提示:
shape=doublecircle表示终止状态width=1.2统一调整节点大小在VS Code中安装GraphViz插件后,可以直接在Markdown中嵌入DOT代码块:
markdown复制```dot
digraph G {
A -> B
}
```
保存时会自动生成图片并插入文档。更专业的做法是使用PlantUML扩展,支持实时预览。
将DOT脚本与文档工具链集成,实现图表自动化更新:
bash复制# 示例:用Makefile自动生成所有图表
DIAGRAMS := $(wildcard diagrams/*.dot)
PNGS := $(patsubst %.dot,%.png,$(DIAGRAMS))
all: $(PNGS)
%.png: %.dot
dot -Tpng $< -o $@
这个Makefile规则会监控diagrams/目录下的DOT文件变化,自动生成对应PNG图片。结合文档生成工具(如Sphinx、Docusaurus),可以实现「修改代码 -> 更新文档 -> 重新部署」的全自动化流程。
GraphViz提供多种布局引擎,应对不同场景:
| 引擎 | 适用场景 | 调用方式 |
|---|---|---|
| dot | 分层有向图(默认) | dot -Tpng file.dot |
| neato | 弹簧模型布局 | neato -Tsvg file.dot |
| circo | 环形布局 | circo -Tpdf file.dot |
| fdp | 无向图力导向 | fdp -Tpng file.dot |
案例:当dot生成的层级图过于复杂时,改用neato可能获得更好效果:
bash复制neato -Goverlap=false -Tpng network.dot -o network.png
连线交叉过多:
dot复制digraph {
graph [splines=polyline] # 改为折线连接
A -> B -> C -> D
A -> C
B -> D
}
节点重叠:
dot复制digraph {
graph [overlap=false]
node [margin=0.3]
A -> B -> C -> A
}
中文乱码:
dot复制digraph {
node [fontname="SimSun"]
edge [fontname="SimSun"]
节点1 [label="中文测试"]
节点1 -> 节点2 [label="关系"]
}
建议将完整字体路径写入环境变量:
bash复制export GV_FONT_PATH=/usr/share/fonts/truetype/simsun.ttc