javaweb-Day13-Redis

TJCcc 发布于 14 天前 22 次阅读


一、Redis 概述

1.1 什么是 Redis

Redis(Remote Dictionary Server)是一款开源的、基于内存的高性能键值对(Key-Value)数据库。与传统关系型数据库不同,Redis 将所有数据存储在内存中,读写速度极快,每秒可处理数万至数十万次请求。

1.2 核心特性

  • 内存存储:数据主要在内存中操作,读写速度可达 10 万+ QPS
  • 丰富的数据结构:支持字符串、哈希、列表、集合、有序集合等
  • 持久化支持:提供 RDB 快照与 AOF 日志两种持久化方案
  • 高可用性:支持主从复制、哨兵模式、集群模式
  • 原子性:单线程模型保证命令原子性执行,支持事务和 Lua 脚本

1.3 适用场景

  • 缓存层:减轻数据库压力,加速热点数据访问
  • 会话存储:保存用户登录状态或临时数据
  • 消息队列:通过 List 或 Stream 实现轻量级消息传递
  • 分布式锁:利用 SETNX 命令实现资源互斥访问
  • 实时排行榜:有序集合(ZSET)支持范围查询和排名计算

1.4 安装与基本配置

# 下载安装
wget https://download.redis.io/redis-stable.tar.gz
tar xzf redis-stable.tar.gz
cd redis-stable
make && make install

核心配置文件 redis.conf 关键参数:

bind 0.0.0.0              # 允许远程连接
port 6379                  # 默认端口
daemonize yes              # 守护进程模式
requirepass yourpass       # 设置密码
maxmemory 2gb              # 内存限制
maxmemory-policy allkeys-lru  # 内存淘汰策略

二、核心数据结构与命令

2.1 数据类型总览

Redis 原生支持的数据结构包括:Strings、Hashes、Lists、Sets、Sorted Sets、Bitmaps、Bitfields、HyperLogLog、地理空间索引(Geospatial)和 Streams。

2.2 字符串(String)

应用场景:计数器、缓存、分布式锁

常用命令

SET key value [EX seconds]   # 设置键值,可选过期时间
GET key                       # 获取值
INCR key                      # 原子递增
DECRBY key 10                 # 原子递减指定数值
MSET key1 value1 key2 value2  # 批量设置
MGET key1 key2                # 批量获取

示例:实现访问量统计

INCR website:views
GET website:views

注意SET 会覆盖已存在的 key,SETNX 则只在 key 不存在时设置;SETEX 可设置带过期时间的键值。

2.3 哈希(Hash)

应用场景:存储对象属性(如用户信息)

常用命令

HSET user:1001 name "Alice" age 25
HGET user:1001 name
HGETALL user:1001              # 获取所有字段
HINCRBY user:1001 age 1        # 字段递增
HDEL user:1001 age             # 删除字段

2.4 列表(List)

应用场景:消息队列、最新消息推送

常用命令

LPUSH messages "msg1"          # 左侧插入
RPUSH messages "msg2"          # 右侧插入
LPOP messages                  # 左侧弹出
RPOP messages                  # 右侧弹出
LRANGE messages 0 -1           # 获取所有元素
LLEN messages                  # 获取列表长度

2.5 集合(Set)

应用场景:去重、标签系统、共同好友

常用命令

SADD tags "redis" "database"   # 添加元素
SMEMBERS tags                  # 获取所有元素
SISMEMBER tags "redis"         # 判断是否存在
SINTER set1 set2               # 交集
SUNION set1 set2               # 并集
SDIFF set1 set2                # 差集

2.6 有序集合(Sorted Set / ZSET)

应用场景:排行榜、优先级队列、延时队列

常用命令

ZADD leaderboard 100 "Alice" 200 "Bob"
ZREVRANGE leaderboard 0 2 WITHSCORES   # 获取前3名及分数
ZINCRBY leaderboard 50 "Alice"         # 增加分数
ZRANK leaderboard "Alice"              # 获取排名
ZREM leaderboard "Bob"                 # 删除元素

