Redis常见面试问题汇总

发布于:2025-03-29 ⋅ 阅读:(22) ⋅ 点赞:(0)

Redis 面试笔记整理

一、Redis 基础知识

1. Redis 概述

Redis 是什么?主要特点有哪些?
  • Redis(Remote Dictionary Server)是一个开源的、基于内存的键值存储系统
  • 主要特点:
    • 高性能:数据存储在内存中,读写速度极快(10万+ QPS)
    • 支持多种数据结构:String、Hash、List、Set、ZSet等
    • 持久化:支持RDB和AOF两种持久化方式
    • 高可用:支持主从复制、哨兵、集群模式
    • 原子操作:支持事务和Lua脚本保证原子性
Redis 和 Memcached 的区别是什么?
特性 Redis Memcached
数据类型 支持多种数据结构 仅支持简单的key-value
持久化 支持RDB和AOF持久化 不支持持久化
集群模式 原生支持Cluster模式 需要客户端实现分布式
线程模型 单线程(6.0+支持多线程I/O) 多线程
内存管理 支持内存淘汰策略 固定大小内存,LRU淘汰
适用场景 缓存、消息队列、计数器等复杂场景 简单的键值缓存场景
Redis 是单线程还是多线程?为什么单线程还能高效?
  • Redis 核心网络模型是单线程的(6.0之前是完全单线程)
  • 高效的原因:
    1. 基于内存操作,没有磁盘I/O瓶颈
    2. 单线程避免了多线程的上下文切换和竞争条件
    3. 非阻塞I/O多路复用(epoll/kqueue)
    4. 精心优化的数据结构(如跳表、哈希表)
Redis 6.0 之后的多线程模型是怎样的?
  • Redis 6.0 引入了多线程I/O(默认关闭,需配置)
  • 模型特点:
    • 主线程仍处理命令执行(保持单线程特性)
    • 新增I/O线程(默认4个)负责:
      • 网络读:解析客户端请求
      • 网络写:返回结果给客户端
    • 配置参数:
      io-threads 4      # 启用4个I/O线程
      io-threads-do-reads yes  # 启用读多线程
      
  • 注意:
    • 实际命令执行仍是单线程
    • 性能提升主要在超高并发网络I/O场景

2. Redis 数据类型

Redis 支持的数据类型及使用场景

String(字符串)
  • 存储结构:二进制安全的字符串,最大512MB
  • 典型场景:
    • 缓存对象(JSON序列化存储)
    • 计数器(INCR/DECR命令)
    • 分布式锁(SETNX实现)
    • 会话管理(Session存储)
  • 特殊操作:
  SETEX key seconds value  # 带过期时间设置
  GETSET key new_value     # 设置新值返回旧值
  MSET/MGET               # 批量操作
Hash(哈希)
  • 存储结构:field-value映射表
  • 典型场景:
    • 存储对象属性(用户资料)
    • 购物车数据(用户ID+商品ID)
    • 配置信息集合
  • 优势:
    • 比String更节省空间(避免重复key)
    • 支持部分字段操作
List(列表)
  • 存储结构:双向链表
  • 典型场景:
    • 消息队列(LPUSH+BRPOP)
    • 最新消息排行(朋友圈)
    • 数据分页(LRANGE)
  • 特点:
  LPUSH/RPUSH  # 左右插入
  BLPOP/BRPOP  # 阻塞式弹出
Set(集合)
  • 存储结构:无序唯一集合
  • 典型场景:
    • 好友关系(共同好友SINTER)
    • 抽奖活动(SRANDMEMBER)
    • 标签系统
  • 核心命令:
  SADD/SREM       # 增删元素
  SISMEMBER       # 判断存在
  SUNION/SDIFF    # 并集/差集
ZSet(有序集合)
  • 存储结构:跳表+哈希表
  • 典型场景:
    • 排行榜(ZREVRANGE)
    • 延迟队列(时间戳作为score)
    • 优先级任务
  • 特点:
  ZADD key score member  # 带分数插入
  ZRANGEBYSCORE         # 按分数范围查询
Bitmap(位图)
  • 存储结构:String的位操作
  • 典型场景:
    • 用户签到(SETBIT)
    • 活跃用户统计
    • 布隆过滤器实现
  • 示例:
  SETBIT sign:2023:user1 1 1  # 第1天签到
  BITCOUNT sign:2023:user1     # 签到总数
