三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

从Redis缓存到MySQL索引:拆解高并发外卖系统后端核心设计

从Redis缓存到MySQL索引:拆解高并发外卖系统后端核心设计

1. 项目概述:从“苍穹外卖”面试题看后端工程师的核心能力栈

最近在帮团队筛选候选人,也和一些朋友交流面试准备,发现“苍穹外卖”这个项目在面试中出现的频率相当高。它不像一个简单的CRUD项目,更像是一个微缩版的、五脏俱全的真实线上系统,涵盖了从用户下单、商家接单、骑手配送、支付结算到数据统计的全链路。面试官通过它,考察的绝不仅仅是你会不会写接口,更深层次的是你对一个完整业务系统的理解深度、技术选型的思考以及应对高并发场景的实战能力。

我自己也复盘过,围绕“苍穹外卖”的面试题,核心其实就几个关键词:RedisMySQL缓存穿透淘汰机制IO多路复用。这几个词串起来,几乎就是后端工程师从数据存储、性能优化到系统稳定性的核心知识图谱。今天,我就结合自己这些年踩过的坑和带人的经验,把这些高频考点掰开揉碎了讲一讲,希望能帮你构建一个清晰、有深度的知识体系,而不仅仅是背几个面试题答案。

2. 核心需求解析:为什么面试总爱问这些?

面试官抛出“苍穹外卖”这个场景,本质上是在模拟一个典型的、有挑战性的互联网应用。我们得先理解这个场景下的核心业务压力和技术挑战,才能明白为什么那些技术点是必考的。

2.1 业务场景下的技术挑战

想象一下“苍穹外卖”的日常:午高峰和晚高峰,成千上万的用户同时浏览商家菜单、下单、查看订单状态。这个过程中,系统面临几个核心压力点:

  1. 极高的读多写少比例:用户浏览菜单、查看商家评分、查询订单状态的频率,远高于下单支付的频率。这意味着缓存是提升性能、降低数据库压力的第一道也是最重要的一道防线。
  2. 数据的强一致性与最终一致性并存:订单状态(如“已支付”、“配送中”、“已完成”)需要强一致性,用户和商家需要立刻看到准确状态。而商家评分、销量统计这类数据,则可以接受短时间内的最终一致性,适合用缓存+异步更新策略。
  3. 瞬时高并发与热点数据:热门商家的菜单、爆款菜品,在高峰时段会被海量请求同时访问。如果所有请求都打到数据库,数据库连接池瞬间就会被撑爆,这就是典型的缓存穿透缓存击穿问题。
  4. 系统资源的有效管理与回收:缓存不能无限增长。哪些数据应该被优先保留(如热门商家信息),哪些数据可以淘汰(如很久没人浏览的冷门菜品),这就需要合理的缓存淘汰机制
  5. 海量连接的高效处理:外卖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_idshop_idorder_id预热到布隆过滤器中。
  • 缓存击穿某个热点Key过期瞬间,大量请求同时涌向数据库。比如,一个销量第一的“招牌黄焖鸡”的菜品详情Keydish:888在高峰时段过期了,瞬间所有用户请求都去查数据库。

    • 解决方案1:互斥锁。当发现缓存失效时,不是所有线程都去查数据库,而是只有一个线程(通过Redis的SETNX命令实现分布式锁)去查库并回写缓存,其他线程等待锁释放后重新读取缓存。这是最经典的方案。
    • 解决方案2:逻辑过期。不给缓存数据设置物理过期时间,而是将过期时间作为一个字段存储在Value中。当发现数据逻辑过期时,同样使用互斥锁,由一个线程去异步更新缓存,其他线程直接返回旧的、逻辑上已过期的数据。这种方式可以避免缓存失效瞬间的卡顿,用户体验更好。对于菜品详情这种允许短暂不一致的数据很适用。
  • 缓存雪崩大量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-lruallkeys-lfu通常是首选。因为我们的目标是尽可能让热点数据(热门商家、热销菜品)留在内存中。如果你能明确区分“缓存数据”(可丢)和“持久数据”(不可丢),可以将持久数据不设过期时间,然后使用volatile-lru策略,这样只会淘汰缓存数据。

内存优化实战技巧:

  1. 监控与预警:使用info memory命令监控used_memorymaxmemory,设置水位线告警(如80%),提前扩容或分析大Key。
  2. 警惕Big Key:一个Key对应的Value过大(如一个存储了10万条用户ID的Set),会导致操作阻塞、网络流量暴增、内存不均。对于外卖系统,不要用一个Key存储全城所有骑手位置,而应按区域拆分。
  3. 善用数据结构:比如存储用户签到,用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 订单表的核心设计思路与索引优化

