我参加过不少架构评审会,最常出现的场面倒不是方案本身有硬伤,而是两个人在讨论里各说各话,争了半天才发现,一个人嘴里的“同步”指的是调用方式,另一个人说的“同步”指的是线程模型。这类概念混淆在系统架构设计里几乎天天发生,而且越是核心的术语,越容易被混着用。今天这篇串讲,就是把我在实际项目、面试、评审里反复遇到的高频易混知识点整理出来,一条一条掰开讲清楚。内容适合正在做架构设计的开发、准备晋升答辩或架构师面试的同学,也适合带团队时想统一技术语言的管理者。
1. 同步异步、阻塞非阻塞:先分清两个维度,再谈四种组合
1.1 这两个概念为什么会缠在一起
我第一次把这两个概念彻底搞清楚,是在排查一个接口超时问题时。当时服务里有一段代码用了异步HTTP客户端,但调用方在后面马上调用了get()等待结果。同事说“这是异步的,不应该阻塞”,另一个同事说“它明明阻塞了线程”。两个人说的都对,但聊不到一块去。
问题出在哪?同步/异步和阻塞/非阻塞实际上是两个独立的维度:
- 同步与异步,回答的是“调用方怎么拿到结果”。同步是调用方主动等待结果返回;异步是调用方不等待结果,结果通过回调、事件、Future通知等机制后续获取。
- 阻塞与非阻塞,回答的是“调用方在等待期间能不能干别的事”。阻塞是当前线程被挂起,直到条件满足;非阻塞是线程可以继续执行其他任务,稍后再来看结果是否就绪。
打个比方。你点了一份外卖,同步就是你一直站在门口等外卖员敲门;异步就是你该干嘛干嘛,外卖到了手机收到通知再去拿。而阻塞就是你站在门口等的时候什么事也做不了,非阻塞就是你一边看电视一边等,隔一会儿看一眼门口有没有人。你看,这两组问题是完全不同的。
1.2 四种组合在架构里的真实场景
把这两个维度组合起来,代码里的行为完全不同:
| 组合维度 | 典型形态 | 架构场景 |
|---|---|---|
| 同步阻塞 | 普通RPC调用、JDBC查询 | 最常见的请求-响应模型 |
| 同步非阻塞 | 轮询Future.isDone()、IO多路复用 |
Netty的Reactor模型、select/poll/epoll |
| 异步阻塞 | async方法里立刻调用get() |
伪异步,白写异步代码 |
| 异步非阻塞 | 消息队列、事件回调、响应式编程 | MQ解耦、Spring WebFlux、Vert.x |
最容易踩坑的是最后两种。很多人以为用了CompletableFuture就是异步非阻塞了,结果在回调链里做了一步计算,计算本身没问题,但外层有人对返回的CompletableFuture直接join()等待,整个调用就又变回了同步阻塞。更常见的是“异步阻塞”:业务代码里写好async方法,调用方为了拿结果立刻.get(),等得死死的,线程池被占满,接口整体RT飙升。
我自己的经验是,在架构评审里遇到争论,先别急着争方案,先问一句:我们讨论的是调用维度,还是线程模型维度? 只要把这个问题对齐,一半的争论会自然消失。比如讨论异步化改造,真正要评估的不是“换了异步API”,而是线程模型是否变化、是否有背压机制、失败后的补偿链路是否设计完整。这些才是异步架构里的核心问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 水平扩展与垂直扩展:加机器之前,先确认瓶颈能不能被分布式解决
2.1 两种扩法的核心差异
“系统扛不住了,加机器”这句话我听多了。但加机器分两种加法,本质逻辑完全不同。
垂直扩展是给单台机器加CPU、加内存、加磁盘。好处是业务代码基本不用改,数据库连接数不用变,事务、锁、本地缓存都还是原来的语义。坏处是有物理上限,一台机器加到一定程度再往上花钱性价比极低,而且仍然存在单点风险。
水平扩展是增加节点数量,把流量和数据分散到多台机器上。好处是理论上可以无限扩展,坏处是架构复杂度非线性上升。很多人把“水平扩展”和“分布式部署”划等号,这是个误区。水平扩展的前提是应用无状态和数据可分片,否则你只是把同一个瓶颈多复制了几份,流量一来照样全挂。
2.2 无状态化是水平扩展的第一关
要做水平扩展,先要回答一个问题:状态存在哪里?
传统单体应用里,用户登录后session存在本地内存,定时任务在每台机器上都可能触发,文件上传到本地磁盘,本地缓存里堆着高频数据。这些在单机时代都不是问题,一旦要水平扩展,全部变成问题:session不一致、任务重复执行、文件访问不到、缓存各自为政。
所以无状态化的关键动作通常是:
- session外置到Redis或独立会话服务
- 本地缓存替换为分布式缓存
- 定时任务加分布式锁,保证同一时刻只有一个节点
