Skip to content

2)Redis

🌱 第 1 题(基础认知)

Q1:你能简单说一下 Redis 是做什么的吗?它为什么快?

内存型键值数据库(NoSQL)。

✅ 预期回答方向:

  • Redis 是一个开源的内存数据结构存储系统;
  • 常用作缓存、中间件;
  • 快的原因主要是内存 + 单线程 + 高效的数据结构。
    • (6.0+ 支持多线程 I/O)

⭐ 加分点:

  • 提到 Redis 支持多种数据结构;
  • 提及“IO 多路复用”机制。

❌ 减分项:

  • 回答模糊,如“就是数据库”;
  • 不知道 Redis 和 MySQL 的区别。

📝 标准参考答案:

Redis 是一个基于内存的、支持多种数据结构的高性能 Key-Value 存储系统
它常用于缓存加速、分布式锁、消息队列、排行榜、会话存储等场景

它之所以快,主要有以下几点原因:

  1. 数据存储在内存中,访问速度远高于磁盘 IO;
  2. 使用单线程模型,避免了上下文切换带来的开销;
  3. 基于高效的数据结构(如跳表、哈希表、压缩列表等);
  4. 使用 IO 多路复用技术(epoll)提高了并发性能。

🌿 第 2 题(数据结构与应用)

Q2:Redis 支持哪些常用数据结构?你用过它们在哪些场景?

✅ 预期回答方向:

  • 讲出 5 种核心数据结构:String、List、Set、Hash、ZSet;
  • 能结合业务场景。

⭐ 加分点:

  • 额外提到 BitMap、HyperLogLog、Geo、Stream;
  • 举出多个具体场景。

❌ 减分项:

  • 不知道 Set 和 ZSet 的区别;
  • 场景模糊,如“都用来存缓存”。

📝 标准参考答案:

Redis 支持多种数据结构,我在项目中常用的有:

  1. String:最常见的键值对形式,用于缓存页面数据、Token、验证码等;
  2. Hash:类似对象结构,用于缓存用户信息,如 user:123 => {name:张三, age:20}
  3. List:有序列表,可用作消息队列,如异步日志、任务列表;
  4. Set:无序集合,常用于去重,如“每日签到用户 ID 集合”;
  5. ZSet(有序集合):支持按照分数排序,比如用于“积分排行榜”、“活跃度榜单”;

进阶结构中,我还用过:

  • BitMap:用于统计用户登录状态(0/1);
  • HyperLogLog:用于网页的 UV 去重计数;
  • Stream:用于日志流/消息流处理。

对比表格

结构有序性唯一性核心能力时间复杂度典型场景
String键唯一存储简单数据、原子操作O(1)缓存、计数器
List是(顺序)元素可重复双向操作、顺序访问头尾操作 O(1),中间 O(n)消息队列、操作日志
Set元素唯一集合运算(交并差)添加/查询 O(1)标签、去重
Hash字段唯一结构化数据存储、字段级操作字段操作 O(1)用户信息、商品属性
ZSet是(分数)元素唯一按分数排序、范围查询插入/查询 O(log n)排行榜、优先级队列

选择建议

  1. 需要简单键值存储String
  2. 维护顺序且允许重复List
  3. 去重且无需顺序Set
  4. 存储对象属性Hash
  5. 按分数排序或范围查询ZSet

🌳 第 3 题(缓存机制)

Q3:Redis 做缓存时,如何防止缓存穿透、击穿、雪崩?

✅ 预期回答方向:

  • 逐个解释三种问题;
  • 给出具体解决方案。

⭐ 加分点:

  • 使用布隆过滤器、互斥锁、预热、降级等策略;
  • 有实战经验。

❌ 减分项:

  • 混淆三个概念;
  • 策略空泛,不落地。

📝 标准参考答案:

这三个是 Redis 缓存的常见问题,解决方案如下:

  1. 缓存穿透:指请求的数据在数据库也不存在,导致每次都打数据库。
    解决方案
  • 布隆过滤器:提前记录所有合法 Key;
  • 缓存空值:如设置“null”值 + 短 TTL;
  1. 缓存击穿:某个热点 key 正好过期,瞬间大量请求打向数据库。
    解决方案
  • 加互斥锁(互斥缓存);
  • 设置随机过期时间,避免同一时间过期;
  1. 缓存雪崩:大量 key 同时过期,数据库被压垮。
    解决方案
  • 给 key 设置随机 TTL(如 60s ± 10s);
  • 分布式预热 / 双写缓存 / 降级处理;

在项目中,我们就遇到过缓存击穿问题,通过使用 Redisson 分布式锁进行互斥构建解决了数据库瞬时压力问题。

名词解析

1. 分布式预热

就像在寒冬来临前,所有供暖站提前烧热管道,分布式预热是系统启动时,将高频数据预先加载到各个节点的缓存中,避免用户首次访问时集体“挤爆”数据库。如同提前给每台电梯预调至早高峰楼层,减少等待时间。

2. 双写缓存

类似寄快递时同时填写电子单和纸质单,双写缓存要求每次更新数据时,同时写入缓存和数据库。这就像餐厅换菜单时,服务员(缓存)和后厨(数据库)同步更新菜名,避免客人点到已下架的菜,但需警惕“手忙脚乱写错单”的一致性问题。

3. 降级处理