HyperLogLog(基数统计)
  • 存储结构:概率算法
  • 典型场景:
    • UV统计(去重计数)
    • 大规模数据去重
  • 特点:
    • 固定12KB存储空间
    • 误差率约0.81%
      PFADD/PFCOUNT # 添加/计数
GEO(地理空间)
  • 存储结构:ZSet扩展
  • 典型场景:
    • 附近的人
    • 位置搜索
    • 距离计算
  • 核心命令:
  GEOADD key 经度 纬度 member
  GEORADIUS  # 半径查询
Stream(消息流)
  • 存储结构:持久化消息队列
  • 典型场景:
    • 消息队列(替代List)
    • 事件溯源
    • 日志收集
  • 特点:
  XADD/XREAD        # 生产/消费
  Consumer Groups   # 消费者组支持

底层实现原理

ZSet实现(跳表+哈希表)
  • 跳表结构:
    • 多层链表,查询复杂度O(logN)
    • 支持范围查询
  • 哈希表:
    • 存储member到score的映射
    • O(1)时间复杂度查询单个元素
  • 内存优化:
    • 元素小于128字节且数量<512时使用ziplist
Hash实现(压缩列表或哈希表)
  • ziplist条件:
    • 所有field/value长度<64字节
    • field数量<512
  • hashtable:
    • 字典结构(数组+链表)
    • 渐进式rehash策略
  • 转换阈值:
  hash-max-ziplist-entries 512
  hash-max-ziplist-value 64

3. Redis 持久化

Redis 的持久化机制

Redis 提供两种持久化方式:

  1. RDB(Redis Database):定时生成内存快照
  2. AOF(Append Only File):记录所有写操作命令

RDB 和 AOF 的区别与优缺点

特性 RDB AOF
持久化方式 二进制快照 命令追加日志
文件大小 较小(压缩存储) 较大(持续增长)
恢复速度 慢(需重放命令)
数据安全性 可能丢失最后一次快照后的数据 可配置不同刷盘策略保证数据安全
性能影响 保存时影响性能 写入性能影响较小
恢复优先级 高(默认优先使用AOF恢复)

RDB 触发方式

  1. 手动触发:
   SAVE    # 同步保存,阻塞主线程
   BGSAVE  # 后台异步保存(fork子进程)
  1. 自动触发(redis.conf配置):
   save 900 1     # 900秒内至少1个key变化
   save 300 10    # 300秒内至少10个key变化
   save 60 10000  # 60秒内至少10000个key变化

AOF 重写机制

  1. 作用:

    • 压缩AOF文件体积(去除冗余命令)
    • 重建更优的持久化命令序列
  2. 触发方式:

    • 手动触发:BGREWRITEAOF
    • 自动触发(需配置):

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

  1. 优化策略:
    • 使用AOF重写缓冲区
    • 配置合理的重写触发阈值
    • 开启AOF-RDB混合模式(4.0+)

生产环境配置建议

  1. 选择策略:

    • 数据安全性要求高:AOF(appendfsync always)
    • 快速恢复优先:RDB
    • 折中方案:同时开启RDB+AOF
  2. 推荐配置:

   appendonly yes                  # 开启AOF
   appendfsync everysec            # 折中刷盘策略
   aof-use-rdb-preamble yes        # 开启混合持久化(4.0+)
   save 900 1                      # 保留RDB触发条件
  1. 注意事项:
    • 同时开启时重启优先加载AOF文件
    • 混合模式结合两者优势(4.0+版本)
    • 监控持久化文件大小和性能影响

二、Redis 高可用与集群

1. 主从复制

Redis 主从复制原理
  1. 全量同步过程

    • 从节点发送SYNC命令
    • 主节点执行BGSAVE生成RDB文件
    • 主节点将RDB文件发送给从节点
    • 从节点清空数据后加载RDB
    • 主节点将缓冲区的写命令发送给从节点
  2. 增量同步过程

    • 主节点维护复制积压缓冲区(repl_backlog)
    • 从节点断线重连后发送PSYNC命令
    • 主节点发送缓冲区中缺失的命令
  3. 关键参数

   repl-backlog-size 1mb    # 复制积压缓冲区大小
   repl-backlog-ttl 3600    # 缓冲区保留时间(秒)
