学了六周,Redis 走过缓存三问题、分布式锁、秒杀,微服务走过 Nacos、Feign、Gateway、Session、Seata、RabbitMQ。回头看,这些技术在教程里出现时都带着同一个暗示:微服务都在用、大厂都在用。但"都在用"不构成理由。
这篇换一个判断标准:"不可替代"不是"用了更好",而是"不用它,现有方案在这个场景下会物理性失败"------要么扛不住量,要么根本做不了。按这个标准,把 Redis 和微服务各放回它们真正无可替代的场景里,也把它们不该出现的场景一并划清。
一、Redis:MySQL 到不了的地方
Redis 常被介绍成"缓存工具",但缓存只是它最浅的用法。真正让 Redis 不可替代的,是三件事的叠加:内存速度、特殊数据结构、单命令原子性。MySQL 在这三件事上不是"慢一点",是做不了。
1. 热点读:QPS 的天花板问题
双十一商品页,同一件商品被百万人同时看。MySQL 单机的随机磁盘读撑到几千 QPS 就开始排队,读写混跑时更糟;Redis 基于内存,单实例轻松十万级 QPS。
这不是优化问题,是量级问题:热点读超过磁盘 IO 的承受力之后,加索引、调 SQL 都没用,数据必须有一份在内存里。 旁路缓存、缓存击穿防护、雪崩 TTL 抖动------前面学的那套,本质都是在伺候这份"内存里的副本"怎么不出错。
2. 共享状态:多实例的登录态
Session 篇拆过:登录态放 JVM 内存,扩容到两台实例就时有时无。MySQL 当然也能存登录态------但每个请求都为一个 token 查一次库,高并发下这是一笔不必要的磁盘开销。Redis 用内存的读写速度,让"所有实例共享一份登录态"这件事的成本低到可以每个请求都做。
"实例无状态、状态外置"这个微服务前提,靠的就是外置的目标足够快------快到用户感知不到那是一次跨网络的查询。
3. 原子操作:秒杀和限流
秒杀扣库存,100 个线程同时抢 10 张券。MySQL 靠行锁串行化,锁竞争一激烈,连接池先被打满;Redis 的 DECR 是单命令原子操作,天然免锁。限流计数同理------固定窗口的 INCR 一次调用完成"读+改+写",MySQL 得开事务、加锁、提交,三倍开销。
当场景要求"高频的读改写不互相阻塞",行锁模型就是天花板,Redis 的单线程命令模型反而是优势。
4. SQL 表达不了的数据结构
- 排行榜:百万用户按分数排名,取前 100、查某人第几名------ZSet 用跳表实现,排序和排名查询都是 O(logN)。MySQL 用窗口函数能写出同样的结果,但每次查询都是全量排序级别的开销
- UV 统计:HyperLogLog 用 12KB 固定内存估算亿级基数,误差 0.81%。MySQL 要么存全量明细去重,要么精度和内存双双爆炸
- 签到:BitMap 一个人一年 365 天就是 365 bit,查"连续签到天数"是位运算。MySQL 存 365 行记录,查询还得自己拼
这些不是"Redis 更快",是数据结构和业务问题刚好对上了------SQL 的表结构是二维的,装不下排名、基数、位图这类问题的形状。
划清边界
Redis 不可替代的地方说完了,它替代不了的地方同样清楚:复杂关联查询、事务性写入、需要持久保证的数据------这些仍然是数据库的领地。Redis 是"快的那一半",不是数据库的平替。
二、微服务:代码库和组织撑不住的时候
先说一个和"不可替代"相反的判断:大部分项目,单体是更优解。 一个三五个人、业务稳定的系统,拆成微服务是纯亏------网络开销、部署复杂度、分布式问题全部提前到账,而拆分的收益根本用不上。这条底线不设,后面的话就成了无条件推销。
微服务真正不可替代,出现在三个信号亮起的时候:
信号一:人多到一个代码库装不下
200 人的团队改同一个仓库,合并冲突消耗的时间开始超过写代码的时间;A 模块要上线,必须等 B 模块测试完才能一起发------发布从"各发各的"变成"排队过独木桥"。代码库的边界必须跟组织的边界对齐,这不是技术选择,是协作成本逼出来的。
信号二:不同模块的负载特征分化
大促时订单服务要扩到 10 倍,搜索服务反而因为用户都在抢购而变闲,商品浏览负载平稳。单体只能整体扩容------为订单峰值付出的机器,闲置模块也得跟着搬进去。拆开之后,每个服务按自己的曲线伸缩,这是单体在架构上做不到的事。
信号三:故障域必须隔离
支付服务出 bug 时,浏览、搜索这些完全健康的流量不该跟着遭殃。单体里一个线程池打满就是全站打满;拆开之后,故障被关在服务边界里,爆炸半径从全站缩到单个功能。
拆开之后,账单开始分期
三个信号亮起、拆分完成的那一刻,一堆新问题同时出现------这六周学的组件,每一个都是给拆分"还债"的:
- 地址管理乱了 → Nacos 收走"服务在哪"
- 调用代码散了 → Feign 收走"怎么调别人"
- 外部入口多了 → Gateway 收走"流量怎么进"
- 用户状态断了 → Session/JWT 方案收走"状态放哪"
- 跨库一致性没了 → Seata 接住"要么全成功要么全回滚"
- 同步调用扛不住 → RabbitMQ 接住"可以稍后做的事"
看清楚这个顺序:不是先有微服务再找问题,而是先付了拆分的钱,再用这些组件分期偿还。 每一个组件都不可替代------但仅当拆分本身不可替代。回到信号没亮就别拆,单体不需要 Nacos,一个 JVM 里 @Transactional 就是全局事务。
三、两者的共同点:用网络,换两样东西
放在一起看,Redis 和微服务做的事在结构上是同一件事:
Redis:把热点数据和状态从磁盘搬到内存,代价是每次访问多一跳网络------用网络往返,换内存级速度。
微服务:把一个进程里的调用变成跨进程的通信,代价是每次协作多一跳网络------用网络通信,换隔离和弹性。
代价也随之对称:状态分散了,一致性要额外保证(缓存一致性、分布式事务);链路变长了,排查要额外工具(日志追踪、链路透传);节点变多了,协调要额外组件(注册中心、配置中心)。六周里反复出现的"没有银弹,只有取舍",在收官这里可以说得更具体:这两项技术卖的是同一种商品------"用复杂度和一跳延迟,换性能上限和组织弹性",区别只是商品规格。
四、带走的不是组件,是问题清单
六周下来,具体命令和配置注定会忘,strip_prefix 写法、getOrder 数值这些随时可以查文档。真正沉淀下来的,是拿到任何新技术时先问的三个问题:
- 这个状态该放在哪?------放对了位置,Redis/JWT/数据库的选择自然清楚
- 这个耦合值不值得拆?------三个信号没亮,微服务就是负收益
- 这笔复杂度,业务真的需要吗?------Seata、MQ、网关,引进之前先算账
组件会过时,这三个问题不会。以后碰到现在还没流行的新框架,先过一遍这三个问题,再决定动手。