2.7 通用命令

KEYS pattern                # 查找匹配的 key(生产环境慎用!)
EXISTS key                  # 判断 key 是否存在
EXPIRE key seconds          # 设置过期时间
TTL key                     # 查看剩余过期时间
DEL key                     # 删除 key
UNLINK key                  # 异步删除(非阻塞)
TYPE key                    # 查看 key 的数据类型

三、持久化机制

持久化是确保数据不丢失的关键机制。Redis 提供了两种持久化方案。

3.1 RDB 持久化(快照)

原理:定时将内存数据保存到磁盘文件(dump.rdb)

触发方式

  1. SAVE 命令:同步执行,会阻塞主进程
  2. BGSAVE 命令:异步执行,fork 子进程完成持久化
  3. Redis 停机时:自动执行一次 RDB
  4. 自动触发条件:配置文件中的 save 规则

配置示例

save 900 1      # 900秒内至少1次修改触发
save 300 10     # 300秒内至少10次修改触发
save 60 10000   # 60秒内至少10000次修改触发
dbfilename dump.rdb
dir /var/lib/redis

工作原理:bgsave 时 fork 主进程得到子进程,子进程共享主进程的内存数据。采用 copy-on-write 技术:主进程读操作访问共享内存,写操作则拷贝一份数据再执行。

优缺点

  • ✅ 文件体积小,恢复速度快
  • ✅ 适合备份和灾难恢复
  • ❌ 可能丢失最后一次快照后的数据
  • ❌ fork 子进程耗时,内存占用翻倍

3.2 AOF 持久化(追加日志)

原理:记录所有写操作命令,重启时重放

配置

appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec    # 同步策略

同步策略对比

策略说明安全性性能
always每个写命令都同步最高最差
everysec每秒同步一次较高(最多丢1秒)平衡(推荐)
no由操作系统决定较低最好

AOF 重写:AOF 文件会记录对同一个 key 的多次写操作,但只有最后一次有效。通过 bgrewriteaof 命令可执行重写,用最少命令达到相同效果,解决文件膨胀问题。

3.3 混合持久化(Redis 4.0+)

最佳实践:同时开启 RDB + AOF,或启用混合持久化。

混合持久化模式下,重写后的新 AOF 文件开头是 RDB 格式的全量数据,后续是增量 AOF 日志。重启时先加载 RDB 内容,再重放 AOF 日志,兼顾恢复速度与数据安全性。

配置

aof-use-rdb-preamble yes

四、高可用架构

单机 Redis 存在单点故障风险,需要通过高可用架构保障服务连续性。

4.1 主从复制(Replication)

架构:一主多从,从节点同步主节点数据,实现读写分离

建立主从连接三种方式

# 方式一:客户端命令
SLAVEOF <masterip> <masterport>

# 方式二:启动参数
redis-server --slaveof <masterip> <masterport>

# 方式三:配置文件
replicaof 192.168.1.100 6379

复制流程

  1. 全量同步:从服务器发送 PSYNC,主服务器执行 BGSAVE 生成 RDB 并发送
  2. 增量同步:主服务器将复制积压缓冲区中的命令持续发送给从服务器

触发全量同步的情况:首次连接、复制 ID 不匹配、偏移量不在积压缓冲区范围内。

特点

  • ✅ 简单易实现,低成本扩展读性能
  • ✅ 数据冗余备份
  • ❌ 故障切换需人工干预
  • ❌ 写性能无法扩展

4.2 哨兵模式(Sentinel)

为什么需要哨兵:主从复制中主节点宕机后需要人工介入,哨兵实现了自动故障转移。

核心功能

  • 监控:监控主从 Redis 实例是否在线
  • 自动故障转移:主节点宕机后自动将从节点提升为主节点
  • 通知:实例下线后通知其他组件

