2、单屏架构的黄金时代:单体应用与单一数据库的甜蜜与苦涩

说实话,每次回想起那个年代,我心里都有点复杂。

那时候我刚入行,接手的是一个典型的单体应用。一个项目,一个代码仓库,一个数据库。前端、后端、业务逻辑全揉在一起。部署?一台服务器搞定。开发?一个IDE打开,从头写到尾。

听起来是不是很爽?

嗯,确实爽过。但后来也苦过。

2.1 什么是单体应用?

单体应用,说白了就是把所有功能都塞进一个进程里。用户管理、订单处理、支付逻辑、报表生成……全在一个包里。

我习惯把它比作一个“大箱子”。所有东西都往里扔,找的时候翻一翻就行。早期项目,这招特别好使。

单体应用的核心特征:

  • 所有代码在一个项目里编译、打包
  • 部署时只产出一个 WAR/JAR 包
  • 所有模块共享同一个数据库实例
  • 扩展时只能整体水平复制

举个例子,一个典型的电商单体应用,结构大概是这样:

my-shop/
├── src/
│   ├── main/
│   │   ├── java/com/shop/
│   │   │   ├── controller/    # 控制器层
│   │   │   ├── service/       # 业务逻辑层
│   │   │   ├── dao/           # 数据访问层
│   │   │   └── model/         # 实体模型
│   │   └── resources/
│   │       ├── application.yml
│   │       └── mapper/        # MyBatis映射
│   └── webapp/
│       ├── WEB-INF/
│       └── static/            # JS、CSS、图片
└── pom.xml                    # 一个pom管理所有依赖

你看,所有东西都在一个目录树里。开发时爽,但维护时……嗯,后面再说。

2.2 甜蜜期:为什么大家都爱它?

我记得2015年那会儿,我带一个5人团队做内部管理系统。从立项到上线,只用了三周。为什么这么快?因为单体架构太适合小团队了。

开发效率高

你想想看,一个项目,一个IDE窗口。改个接口,调个页面,全在同一个代码库里。不需要跨服务联调,不需要考虑网络延迟。本地启动,直接调试。

部署简单

打个包,扔到Tomcat的webapps目录下,重启完事。那时候我经常跟运维说:“就一个war包,你看着办。”运维也乐得清闲。

测试成本低

写个集成测试,从Controller到数据库,一条链路跑通。不需要Mock服务,不需要搭建复杂的测试环境。我习惯在测试类里直接注入所有Bean,跑起来特别快。

事务管理方便

这一点太重要了。一个@Transactional注解,整个方法都在一个事务里。扣库存、减余额、生成订单,要么全成功,要么全回滚。我在项目中遇到过好几次因为分布式事务搞不定的情况,那时候特别怀念单体应用的一把锁搞定一切。

个人经验: 如果你的团队不超过10人,业务复杂度不高,用户量在几千级别,单体应用绝对是最优解。别为了“架构先进”而过度设计。

2.3 苦涩期:当甜蜜变成负担

但是,好景不长。

项目上线半年后,用户量从几百涨到了几万。功能从十几个模块膨胀到了五十多个。这时候,问题开始冒出来了。

2.3.1 代码耦合:改一处,崩一片

这是最让人头疼的。我记得有一次,一个同事改了一个工具类的静态方法,结果影响了三个业务模块的订单生成逻辑。线上出了故障,排查了整整一个下午。

为什么会这样?因为所有模块都在同一个进程里,类加载器共享同一个命名空间。你改了一个公共方法,所有调用方都得跟着测一遍。

避坑指南: 我曾经见过一个项目,单体应用的代码量超过50万行。每次发布光编译就要20分钟。更可怕的是,没人敢重构——因为不知道改了哪里会出问题。

2.3.2 数据库瓶颈:单库扛不住了

单一数据库,说白了就是所有业务都挤在一个库里。用户表、订单表、商品表、日志表……全在一个实例上。

一开始还好,数据量上来了,慢查询就多了。我印象最深的一次,是报表模块跑了一个全表扫描,直接把数据库CPU打满,导致用户登录都超时了。

常见问题:

  • 连接池争抢:报表查询占着连接不放,用户请求排队等待
  • 锁竞争:一个长事务锁住了某张表,其他操作全部阻塞
  • 备份困难:单库数据量太大,备份时间窗口不够用

2.3.3 扩展性差:只能垂直扩展

用户量上来了怎么办?加机器。但单体应用只能整体复制。你想想看,如果只是订单模块压力大,你也得把整个应用复制一份。这就导致资源浪费严重。

我习惯把这种扩展方式叫做“大水漫灌”——不管哪里缺水,先灌满再说。成本高,效率低。

2.3.4 发布风险高

这个我深有体会。有一次,我只是改了一个支付页面的文案,但因为和订单模块在同一个包里,不得不把整个应用重新发布。结果发布过程中,订单模块的一个缓存问题导致线上短暂不可用。

说白了,单体应用的发布是“全有或全无”。改一行代码,也得重启整个进程。这在早期还能忍,到了后期,每周发布一次都提心吊胆。

2.4 什么时候该告别单体?

这个问题没有标准答案。但我个人有几个判断指标:

指标 警戒线 说明
代码行数 超过20万行 编译时间超过5分钟,开发效率明显下降
团队人数 超过15人 代码冲突频繁,沟通成本剧增
数据库QPS 超过5000 单库开始出现性能瓶颈
发布频率 每周超过2次 每次发布都是全量发布,风险高

当然,这些数字不是绝对的。我见过一个项目,代码量30万行,但团队配合默契,依然跑得挺好。也见过一个项目,代码量才5万行,但因为设计混乱,已经寸步难行。

核心观点: 单体架构本身不是问题,问题在于它是否还适合你当前的业务规模和团队规模。不要为了“微服务”而微服务,也不要死守着单体不放。

2.5 小结

单体应用与单一数据库,是软件架构的起点,也是很多人的初恋。它简单、直接、高效,适合小团队、小项目、早期阶段。

但它的甜蜜是有代价的。当业务复杂到一定程度,代码耦合、数据库瓶颈、扩展困难这些问题就会像滚雪球一样越滚越大。

嗯,下一章我们会聊聊,当单体扛不住的时候,我们是怎么一步步走向多屏架构的。那又是一段新的故事了。