从Redis缓存到MySQL索引:拆解高并发外卖系统后端核心设计
1. 项目概述:从“苍穹外卖”面试题看后端工程师的核心能力栈
最近在帮团队筛选候选人,也和一些朋友交流面试准备,发现“苍穹外卖”这个项目在面试中出现的频率相当高。它不像一个简单的CRUD项目,更像是一个微缩版的、五脏俱全的真实线上系统,涵盖了从用户下单、商家接单、骑手配送、支付结算到数据统计的全链路。面试官通过它,考察的绝不仅仅是你会不会写接口,更深层次的是你对一个完整业务系统的理解深度、技术选型的思考以及应对高并发场景的实战能力。
我自己也复盘过,围绕“苍穹外卖”的面试题,核心其实就几个关键词:Redis、MySQL、缓存穿透、淘汰机制、IO多路复用。这几个词串起来,几乎就是后端工程师从数据存储、性能优化到系统稳定性的核心知识图谱。今天,我就结合自己这些年踩过的坑和带人的经验,把这些高频考点掰开揉碎了讲一讲,希望能帮你构建一个清晰、有深度的知识体系,而不仅仅是背几个面试题答案。
2. 核心需求解析:为什么面试总爱问这些?
面试官抛出“苍穹外卖”这个场景,本质上是在模拟一个典型的、有挑战性的互联网应用。我们得先理解这个场景下的核心业务压力和技术挑战,才能明白为什么那些技术点是必考的。
2.1 业务场景下的技术挑战
想象一下“苍穹外卖”的日常:午高峰和晚高峰,成千上万的用户同时浏览商家菜单、下单、查看订单状态。这个过程中,系统面临几个核心压力点:
- 极高的读多写少比例:用户浏览菜单、查看商家评分、查询订单状态的频率,远高于下单支付的频率。这意味着缓存是提升性能、降低数据库压力的第一道也是最重要的一道防线。
- 数据的强一致性与最终一致性并存:订单状态(如“已支付”、“配送中”、“已完成”)需要强一致性,用户和商家需要立刻看到准确状态。而商家评分、销量统计这类数据,则可以接受短时间内的最终一致性,适合用缓存+异步更新策略。
- 瞬时高并发与热点数据:热门商家的菜单、爆款菜品,在高峰时段会被海量请求同时访问。如果所有请求都打到数据库,数据库连接池瞬间就会被撑爆,这就是典型的缓存穿透和缓存击穿问题。
- 系统资源的有效管理与回收:缓存不能无限增长。哪些数据应该被优先保留(如热门商家信息),哪些数据可以淘汰(如很久没人浏览的冷门菜品),这就需要合理的缓存淘汰机制。
- 海量连接的高效处理:外卖APP通常使用长连接(如WebSocket)来推送订单状态变更给骑手和商家。服务器需要同时维持数十万甚至上百万的连接,并能在任何连接有数据到达时快速响应。用传统的“一个线程处理一个连接”的阻塞IO模型,服务器资源根本不够用,这时就必须用到IO多路复用技术。
理解了这些业务挑战,你就会发现,面试官问的每一个技术点,都不是孤立的,而是为了解决这些具体问题而存在的。你的回答如果能体现出这种“问题驱动技术选型”的思路,分数会高很多。
2.2 面试考察的能力维度
基于上述挑战,面试官通过相关问题主要考察你以下几个维度:
- 基础扎实度:对Redis、MySQL等核心组件的基本原理是否真正理解,而不是只会用API。
- 场景化设计能力:能否根据“外卖”这个具体业务场景,设计出合理的缓存策略、数据库表结构、并发控制方案。
- 问题排查与解决能力:当系统出现性能瓶颈(如接口超时)或数据错误时,你的排查思路是什么?能否联想到缓存、数据库、网络IO等各个层面。
- 技术深度与广度:是否了解这些技术背后的机制(如Redis的线程模型、MySQL的索引原理、操作系统的IO模型),并能将它们关联起来。
接下来,我们就针对这几个核心关键词,进行深度拆解。
3. Redis深度剖析:不止是缓存
在“苍穹外卖”里,Redis的角色绝对是C位。它不仅仅是缓存,更是高性能的数据结构服务器和消息队列。
3.1 缓存穿透、击穿、雪崩的区分与实战解决方案
这是Redis面试的“老三样”,但很多人直到面试时还分不清。我们结合外卖场景来彻底搞懂。
缓存穿透:查询一个根本不存在的数据。比如,请求一个不存在的
order_id来查订单详情。缓存没有,数据库也没有。大量这样的恶意请求会直接穿透缓存,压垮数据库。- 解决方案1:缓存空对象。当从数据库查不到时,在Redis里也缓存一个空值(如
SET order:99999 “”),并设置一个较短的过期时间(如30秒)。下次同样的请求就直接在缓存层返回空了。注意:需要防范大量不同的不存在的Key打满缓存,可以配合下面方案2。 - 解决方案2:布隆过滤器。在查询缓存前,先用布隆过滤器判断Key是否存在。如果布隆过滤器说“不存在”,那这个Key一定不存在,直接返回,无需查询缓存和数据库。布隆过滤器说“存在”,则再去缓存查询。这是一种空间效率极高的概率型数据结构,非常适合这种场景。在外卖系统中,可以将所有有效的
user_id、shop_id、order_id预热到布隆过滤器中。
- 解决方案1:缓存空对象。当从数据库查不到时,在Redis里也缓存一个空值(如
缓存击穿:某个热点Key过期瞬间,大量请求同时涌向数据库。比如,一个销量第一的“招牌黄焖鸡”的菜品详情Key
dish:888在高峰时段过期了,瞬间所有用户请求都去查数据库。- 解决方案1:互斥锁。当发现缓存失效时,不是所有线程都去查数据库,而是只有一个线程(通过Redis的
SETNX命令实现分布式锁)去查库并回写缓存,其他线程等待锁释放后重新读取缓存。这是最经典的方案。 - 解决方案2:逻辑过期。不给缓存数据设置物理过期时间,而是将过期时间作为一个字段存储在Value中。当发现数据逻辑过期时,同样使用互斥锁,由一个线程去异步更新缓存,其他线程直接返回旧的、逻辑上已过期的数据。这种方式可以避免缓存失效瞬间的卡顿,用户体验更好。对于菜品详情这种允许短暂不一致的数据很适用。
- 解决方案1:互斥锁。当发现缓存失效时,不是所有线程都去查数据库,而是只有一个线程(通过Redis的
缓存雪崩:大量Key在同一时间点或时间段内过期,导致所有请求都打到数据库。比如,系统初始化时批量加载的商家信息,都设置了相同的1小时过期时间,1小时后集体失效。
- 解决方案:差异化过期时间。这是治本之策。在设置缓存过期时间时,使用一个基础时间加上一个随机偏移量。例如:
TTL = 3600 + Random(-300, 300),这样Key的过期时间就均匀分布在1小时前后5分钟内,避免了集体失效。
- 解决方案:差异化过期时间。这是治本之策。在设置缓存过期时间时,使用一个基础时间加上一个随机偏移量。例如:
实操心得:在实际项目中,我们通常会组合使用这些方案。例如,对于核心的、访问量大的数据(如热门商家信息),采用“逻辑过期+互斥锁”防击穿;对于可能不存在的查询(如根据手机号查用户),使用“布隆过滤器”防穿透;对于所有缓存Key,强制使用随机过期时间防雪崩。把这些策略写成公司内部的缓存组件规范,能避免很多线上问题。
3.2 Redis淘汰策略与内存管理
Redis内存满了怎么办?这是面试高频题。Redis提供了8种淘汰策略,由配置项maxmemory-policy控制:
| 策略 | 含义 | 适用场景 |
|---|---|---|
| noeviction | 不淘汰,新写入操作报错。 | 对数据一致性要求极高,宁愿报错也不能丢数据的场景。(生产环境慎用) |
| allkeys-lru | 从所有Key中,淘汰最近最少使用的。 | 最常用。适用于缓存场景,希望保留热点数据。 |
| volatile-lru | 从设置了过期时间的Key中,淘汰最近最少使用的。 | 缓存数据有明确的生命周期,且希望保留热点数据。 |
| allkeys-random | 从所有Key中,随机淘汰。 | 所有Key访问概率差不多,无明确热点。 |
| volatile-random | 从设置了过期时间的Key中,随机淘汰。 | 有过期时间的Key访问概率差不多。 |
| volatile-ttl | 从设置了过期时间的Key中,淘汰剩余生存时间最短的。 | 希望尽快清理掉即将过期的数据,为新数据腾空间。 |
| allkeys-lfu | 从所有Key中,淘汰最不经常使用的。 | Redis 4.0+。能更好地区分高频和低频访问,比LRU更精准。 |
| volatile-lfu | 从设置了过期时间的Key中,淘汰最不经常使用的。 | Redis 4.0+。同上,但只针对有过期时间的Key。 |
如何选择?对于“苍穹外卖”这类缓存系统,allkeys-lru或allkeys-lfu通常是首选。因为我们的目标是尽可能让热点数据(热门商家、热销菜品)留在内存中。如果你能明确区分“缓存数据”(可丢)和“持久数据”(不可丢),可以将持久数据不设过期时间,然后使用volatile-lru策略,这样只会淘汰缓存数据。
内存优化实战技巧:
- 监控与预警:使用
info memory命令监控used_memory和maxmemory,设置水位线告警(如80%),提前扩容或分析大Key。 - 警惕Big Key:一个Key对应的Value过大(如一个存储了10万条用户ID的Set),会导致操作阻塞、网络流量暴增、内存不均。对于外卖系统,不要用一个Key存储全城所有骑手位置,而应按区域拆分。
- 善用数据结构:比如存储用户签到,用String存一个月的签到记录需要30个Key,而用BitMap只需要1个Key,极大节省内存。存储用户点赞关系,用Set可能很大,如果只需要判断是否存在,可以考虑用BloomFilter替代。
3.3 Redis持久化:RDB与AOF的抉择
Redis是内存数据库,数据如何不丢?这就涉及到持久化。两种主流方式:RDB和AOF。
- RDB:在指定时间间隔生成数据集的时间点快照。文件紧凑,恢复速度快。但会丢失最后一次快照后的所有数据。
- AOF:记录每个写操作命令,以日志形式追加。数据安全性高,最多丢失1秒数据(
appendfsync everysec配置下)。但文件体积大,恢复速度慢。
生产环境常用策略:两者结合使用。
- 使用AOF作为主持久化方式,保证数据安全。配置为
appendfsync everysec,在性能和数据安全间取得平衡。 - 定期(如每天)手动执行
BGSAVE命令创建RDB快照,用于历史备份、灾难恢复和数据迁移,因为RDB文件更小、恢复更快。 - 定期(如每周)对AOF文件进行重写(
BGREWRITEAOF),压缩文件体积。
在外卖系统中,订单状态、支付信息等核心数据不容有失,必须依赖AOF的持久化能力。而商家菜单等相对静态的数据,即使有少量丢失,也可以通过后台系统快速重建。
4. MySQL实战:支撑订单生命周期的数据库设计
Redis再快,数据的“单点真理”最终还是要落在MySQL上。外卖系统的数据库设计,核心在于如何高效、准确地管理订单的生命周期。
4.1 订单表的核心设计思路与索引优化
订单表是核心中的核心。设计时需要考虑几个关键点:
- 分库分表:订单量巨大,必须考虑水平拆分。常见的分片键是
user_id(按用户哈希)或order_id(包含时间戳的雪花ID)。按user_id分,方便查询用户历史订单。按order_id分,数据分布更均匀。实操中,我们常采用order_id分片,同时为user_id创建全局二级索引(如通过ES或另一张索引表)来支持用户维度的查询。 - 字段设计:
order_id:主键,使用分布式ID生成器(雪花算法)。user_id,shop_id:外键,建立索引。status:订单状态(1待支付、2已支付、3商家接单、4骑手取餐、5配送中、6已完成、7已取消)。这是最频繁的查询和更新字段之一。amount:订单金额。create_time,update_time:创建和更新时间。address_id:配送地址。rider_id:骑手ID。- 冗余字段:为了减少关联查询,通常会冗余存储
user_name,shop_name,shop_address等。用空间换时间,在互联网高并发场景下是常见做法。
- 索引优化:
- 主键索引:
order_id, 聚簇索引。 - 联合索引:
(user_id, create_time) DESC。这是查询“我的订单列表”最常用的SQL:SELECT * FROM orders WHERE user_id = ? ORDER BY create_time DESC LIMIT 0, 20。这个索引能完美覆盖查询和排序。 - 单列索引:
shop_id,status,rider_id。用于商家后台查单、按状态筛选订单、骑手查单等场景。 - 避免无效索引:像
status这种区分度很低的字段(可能90%的订单都是“已完成”),单独建索引效果可能很差。但如果和create_time组成联合索引(status, create_time),用于查询“今天未完成的订单”,效果就会很好。这就是索引左前缀原则的应用。
- 主键索引:
踩坑记录:曾经有个慢查询,是商家后台的“订单管理”页面查询很慢。排查发现,查询条件是
shop_id = ? AND status IN (3,4,5) AND create_time BETWEEN ? AND ?,但索引只建了(shop_id)。当商家订单量达到百万级时,这个查询需要回表数十万次。后来我们建立了(shop_id, status, create_time)的联合索引,查询速度从数秒降到几十毫秒。核心原则:索引的设计必须贴合最核心的查询SQL。
4.2 事务与并发控制:确保订单状态正确流转
订单状态从“待支付”到“已完成”,涉及多次更新,必须保证原子性和一致性。这里最经典的并发问题就是“超卖”和“状态机错乱”。
利用数据库事务:任何涉及订单核心状态变更和库存扣减的操作,都必须放在一个数据库事务中。例如,用户支付成功的回调接口中,要执行“更新订单状态为已支付”和“扣减菜品库存”两个操作,必须原子化。
START TRANSACTION; UPDATE orders SET status = 2 WHERE order_id = ? AND status = 1; -- 乐观锁,确保状态是从1变为2 UPDATE dish SET stock = stock - ? WHERE dish_id = ? AND stock >= ?; -- 防止超卖 COMMIT;如果更新行数为0,说明订单状态不对或库存不足,需要回滚并给用户明确提示。
使用乐观锁:如上例所示,在UPDATE语句的WHERE条件中加入状态判断,这就是一种乐观锁的实现。它比悲观锁(
SELECT ... FOR UPDATE)性能更好,在高并发场景下更推荐。状态机设计:订单状态必须有清晰、严谨的流转规则。例如,“已取消”的订单不能再变为“配送中”。这需要在业务代码层做严格校验。可以定义一个
OrderStatusEnum枚举,并维护一个状态转移矩阵(Map<当前状态, Set<下一合法状态>>),在变更状态前进行校验。
4.3 读写分离与分库分表实践
当单表数据超过千万,或QPS超过单库承受能力时,就必须考虑拆分。
- 读写分离:这是第一步。利用MySQL主从复制,将写操作(下单、更新状态)指向主库,将大量的读操作(查订单、查商家)指向多个从库。通过中间件(如ShardingSphere-Proxy、MyCat)或客户端框架(如ShardingSphere-JDBC)可以透明地实现。
- 分库分表:
- 水平分表:如前所述,按
order_id或user_id的哈希值,将订单表拆分到多个物理表(如order_00到order_15)。 - 垂直分库:将不同业务域的表拆分到不同的数据库实例。例如,将用户、订单相关的表放在“交易库”,将商家、菜品相关的表放在“商品库”,将骑手、轨迹相关的表放在“运力库”。这样可以降低单库压力,也方便不同团队维护。
- 水平分表:如前所述,按
实施难点与解决方案:
- 分布式事务:一个下单操作,可能涉及“交易库”写订单、“商品库”扣库存。这就需要分布式事务解决方案,如Seata的AT模式、或基于消息队列的最终一致性方案(更常用)。例如,扣减库存成功后,发送一条MQ消息,订单服务监听消息再创建订单。
- 全局唯一ID:分表后,数据库自增ID不可用,必须使用分布式ID生成器,如雪花算法。
- 跨分片查询:例如,运营想查全平台某一天的订单总额。解决方案是:1)建立专门的OLAP数仓,定时同步数据;2)使用Elasticsearch等搜索引擎建立二级索引,进行复杂查询。
5. 高并发基石:深入理解IO多路复用
当你的外卖APP有百万用户在线,服务器是如何同时处理这么多网络连接的?答案就是IO多路复用。这是理解Redis、Nginx、Netty这些高性能中间件为何如此之快的钥匙。
5.1 从BIO到NIO:演进之路
- BIO:同步阻塞IO。为每个连接创建一个线程。连接少时没问题,但连接数上万时,线程上下文切换的开销巨大,内存耗尽。这就是经典的“C10K问题”。
- NIO:同步非阻塞IO。核心是
Selector(选择器)。一个线程可以管理多个连接(Channel)。线程不断轮询Selector,看哪些Channel上有事件(连接、读、写)就绪,然后只处理这些就绪的事件。这样,一个线程就能处理大量连接。- 外卖场景联想:你的服务器就是一个餐厅前台(Selector),有很多外卖骑手(Channel)在等待。传统方式(BIO)是每个骑手配一个服务员(线程)。现在,只有一个前台,骑手来了登记一下要做什么(注册事件),然后就去旁边等着。前台不断看大屏幕(轮询),哪个骑手的餐好了(事件就绪),就叫他来取。效率极大提升。
5.2 Reactor模式:高性能网络编程的骨架
IO多路复用是机制,Reactor模式是使用这一机制的设计模式。它定义了事件分发和处理的架构。
- 单Reactor单线程:Redis 6.0之前的工作模式。所有事件(连接、读写)都由一个线程处理。简单高效,但无法利用多核,且一个慢操作会阻塞所有客户端。
- 单Reactor多线程:一个线程(主Reactor)只负责接收新连接,然后将建立好的连接分发给多个工作线程(SubReactor)去处理IO读写和业务逻辑。这是更常见的模式。
- 主从Reactor多线程:Netty、Nginx采用。有主从两组Reactor。主Reactor负责接收连接,然后分发给多个从Reactor。每个从Reactor在一个独立线程中运行,负责管理一批连接的IO事件。业务逻辑可能再交给额外的线程池处理。这种模式将连接建立和IO处理进一步分离,扩展性最强。
为什么Redis之前用单线程?因为Redis的操作都是内存操作,速度极快,瓶颈在网络IO和内存访问,而不是CPU。单线程避免了多线程的锁竞争和上下文切换开销,反而使实现简单、性能稳定。Redis 6.0引入多线程,主要是为了处理网络IO的读写(这部分仍然是多路复用),而命令执行依然是单线程,保证了原子性。
5.3 在外卖系统中的应用
- Web服务器:你的Spring Boot应用,底层通过Tomcat或Netty处理HTTP请求,它们都使用了NIO和Reactor模式来支持高并发连接。
- 消息推送:向骑手和商家实时推送订单状态变更,通常使用WebSocket或长轮询。服务端维持海量长连接,正是IO多路复用的用武之地。Netty是构建此类服务的首选框架。
- RPC框架:微服务间的调用,如订单服务调用支付服务,底层的网络通信库(如Dubbo使用的Netty, gRPC)也基于此模型。
理解IO多路复用,能让你在面试中解释清楚“为什么我的服务能抗住高并发”,而不仅仅是说“我们用了Redis和MQ”。
6. 系统设计实战:构建一个抗压的外卖订单系统
让我们把上面的知识点串起来,设计一个简化的“下单-支付-推送”流程,看看如何应用这些技术。
6.1 下单流程的缓存与数据库协同
查询菜品库存(读多):
- 请求到达网关,先查询Redis缓存:
GET dish_stock:123。 - 如果缓存命中,直接返回。
- 如果缓存未命中(穿透),查询数据库,并将结果写入Redis,设置随机过期时间(如5分钟+随机数)。对于不存在的菜品ID,缓存空值短时间。
- 防击穿:对于热门菜品,在缓存未命中时,使用Redis分布式锁,只让一个请求去查库回种缓存。
- 请求到达网关,先查询Redis缓存:
创建订单(写):
- 生成分布式订单ID。
- 在一个数据库事务中:插入订单主表、插入订单明细表、扣减菜品库存(数据库行锁或乐观锁保证)。
- 事务成功后,异步执行以下操作:
- 将订单信息放入Redis缓存(Key如
order:${orderId}),设置过期时间(如30分钟),方便用户快速查询。 - 更新Redis中该商家的“今日订单数”等统计信息(使用
INCR命令)。 - 清除或更新相关菜品的库存缓存(
DEL dish_stock:123),保证下次读取时获取最新数据。
- 将订单信息放入Redis缓存(Key如
6.2 订单状态推送与异步处理
支付回调:
- 支付平台回调通知支付成功。
- 更新订单状态为“已支付”。这里要防重:先查Redis(或数据库)判断该订单是否已处理过,或使用数据库乐观锁(
UPDATE ... WHERE status=待支付)。 - 状态更新后,向消息队列(如RocketMQ/Kafka)发送一条事件消息,主题为
ORDER_PAID,消息体包含orderId。
消息驱动与推送:
- 商家接单系统:订阅
ORDER_PAID消息,通知商家有新订单。商家端通过WebSocket长连接(基于Netty实现,IO多路复用管理连接)接收实时通知。 - 骑手调度系统:也订阅
ORDER_PAID消息,进行智能派单或抢单。 - 用户端推送:用户APP通过长连接等待订单状态更新。当订单服务更新状态后,会通过WebSocket网关向指定用户连接推送消息。
- 商家接单系统:订阅
这样设计的好处:
- 解耦:支付回调服务只负责更新状态和发消息,不关心谁来处理。新增一个需要感知支付成功的服务(如发优惠券),只需订阅消息即可。
- 削峰填谷:高峰期的支付成功消息可以堆积在MQ中,下游服务按能力消费,避免被冲垮。
- 最终一致性:通过消息队列保证了跨服务的数据最终一致。
7. 面试复盘与深度问题准备
最后,我们来模拟一下面试官可能追问的深度问题,并给出回答思路。
7.1 缓存与数据库双写一致性问题
问题:更新数据库后,是先更新缓存还是先删除缓存?如果删除缓存失败怎么办?
回答思路(结合外卖场景): 这是一个经典难题,没有银弹,只有权衡。常用策略是“Cache Aside Pattern”的变种。
- 首选策略:先更新数据库,再删除缓存。
- 原因:先删缓存再更新数据库,在并发下更容易导致长时间的数据不一致(另一个线程在缓存删除后、数据库更新前读到了旧值并回种了缓存)。
- 外卖场景:更新商家地址。先更新数据库,再删除
shop:info:${id}缓存。即使删除缓存失败,顶多是下次读到旧地址(短暂不一致),可以通过缓存过期或后续重试来修正。
- 保证最终一致性:删除缓存可能失败。可以:
- 重试机制:将删除失败的Key放入消息队列,异步重试删除。
- 设置合理的过期时间:给缓存数据设置一个不太长的过期时间(如10分钟),作为兜底,确保最终一致。
- 监听数据库Binlog:使用Canal等工具监听MySQL的Binlog,当感知到数据变更时,自动删除或更新对应的缓存。这是一劳永逸的方案,但对架构复杂度要求高。
7.2 如何设计一个分布式锁?
问题:除了用Redis的SETNX,分布式锁还要考虑什么?
回答思路:SETNX只是基础,生产环境必须考虑完备。
- 原子性加锁与设置过期时间:必须用一条命令完成,防止设置过期时间前客户端崩溃导致死锁。Redis 2.8后可以用:
SET lock_key unique_value NX PX 30000。 - 唯一标识与防误删:锁的值必须是一个唯一标识(如UUID+线程ID)。解锁时要用Lua脚本先判断当前锁的值是否是自己设置的,再删除。防止误删其他客户端的锁。
- 锁续期:如果业务执行时间可能超过锁过期时间,需要有一个“看门狗”线程定时续期。Redisson客户端库实现了这个逻辑。
- 高可用:单点Redis宕机会导致锁失效。可以考虑RedLock算法(多个独立Redis实例),但争议较大。更主流的是用ZooKeeper或etcd的临时有序节点来实现,但性能不如Redis。选择取决于场景:要性能选Redis(配合红锁或集群),要强一致选ZooKeeper。
7.3 如果Redis集群挂了,如何降级?
问题:缓存全盘失效,流量直接打到数据库,如何保护系统?
回答思路:这是关于系统弹性和高可用的思考。
- 事前预防:
- 集群与分片:使用Redis Cluster或Codis,避免单点故障。
- 多级缓存:本地缓存(如Caffeine) + Redis缓存。即使Redis挂掉,本地缓存还能扛一部分热点请求。
- 缓存预热:在系统启动或低峰期,提前加载热点数据到缓存。
- 事中熔断与降级:
- 熔断器:使用Hystrix或Sentinel,当访问Redis的失败率达到阈值,快速失败,直接走降级逻辑(如查数据库,但限流),避免线程池被拖垮。
- 降级逻辑:缓存失效时,业务上能否接受返回默认值、简化数据或排队提示?例如,外卖菜品详情页,如果缓存挂了,可以降级为只返回基础信息(名称、价格),不返回复杂的描述和图片。
- 事后快速恢复:
- 备份与恢复:有最近的RDB/AOF备份可以快速恢复数据。
- 流量限制:缓存恢复期间,通过网关对非核心接口进行限流,优先保障核心下单、支付链路。
准备“苍穹外卖”面试题,本质上是在梳理一个后端工程师面对高并发、大数据量、分布式环境时的核心知识体系和解决方案。它要求你不仅知道工具怎么用,更要理解工具背后的原理,以及如何在具体的业务场景中做出合理的技术选型和架构折衷。希望这篇长文能帮你把散落的知识点串联成网,在面试中展现出你系统性的思考能力。记住,最好的准备就是理解原理,并结合实际场景去思考和表达。