工作流程

  1. 主观下线(SDOWN) :单个哨兵认为 Redis 实例不可用
  2. 客观下线(ODOWN) :多个哨兵达成共识(达到 quorum 配置值),认为主节点确实下线
  3. 领导者选举:哨兵集群通过 Raft 算法选举出一个领导者哨兵
  4. 故障转移:领导者哨兵从从节点中选举新主节点,通知其他从节点复制新主节点

部署建议:哨兵必须集群部署,奇数个节点(最低 3 节点)。

脑裂问题:网络分区可能导致同一个 Redis 系统出现多个主节点,造成数据不一致。解决方案:配置合理的 quorum 数量,部署奇数个哨兵,客户端使用哨兵模式的连接池。

4.3 Redis Cluster(集群模式)

适用场景:数据量过大、需要水平扩展的大规模高并发场景。

核心原理:基于哈希槽(Hash Slot)进行数据分片。

哈希槽算法

hash_slot = CRC16(key) % 16384

一共有 16384 个槽位,每个分片负责一部分槽位。

分片分配示例(3 个分片):

  • 0号分片:[0, 5461],共 5462 个槽位
  • 1号分片:[5462, 10923],共 5462 个槽位
  • 2号分片:[10924, 16383],共 5460 个槽位

三种分片算法对比

算法说明扩容成本数据均衡
哈希求余key 对分片数取模极高(大量数据搬运)均衡
一致性哈希环形空间 + 顺时针查找较低可能倾斜
哈希槽(Redis 采用)16384 个槽位灵活分配可控可灵活调整

集群部署示例

redis-cli --cluster create \
  192.168.0.1:7000 192.168.0.2:7001 \
  192.168.0.3:7002 --cluster-replicas 1

五、内存管理与淘汰策略

5.1 过期键删除策略

Redis 采用 惰性删除 + 定期删除 的组合策略:

  • 惰性删除:访问 key 时检查是否过期,过期则删除
  • 定期删除:后台周期性执行 activeExpireCycle 函数,从设置了过期时间的键中随机抽样检测并删除

性能控制:每次执行最多占用 CPU 时间不超过 25ms,每次默认检查 20 个键,采用随机抽样而非全表扫描。

大量键同时过期的优化

  • 分散过期时间:添加随机扰动值
  • 监控预警:通过 INFO 命令监控 expired_keys 指标
  • 使用 UNLINK 而非 DEL 进行异步删除

5.2 内存淘汰策略

当内存达到 maxmemory 限制时触发淘汰。

八种淘汰策略

策略说明
noeviction默认策略,拒绝所有可能导致内存增加的命令
allkeys-lru从所有键中移除最近最少使用的键
volatile-lru仅从设置了过期时间的键中移除最近最少使用的
allkeys-random从所有键中随机移除
volatile-random仅从设置了过期时间的键中随机移除
volatile-ttl移除设置了过期时间且剩余 TTL 最短的键
allkeys-lfu从所有键中移除最不经常使用的键(Redis 4.0+)
volatile-lfu仅从设置了过期时间的键中移除最不经常使用的键(Redis 4.0+)

LRU vs LFU

  • LRU(最近最少使用):基于"最近使用过的数据很可能再次被使用"的假设。Redis 采用近似算法,通过随机采样实现
  • LFU(最不经常使用):关注访问频率,适合有明显热点数据的场景。通过衰减机制和频率计数器实现

配置建议

maxmemory 2gb
maxmemory-policy allkeys-lru

六、事务、管道与 Lua 脚本

6.1 管道(Pipeline)

目的:节约网络传输时间,提升批量操作效率

原理:客户端一次性将多个命令打包发送,服务器按顺序处理并一次性返回所有结果。

注意:Pipeline 不具备事务性,不保证"要么全部成功,要么全部失败"。

适用场景:需要一次执行大量读写操作,且命令间彼此独立。