订单表是核心中的核心。设计时需要考虑几个关键点:

  1. 分库分表:订单量巨大,必须考虑水平拆分。常见的分片键是user_id(按用户哈希)或order_id(包含时间戳的雪花ID)。按user_id分,方便查询用户历史订单。按order_id分,数据分布更均匀。实操中,我们常采用order_id分片,同时为user_id创建全局二级索引(如通过ES或另一张索引表)来支持用户维度的查询。
  2. 字段设计
    • 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_nameshop_nameshop_address等。用空间换时间,在互联网高并发场景下是常见做法。
  3. 索引优化
    • 主键索引order_id, 聚簇索引。
    • 联合索引(user_id, create_time) DESC。这是查询“我的订单列表”最常用的SQL:SELECT * FROM orders WHERE user_id = ? ORDER BY create_time DESC LIMIT 0, 20。这个索引能完美覆盖查询和排序。
    • 单列索引shop_idstatusrider_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 事务与并发控制:确保订单状态正确流转

订单状态从“待支付”到“已完成”,涉及多次更新,必须保证原子性和一致性。这里最经典的并发问题就是“超卖”和“状态机错乱”。

  1. 利用数据库事务:任何涉及订单核心状态变更和库存扣减的操作,都必须放在一个数据库事务中。例如,用户支付成功的回调接口中,要执行“更新订单状态为已支付”和“扣减菜品库存”两个操作,必须原子化。

    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,说明订单状态不对或库存不足,需要回滚并给用户明确提示。

  2. 使用乐观锁:如上例所示,在UPDATE语句的WHERE条件中加入状态判断,这就是一种乐观锁的实现。它比悲观锁(SELECT ... FOR UPDATE)性能更好,在高并发场景下更推荐。

  3. 状态机设计:订单状态必须有清晰、严谨的流转规则。例如,“已取消”的订单不能再变为“配送中”。这需要在业务代码层做严格校验。可以定义一个OrderStatusEnum枚举,并维护一个状态转移矩阵(Map<当前状态, Set<下一合法状态>>),在变更状态前进行校验。

4.3 读写分离与分库分表实践