主从复制常见问题
  1. 数据延迟

    • 原因:网络延迟、主节点写入量过大
    • 监控:INFO replication查看slave_repl_offset
  2. 主从切换问题

    • 故障转移时可能丢失部分数据
    • 新主节点选举期间服务不可用
  3. 其他问题

    • 全量同步导致主节点内存峰值
    • 从节点过期key处理不及时
    • 网络闪断导致频繁全量同步
主从复制性能优化
  1. 网络优化
    • 主从节点同机房部署
    • 增大复制缓冲区大小:
repl-backlog-size 256mb
  1. 配置优化

    • 关闭从节点AOF(除非需要)
    • 设置合理的主节点保存间隔:
      save 300 100
  2. 其他优化

    • 使用SSD磁盘提高RDB传输速度
    • 适当增大TCP缓冲区大小
    • 监控复制延迟(redis-cli --latency)
  3. 特殊场景优化

    • 级联复制减轻主节点压力
    • 读写分离时控制从节点数量

2. Redis Sentinel(哨兵)

Redis Sentinel 的作用

  1. 核心功能

    • 监控:持续检查主从节点运行状态
    • 通知:通过API向管理员发送故障报警
    • 自动故障转移:主节点故障时提升从节点为新主节点
    • 配置中心:客户端自动获取最新主节点地址
  2. 故障转移流程

    • 检测主节点失效(主观下线→客观下线)
    • 选举领头Sentinel
    • 领头Sentinel执行故障转移:
      1. 选择最优从节点
      2. 将其提升为主节点
      3. 配置其他从节点复制新主节点
      4. 通知客户端配置变更

故障检测机制

  1. 主观下线(SDOWN)

    • 单个Sentinel实例检测到主节点无响应
    • 判定条件(可配置):
      down-after-milliseconds 5000 # 5秒无响应
  2. 客观下线(ODOWN)

    • 多个Sentinel达成共识认为主节点不可用
    • 判定条件:
      quorum 2 # 需要至少2个Sentinel同意

Sentinel 选举机制

  1. 选举触发条件

    • 主节点被判定为客观下线
    • 需要执行故障转移操作
  2. 选举规则

    • 基于Raft算法实现
    • 每个发现主节点下线的Sentinel都会要求其他Sentinel选举自己为leader
    • 先到先得原则(先发送选举请求的优先)
    • 获得多数票(>N/2+1)的Sentinel成为leader
  3. 选举相关配置
    sentinel monitor mymaster 127.0.0.1 6379 2 # 监控的主节点名称、IP、端口、quorum数
    sentinel failover-timeout mymaster 180000 # 故障转移超时时间(毫秒)

  4. 选举失败处理

    • 若选举超时(failover-timeout)仍未选出leader
    • 等待一段时间后重新发起选举

3. Redis Cluster(集群)

Redis Cluster 数据分片

  1. 哈希槽分配

    • 整个集群被划分为16384个哈希槽(slot)
    • 每个key通过CRC16算法计算后取模:slot = CRC16(key) % 16384
    • 每个节点负责一部分哈希槽范围
  2. 槽分配示例

    • 节点A:0-5460
    • 节点B:5461-10922
    • 节点C:10923-16383
  3. 数据分布特性

    • 支持key哈希标签:{user1000}.profile{user1000}.account会被分配到相同slot
    • 迁移过程中允许部分key访问

节点通信机制

  1. Gossip协议

    • 节点间通过PING/PONG消息保持通信
    • 每个节点随机选择部分节点进行通信(默认每秒10次)
    • 传播的信息包括:
      • 节点状态
      • 哈希槽分配
      • 集群配置epoch
  2. 通信优化
    cluster-node-timeout 15000 # 节点超时时间(毫秒)
    cluster-require-full-coverage yes # 是否需要所有槽被覆盖

集群扩缩容

  1. 扩容流程
   # 添加新节点
redis-cli --cluster add-node new_host:new_port existing_host:existing_port  
   # 重新分配槽
redis-cli --cluster reshard existing_host:existing_port
   # 输入要迁移的槽数量
   # 选择接收这些槽的目标节点ID
   # 选择从哪些源节点迁移槽
  1. 缩容流程
   # 迁移待删除节点的槽到其他节点
   redis-cli --cluster reshard del_node_host:del_node_port
   
   # 删除空节点
   redis-cli --cluster del-node del_node_host:del_node_port node_id
  1. 自动平衡
   redis-cli --cluster rebalance --use-empty-masters