6.2 事务(Transaction)

核心命令

  • MULTI:开启事务,命令入队但不执行
  • EXEC:提交事务,按顺序执行所有命令
  • DISCARD:取消事务(清空队列)
  • WATCH:乐观锁,监视 key,若被修改则事务取消

示例

WATCH gulu
MULTI
SET gulu 2000
EXEC    # 如果 gulu 被其他客户端修改,返回 nil

事务局限性

  • 不支持回滚:命令执行出错不会回滚之前的命令
  • ✅ 保证命令按顺序原子执行

6.3 Lua 脚本

优势:整个脚本在服务端原子执行,网络开销最小,是保证复杂操作原子性的"终极武器"。

执行方式

  • EVAL:直接执行 Lua 脚本
  • SCRIPT LOAD + EVALSHA:预加载脚本,后续通过 SHA1 调用(推荐)

注意:Lua 脚本会占用较多计算和内存资源,过于复杂的脚本可能导致资源耗尽。

七、缓存三大问题与解决方案

7.1 缓存穿透

现象:查询数据库中根本不存在的数据,请求绕过缓存直接冲击数据库

解决方案

  1. 布隆过滤器:在缓存前加一层布隆过滤器,拦截不存在的数据请求
  2. 空值缓存:对查询结果为 null 的数据也进行缓存(过期时间 2-5 分钟)

7.2 缓存击穿

现象:单个热点 key 在缓存过期瞬间,突发大量请求直接击穿到数据库

典型案例:秒杀商品详情、热点新闻内容

解决方案:使用 互斥锁(分布式锁)

  • 获取锁的线程负责查询数据库并回填缓存
  • 未获取锁的线程短暂等待后重试
  • 锁超时时间需大于数据库查询时间

7.3 缓存雪崩

现象:大量缓存 key 在同一时间同时失效,导致大量请求直接打到数据库

解决方案

  • 设置缓存过期时间时添加随机值,避免同时失效
  • 使用互斥锁或分布式锁
  • 搭建高可用 Redis 集群

八、性能优化最佳实践

8.1 内存优化

  • 使用 ziplist 编码压缩小数据
  • 设置合理的 maxmemory-policy
  • 开启内存碎片整理

8.2 批量操作

  • 使用 Pipeline 提升吞吐量
  • 使用 MSET/MGET 替代多次 SET/GET

8.3 慢查询监控

slowlog-log-slower-than 10000   # 10毫秒
slowlog-max-len 128

8.4 安全防护

requirepass your_strong_password
bind 127.0.0.1   # 仅本地访问

九、面试高频考点速查

考点核心要点
Redis 为什么快基于内存、单线程(避免上下文切换和锁竞争)、IO 多路复用
单线程 vs 多线程Redis 核心操作单线程,但 Redis 6.0+ 在网络 IO 方面引入多线程
RDB vs AOFRDB:快照,恢复快但可能丢数据;AOF:日志,安全但恢复慢
缓存穿透/击穿/雪崩布隆过滤器/互斥锁/过期时间加随机值
哨兵 vs 集群哨兵:高可用(故障自动转移);集群:高可用 + 水平扩展(数据分片)
分布式锁SETNX + 过期时间 + Redisson 看门狗机制

十、总结

Redis 作为当今最流行的内存数据库,其知识体系可以概括为三个层次:

  1. 基础层:数据结构与命令、持久化机制——这是日常开发的核心
  2. 进阶层:主从复制、哨兵、集群、事务与 Lua 脚本——解决生产环境的高可用和复杂业务需求
  3. 专家层:内存淘汰策略、性能调优、缓存问题治理、源码原理——应对大规模高并发场景

掌握 Redis,不仅要"会用",更要理解其背后的设计哲学——在性能数据安全之间做精妙的权衡与取舍。

唯有极致沉淀,才能造就辉煌。
最后更新于 2026-09-07