当单表数据超过千万,或QPS超过单库承受能力时,就必须考虑拆分。

  1. 读写分离:这是第一步。利用MySQL主从复制,将写操作(下单、更新状态)指向主库,将大量的读操作(查订单、查商家)指向多个从库。通过中间件(如ShardingSphere-Proxy、MyCat)或客户端框架(如ShardingSphere-JDBC)可以透明地实现。
  2. 分库分表
    • 水平分表:如前所述,按order_iduser_id的哈希值,将订单表拆分到多个物理表(如order_00order_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 在外卖系统中的应用

  1. Web服务器:你的Spring Boot应用,底层通过Tomcat或Netty处理HTTP请求,它们都使用了NIO和Reactor模式来支持高并发连接。
  2. 消息推送:向骑手和商家实时推送订单状态变更,通常使用WebSocket或长轮询。服务端维持海量长连接,正是IO多路复用的用武之地。Netty是构建此类服务的首选框架。
  3. RPC框架:微服务间的调用,如订单服务调用支付服务,底层的网络通信库(如Dubbo使用的Netty, gRPC)也基于此模型。

理解IO多路复用,能让你在面试中解释清楚“为什么我的服务能抗住高并发”,而不仅仅是说“我们用了Redis和MQ”。

6. 系统设计实战:构建一个抗压的外卖订单系统

让我们把上面的知识点串起来,设计一个简化的“下单-支付-推送”流程,看看如何应用这些技术。

6.1 下单流程的缓存与数据库协同

  1. 查询菜品库存(读多)

    • 请求到达网关,先查询Redis缓存:GET dish_stock:123
    • 如果缓存命中,直接返回。
    • 如果缓存未命中(穿透),查询数据库,并将结果写入Redis,设置随机过期时间(如5分钟+随机数)。对于不存在的菜品ID,缓存空值短时间。
    • 防击穿:对于热门菜品,在缓存未命中时,使用Redis分布式锁,只让一个请求去查库回种缓存。
  2. 创建订单(写)

    • 生成分布式订单ID。
    • 在一个数据库事务中:插入订单主表、插入订单明细表、扣减菜品库存(数据库行锁或乐观锁保证)。
    • 事务成功后,异步执行以下操作:
      • 将订单信息放入Redis缓存(Key如order:${orderId}),设置过期时间(如30分钟),方便用户快速查询。
      • 更新Redis中该商家的“今日订单数”等统计信息(使用INCR命令)。
      • 清除或更新相关菜品的库存缓存(DEL dish_stock:123),保证下次读取时获取最新数据。

6.2 订单状态推送与异步处理

  1. 支付回调

    • 支付平台回调通知支付成功。
    • 更新订单状态为“已支付”。这里要防重:先查Redis(或数据库)判断该订单是否已处理过,或使用数据库乐观锁(UPDATE ... WHERE status=待支付)。
    • 状态更新后,向消息队列(如RocketMQ/Kafka)发送一条事件消息,主题为ORDER_PAID,消息体包含orderId
  2. 消息驱动与推送

    • 商家接单系统:订阅ORDER_PAID消息,通知商家有新订单。商家端通过WebSocket长连接(基于Netty实现,IO多路复用管理连接)接收实时通知。
    • 骑手调度系统:也订阅ORDER_PAID消息,进行智能派单或抢单。
    • 用户端推送:用户APP通过长连接等待订单状态更新。当订单服务更新状态后,会通过WebSocket网关向指定用户连接推送消息。

这样设计的好处

  • 解耦:支付回调服务只负责更新状态和发消息,不关心谁来处理。新增一个需要感知支付成功的服务(如发优惠券),只需订阅消息即可。
  • 削峰填谷:高峰期的支付成功消息可以堆积在MQ中,下游服务按能力消费,避免被冲垮。
  • 最终一致性:通过消息队列保证了跨服务的数据最终一致。

7. 面试复盘与深度问题准备

最后,我们来模拟一下面试官可能追问的深度问题,并给出回答思路。

7.1 缓存与数据库双写一致性问题

问题:更新数据库后,是先更新缓存还是先删除缓存?如果删除缓存失败怎么办?

回答思路(结合外卖场景): 这是一个经典难题,没有银弹,只有权衡。常用策略是“Cache Aside Pattern”的变种。

  1. 首选策略:先更新数据库,再删除缓存
    • 原因:先删缓存再更新数据库,在并发下更容易导致长时间的数据不一致(另一个线程在缓存删除后、数据库更新前读到了旧值并回种了缓存)。
    • 外卖场景:更新商家地址。先更新数据库,再删除shop:info:${id}缓存。即使删除缓存失败,顶多是下次读到旧地址(短暂不一致),可以通过缓存过期或后续重试来修正。
  2. 保证最终一致性:删除缓存可能失败。可以:
    • 重试机制:将删除失败的Key放入消息队列,异步重试删除。
    • 设置合理的过期时间:给缓存数据设置一个不太长的过期时间(如10分钟),作为兜底,确保最终一致。
    • 监听数据库Binlog:使用Canal等工具监听MySQL的Binlog,当感知到数据变更时,自动删除或更新对应的缓存。这是一劳永逸的方案,但对架构复杂度要求高。

7.2 如何设计一个分布式锁?

问题:除了用Redis的SETNX,分布式锁还要考虑什么?

回答思路SETNX只是基础,生产环境必须考虑完备。

  1. 原子性加锁与设置过期时间:必须用一条命令完成,防止设置过期时间前客户端崩溃导致死锁。Redis 2.8后可以用:SET lock_key unique_value NX PX 30000
  2. 唯一标识与防误删:锁的值必须是一个唯一标识(如UUID+线程ID)。解锁时要用Lua脚本先判断当前锁的值是否是自己设置的,再删除。防止误删其他客户端的锁。
  3. 锁续期:如果业务执行时间可能超过锁过期时间,需要有一个“看门狗”线程定时续期。Redisson客户端库实现了这个逻辑。
  4. 高可用:单点Redis宕机会导致锁失效。可以考虑RedLock算法(多个独立Redis实例),但争议较大。更主流的是用ZooKeeper或etcd的临时有序节点来实现,但性能不如Redis。选择取决于场景:要性能选Redis(配合红锁或集群),要强一致选ZooKeeper。

7.3 如果Redis集群挂了,如何降级?

问题:缓存全盘失效,流量直接打到数据库,如何保护系统?

回答思路:这是关于系统弹性和高可用的思考。

  1. 事前预防
    • 集群与分片:使用Redis Cluster或Codis,避免单点故障。
    • 多级缓存:本地缓存(如Caffeine) + Redis缓存。即使Redis挂掉,本地缓存还能扛一部分热点请求。
    • 缓存预热:在系统启动或低峰期,提前加载热点数据到缓存。
  2. 事中熔断与降级
    • 熔断器:使用Hystrix或Sentinel,当访问Redis的失败率达到阈值,快速失败,直接走降级逻辑(如查数据库,但限流),避免线程池被拖垮。
    • 降级逻辑:缓存失效时,业务上能否接受返回默认值、简化数据或排队提示?例如,外卖菜品详情页,如果缓存挂了,可以降级为只返回基础信息(名称、价格),不返回复杂的描述和图片。
  3. 事后快速恢复
    • 备份与恢复:有最近的RDB/AOF备份可以快速恢复数据。
    • 流量限制:缓存恢复期间,通过网关对非核心接口进行限流,优先保障核心下单、支付链路。

准备“苍穹外卖”面试题,本质上是在梳理一个后端工程师面对高并发、大数据量、分布式环境时的核心知识体系和解决方案。它要求你不仅知道工具怎么用,更要理解工具背后的原理,以及如何在具体的业务场景中做出合理的技术选型和架构折衷。希望这篇长文能帮你把散落的知识点串联成网,在面试中展现出你系统性的思考能力。记住,最好的准备就是理解原理,并结合实际场景去思考和表达。

← 返回列表