请求重定向机制

  1. MOVED重定向

    • 当客户端访问错误的节点时
    • 节点返回:MOVED 1234 10.0.0.2:6379
    • 表示key属于槽1234,应由10.0.0.2:6379处理
    • 客户端应更新本地slot缓存
  2. ASK重定向

    • 发生在集群迁移过程中
    • 返回格式:ASK 1234 10.0.0.2:6379
    • 表示key正在迁移,临时访问目标节点
    • 客户端需先发送ASKING命令
  3. 智能客户端处理

    • 维护slot与节点的映射关系
    • 自动处理重定向请求
    • 定期更新集群拓扑信息

三、Redis 缓存问题

1. 缓存穿透、缓存雪崩、缓存击穿

缓存穿透

问题定义:大量请求查询不存在的数据,绕过缓存直接访问数据库

解决方案

  1. 布隆过滤器(Bloom Filter):

    • 前置过滤器拦截不存在key的请求
    • 优点:内存占用极小
    • 缺点:存在误判率(可配置)
  2. 空值缓存:

    • 对查询结果为null的key也进行缓存
    • 设置较短过期时间:SET key_null "" EX 300
  3. 其他方案:

    • 接口层增加参数校验
    • 用户权限校验前置
缓存雪崩

问题定义:大量缓存同时失效,导致请求直接冲击数据库

解决方案

  1. 过期时间随机化:

    • 基础时间 + 随机偏移量
      SET key value EX ${1800 + random(300)}
  2. 多级缓存架构:

    • 本地缓存(Caffeine) + Redis集群
    • 不同层级设置不同过期策略
  3. 其他方案:

    • 热点数据永不过期(后台异步更新)
    • 熔断降级机制(Hystrix/Sentinel)
缓存击穿

问题定义:热点key突然失效,大量并发请求直接访问数据库

解决方案

  1. 互斥锁(分布式锁):
    • 使用SETNX实现锁机制
if (redis.setnx("lock_key", 1, "EX", 10)) {
	try {
		// 查询数据库
		// 更新缓存
	} finally {
		redis.del("lock_key")
	}
}
  1. 热点数据永不过期:

    • 不设置过期时间
    • 后台定时任务异步更新
  2. 其他方案:

    • 逻辑过期时间(value中存储过期时间戳)
    • 请求合并(将多个并发查询合并为单个查询)

2. 缓存一致性

如何保证缓存与数据库的数据一致性

先更新数据库再删缓存(推荐方案)
  1. 操作流程:

    • 先更新数据库
    • 再删除缓存
    • 后续读取时重新加载缓存
  2. 优点:

    • 实现简单
    • 避免大多数并发问题
  3. 潜在问题及解决方案:

    • 删除缓存失败 → 加入重试机制(消息队列)
    • 读请求在删除前加载旧数据 → 设置较短过期时间
延迟双删策略
  1. 操作流程:

    • 先删除缓存
    • 再更新数据库
    • 延迟一定时间后再次删除缓存
  2. 典型实现:

// 第一次删除
   redis.del(key)
   // 更新数据库
   db.update(data) 
   // 延迟删除(如500ms后)
   Thread.sleep(500)
   redis.del(key)
  1. 适用场景:
    • 对一致性要求高的场景
    • 能容忍短暂延迟
其他保障措施
  1. 版本号机制:

    • 在缓存value中加入数据版本号
    • 更新时校验版本号
  2. 异步监听binlog:

    • 通过canal监听数据库变更
    • 自动更新/删除缓存

缓存更新策略

Cache Aside(旁路缓存)
  • 读流程:

    1. 先查缓存,命中则返回
    2. 未命中则查DB,并写入缓存
  • 写流程:

    1. 直接更新DB
    2. 删除对应缓存
Read/Write Through
  • 读流程:
    缓存作为主要数据源,自动从DB加载

  • 写流程:
    先写缓存,由缓存系统负责同步写入DB

Write Behind
  • 写流程:
    先写缓存,异步批量更新DB
  • 特点:
    高性能但可能丢失数据

3. 缓存淘汰策略

