单体架构图解——一个可部署单元,一个数据库
单体架构的工作原理,在一张交互式画布上讲清楚:一个可部署应用里的 UI、业务逻辑与数据访问模块,一个共享数据库,以及由此产生的扩容天花板。
单体是作为一个单元构建并部署的应用:UI、业务逻辑和数据访问都跑在同一个进程里,面对同一个共享数据库,而扩容意味着把整个东西复制一份。
单体架构图解——一个可部署单元,一个数据库
The interactive FlowJam canvas for this explanation — every lane, row and arrow above is a real QueryChart diagram you can open and edit.
How to read this visual
- 跟着请求从客户端进来,往下穿过三个模块,进入共享数据库,再出去——一个进程里的一次往返。
- 这些模块之所以画在“单体应用”这条带里面,是因为它们共用一个进程、一次部署;这种包含关系就是单体的定义。
- 最后一个方框是结论:扩容的路径在旁边,做的是把整个应用复制一份。
一个进程里的请求路径
“客户端发出请求”进入“请求进入这个唯一的可部署应用”,接着是“UI 模块负责路由与渲染”、“业务逻辑模块处理请求”和“数据访问模块查询数据库”——全都在唯一的那条“单体应用”泳道里。三个模块画成上下叠放,是为了表明它们共用内存、共用进程、共用一次部署:它们当中没有任何一个可以单独部署。
共享数据库
“一个共享数据库存放全部数据”是单体的第二个定义性特征。所有模块读写同一套表结构,关联查询和事务因此变得直截了当——这正是单体在早期特别高产的原因。“数据库”分组画在应用条带之外,因为它是一个独立的进程,但它是共享的:耦合就住在每个模块都依赖的那套表结构里。
扩容天花板
“响应沿着同样的模块返回”完成了这次往返,而“扩容意味着复制整个应用”就是判决。一个繁忙的功能逼得整个代码库和它的连接池被搬到另一台服务器上,把容量浪费在那些并不繁忙的部分上。最终促使团队拆成微服务的,正是这种低效,而不是任何技术上的失败。
Key relationships and takeaways
- 所有模块共用一个进程、一个代码库、一次部署——这种包含关系就是定义本身。
- 共享数据库把每个模块都耦合到同一套表结构上。
- 请求在一次往返中穿过每一个模块——中间没有独立的服务调用。
- 扩容的粒度很粗:不管哪个功能繁忙,被复制的都是整个应用。
- 单体的开发速度是真实的;它拿来交换的,正是那道扩容天花板。
When to use this visual
- 解释一个团队的应用为什么开发起来容易,扩容起来却很难。
- 讲清楚那个让微服务变得可理解的对比——只有对着单体的天花板,拆分才说得通。
- 在规划如何拆解之前,先把遗留系统的结构记录下来。
运作方式
把模块改成你代码库里的名字
把 UI、业务逻辑和数据访问换成你实际的分层与模块,再补上这三个通用模块没覆盖到的。
标出真实的扩容瓶颈
在最后一个方框上注明真正逼着你扩容的那个功能或那条查询,以及复制整个应用所浪费掉的容量。
画出可抽取的候选模块
在最有希望变成一个服务的模块外面加一圈虚线边界,并注明要切断哪些耦合。
连到微服务这个替代方案
标出候选模块之后,接到微服务那张图解,把目标架构并排比较。
常见问题
什么是单体架构?
单体是这样一种应用:它的 UI、业务逻辑和数据访问被当作一个单元一起构建、部署和扩容——一个代码库产出一个可部署制品,运行在一个共享数据库之上。图上把请求画成在同一条应用泳道内穿过全部三个模块,因为这种包含关系就是定义。
团队为什么大多从单体开始?
因为对大多数团队、对一个应用生命周期的大部分时间来说,单体是最快能交付的方式。模块之间没有网络,共享的表结构让关联查询和事务都很简单,部署也只有一个制品。图上那个共享数据库方框把这一点直接说了出来——只有当独立扩容或团队自治成为约束瓶颈时,这笔交换才开始变得不划算。
单体最主要的问题是什么?
扩容天花板和耦合。扩容意味着复制整个应用,所以一个繁忙的功能会在其余所有部分上浪费容量;而每一次改动都要碰一个所有人都依赖的代码库和表结构。随着团队和代码库变大,这两个效应会拖慢部署、逼出大量协调——团队正是在这种条件下开始抽取服务的。
单体怎么变成微服务?
循序渐进,一次抽取一项能力。第一步是找出一个能独立存在的模块——它有自己边界清晰的数据——然后给它自己的数据库、一个 API,最终让它独立部署。图解里那个画出可抽取候选模块的步骤演示的正是这件事:标出模块,切断耦合,然后拆分。常见的错误是一次性重写整个单体,迁移就是这样失败的。
在 QueryChart(FlowJam)中编辑这张图解
把上面这张单体画布原样打开成你自己的图表,把模块改成你代码库里的名字,画出你的扩容瓶颈。