Redis
一、基础篇
1. Redis核心定义与特性
- 定义:基于键值对的NoSQL数据库,数据存储于内存,支持持久化,响应速度达微秒级。
- 与MySQL区别:Redis是非关系型(键值对,内存存储),MySQL是关系型(行列结构,磁盘存储);实际开发中常搭配使用,Redis作为缓存减轻MySQL压力。
- 部署方式:
- 单机版:本地解压运行
redis-server
,或Docker部署(docker run -d --name redis -p 6379:6379 redis:7.0-alpine
)。 - 哨兵模式:一主两从+3个哨兵节点,配置主从地址、故障判定时间(如
sentinel down-after-milliseconds mymaster 5000
)。 - 集群模式(Redis Cluster):3主3从起步,自动分片,通过
redis-cli --cluster create
创建。
- 单机版:本地解压运行
2. 数据类型
(1)基础数据类型
类型 | 底层结构 | 核心特性 | 应用场景 |
---|---|---|---|
字符串String | SDS(简单动态字符串) | 存储文本/数字/二进制,最大512MB;O(1)获取长度,二进制安全,自动扩容避免缓冲区溢出。 | 缓存对象、计数器(INCR)、分布式锁(SET NX)、共享Session。 |
列表List | Redis 3.2前:压缩列表+双向链表;3.2后:quicklist(双向链表+压缩列表);7.0后:listpack替代压缩列表。 | 有序,支持首尾增删(LPUSH/RPOP),适合FIFO场景,查询中间元素慢(O(N))。 | 消息队列(简单版)、任务列表、最新消息排行。 |
哈希Hash | 压缩列表(元素少+小)/哈希表(元素多+大) | 键值对集合,适合存储对象,支持单个字段增删改查(HSET/HGET)。 | 缓存用户信息、商品属性、购物车。 |
集合Set | 整数集合(整数元素+少)/哈希表 | 无序无重复,支持交并差集(SINTER/SUNION/SDIFF),查询效率O(1)。 | 点赞/取消点赞、共同关注、标签去重、抽奖。 |
ZSet(有序集合) | 压缩列表(元素<128+值<64B)/跳表+哈希表 | 按score排序,支持范围查询(ZRANGE/ZREVRANGE),查询/插入O(logN)。 | 排行榜(点赞/访问量排行)、优先级队列、电话姓名排序。 |
(2)扩展数据类型
- Bitmap:二进制位存储,1亿用户签到仅需12MB,用于状态标记(签到、活跃)。
- HyperLogLog:概率性基数统计,12KB内存统计海量数据去重个数,误差率0.81%,用于UV统计。
- GEO:存储地理坐标,支持距离计算、范围查询,底层基于ZSet+Geohash编码,用于“附近的人”“商家定位”。
3. 高性能原因
- 内存存储:数据全在内存,读写速度远快于磁盘(内存读写≈100ns,磁盘≈1ms)。
- IO多路复用:单线程通过(IO多路复用程序)epoll(Linux)/kqueue(macOS)监听多客户端连接,避免多线程上下文切换开销,高效处理并发请求。
- 单线程模型(6.0前):无锁竞争,命令执行原子性;6.0后引入多线程,仅处理网络IO(读写/解析请求),命令执行仍单线程,充分利用多核CPU。
- 优化数据结构:如String用SDS(动态字符串,预分配空间减少内存碎片)、ZSet用跳表(高效范围查询)。
4. 常用命令
- 通用命令:EXPIRE(设置过期)、DEL(删除键)、KEYS(模糊匹配键)。
- String关键命令:
SET key value [EX seconds] [NX]
(支持过期、条件写入,如NX仅键不存在时设置,用于分布式锁);INCR(原子自增,用于计数器)。 - ZSet核心命令:ZADD(添加带分数成员)、ZRANGE(按索引范围查询)、ZREVRANGE(按分数降序查询)。
二、持久化篇
1. 两种持久化方式
(1)RDB(快照持久化)
- 原理:指定时间间隔内生成内存数据二进制快照(.rdb文件),通过
save
(阻塞主进程)或bgsave
(fork子进程,主进程不阻塞)触发。 - 自动触发场景:配置
save 900 1
(15分钟1个键变更)、主从复制首次连接、关闭Redis(未开启AOF时)。 - 优缺点:恢复快、文件小;但可能丢失两次快照间数据。
(2)AOF(命令日志持久化)
- 原理:记录所有写命令到AOF文件,重启时重放命令恢复数据;写命令先存AOF缓冲区,再按刷盘策略同步到磁盘。
- 刷盘策略:
always
:每次命令后立即刷盘,数据无丢失,性能差。everysec
:每秒刷盘,最多丢1秒数据,性能均衡(默认)。no
:依赖OS刷盘,性能好,数据丢失风险高。
- 重写机制:解决AOF文件膨胀,通过
BGREWRITEAOF
(手动)或配置auto-aof-rewrite-percentage 100
(文件增长100%且≥64MB时自动重写),剔除冗余命令(如多次SET同一键合并为1次)。
2. 混合持久化(Redis 4.0+)
- 原理:AOF重写时,先以RDB格式存储内存快照,再追加重写期间的AOF命令,兼顾RDB恢复速度和AOF数据完整性。
- 配置:
aof-use-rdb-preamble yes
;恢复时先加载RDB,再重放AOF命令。
3. 数据恢复
- 启动优先级:优先加载AOF文件,无AOF则加载RDB;AOF损坏可通过
redis-check-aof --repair
修复,RDB仅支持完整性检查(redis-check-rdb
)。
三、高可用篇
1. 主从复制
(1)核心概念
- 架构:主节点处理写请求,从节点同步主节点数据并处理读请求,实现读写分离;支持一主多从、树状主从(跨地域部署,减少主节点负载)。
- 同步过程:
- 建立连接:从节点执行
replicaof 主节点IP 端口
,发送psync
命令请求同步。 - 数据同步:首次连接触发全量同步(主节点生成RDB、发送RDB+缓存命令,从节点清空数据加载RDB);后续增量同步(主节点将写命令存入复制积压缓冲区,从节点断连重连后按需同步)。
- 建立连接:从节点执行
(2)问题与解决
- 数据不一致:异步复制导致,优化方案:部署同区域节点、增大复制积压缓冲区(
repl-backlog-size 1mb
)、监控主从偏移量差值。 - 脑裂问题:主节点与哨兵/从节点断连但仍接客户端写请求,配置
min-slaves-to-write 1
(主节点需至少1个从节点在线才接受写请求)、min-slaves-max-lag 10
(从节点最大延迟10秒)避免数据丢失。
2. 哨兵机制(Sentinel)
- 核心功能:监控主从节点、自动故障转移、通知客户端。
- 工作流程:
- 定时监控:每秒发送PING检测节点存活,超时标记“主观下线”。
- 客观下线:哨兵间交互,超过
quorum
(如2个)哨兵认为主节点下线,标记“客观下线”。 - 领导者选举:通过Raft算法,候选哨兵请求投票,获半数以上选票成为领导者。
- 故障转移:领导者挑选新主节点→向新主节点发
SLAVEOF NO ONE
→其他从节点指向新主节点→通知客户端。
3. Redis Cluster(集群)
- 数据分区:将16384个槽位(slot)分配给主节点,键通过
CRC16(key) % 16384
映射到对应槽位,实现数据分片。 - 高可用:每个主节点配从节点,主节点故障时从节点自动升为主节点;支持动态伸缩(添加/删除节点,重新分配槽位)。
四、缓存设计篇
1. 缓存问题与解决方案
缓存三大核心问题
问题类型 | 定义 | 发生场景 | 解决方案 |
---|---|---|---|
缓存击穿 | 热点Key(高并发访问的Key)过期失效,大量请求瞬间直达数据库,导致DB压力骤增甚至宕机 | 1. 电商大促期间爆款商品详情Key过期;2. 热点新闻话题的缓存Key过期 | 1. 互斥锁机制:请求未命中缓存时,通过Redis的SET key value NX EX 获取互斥锁,仅获锁线程查询DB并回写缓存,其他线程等待重试(如用Redisson的RLock );2. 热点Key永不过期:对核心热点Key不设置过期时间,通过后台定时任务更新缓存数据,避免过期失效; 3. 预热+主动更新:提前将热点数据加载到缓存(缓存预热),并在数据变更时主动更新缓存,而非依赖过期淘汰; 4. 备用缓存:为热点Key设置两个缓存,主缓存设正常过期时间,备用缓存设较长过期时间,主缓存失效时切换到备用缓存 |
缓存穿透 | 客户端请求的Key在缓存和数据库中均不存在,请求直接穿透缓存冲击数据库,若恶意伪造大量不存在的Key,会导致DB过载 | 1. 黑客伪造不存在的用户ID、商品ID发起请求;2. 业务逻辑设计缺陷导致查询不存在的数据 | 1. 布隆过滤器:在缓存前加布隆过滤器,将DB中所有存在的Key哈希到过滤器中,请求先经过过滤器,不存在的Key直接拦截(误判率可通过哈希函数数量和位数组大小调整,如12KB内存支持百万级Key,误判率0.81%); 2. 缓存空值:对不存在的Key,在缓存中存储空值(如 null )并设置较短过期时间(如5分钟),避免同一Key重复穿透;3. 接口参数校验:在业务层对请求参数进行合法性校验(如用户ID格式、商品ID范围),直接拦截非法请求; 4. IP限流+黑名单:对高频请求不存在Key的IP进行限流,或加入黑名单,防止恶意攻击 |
缓存雪崩 | 大量缓存Key在同一时间段内集中过期,或缓存集群整体故障(如断电、网络中断),导致所有请求瞬间涌向数据库,引发DB雪崩 | 1. 缓存服务重启,所有Key同时失效;2. 批量设置Key时使用相同过期时间(如大促前批量预热缓存,均设24小时过期);3. 缓存集群宕机 | 1. 过期时间随机化:为Key设置过期时间时添加随机值(如基础过期时间±5分钟 ),避免大量Key同时过期;2. 缓存集群高可用:部署Redis哨兵或Cluster集群,主节点故障时从节点自动切换,避免单点失效; 3. 多级缓存架构:引入本地缓存(如Caffeine、Guava Cache),当分布式缓存失效时,先从本地缓存获取数据,减少DB冲击; 4. 熔断降级机制:使用Sentinel、Hystrix等组件,当DB压力超过阈值时,触发熔断,返回默认数据(如“服务繁忙,请稍后再试”),保护DB; 5. 持久化保障:开启RDB+AOF混合持久化,缓存集群故障重启后快速恢复数据,缩短缓存不可用时间 |
热Key、大Key、无底洞
问题类型 | 定义 | 解决方案 |
---|---|---|
热Key | 短时间内被高频访问的Key(如每秒上万次请求),导致存储该Key的Redis节点CPU、网络占用飙升,成为系统瓶颈 | 1. Key分片:将热Key拆分为多个子Key(如“商品:1001”拆为“商品:1001_01”“商品:1001_02”),分散到不同Redis节点; 2. 双层缓存:本地缓存(如应用内存)+分布式缓存,优先从本地缓存获取,减少分布式缓存访问压力; 3. 独立节点存储:将热Key迁移到专门的Redis节点,避免影响其他业务Key; 4. 预计算+异步更新:对热Key对应的高频查询结果预计算,通过异步任务定时更新缓存,减少实时计算压力 |
大Key | 单个Key占用内存过大(通常指超过100MB,如存储大量元素的Hash、List),导致Redis内存占用过高、网络传输延迟大、删除时阻塞主进程 | 1. Key拆分: - Hash类型:按字段拆分(如“user:1001”拆为“user:1001:info”“user:1001:orders”); - List类型:按时间/页数拆分(如“log:202405”拆为“log:20240501”“log:20240502”); 2. 数据压缩:对存储的Value进行压缩(如用Gzip、Snappy),减少内存占用; 3. 异步删除:使用Redis的 UNLINK 命令(替代DEL ),异步删除大Key,避免阻塞主进程;4. 存储优化:避免用String存储大二进制数据(如图片),改用OSS存储,Redis仅存文件URL |
缓存无底洞 | 随着客户端并发量增加,缓存节点的连接数、网络IO激增,即使增加缓存节点,性能提升也不明显,出现“越扩越慢”的现象 | 1. 减少缓存访问次数: - 合并请求(如批量查询用户信息时,用 MGET 替代多次GET );- 结果缓存(将多步查询的合并结果缓存,避免多次访问缓存); 2. 客户端优化: - 使用连接池(如Jedis Pool、Redisson),减少TCP连接建立/关闭开销; - 开启Pipeline,批量发送命令,减少网络往返次数; 3. 缓存架构优化: - 引入代理层(如Twemproxy、Codis),统一管理缓存节点,减少客户端与节点的直接连接; - 按业务模块拆分缓存集群,避免单一集群承载所有业务 |
2. 缓存一致性
- 缓存与数据库一致性:更新策略(先更DB再删缓存,避免缓存脏读)、定时任务校验、Canal监听DB binlog同步缓存。
- 本地缓存与分布式缓存一致性:本地缓存设置短过期时间、分布式缓存更新时主动刷新本地缓存、避免本地缓存存储易变数据。
3. 缓存预热
- 定义:系统启动前加载热点数据到缓存,避免启动后大量请求直达DB。
- 实现方式:写脚本批量加载、预热程序读取DB热点数据写入缓存、利用Redis持久化文件快速加载。
五、运维篇
1. 内存管理
- 内存不足处理:增大Redis内存(
maxmemory 4gb
)、启用内存淘汰策略、删除大Key/过期Key。 - 过期策略:
- 惰性删除:访问Key时检查过期,不消耗CPU,可能浪费内存。
- 定期删除:每隔一段时间扫描部分过期Key,平衡CPU和内存。
- 内存淘汰策略:
allkeys-lru
:淘汰所有Key中最近最少使用的(默认)。volatile-lru
:仅淘汰设置过期时间的Key中最近最少使用的。allkeys-lfu
/volatile-lfu
:基于最近最少频率使用淘汰。
2. 阻塞问题排查
- 原因:大Key删除、持久化(如save阻塞)、网络IO瓶颈。
- 解决:用
redis-cli info stats
查看阻塞统计、避免KEYS
(用SCAN
替代)、异步删除大Key(UNLINK
命令)、优化持久化策略(如用bgsave替代save)。
六、应用篇
1. 分布式锁
- 实现方式:
SET lock:key random_value EX 10 NX
(原子性获取锁),释放时用Lua脚本校验value(避免误删他人锁)。 - 优化:锁自动续期(如Redisson的watch dog机制)、避免死锁(设置过期时间)。
2. 消息队列
- 异步队列:用List实现,
LPUSH
生产消息,BRPOP
消费消息(阻塞避免轮询)。 - 延时队列:用ZSet实现,消息作为成员,过期时间作为score,定时
ZRANGEBYSCORE
获取到期消息。
3. 限流
- 令牌桶算法:用Lua脚本实现,如每秒生成2个令牌,桶容量10,请求时消耗1个令牌,无令牌则限流。
七、底层结构篇
1. 核心底层结构
数据类型 | 底层结构 | 特点 |
---|---|---|
String | SDS(简单动态字符串) | 记录长度,预分配空间,避免内存碎片。 |
List | quicklist(双向链表+压缩列表) | 平衡内存和性能,小数据用压缩列表,大数据拆分为多个quicklist节点。 |
Hash | 哈希表+压缩列表 | 元素少且小时用压缩列表,否则用哈希表。 |
Set | 整数集合+哈希表 | 元素为整数且少时用整数集合,否则用哈希表。 |
ZSet | 跳表+哈希表 | 跳表支持高效范围查询,哈希表快速查找成员分数。 |
2. 关键结构详解
- 跳表:多层索引结构,平均查找时间O(log n),支持快速插入、删除、范围查询,是ZSet的核心结构。
- 压缩列表(ziplist):连续内存存储,节省空间;Redis 7.0用listpack替代,解决ziplist的“连锁更新”问题(每个节点记录自身长度,而非前一节点长度)。
- 哈希表:链式哈希解决冲突,负载因子超过1时扩容,采用渐进式rehash避免阻塞。