Redis内存淘汰策略

  1. 不淘汰:
    noeviction → 内存满时拒绝写入

  2. 全局淘汰:
    allkeys-lru → 最近最少使用
    allkeys-lfu → 最不经常使用
    allkeys-random → 随机淘汰

  3. 有过期时间的key淘汰:
    volatile-lru → 在过期key中LRU
    volatile-lfu→ 在过期key中LFU
    volatile-ttl → 淘汰剩余TTL最短的
    volatile-random → 随机淘汰过期key

策略选择建议

  1. 常规场景:
    allkeys-lru(平衡性好)

  2. 热点数据明显:
    allkeys-lfu(更精准)

  3. 严格保证数据不过期:
    noeviction + 监控

  4. 混合使用场景:
    volatile-lru + 合理设置TTL

  5. 配置方式:
    maxmemory-policy allkeys-lru
    maxmemory 4gb # 设置最大内存

四、Redis 高级特性与优化

1. Redis 事务与 Lua 脚本

Redis 事务(MULTI/EXEC)工作机制
  1. 基本流程
    • 使用MULTI开始事务
    • 将多个命令入队
    • 使用EXEC执行所有命令
    • 示例:
 MULTI
 SET key1 value1
 INCR key2
 EXEC
  1. ACID特性支持

    • 原子性(A):单条命令原子执行,但事务不保证(某条失败不影响其他命令)
    • 一致性(C):总能保证数据一致性
    • 隔离性(I):单线程模型天然隔离
    • 持久性(D):取决于持久化配置
  2. 事务特点

    • 不支持回滚(与关系型数据库不同)
    • 命令执行期间不会被其他客户端打断
WATCH 命令(乐观锁实现)
  1. 工作原理
    • 监视一个或多个key
    • 如果在EXEC前这些key被修改,则事务失败
    • 示例:
 WATCH balance
 val = GET balance
 MULTI
 SET balance val-100
 EXEC
  1. 典型应用场景

    • 账户余额修改
    • 库存扣减
    • 需要CAS(Compare-And-Swap)的场景
  2. 注意事项

    • 需要配合重试机制使用
    • 不适用于高竞争场景(可能导致大量重试)
Lua 脚本优势及原子性保证
  1. 推荐使用原因

    • 减少网络开销(多个操作合并)
    • 避免竞态条件(脚本整体原子执行)
    • 复杂操作封装(如分布式锁实现)
    • 执行效率高(脚本会被缓存)
  2. 原子性保证机制

    • Redis单线程执行Lua脚本
    • 脚本执行期间不会处理其他命令
    • 示例原子操作:
 local current = redis.call('GET', KEYS[1])
 if current == ARGV[1] then
   return redis.call('SET', KEYS[1], ARGV[2])
 end
  1. 最佳实践
    • 控制脚本复杂度(避免长时间阻塞)
    • 使用SCRIPT LOAD预加载脚本
    • 避免在脚本中使用随机写操作

2. Redis 性能优化

Redis Pipeline 机制

  1. 基本原理

    • 将多个命令打包一次性发送
    • 减少网络往返时间(RTT)
    • 服务端按顺序执行并批量返回结果
  2. 性能提升方式

    • 典型提升5-10倍吞吐量
    • 适合批量写入/读取场景
    • 示例实现:
Pipeline p = jedis.pipelined();
 for(int i=0; i<10000; i++){
     p.set("key"+i, "value"+i);
 }
 p.sync();
  1. 注意事项
    • 单次Pipeline不宜包含过多命令
    • 不适用于有命令依赖的场景
    • 需要合理设置超时时间

BigKey 问题处理

  1. 排查方法
    • 使用内置命令:
 redis-cli --bigkeys
 MEMORY USAGE key
  • 扫描分析工具:
    redis-rdb-tools分析RDB文件
  1. 优化方案

    • 大Key拆分:
      将hash拆分为多个小hash
    • 数据压缩:
      使用Snappy等算法压缩value
    • 存储优化:
      改用更适合的数据结构
  2. 预防措施

    • 设置监控告警
    • 避免存储超大value(>10KB)
    • 定期扫描清理

慢查询分析

  1. 配置与查看
   - 设置阈值(微秒):
     slowlog-log-slower-than 10000
   - 保留条数:
     slowlog-max-len 128
   - 查看慢日志:
     SLOWLOG GET 10
  1. 常见优化方向

    • 避免O(N)命令操作大数据集
    • 优化复杂Lua脚本
    • 合理使用索引
  2. 分析工具

    • redis-faina分析监控日志
    • 可视化工具展示慢查询趋势

