欢迎打开:看图读懂微服务(60张图把5个核心概念讲明白)
这是一份通识科普,不是代码教程。我们一行代码都不写,只讲一件事:为什么要这样设计。
因为读懂"为什么",比记住代码怎么写重要100倍。
我们会沿着一条主线走:从单体架构 出发,一步步拆分服务 ,做服务发现 ,升级到gRPC ,最后加上API网关。整段路讲完,你就能建立起对微服务的整体直觉。
准备好了吗?我们从第一张图开始。
这套内容适合谁?
门槛其实很低。你只要知道 HTTP 是什么,听过 JSON,平时用过 App 或网站就够了。
你完全不需要会写 Go 代码,不用装过 Docker,也不用看过 Kubernetes。这些可以统统划掉。
我们的目标读者是那种听过"微服务、gRPC、服务发现、网关"这些词,但讲不清它们到底怎么协作的人。如果你想快速建立微服务架构的整体直觉,而不是深陷某个框架的细节,那你就是目标读者。
记住:读懂"为什么这样设计",比看懂代码重要得多。
我们用一个最贴近生活的例子贯穿全文:电商下单。
整个流程其实就三步:
- 查用户信息:确认是不是合法用户。
- 扣库存:看这件商品还有没有货。
- 创建订单:生成订单号,保存下来。下单成功。
看似简单的三步,背后却藏着微服务所有的核心问题。请记住这句话:当这三部分别由三个独立程序完成时,它们之间怎么协作?
带着这个问题,我们开始一段从单体到微服务的演化之旅。
全文地图:五章解决五个问题
在正式开始前,先看这张全文地图。五章解决五个问题:
- 业务刚起步怎么写最简单? → 单体架构
- 服务拆开了,互相怎么找到对方? → 服务拆分
- 服务越来越多,谁管理它们的地址? → 服务发现
- 服务间通信用什么协议最高效? → gRPC
- 前端调不动内部协议怎么办? → API网关
这里有一个重要规律:每一章都遵循同一个节奏:遇到痛点 → 引入方案 → 解决痛点 → 然后出现新的痛点。
这不是巧合,而是技术演化的普遍规律。理解了这个节奏,你就掌握了判断任何新技术该不该用的思维框架。
第一章 单体架构:所有东西住一栋楼里
这一章要回答的核心问题是:业务刚起步怎么写最简单?
答案是把所有功能塞进一个程序里。这就是"单体"。
我们会用一个生活场景让你秒懂,为什么单体是几乎所有项目的起点;也会讲清楚它什么时候开始让你"痛",痛到该拆。记住,单体一点都不落后,它是性价比最高的开局方式。
要理解单体,先想象一栋商住两用的大楼。一楼是餐厅,二楼是超市,三楼是仓库,四楼是收银。所有功能都住在一栋楼里,共享同一套水电、电梯和地下室。软件里的单体应用,就是这栋楼:用户模块、订单模块、库存模块、支付模块全塞在一个程序里,底下共用一个数据库。
这里有一个关键洞察,一定要记住:在单体里,用户模块想调用订单模块,本质上就是一个函数调用,同一个进程,纳秒级完成,零网络成本。
正因为这么直接、这么快,单体才成为几乎所有项目的起点。很多人一听微服务就觉得单体"落后",这是大错特错。单体有四大优点:
- 开发快:函数直调,没有网络开销,纳秒级完成。
- 部署简单:一个程序扔上去就行。
- 调试方便:一个断点能看完整调用链。
- 资源省:一个进程、一个连接池就够。
所以,项目早期,单体是性价比最高的选择。判断标准也很简单:单体什么时候开始让你"痛",什么时候才考虑拆。 Netflix花了7年才从单体迁到微服务,亚马逊也是业务涨到一定规模才拆。所以,千万不要为拆而拆。
这张时序图是全篇最重要的对比基准,请务必记住它。 后面每一章本质上都在改这张图里的"函数调用"。
看一次下单在单体里怎么发生:客户端发 POST 请求,单体应用收到后,第一步查用户是一次函数调用,第二步扣库存又是一次函数调用,第三步保存订单还是函数调用。全程都在同一个进程里,纳秒级完成,零网络成本。
记住这张图,尤其是"函数调用 = 纳秒级"这一点。因为微服务的所有复杂度,都源于把这种纳秒级调用变成了毫秒级的网络调用。
单体早期很爽,但业务长大后会暴露四个致命问题:
- 部署笨重:改一行代码也要重新部署整个应用。
- 扩容低效:你只想给库存加机器,结果整个应用都跟着扩了。
- 故障蔓延:库存模块一个内存泄漏,整个进程全崩,连登录都用不了。
- 协作冲突:多个团队改同一份代码,天天合并冲突。
痛到这份上,团队就会冒出一个想法:能不能把每个模块独立部署、独立扩容、独立背锅?
这个想法,正是微服务的起点。
第二章 服务拆分:从函数调用到网络调用
这一章要回答:把单体拆开后,两个独立程序之间怎么通信?
答案分两步:先用 HTTP 网络请求替代原来的函数调用,然后马上会发现这条路藏着一堆新坑。
副标题是"从函数调用到网络调用"。我们会看到拆分换来哪些好处,又会付出什么代价,尤其是新手最容易踩的第一个坑:把对方的IP地址写死在代码里。
拆分的核心动作,就是把住在一栋楼里的模块,搬成住进独立别墅。拆之前,所有模块都在一个进程里,共用一个数据库;拆之后,Order服务和User服务各自独立,连数据库都分家了,每个服务有自己的库,互相不能直接访问。
但注意,右边多出了一条虚线,标注着"怎么协作?"。这就是拆分后立刻撞上的核心问题:原来模块之间是函数调用,现在变成了两个独立程序,它们到底该怎么找到对方、怎么通信?这个问题会贯穿接下来几章。
拆分换来四大收益:独立部署 (改订单不用重启用户)、独立扩容 (大促时只给库存加机器)、故障隔离 (库存崩了不影响登录)、团队解耦(不同团队各管一摊)。
但代价就一个,而且很扎眼:原来的函数调用,必须变成网络调用。 函数调用是纳秒 级,网络调用是毫秒 级,慢了大约100万倍。这个数字差距一定会让你印象深刻。记住,这是微服务天生的代价。第四章的 gRPC,就是为了缩小这个差距而生的。
拆分之后,让 Order 调用 User,新手第一反应往往是直接把对方地址写在代码里。这种做法叫"硬编码IP",短期跑得通,上生产立刻爆炸。它有"四宗罪":
- 环境耦合:换开发、测试、生产环境,必改代码。
- 重启失效:对方换个端口就调不通。
- 无法扩容:写死的IP没法做负载均衡。
- 配置爆炸:几十个服务互相写死IP,改一处要查十处。
对比三种方案------硬编码、配置文件、服务发现------只有服务发现能做到动态、自愈,还天然支持扩容。 这就引出了第三章。
本章的核心结论是:拆分完成了,但调用地址写死,换个环境就崩。顺着这个痛点往下问:怎么让服务动态地找到对方?答案呼之欲出:我们需要一个"服务电话簿",让每个服务自己上报位置,调用方动态查询。这个东西,就叫 服务发现。
第三章 服务发现:让服务自己找到彼此
这是整个微服务架构的心脏。要回答的问题是:服务越来越多,谁在哪台机器、哪个端口?硬编码IP一换环境就崩。
答案是引入一个"服务电话簿",让服务自己上报位置,调用方动态查询。这一章我们会先讲一个你天天在用的类比,再拆解注册、心跳、发现这三个动作。
讲服务发现之前,先想一个你每天都在用的东西:外卖App。你想点麦当劳,会去背每家餐厅的电话吗?不会。你打开App搜"麦当劳",App从商家电话簿里查到号码,下单。商家搬家了?在App上更新一下,下次你查到的就是新号码。服务发现一模一样:Order想调User,不记IP,去问 Consul 这个注册中心。User换机器了,自己重新登记,下次查询自动拿到新地址。
一句话定义:服务发现就是所有服务登记位置的"电话簿",动态查询,位置变了不用改代码。
整个服务发现机制可以用三个动作完整概括,这张图建议截图保存:
- 注册:服务一启动,就告诉 Consul "我叫什么,在哪台机器,哪个端口"。就像新员工入职第一天去HR那登记。
- 心跳:服务每隔几秒发一次"我还活着"。就像值夜班的保安,每小时按一次报平安钮。
- 发现:调用方要调谁,先问 Consul 拿到对方的健康地址。就像寄快递前先查收件人地址再寄出。
光注册还不够,万一某个进程崩了,Consul还以为它在线,调用方就会惨兮兮地拿到一个死地址。于是有了第二个动作:心跳。服务每隔10秒向Consul发一次"我还活着",Consul收到了就把它标记为健康。那进程真崩了怎么办?心跳会超时,连续三次没收到,Consul就把这个实例从列表里剔除,调用方再也拿不到它。整个过程完全自动,不需要人工干预。这就是为什么我们说微服务能"自愈":服务挂了,系统自己感知,自己摘除,照样能跑。
把注册、心跳、发现三步合起来,就得到一张全景图:中间是 Consul 这个注册中心,左边是服务提供方(User, Order, Inventory),他们启动时注册,定期发心跳;右边是消费方(Order和网关),他们要调别人时就来查询地址。
这里有一个关键洞察:在微服务里,每个服务都同时扮演两个角色。 同一个服务,启动时是 Provider 去注册,调别人时是 Consumer 来查询,这两件事就在同一个进程里完成。所以,服务发现不是某个独立组件,而是每个服务都自带的能力。
常见的工具:Consul 最通用,功能丰富;Eureka 是Java经典,但已停止维护;etcd 是Kubernetes底层用的;Nacos 在国内很流行,发现加配置中心一体。如果已经在用K8s,直接用K8s Service就行,不用额外装组件。原理都一样,只是实现细节不同。
至此,第二章硬编码IP的"四宗罪"全部治愈:位置无关、自动自愈、天然支持扩容、集中管理。但新痛点又来了:HTTP + JSON 又慢又啰嗦,还没类型保障。 这就引出了第四章的gRPC。
第四章 gRPC:服务间通信的高速公路
要回答的问题是:HTTP + JSON 又慢又啰嗦,还没类型保障,改一个字段名,调用方就崩。
答案是用 gRPC + Protobuf,把通信契约写得清清楚楚,速度还能快5-10倍。这一章我们会从一个真实的事故场景讲起,看清JSON到底错在哪。
来看一个会让你感同身受的真实场景:某个周三,User团队为了统一风格,把字段 userId 改成了 user_id。单元测试过了,编译过了,部署也成功了。可 Order 服务毫不知情,还在按 userId 取值,结果解析出来全是零,下了一大堆 userId=0 的脏订单。凌晨两点,大家被叫醒排查。根因是什么?JSON是无Schema的文本协议,没有契约约束。 你改了字段名,编译器不报错,运行时才崩。
gRPC的解法就是用 Proto契约文件 强约束。改了字段,对方重新生成代码时,编译期立刻报错,根本到不了生产。
gRPC有三大秘密武器,每个都对应解决HTTP+JSON的一个痛点:
- Protobuf:一种二进制编码格式,体积小、解析快,专治"慢和啰嗦"。
- HTTP/2:新的传输协议,支持多路复用,并发高、延迟低。
- Proto契约文件:一份Schema,改了字段编译期就报错,专治"无类型"。
其中,契约是gRPC的灵魂。它最大的价值,就是把原本的"运行时事故"提前到了"编译器报错",工程质量直接提升一个数量级。
Protobuf凭什么这么快?拿传输一个用户对象举例(ID是1,Name是小明)。同样的数据,JSON大约要28字节,Protobuf只要11字节,体积小了六成。两个技巧:第一,用编号代替字段名,"user"这七个字符压缩成字段编号只要一个字节;第二,紧凑二进制,数字1不再存成文本字符,而是直接存成八位二进制。结果就是体积小六成,解析快5-10倍。单个请求感觉不明显,但每天几十亿次内部调用,省下来的带宽和CPU是惊人的。
gRPC这么好,为什么不全用它?因为它们适合不同场景:
- REST + JSON:对外友好。HTTP协议、文本格式,浏览器、App、第三方都能直接调。
- gRPC + Protobuf:内部高效。HTTP/2、二进制格式,性能快5-10倍,但浏览器原生不支持。
一句话记住:对外的门面用 REST,内部的管道用 gRPC。
看一次完整的gRPC调用时序:Order作为客户端,构造一个强类型请求调用 client.GetUser,就像调本地函数一样。底层用Protobuf编码成几十个字节发出去。User服务收到后,强类型解码,查到结果返回。Order拿到的也是强类型响应,编译期就做完类型检查。对比第二章的HTTP版本,差异很明显:类型安全、二进制小快稳、代码还简洁。
但这时候,前端同学找上门了:"gRPC我调不动啊!"浏览器不原生支持,每个服务开公网端口不安全,鉴权还得在每个服务重复实现。这就引出了第五章的API网关。
第五章 API网关:统一对外的大门
要回答的问题是:前端调不动gRPC,每个服务都暴露端口既混乱又不安全。
答案是引入一个网关,对外用 REST,对内用 gRPC,由它来承担协议翻译和统一入口。这一章会用"大厦前台"这个最贴切的类比开场。
理解网关,用大厦前台来类比最贴切。访客来办事,不会直接冲进各个部门,而是先到前台。前台做的四件事,正是网关的四大职责:
- 路由分发:问访客找谁、有什么事,对应按URL把请求转发到对应服务。
- 协议翻译:像给外宾当翻译,对外说REST,对内说gRPC。
- 统一鉴权:查访客证件、登录态校验,所有服务共享。
- 横切关注点:记录来访登记,对应日志、限流、熔断、监控。
一句话:API网关就是系统对外的唯一大门。
加上网关后,整个系统变成了三层:最上面是接入层(前端、App、第三方),全部用REST+JSON;中间是网关层,对外开一个REST端口,对内说gRPC,同时承担鉴权、路由、限流、日志;最下面是服务层(User, Order, Inventory),全用gRPC内部通信。而服务发现 Consul 贯穿这三层,网关要调内部服务,也得先通过Consul查地址。
三个关键变化:前端从此只和一个地址打交道;内部服务完全隐藏;网关承担所有的翻译和鉴权。
网关最大的价值是收口横切关注点 。什么叫横切关注点?就是所有服务都需要、但又跟业务核心无关的功能,比如鉴权、日志、限流、熔断、调用链追踪。没有网关时,每个服务都得自己实现一遍,同样逻辑写三遍,改一处要改三处。有了网关,这些统一在网关做一遍,全局生效。业务服务就能专注业务。这就是网关最实在的价值:让写业务的人,不用再操心那些重复的杂活。
这张全家福时序图是全篇的高潮,它把第一到第四章的所有组件完整串了起来:前端发一个POST请求,先到网关,网关做鉴权和路由,接着通过Consul查到Order的地址,把REST翻译成gRPC调过去。Order又通过Consul查到User的地址,gRPC调用拿回用户信息。最后,网关把gRPC响应翻译回REST,返回给前端。
三个关键结论:
- 前端全程只感知 REST。
- 内部全是 gRPC 高效通信。
- 网关承担了所有的翻译和鉴权。
这就是一套完整的微服务架构。
回顾与地图:记住这五件事
用一张时间线回顾你走过的每一步:
- 第一站,单体:所有功能放进一个进程,函数调用纳秒级。业务长大,模块相互拖累。
- 第二站,拆分:把一栋楼拆成多栋楼,函数调用变成网络调用。硬编码IP,换环境就崩。
- 第三站,服务发现:服务越来越多怎么找?引入Consul当电话簿,注册、心跳、发现三步走。
- 第四站,gRPC:HTTP+JSON又慢又没类型。协议升级,用上Protobuf、HTTP/2和契约。
- 第五站,网关:前端调不动,内部全暴露。对外REST,对内gRPC,鉴权、路由、横切全收口。
"遇到痛点 → 引入方案 → 解决痛点 → 又出现新痛点",这张图是整篇教程的灵魂。
如果你只能记住五件事,请记住这张图:
- 服务拆分:按业务边界拆,各自独立部署。
- 服务发现:Consul当电话簿,注册、心跳、发现。
- gRPC:Protobuf编码 + HTTP/2 + 契约。
- API网关:对外的唯一入口,做协议翻译和鉴权。
- 服务间通信:对外REST,对内gRPC。
这五者不是孤立的:拆分产生寻址需求,于是有服务发现;拆分产生通信需求,于是有gRPC;gRPC对外收口,于是有网关。五个概念,缺一不可。
最后,给你一张进阶地图。学习路径按这个顺序性价比最高:
- 先用 Docker + Compose 一键拉起多服务。
- 再学链路追踪,不然多服务报错你会抓狂。
- 然后是熔断限流,因为第一次线上事故多半是雪崩。
- 接着配置中心,服务一多配置散落就是噩梦。
- 再到 Kubernetes,真正的云原生。
- 最后才是 go-zero、Kitex 这些框架。先懂原理,再用框架,事半功倍。
也要记住五个反模式:别一开始就拆;别按技术拆,要按业务拆;别共享数据库;别追求强一致;别盲目堆组件。
比记住任何具体技术更重要的,是建立痛点驱动的工程思维。助你在微服务的路上越走越稳。