十万个 why 选题 — 架构认知方向(20260628)
大约 27 分钟
十万个 why 选题 — 架构认知方向(20260628)
选题方向:不局限于某个技术点的源码细节,而是聚焦在架构层面的"为什么这么设计",开发者日常工作中能接触到、但往往不深究的组件组合与职责分工问题。每个标题都带有"反直觉"或"看似多余"的冲突感。
一、网关 / 流量入口方向
1. 十万个why:有了 Spring Cloud Gateway,为什么前面还得挡一层 Nginx?
- 反差点:Gateway 已经能路由转发了 vs 还要再加一层
- 切入角度:Nginx 扛的是 L4/L7 的原始流量、SSL 终结、静态资源托管、连接级限流;Gateway 做的是业务级路由、鉴权、灰度这些应用层的事。Nginx 挂了是所有服务不可用,Gateway 挂了可以横向扩容再起。两者的故障域和职责完全不同
2. 十万个why:Nginx 已经能做负载均衡了,为什么还需要 Nacos 做服务注册发现?
- 反差点:Nginx upstream 也能配多个后端地址 vs 还要搞注册中心
- 切入角度:Nginx 的 upstream 是静态配置,服务扩缩容要改配置 reload;注册中心是动态感知,服务启停自动注册注销,配合客户端负载均衡可以做更细粒度的流量控制
3. 十万个why:明明 Spring Cloud Gateway 自带限流,为什么大厂还要在 Nginx 层再做一道限流?
- 反差点:Gateway 已经有 RequestRateLimiter vs 还要在 Nginx 层重复做
- 切入角度:Gateway 的限流是应用层的,请求已经进入 JVM 了才被拦截,高压下 JVM 本身就可能扛不住;Nginx 的 limit_req 是在连接层直接拒绝,根本不把请求传给后端,是保护整个后端集群的最后一道防线
二、缓存 / 数据层架构方向
4. 十万个why:业务系统已经用了 Redis 做缓存,为什么还要在前面加一层本地缓存(Caffeine)?
- 反差点:Redis 已经够快了 vs 还要再加一层
- 切入角度:Redis 再快也有网络 RTT(通常 0.5-2ms),本地缓存是纯内存读取(纳秒级);热点 Key 在高并发下所有请求打到同一个 Redis 节点,本地缓存可以扛住大部分流量不出内网;多级缓存的一致性用发布订阅或版本号来解决
5. 十万个why:Redis 已经是内存数据库了,为什么还需要持久化(RDB/AOF)?不是说内存里的数据重启就没了吗?
- 反差点:内存数据库 vs 还要写磁盘
- 切入角度:Redis 不只是缓存,很多场景下它存的是业务状态数据(分布式锁、计数器、排行榜)。重启丢了不是"缓存穿透重建"那么简单,是业务数据丢失。RDB 和 AOF 的取舍本质是"恢复速度"和"数据完整性"的权衡
6. 十万个why:MySQL 已经有主从复制了,为什么写操作还是只走主库,不能主从同时写?
- 反差点:两个库都有完整数据 vs 只有一个能写
- 切入角度:多主写入的核心难题是冲突检测——两个库同时更新了同一行数据以谁为准?MySQL 的主从复制是异步的,binlog 传输有延迟,同时写入就可能出现覆盖。Galera Cluster 虽然能多主写,但付出的代价是所有节点同步确认,写性能直接腰斩
7. 十万个why:都 2026 年了,为什么数据库分页还是用 LIMIT OFFSET 而不是游标分页?
- 反差点:所有人都知道 OFFSET 深分页有性能问题 vs 还在用
- 切入角度:游标分页要求排序字段唯一且稳定、不支持"跳到第 N 页"的交互方式、前端分页组件(antd/element)默认都是页码模式;大部分业务场景数据量在几万条以内根本触发不了深分页的性能问题,OFFSET 够用
三、微服务架构方向
8. 十万个why:微服务都有自己的数据库了,为什么还需要搞一套分布式事务(Seata)?
- 反差点:微服务就是要独立自治 vs 还要跨服务做事务
- 切入角度:理想的微服务确实不应该有跨库事务,但现实业务做不到——下单要扣库存、扣余额、创建物流单,这三个操作跨了三个服务三个库。Seata 的 AT 模式本质是"先执行后补偿",不是传统的 2PC 强一致。更多时候用消息队列做最终一致性比 Seata 更靠谱
9. 十万个why:微服务之间用 HTTP 调用就行了,为什么还要引入 gRPC?
- 反差点:RESTful API 简单通用 vs 还要搞 Protobuf 和代码生成
- 切入角度:HTTP/1.1 + JSON 的开销在内部调用链路上被放大——序列化反序列化慢、Header 冗余、不支持双向流;gRPC 用 HTTP/2 多路复用 + Protobuf 二进制序列化,内部服务间调用延迟能低 30%-50%。但对外暴露 API 还是用 REST,因为浏览器和第三方对接更方便
10. 十万个why:都已经用了 Kubernetes,为什么还需要 Spring Cloud 那套注册中心和配置中心?
- 反差点:K8s 的 Service 已经能做服务发现和负载均衡 vs 还用 Nacos
- 切入角度:K8s Service 是基于 DNS/IP 的四层负载均衡,不支持灰度发布、权重路由、元数据匹配这些应用层的流量控制;K8s ConfigMap 改了之后需要重启 Pod 才能生效,Nacos 支持热更新不重启。但如果你不需要这些高级特性,确实可以去掉 Spring Cloud 直接用 K8s 原生能力
11. 十万个why:服务之间的调用链路已经有了 Feign,为什么还要搞一套 MQ(消息队列)?
- 反差点:Feign 直接调多简单 vs 中间还要加个消息队列
- 切入角度:Feign 是同步调用,调用方要等被调方返回才能继续;MQ 是异步解耦,调用方发完消息就走了。下游服务挂了 Feign 直接报错影响上游,MQ 消息在 Broker 里攒着等下游恢复了慢慢消费。但也不是什么都适合走 MQ——需要实时拿返回值的场景还是得同步调
四、部署 / DevOps 方向
12. 十万个why:Docker 容器已经能隔离应用了,为什么还需要 Kubernetes?
- 反差点:Docker run 一下就能跑 vs 还要搞这么复杂的编排系统
- 切入角度:Docker 解决的是"单个应用怎么打包和运行",K8s 解决的是"几十上百个容器怎么管理"——自动扩缩容、滚动更新、健康检查自动重启、服务发现、资源配额。你只有三五个服务用 docker-compose 就够了,服务多了才需要 K8s
13. 十万个why:GitLab CI/CD 已经能自动部署了,为什么大厂还要搞一套自研的发布平台?
- 反差点:GitLab Runner 配个 pipeline 就能部署 vs 还要自己造轮子
- 切入角度:GitLab CI 的审批能力弱(谁点了 approve 就上线了)、不支持分批灰度发布、没有跟 CMDB 和监控平台联动的回滚能力、不支持跨环境的配置差异管理。大厂发布平台的核心不是"把代码部署上去",而是"出了问题能 30 秒内回滚并定位到哪次发布引入的"
14. 十万个why:Spring Boot 项目打成 JAR 包直接 java -jar 就能跑,为什么还要打成 Docker 镜像?
- 反差点:JAR 包已经是自包含的 vs 还要再套一层 Docker
- 切入角度:JAR 包能跑的前提是机器上装了对的 JDK 版本、环境变量配对了、端口没被占用。Docker 镜像把 JDK、配置、应用全打包在一起,换任何一台机器都能跑。运维不用关心"这个应用需要 JDK 17 还是 21",
docker pull+docker run搞定
五、安全 / 认证方向
15. 十万个why:HTTPS 已经加密传输了,为什么接口的敏感参数还要再做一次加密?
- 反差点:TLS 都加密了 vs 参数还要自己加密一遍
- 切入角度:HTTPS 保护的是传输链路,请求到了服务端就是明文了。如果有网关层的日志记录、链路追踪的全量日志采集、甚至运维人员可以看到请求体,敏感信息(身份证号、银行卡号)就在日志里裸奔。参数级加密是端到端的,即使中间环节能看到请求体也看不到原文
16. 十万个why:Session 用了十几年好好的,为什么现在都要换成 JWT?
- 反差点:Session 简单可靠 vs JWT 还有过期、续签、注销的一堆问题
- 切入角度:Session 依赖服务端存储(内存或 Redis),有状态的;JWT 是无状态的,不需要服务端存储,天然适合微服务和多实例部署。但 JWT 的代价是不能主动让一个 Token 失效(除非加黑名单,那又回到有状态了),所以很多系统实际上是 JWT + Redis 黑名单的混合方案
六、日志 / 监控方向
17. 十万个why:应用日志已经打到文件里了,为什么还要搞一套 ELK(Elasticsearch + Logstash + Kibana)?
- 反差点:tail -f 看日志就行 vs 搞这么重的一套系统
- 切入角度:单台机器看日志没问题,20 台机器呢?一个请求经过了 5 个服务,你要 SSH 到 5 台机器分别 grep 日志拼凑调用链路?ELK 做的是日志集中化和可搜索化。但很多小团队直接用 Loki + Grafana 就够了,没必要上完整的 ELK
18. 十万个why:Spring Boot Actuator 已经有健康检查了,为什么还需要 Prometheus + Grafana?
- 反差点:/actuator/health 已经能看服务状态 vs 还要额外部署监控系统
- 切入角度:Actuator 是点对点的——你得知道服务部署在哪台机器上才能去访问它。Prometheus 是拉模式的监控,自动采集所有注册服务的指标数据,历史存储 + 聚合查询 + 告警规则 + 可视化面板。"你的 API P99 延迟从昨天的 50ms 涨到了今天的 200ms"——这种趋势分析 Actuator 做不了
七、存储 / 文件方向
19. 十万个why:服务器本地磁盘存文件就行了,为什么非要用 MinIO / OSS 这种对象存储?
- 反差点:文件不就是存磁盘上吗 vs 还要搞个专门的存储服务
- 切入角度:单台机器存文件,这台机器挂了文件就没了;应用如果部署了多个实例,用户上传到 A 实例的文件在 B 实例上访问不到;扩容的时候磁盘空间不好管理。对象存储做的是独立于应用的文件管理——高可用、CDN 加速、权限控制、生命周期管理,应用实例随便扩缩容文件不受影响
20. 十万个why:数据库 BLOB 字段可以直接存文件,为什么没人这么干?
- 反差点:数据库本来就能存二进制数据 vs 没人这么用
- 切入角度:一个 5MB 的图片存到数据库里,数据库的表空间就大了 5MB,备份恢复的时间变长,binlog 同步的流量变大,查询这条记录的时候即使你不需要文件内容也要传输这 5MB。文件和结构化数据的访问模式完全不同——文件是大块顺序读写,数据库擅长的是小块随机查询。混在一起两边的性能都拉垮
八、前后端协作 / API 设计方向
21. 十万个why:后端返回的 JSON 结构已经足够清晰了,为什么前端还要搞一套 TypeScript 类型定义?
- 反差点:JSON 本身就是自描述的 vs 还要额外写一遍类型
- 切入角度:JSON 是运行时才知道结构的,拼错了字段名只有跑起来才报错;TypeScript 是编译时检查,后端改了字段名前端编译阶段就能发现。用 OpenAPI/Swagger 自动生成 TS 类型定义可以做到后端改接口、前端类型自动同步
22. 十万个why:后端直接返回数据库查出来的实体不就行了,为什么还要再封装一层 VO/DTO?
- 反差点:数据库实体类的字段跟前端要的一模一样 vs 还要多写一个类做转换
- 切入角度:数据库实体里可能有 password、is_deleted、internal_remark 这些不应该暴露给前端的字段;实体类的字段命名跟前端的命名习惯可能不一样(created_at vs createTime);有些接口需要把多个实体的数据组合在一起返回;直接暴露实体类意味着数据库表结构的改动会直接影响接口格式,前后端耦合
23. 十万个why:GET 请求能传参数,为什么很多复杂查询接口还是用 POST?
- 反差点:RESTful 规范说查询应该用 GET vs 实际项目里大量查询用 POST
- 切入角度:GET 请求的参数在 URL 里,URL 长度有限制(大部分浏览器和服务器限制在 2KB-8KB);查询条件多了 URL 会特别长而且不好编码嵌套对象;Nginx 的 access_log 会记录完整 URL,敏感查询条件会出现在日志里。POST body 没有长度限制,也不会出现在 URL 和日志里
九、消息队列 / 异步方向
24. 十万个why:Spring 的 @Async 已经能异步执行了,为什么还要引入消息队列?
- 反差点:加个注解就能异步 vs 还要部署和维护一个 MQ
- 切入角度:@Async 是进程内的异步,应用重启了正在执行的异步任务就丢了;MQ 的消息持久化在 Broker 里,应用重启了消息还在,恢复后继续消费。而且 @Async 没有重试、死信、延迟投递这些能力,高可靠场景下不够用
25. 十万个why:Kafka 明明是消息队列,为什么很多公司拿它当数据库用(存日志、存事件)?
- 反差点:消息队列不是消费完就该删掉吗 vs 当数据库用
- 切入角度:Kafka 的消息是持久化的,配了 retention 可以存几天甚至永久;Consumer Group 的 offset 机制让不同的消费者可以独立消费、可以回溯重新消费;分区有序 + 不可变追加写入的特性天然适合事件溯源和审计日志。它不是"当数据库用",而是它本来就是一个分布式日志系统
十、实战踩坑 / "看起来没必要但出过事"方向
26. 十万个why:接口已经做了参数校验(@Valid),为什么 Service 层还要再校验一遍?
- 反差点:Controller 层已经校验了 vs Service 还要重复校验
- 切入角度:Service 不只是被 Controller 调用,还可能被定时任务、MQ 消费者、RPC 接口调用,这些入口不经过 Controller 的参数校验。如果 Service 不做校验,换个入口进来的脏数据就直接写进数据库了
27. 十万个why:Spring Boot 默认的 Tomcat 已经是生产级的容器了,为什么有些团队还要换成 Undertow?
- 反差点:Tomcat 稳定可靠用了二十年 vs 还要换
- 切入角度:Undertow 是 NIO 非阻塞架构,在高并发短连接场景下吞吐量比 Tomcat 高 10%-20%;内存占用更小;支持 HTTP/2 的开箱即用体验更好。但大部分业务系统的瓶颈不在 Web 容器上而在数据库和外部服务调用上,换了 Undertow 体感提升可能不明显
28. 十万个why:配置信息放在 application.yml 里就行了,为什么还要搞个配置中心(Nacos/Apollo)?
- 反差点:yml 文件简单直接 vs 还要额外部署一套系统
- 切入角度:改 yml 要重新打包部署才能生效;多环境(dev/test/prod)的配置管理靠不同的文件容易出错;敏感配置(数据库密码、API Key)不应该提交到 Git 仓库。配置中心的核心价值是热更新、多环境管理、配置审计和权限控制
29. 十万个why:代码里直接写 SQL 不就行了,为什么还要用 MyBatis 的 XML 映射?
- 反差点:SQL 写在注解里或者代码里更方便 vs 还要维护一堆 XML 文件
- 切入角度:简单的 CRUD 确实用注解更方便(MyBatis-Plus 甚至不用写 SQL);但复杂 SQL(多表 JOIN、动态条件拼接、批量操作)写在注解里可读性极差,XML 的 if/foreach/choose 标签做动态 SQL 远比在代码里拼字符串安全和清晰。实际项目里大部分团队是混用的——简单的用注解,复杂的用 XML
30. 十万个why:数据库连接池(HikariCP)的连接数为什么不是设得越大越好?
- 反差点:连接多了不是应该更快吗 vs 连接设多了反而更慢
- 切入角度:数据库服务器处理并发连接是有开销的——每个连接要分配内存、维护状态、做线程调度。连接数超过 CPU 核心数之后,线程上下文切换的开销反而拖慢了整体性能。HikariCP 作者给过一个经验公式:最优连接数 ≈ CPU 核心数 × 2 + 磁盘数。大部分场景下 10-20 个连接就够了,设 200 个反而更慢
第二批:更有深度的架构选题(知乎热点灵感提炼)
选题标准升级:不是"有经验的人一看就知道答案"的常识题,而是即使有经验也容易踩坑、需要展开分析才能说清楚、评论区会有争论的题目。
十一、"看似做了微服务,实际是分布式单体"方向
31. 十万个why:拆成了 20 个微服务,为什么一个服务发版整条链路都得跟着重新测试?
- 反差点:微服务的核心承诺就是独立部署 vs 改一个服务全链路回归
- 切入角度:服务间共享数据库表、接口契约没有版本管理、同步调用链过长导致任何一个节点的接口变更都是破坏性的。这就是"分布式单体"——享受了分布式的所有运维复杂度,但没享受到独立部署的好处。判断标准:如果你不能单独发一个服务而不担心别的服务挂掉,你做的就不是微服务
32. 十万个why:微服务之间明明有各自的数据库,为什么还是有人偷偷跨库 JOIN?
- 反差点:每个服务独立数据库是微服务铁律 vs 实际项目里跨库查询到处都是
- 切入角度:业务需要关联查询的时候,走 API 聚合性能太差(N+1 问题),走事件驱动做数据冗余又要维护一致性。很多团队"务实"地选择了直连别人的库做 JOIN——短期解决问题,长期服务边界彻底失效,数据库 schema 变更成了跨团队协调噩梦。正确做法是 CQRS 读写分离 + 物化视图,但成本确实高
33. 十万个why:团队只有 5 个人,为什么技术负责人还要上全套微服务(Nacos + Gateway + Seata + Sentinel)?
- 反差点:微服务是大厂的解决方案 vs 小团队也在用
- 切入角度:知乎/V2EX 高频争论话题。5 个人维护 20 个服务 + 一套注册中心 + 一套配置中心 + 一套链路追踪,运维成本比开发成本还高。模块化单体(Maven 多模块 + 清晰的包边界)在 10 人以下团队几乎总是更优选择。微服务解决的是"组织规模扩大后的协作问题",不是技术问题
十二、"幂等性"与"重复执行"方向(生产事故高发区)
34. 十万个why:用户只点了一次提交按钮,为什么后台生成了两条一模一样的订单?
- 反差点:按钮加了防重复点击 vs 还是重复了
- 切入角度:前端防抖只能防用户手抖,防不了网络层的重试——浏览器超时自动重发、Nginx 的 proxy_next_upstream 自动重试、Spring Cloud 的 Feign/Ribbon 默认开启重试。一个请求从用户到数据库可能经过了三四层重试机制,每一层都可能产生重复。服务端必须做幂等设计(唯一请求 ID + 数据库唯一约束),前端防抖是锦上添花不是解决方案
35. 十万个why:MQ 消费者明明已经处理成功了,为什么同一条消息又被投递过来了?
- 反差点:消费成功了 vs 又收到了
- 切入角度:消费者处理成功但 ACK 还没发回 Broker 就挂了,Broker 认为没消费成功重新投递;消费者处理成功了但 offset 还没提交就发生了 rebalance,新的消费者从旧 offset 开始消费。MQ 的语义是"至少一次投递"不是"恰好一次",消费者必须自己做幂等。这不是 MQ 的 bug,是 CAP 定理下的必然取舍
十三、"事务边界"方向(高频翻车现场)
36. 十万个why:@Transactional 方法里调了一个 HTTP 接口,为什么数据库连接池被打满了?
- 反差点:就是在事务里多调了个接口而已 vs 连接池满了全系统卡死
- 切入角度:事务方法从开始到结束一直占着一个数据库连接。如果方法里调了 HTTP 接口,而对方响应慢(比如 3 秒),这 3 秒内数据库连接一直被占着什么也不干。并发一上来,连接池里的连接全被"等 HTTP 响应"的事务占住了,后面的请求拿不到连接直接报超时。这是生产环境最常见的连接池打满场景之一
37. 十万个why:两个 @Transactional 方法互相调用,为什么数据没有回滚?
- 反差点:两个方法都加了事务注解 vs 回滚没生效
- 切入角度:Spring 事务是基于 AOP 代理的。同一个类里 A 方法调 B 方法,走的是 this 引用不是代理对象,B 方法上的 @Transactional 注解根本不生效。加上如果 Propagation 配置不对(REQUIRES_NEW vs REQUIRED),事务边界跟你想的完全不一样。这个坑几乎每个 Spring 开发者都踩过至少一次
十四、"看起来是性能问题,其实是架构问题"方向
38. 十万个why:数据库 CPU 飙到 90%,加了索引之后为什么反而更慢了?
- 反差点:加索引应该变快 vs 反而更慢了
- 切入角度:索引不是越多越好——写入操作(INSERT/UPDATE/DELETE)时每个索引都要同步更新,索引太多写入性能直线下降。更隐蔽的是"索引合并"问题:MySQL 优化器在某些情况下会同时走多个单列索引然后合并结果,这个合并操作本身比全表扫描还慢。有时候删掉几个无用索引反而能提升性能
39. 十万个why:Redis 单节点能扛 10 万 QPS,为什么我们的 Redis 集群 3 万 QPS 就开始超时了?
- 反差点:官方性能数据 10 万 QPS vs 实际 3 万就扛不住
- 切入角度:10 万 QPS 是用 redis-benchmark 在本机测的简单 GET/SET 命令。实际业务里用了 HGETALL 大 Hash、LRANGE 长列表、Lua 脚本、Pipeline 没做好导致大量小请求走了多次网络往返、Key 集中在少数 Slot 导致热点节点。集群模式下 MGET 跨 Slot 还要做 ASK/MOVED 重定向,性能比单节点差很多
40. 十万个why:服务从 Spring MVC 迁移到 WebFlux 响应式,为什么 QPS 反而下降了?
- 反差点:响应式号称高性能高并发 vs 迁移后更慢了
- 切入角度:WebFlux 的优势是用少量线程处理大量 I/O 等待,前提是整条链路都是非阻塞的。只要你的代码里调了一个阻塞操作(同步 JDBC、同步 HTTP 客户端、synchronized 锁),就会阻塞 EventLoop 线程,而 WebFlux 默认只有 CPU 核心数个 EventLoop 线程,阻塞一个影响全局。半响应式比全同步更危险
十五、"日常开发中容易忽略的架构隐患"方向
41. 十万个why:所有接口的超时时间都设成了 30 秒,为什么一个下游服务慢了整个系统就雪崩了?
- 反差点:30 秒超时够宽裕了 vs 整个系统崩了
- 切入角度:调用链路 A → B → C → D,每一层都设了 30 秒超时。D 服务慢了响应要 25 秒,C 等了 25 秒,B 等 C 等了 25 秒,A 等 B 也等了 25 秒。这 25 秒内 A 的 Tomcat 线程一直被占着,并发一上来 200 个线程全被占满,新请求全部排队超时。超时时间必须逐层递减(外层 < 内层之和),而不是全设一样的值
42. 十万个why:线上日志级别设成了 INFO,为什么一到大促系统就卡顿?
- 反差点:INFO 已经是合理的日志级别了 vs 大促期间导致卡顿
- 切入角度:QPS 从平时的 1000 飙到大促的 50000,每个请求打 5 条 INFO 日志就是每秒 25 万条日志。日志框架(Logback/Log4j2)的 Appender 要做序列化、格式化、写磁盘/发送到日志采集器,这个开销在高 QPS 下不可忽略。异步 Appender 的队列满了会阻塞业务线程,磁盘 IO 打满了更是全局影响。大促前把非关键路径的日志临时调到 WARN 是标准操作
43. 十万个why:明明做了读写分离(主库写从库读),为什么用户刚注册完就查不到自己的信息?
- 反差点:读写分离是标准架构 vs 数据"丢了"
- 切入角度:MySQL 主从复制有延迟(通常几十毫秒到几秒)。用户在主库注册完后,下一个请求路由到从库去查,这时候从库还没同步到这条记录,就返回了"用户不存在"。解决方案:写后读强制走主库(设一个标记位,几秒内的读请求走主库)、用半同步复制(semi-sync)降低延迟、业务层做补偿查询
44. 十万个why:定时任务在单机上跑得好好的,为什么部署成多实例之后同一个任务被执行了多次?
- 反差点:@Scheduled 单机没问题 vs 多实例重复执行
- 切入角度:@Scheduled 是进程级的调度器,每个 JVM 实例都有自己独立的调度线程。部署了 3 个实例就有 3 个调度器,同一个 cron 到了时间 3 个实例各执行一次。解决方案:分布式调度器(XXL-JOB / ElasticJob)、用 Redis 分布式锁做抢占(只有抢到锁的实例执行)、只在一台机器上部署定时任务(最简单但不高可用)
十六、"AI 时代的架构新问题"方向
45. 十万个why:给大模型接了公司的知识库(RAG),为什么回答的内容有时候比不接知识库还离谱?
- 反差点:接了知识库应该更准确 vs 反而更离谱
- 切入角度:RAG 检索到了不相关的文档块塞进上下文,模型被误导后产生了基于错误上下文的"有理有据的胡说八道"。比不接知识库时模型至少会说"我不确定",接了知识库之后模型会信心满满地引用错误内容。RAG 的检索精度比生成质量更重要,检索不准不如不检索
46. 十万个why:AI Agent 编排了 5 个工具调用来完成一个任务,为什么成功率还不到 60%?
- 反差点:每个工具单独调用成功率都在 95% 以上 vs 串起来只有 60%
- 切入角度:串行调用链路的成功率是各环节成功率的乘积。0.95 × 0.95 × 0.95 × 0.95 × 0.95 = 0.77,加上 LLM 在工具参数生成上的准确率(可能只有 90%),整体成功率就是 0.77 × 0.9 ≈ 0.59。Agent 的可靠性工程不是让每个环节更强,而是减少环节数量、加重试和回退、用 Workflow 替代自由编排
47. 十万个why:用 AI 生成的代码通过了所有单元测试,为什么上线之后还是出了 bug?
- 反差点:测试全绿 vs 线上出 bug
- 切入角度:AI 生成代码的同时也可能生成了"看起来在测但实际什么也没测"的单元测试——断言永远为 true、mock 了所有外部依赖导致测试跟真实环境完全脱节、只测了 happy path 没覆盖边界情况。通过率 100% 不等于覆盖率足够,更不等于测试本身是正确的。AI 生成的测试需要人审查测试逻辑,不只是看通不通过
48. 十万个why:大模型 API 的响应时间动辄 3-10 秒,为什么不能像普通接口一样用缓存来优化?
- 反差点:Redis 缓存不就能解决慢请求吗 vs 缓存不住
- 切入角度:大模型的输入是自然语言,同一个意思可以有无数种表达方式。"帮我写个排序算法"和"写一个排序的代码"语义相同但字符串完全不同,传统的 Key 精确匹配缓存命中率极低。用 Embedding 做语义缓存(向量相似度匹配)可以提高命中率,但引入了"相似度阈值"的调优问题——阈值太高命中不了,太低返回了不相关的缓存结果
十七、"数据一致性的真实世界"方向
49. 十万个why:明明用了 Redis 分布式锁,为什么还是出现了超卖?
- 反差点:分布式锁保证互斥 vs 还是超卖了
- 切入角度:客户端 A 拿到锁后执行业务逻辑超时了(比如 GC 停顿),锁过期自动释放。客户端 B 拿到锁开始执行,此时 A 的 GC 结束继续执行,两个客户端同时在操作库存。Redisson 的看门狗(Watchdog)机制可以自动续期,但如果网络分区导致续期失败也会有问题。分布式锁不是银弹,核心业务必须在数据库层面做最终的一致性保障(乐观锁 + 版本号)
50. 十万个why:延迟双删方案在高并发场景下为什么还是有脏数据?
- 反差点:删缓存→更新DB→延迟再删缓存 vs 还是有脏读
- 切入角度:延迟双删的"延迟"时间是估算的(通常设 500ms-1s)。如果从库的主从复制延迟超过了这个时间,第二次删除缓存之后,又有请求去从库读到了旧数据写回缓存。真正靠谱的方案是订阅 binlog(Canal/Debezium)做缓存更新,让数据库的变更事件驱动缓存失效,而不是在业务代码里猜延迟时间