连接数优化

  1. 连接池配置

    • 最大连接数:
      maxTotal 50
    • 最大空闲连接:
      maxIdle 10
    • 最小空闲连接:
      minIdle 5
  2. 优化建议

    • 合理设置超时时间:
      timeout 3000
    • 使用连接池复用连接
    • 避免短连接操作
  3. 问题排查

    • 查看当前连接:
      CLIENT LIST
    • 分析连接来源:
      INFO clients
    • 紧急断开连接:
      CLIENT KILL

3. Redis 网络与安全

Redis 通信协议 (RESP)

  1. 协议特点

    • 二进制安全的文本协议
    • 简单易实现
    • 支持多种数据类型编码
  2. 协议格式

    • 简单字符串: “+OK\r\n”
    • 错误: “-Error message\r\n”
    • 整数: “:1000\r\n”
    • 批量字符串: “$6\r\nfoobar\r\n”
    • 数组: “*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n”
  3. 工作方式

    • 客户端-服务端基于TCP通信
    • 默认端口6379
    • 支持管道和批量操作

安全认证配置

  1. 密码认证设置
    redis.conf配置
    requirepass your_strong_password

    客户端认证
    AUTH your_strong_password

  2. 防护措施

    • 修改默认端口
    • 绑定指定IP:
      bind 127.0.0.1
    • 禁用危险命令:
      rename-command FLUSHALL ""
    • 启用保护模式:
      protected-mode yes
  3. 网络安全

    • 使用SSL隧道
    • 配置防火墙规则
    • 启用ACL(Redis 6.0+)

SCAN vs KEYS 命令

  1. SCAN命令特点

    • 增量式迭代
    • 不阻塞服务器
    • 可能返回重复key
    • 语法:
      SCAN cursor [MATCH pattern] [COUNT count]
  2. KEYS命令风险

    • 一次性返回所有匹配key
    • 大数据量时会阻塞服务
    • 导致性能骤降
  3. 生产环境建议

    • 永远禁用KEYS *
    • 使用SCAN替代大数据量查询
    • 监控脚本示例:
 local cursor = "0"
 repeat
   local reply = redis.call("SCAN", cursor, "MATCH", "user:*")
   cursor = reply[1]
   -- 处理返回的keys
 until cursor == "0"

五、Redis 应用场景

1. 常见业务场景

分布式锁实现

核心方案

  1. 基础实现:
    SET lock_key unique_value NX EX 10

    • NX: 仅当key不存在时设置
    • EX: 设置过期时间(秒)
    • unique_value: 唯一标识(通常使用UUID)
  2. 优化版(Lua脚本保证原子性):

 if redis.call("SETNX", KEYS[1], ARGV[1]) == 1 then
       return redis.call("EXPIRE", KEYS[1], ARGV[2])
   else
       return 0
   end
  1. 注意事项:
    • 必须设置过期时间
    • 解锁需验证value值
    • 考虑锁续期问题
限流方案

INCR计数器方案

  1. 固定窗口:
 local counter = redis.call("INCR", KEYS[1])
   if counter == 1 then
       redis.call("EXPIRE", KEYS[1], ARGV[1])
   end
   return counter <= tonumber(ARGV[2])
  1. 令牌桶算法:
    • 使用Hash存储:last_time, tokens
    • Lua脚本计算当前可用令牌数
    • 补充速率控制

参数示例

  • KEYS[1]: rate_limiter:user1
  • ARGV[1]: 时间窗口(如60秒)
  • ARGV[2]: 最大请求数(如100)
排行榜实现

ZSet核心操作

  1. 更新分数:
    ZADD leaderboard 100 "user1"

  2. 获取排名:
    ZREVRANGE leaderboard 0 9 WITHSCORES

  3. 用户排名:
    ZREVRANK leaderboard "user1"

优化技巧

  • 定期持久化到数据库
  • 分片存储超大排行榜
  • 使用ZUNIONSTORE合并多个榜单
消息队列方案

List基础方案

  1. 生产者:
    LPUSH orders "order_data"

  2. 消费者:
    BRPOP orders 30