如同暴雨天关闭公园游乐设施、只保留避雨亭,降级处理是在系统过载时,暂时关闭非核心功能(如商品评论),优先保障核心服务(如支付)。就像节日期间快递公司暂停“次日达”,确保普通包裹不积压,虽不完美,但能“活着撑过洪峰”。

🌲 第 4 题(高并发场景)

Q4:Redis 是单线程的,为什么还能支撑高并发?有没有并发访问下的问题?

✅ 预期回答方向:

  • 内存操作+高效架构带来的并发能力;
  • 单线程指的是“主线程处理命令”;
  • 并发问题如并发写冲突、事务不原子等。

⭐ 加分点:

  • 提到 IO 多路复用;
  • 提及 Lua 脚本保证原子性;
  • 引用真实场景(如并发秒杀)。

❌ 减分项:

  • 完全不了解线程模型;
  • 以为 Redis 没有并发问题。

📝 标准参考答案:

Redis 使用的是单线程模型(指命令处理线程是单线程的),但它本身非常快,主要因为:

  • 所有数据都在内存中,访问速度极快;
  • 单线程避免了加锁开销,指令串行执行;
  • 使用了 epoll 等 IO 多路复用技术处理网络请求;

所以尽管是单线程,也能支撑数万 QPS

但 Redis 在高并发下也会有一些问题,比如:

  • 并发写冲突:多个客户端同时更新同一个 key 时可能会出现“丢更新”;
  • 原子性问题:多个操作组合执行可能不是原子操作;

解决方式包括:

  • 使用 事务(MULTI/EXEC)
  • 使用 Lua 脚本,保障逻辑原子性;
  • 使用 Redisson 分布式锁,控制访问同步。

🪵 第 5 题(系统设计)

Q5:假设你要用 Redis 实现一个“商品秒杀系统”,你会如何设计?有哪些点是关键的?

✅ 预期回答方向:

  • 控制库存扣减;
  • 高并发控制;
  • 数据一致性与最终落库。

⭐ 加分点:

  • 使用 Lua 脚本原子扣减;
  • 消息队列异步下单;
  • 限流、幂等、日志等细节齐全。

❌ 减分项:

  • 直接从 Redis 扣库存写数据库;
  • 没有幂等设计。

📝 标准参考答案:

商品秒杀系统的核心挑战是高并发下的库存扣减和下单控制。我的设计如下:

  1. 初始化库存:将商品库存预加载到 Redis 中,如 goods_stock:10001 = 500

  2. 请求入口限流:Nginx 或网关层用令牌桶/漏斗算法限流,避免 Redis 瞬时压力过大;

  3. 库存扣减(原子操作)

  • 使用 Lua 脚本在 Redis 中进行库存判断和扣减,确保操作原子;
  • 脚本逻辑:if stock > 0 then decr stock else return 0
  1. 异步下单
  • 将成功抢购的用户写入消息队列(如 Kafka);
  • 后端消费者异步消费并落库,确保数据库不被打挂;
  1. 幂等校验:每个用户只能抢一次,用 Redis Set 或布隆过滤器做幂等控制;

  2. 失败处理和监控

  • 秒杀失败的返回友好提示;
  • 加日志监控、报警和黑名单机制;

实战中,我们还使用了 Redis Stream 做消息缓冲和回溯,整体秒杀系统可以稳定支持 10W+ QPS 并发。

名词解析

幂等性(Idempotence)在数学中,指的是一个操作多次执行所产生的影响与一次执行的影响相同。比如,绝对值函数就是幂等的,因为 abs(abs(x))和 abs(x)结果一样。在计算机领域,特别是在网络请求或数据库操作中,幂等性意味着同样的请求执行一次或多次的结果是一致的,不会因为重复执行而导致意外变化。例如,HTTP 的 GET 请求是幂等的,因为它只是获取数据,而 POST 请求通常不是,因为多次提交可能会创建多个资源。

示例代码(伪代码)

友好提示

java
public Response handleSeckillRequest(User user, Item item) {
    // 预检库存
    if (item.getStock() <= 0) {
        return Response.fail("已售罄,下次早点来哦~");
    }
    // 其他校验(如黑名单、限流)
    if (isUserBlocked(user)) {
        return Response.fail("请求过于频繁,请稍后再试");
    }
    // 执行秒杀逻辑...
}

黑名单机制

技术实现(Redis 为例)

java
// 记录用户失败次数
String key = "seckill:block:user:" + userId;
long count = redis.incr(key);
redis.expire(key, 10, TimeUnit.SECONDS); // 10秒窗口

if (count > 5) {
    // 加入黑名单,封禁5分钟
    redis.set("seckill:blacklist:user:" + userId, "1", 5, TimeUnit.MINUTES);
}

总结

通过 友好提示 → 日志埋点 → 监控报警 → 黑名单拦截 的闭环设计,秒杀系统可实现:

  1. 用户侧:明确反馈,减少无效流量。
  2. 系统侧:快速定位问题,防御恶意攻击。
  3. 业务侧:保障核心交易链路,提升整体稳定性。

最终效果类比

像地铁高峰期的限流措施——

  • 显示屏提示“当前站台拥挤”(友好提示);
  • 监控摄像头统计人流量(日志监控);
  • 触发警报时关闭部分入口(黑名单);
  • 引导乘客乘坐公交接驳车(降级方案)。