**Stream方案(Redis 5.0+):

  1. 生产消息:
    XADD mystream * field1 value1 field2 value2

  2. 消费消息:
    XREAD BLOCK 0 STREAMS mystream $

  3. 消费者组:
    XGROUP CREATE mystream mygroup $

Session共享

实现方案

  1. 存储结构:
    SETEX session:sessionid 3600 "user_data"

  2. Spring集成配置:
    spring.session.store-type=redis
    server.servlet.session.timeout=3600

  3. 集群方案:

    • 相同TTL配置
    • 合理设置序列化方式
秒杀系统实现

库存扣减方案

  1. Lua脚本保证原子性:
local stock = tonumber(redis.call("GET", KEYS[1]))
   if stock > 0 then
       redis.call("DECR", KEYS[1])
       return 1
   end
   return 0
  1. 优化措施:
    • 库存预热
    • 分段锁减少竞争
    • 异步扣减+结果通知

参数示例

  • KEYS[1]: seckill:stock:123
  • ARGV[1]: 无(可扩展传用户ID)

2. 高级应用

布隆过滤器实现

  1. RedisBloom模块

    • 加载模块:
      redis-server --loadmodule /path/to/redisbloom.so
    • 基本命令:
      BF.ADD myfilter item1
      BF.EXISTS myfilter item1
    • 参数配置:
      BF.RESERVE myfilter 0.01 100000
  2. 原生实现方案

    • 使用Bitmap+多个哈希函数
    • 示例SETBIT操作:
      SETBIT bloom:filter hash1(item) 1
      SETBIT bloom:filter hash2(item) 1

HyperLogLog统计UV

  1. 基础命令

    • 添加访问记录:
      PFADD uv:20230515 user1 user2 user3
    • 获取统计结果:
      PFCOUNT uv:20230515
    • 合并多日数据:
      PFMERGE uv:week uv:day1 uv:day2
  2. 精度说明

    • 标准误差0.81%
    • 固定使用12KB内存
    • 适合大数据量去重

GEO地理位置

  1. 核心命令

    • 添加位置:
      GEOADD locations 116.404 39.915 "user1"
    • 查询附近的人:
      GEORADIUS locations 116.404 39.915 10 km WITHDIST
    • 计算距离:
      GEODIST locations user1 user2 km
  2. 实现原理

    • 基于ZSet存储
    • 使用Geohash编码
    • 有效距离范围:-180到180经度

BitMap签到功能

  1. 每日签到

    • 用户签到:
      SETBIT sign:user1:202305 15 1
    • 查询签到:
      GETBIT sign:user1:202305 15
    • 统计签到:
      BITCOUNT sign:user1:202305
  2. 连续签到计算

    • 获取位图数据:
      GET sign:user1:202305
    • 客户端解析连续天数
    • 支持多维度统计:
      BITOP AND result sign:user1:202305 sign:user1:202306

六、Redis 与其他技术结合

Spring Boot 集成 Redis

  1. 基础配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=yourpassword
spring.redis.lettuce.pool.max-active=8
  1. 核心组件
  • RedisTemplate: 提供各种数据结构操作
  • StringRedisTemplate: 字符串专用模板
  • LettuceConnectionFactory: 默认连接池(优于Jedis)
  1. 典型用法
@Autowired
private RedisTemplate<String, Object> redisTemplate;

// 操作示例
redisTemplate.opsForValue().set("key", "value");
redisTemplate.opsForHash().put("user", "id", 1001);

Redis + MySQL 配合方案

  1. 缓存策略
  • Cache Aside Pattern:
    1. 读: 先查缓存→未命中查DB→回填缓存
    2. 写: 更新DB→删除缓存
  1. 双写一致性方案
  • 延迟双删:
    1. 删除缓存
    2. 更新数据库
    3. 延迟500ms再次删除
  • 监听binlog变更(通过canal)

Redis 和本地缓存(Caffeine、Guava Cache)如何选择?

选择依据对比
特性 Redis Caffeine/Guava Cache
数据范围 全集群共享 仅单机有效
性能 微秒级(网络IO影响) 纳秒级(内存访问)
容量 支持GB/TB级别 通常MB级别(受JVM堆限制)
数据结构 支持丰富的数据结构 简单KV结构
持久化 支持 不支持
适用场景 分布式缓存、共享数据 高频热点数据、临时数据
电商分层缓存实战示例
场景:商品详情页缓存
  1. 架构设计
    用户请求 → Nginx → 本地缓存(Caffeine)→ Redis集群 → MySQL

  2. 代码实现

// 初始化多级缓存
   @Bean
   public Cache<String, Product> productCache() {
       return Caffeine.newBuilder()
           .maximumSize(10_000)  // 本地缓存1万个商品
           .expireAfterWrite(5, TimeUnit.MINUTES)
           .build();
   }

   // 查询逻辑
   public Product getProduct(Long id) {
       String key = "product:" + id;
       
       // 先查本地缓存
       Product product = productCache.getIfPresent(key);
       if (product != null) return product;
       
       // 再查Redis
       product = redisTemplate.opsForValue().get(key);
       if (product == null) {
           product = productMapper.selectById(id);  // 查数据库
           // 异步回填缓存
           executor.execute(() -> {
               redisTemplate.opsForValue().set(key, product, 1, TimeUnit.HOURS);
               productCache.put(key, product); 
           });
       } else {
           productCache.put(key, product);  // 回填本地缓存
       }
       return product;
   }
  1. 优势分析

    • 本地缓存:扛住90%以上的商品查询请求
    • Redis层:保证集群各节点的数据一致性
    • 数据库:最终数据源,流量削峰达99%
  2. 关键配置

    • 本地缓存TTL(5分钟)< Redis TTL(1小时)
    • 本地缓存最大条目数根据内存调整
    • 使用异步线程回填避免阻塞主流程
选型建议
必须使用Redis的场景
  • 需要跨服务共享的数据(如库存)
  • 需要持久化的关键数据
  • 复杂数据结构需求(如排行榜)
优先本地缓存的场景
  • 极端性能要求(秒杀系统)
  • 不要求强一致性的数据(商品描述)
  • 单机高频访问数据(用户基础信息)

七、Redis 底层实现

Redis 的 SDS(Simple Dynamic String)和 C 字符串区别

特性 SDS C 字符串
长度获取 O(1) 直接读取len属性 O(n) 需要遍历计算
缓冲区安全 自动扩容,不会缓冲区溢出 容易缓冲区溢出
二进制安全 可以存储任意二进制数据 只能存储文本,遇’\0’终止
内存分配 预分配+惰性释放策略 每次修改需重新分配
兼容性 末尾保留’\0’兼容C函数 -

Redis 字典(Hash 表)实现

  1. 核心结构

    • 哈希表数组 + 链表解决冲突
    • 包含两个哈希表(ht[0]和ht[1])用于rehash
  2. 渐进式rehash过程

    • 扩容时机:负载因子 > 1 且允许rehash,或负载因子 > 5 强制rehash
    • 步骤:
      1. 分配ht[1]空间(大小为第一个大于等于ht[0].used*2的2^n)
      2. 维护rehashidx计数器(初始为0)
      3. 每次CRUD操作时迁移ht[0][rehashidx]的键值对
      4. 全部迁移完成后用ht[1]替换ht[0]
  3. 优化特性

    • 单次迁移一个桶(链表)
    • 期间查询会同时查两个表
    • 定时任务辅助迁移

Redis 跳跃表(Skip List)工作原理

  1. 结构特点

    • 多层有序链表(默认最大32层)
    • 每个节点包含:
      • 成员对象(ele)
      • 分值(score)
      • 后退指针(BW)
      • 层数组(level[]包含前进指针和跨度)
  2. 查找过程

    • 从最高层开始遍历
    • 当前节点分值 < 目标分值 → 继续前进
    • 当前节点分值 ≥ 目标分值 → 下降一层
    • 时间复杂度:O(logN)
  3. 插入流程

    • 随机确定节点层数(幂次定律)
    • 逐层更新前后节点指针
    • 更新跨度信息

Redis 过期键删除策略

  1. 惰性删除

    • 触发时机:访问键时检查过期时间
    • 优点:CPU友好
    • 缺点:内存不及时释放
  2. 定期删除

    • 工作流程:
      1. 随机抽取20个键检查
      2. 删除其中已过期的键
      3. 如果超过25%键过期则重复过程
    • 配置参数:
      hz 10 # 每秒执行次数
  3. 内存淘汰触发删除

    • 当内存不足时,按策略淘汰键
    • 相关配置:
      maxmemory-policy volatile-lru