Redis的简单总结

一、Redis 是什么

Redis(Remote Dictionary Server)是一个基于内存的键值数据库,数据存在内存中,读写速度极快(单机 10 万+ QPS)。

核心特点:

  • 基于内存,读写极快

  • 键值模型:Key 是字符串,Value 支持多种类型

  • 单线程执行命令,天然避免并发竞争

  • 支持持久化(RDB / AOF),重启不丢数据

  • 支持主从复制、哨兵、集群等高可用方案

为什么 Redis 单线程还这么快?

  1. 纯内存操作

  2. 核心功能简单(增删改查没有复杂约束和索引)

  3. I/O 多路复用(epoll),一个线程管理多个 Socket

  4. 避免线程切换和锁竞争开销

举例: 用户请求查商品详情 → 先查 Redis 缓存(命中直接返回,亚毫秒级)→ 未命中则查 MySQL → 写回 Redis → 返回。

多路复用

  • 传统阻塞 I/O:餐厅里一个服务员(线程)只服务一桌客人(Socket)。客人看菜单半小时,服务员就在旁边傻站着等,效率极低。

  • I/O 多路复用 (epoll):餐厅里只有一个超级服务员(单线程),但他站在吧台看着所有桌子。哪桌客人举手说"我要点菜/结账"(数据就绪),服务员才过去处理。处理完立刻回到吧台。这样一个人就能高效管理成百上千桌。

二、Redis vs MySQL

维度 MySQL Redis
存储位置 磁盘 内存
速度 毫秒级 微秒级
数据模型 关系表 键值对
容量 TB 级 受内存限制,通常 GB 级
查询 复杂 SQL / JOIN 仅按 Key 查找
事务 ACID 完整事务 简单事务,无回滚

典型架构: MySQL 做主存储,Redis 做缓存层,两者配合。

三、五大基础数据类型

3.1 String(字符串)

核心: 一个 Key 对应一个二进制安全的字符串(最大 512MB)。

命令 作用
SET / GET 设置、读取
MSET / MGET 批量设置、读取
INCR / DECR 原子自增/自减
SET key value EX seconds 设置同时指定过期时间
SETNX 仅 Key 不存在时才设置

举例:

复制代码
SET product:2001 '{"name":"耳机","price":899}' EX 3600   # 缓存商品,1小时过期
INCR views:2001        # 浏览量 +1,返回 1
SETNX lock:order:1 "uuid-xxx"   # 抢分布式锁

场景: 缓存、计数器、分布式锁、Session 存储。

3.2 Hash(哈希)

核心: 一个 Key 对应一个 field → value 映射表,适合存对象,可按字段读写。

命令 作用
HSET / HGET 设置/获取单个字段
HMSET 批量设置字段
HGETALL 获取全部字段和值
HDEL 删除字段
HEXISTS 判断字段是否存在

举例:

复制代码
HSET user:1001 name "小林" level 5 city "上海"
HGET user:1001 name          # "小林"
HGETALL user:1001            # name、level、city 全部返回
HSET product:2001 stock 119  # 只改库存字段,不用重写整个 JSON

场景: 用户信息、商品详情、购物车。

3.3 List(列表)

核心: 双向链表,有序,可从两端压入/弹出。

命令 作用
LPUSH / RPUSH 左侧/右侧插入
LPOP / RPOP 左侧/右侧弹出
LRANGE start stop 按索引范围查看(0 -1 = 全部)
LLEN 列表长度
BRPOP / BLPOP 阻塞式弹出(可做队列消费)

举例:

复制代码
LPUSH feed:user:1001 "post:503" "post:502"   # 发动态
LRANGE feed:user:1001 0 9                      # 看最新 10 条
​
# 消息队列:生产者推入
RPUSH mq:order "order:9001"
# 消费者阻塞等待
BRPOP mq:order 0   # 返回 "order:9001"

场景: 消息队列(轻量)、最新动态时间线、秒杀排队。

3.4 Set(集合)

核心: 无序、不重复的字符串集合,支持交/并/差集运算。

命令 作用
SADD 添加成员(自动去重)
SMEMBERS 列出所有成员
SISMEMBER 判断是否为成员
SINTER / SUNION / SDIFF 交集/并集/差集
SCARD 集合大小

举例:

复制代码
SADD tag:数码 phone audio laptop
SADD tag:音频 audio speaker
SINTER tag:数码 tag:音频      # 交集:audio(共同的标签)
​
SADD user:1001:follows 2002 2003 2005
SADD user:1002:follows 2003 2005 2008
SINTER user:1001:follows user:1002:follows   # 共同好友:2003、2005

场景: 标签系统、共同好友、点赞用户去重、今日已领券用户。

3.5 Sorted Set(有序集合 / ZSet)

核心: 成员不重复,每个成员带一个 score(分数),按 score 自动排序。

命令 作用
ZADD 添加成员及分数
ZRANGE / ZREVRANGE 按排名升序/降序取
ZRANK / ZREVRANK 查成员排名
ZSCORE 查成员分数
ZRANGEBYSCORE 按分数区间取成员

举例:

复制代码
ZADD hot:sales 1200 product:A 8750 product:B 9800 product:C
ZREVRANGE hot:sales 0 2 WITHSCORES   # 销量榜 Top 3
​
# 延迟队列:score = 到期时间戳
ZADD delay:task 1710000000 "refund:9001"
ZRANGEBYSCORE delay:task 0 1710000000   # 拉到期的任务

场景: 排行榜、延迟队列、按时间排序的消息流。

ZSet 的底层原理

ZSet 为什么查询和插入这么快? ==> 因为 跳表 Skip List

概念:它是在普通的有序链表上,增加了多级"索引",从而实现了类似二分查找的速度(时间复杂度O(log N))。

【跳表查询过程演示:查找元素 7】

Level 3: 1 ------------------------------------------> 10

Level 2: 1 ----------------> 5 ----------------------> 10

Level 1: 1 ----> 3 ----> 5 ----> 7 ----> 9 ----> 10

查找路径:

  1. 先看 L3:1 比 7 小,往右看是 10,比 7 大,于是降级到 L2。

  2. 再看 L2:从 1 走到 5,5 比 7 小,往右看是 10,比 7 大,降级到 L1。

  3. 最后看 L1:从 5 走到 7,找到目标!(跳过了 3, 9 的遍历)

数据类型选型口诀

复制代码
单值缓存用 String    →  JSON 缓存、计数器
对象多字段用 Hash     →  用户信息、商品详情
排队或时间线用 List   →  消息队列、动态列表
去重和集合运算用 Set  →  标签、共同好友
排序排名用 ZSet      →  排行榜、延迟队列

四、三个进阶数据类型

4.1 Bitmap(位图)

核心: 把 String 当成二进制位数组,每个 bit = 0/1,极省空间。

举例:

复制代码
SETBIT sign:user:1001:2025 0 1    # 第1天签到
SETBIT sign:user:1001:2025 2 1    # 第3天签到
BITCOUNT sign:user:1001:2025      # 统计签到总天数 → 2

场景: 签到统计、日活标记(一个用户一年只需 365 bit ≈ 46 字节)。

4.2 HyperLogLog(基数统计)

核心: 用固定极小的内存(约 12KB)估算"去重后有多少个不同元素",有约 0.81% 误差,不能列出具体元素。

举例:

复制代码
PFADD uv:home:today user_1001 user_1002 user_1001
PFCOUNT uv:home:today             # 估算 ≈ 2(重复 user_1001 只算一次)
​
PFMERGE uv:total uv:sectionA uv:sectionB  # 合并多个 HLL

场景: 海量 UV 统计、去重计数(能接受误差)。

4.3 Stream(消息流,Redis 5.0+)

核心: 持久化的日志型消息流,带消息 ID、消费者组、ACK 确认机制。

举例:

复制代码
XADD orders * type paid order_id 10001    # 生产消息
XREAD COUNT 2 STREAMS orders 0            # 从开头读2条
XGROUP CREATE orders cg1 0 MKSTREAM       # 创建消费者组
XREADGROUP GROUP cg1 worker1 COUNT 10 STREAMS orders >   # 组内消费

Stream vs List 做队列对比:

对比 List Stream
消息标识 无内置 ID 内置时间有序 ID
消费语义 弹出即拿走 消费者组、pending、ACK
历史回溯 弹出后难追溯 可保留日志、按范围查

场景: 需要可靠性消息队列的场景(简单场景用 List 就够了)。

五、Key 管理与过期策略

5.1 Key 命名规范

推荐格式: 业务:对象:id[:字段]

复制代码
user:1001:name        # 用户 1001 的姓名
order:20240324:items  # 订单明细
cache:product:88392   # 商品缓存
  • 好:user:1001:name → 一眼看出业务、实体、字段

  • 坏:ax1data → 无从辨认

5.2 常用 Key 命令

命令 作用
EXPIRE key 秒 设置过期时间
TTL key 查剩余过期秒数(-1 永不过期,-2 不存在)
PERSIST key 移除过期时间
EXISTS key 判断是否存在
TYPE key 查看 value 类型
DEL key 删除
SCAN cursor MATCH pattern 渐进式遍历(生产用,不阻塞)

5.3 过期删除策略

惰性删除 + 定期删除 相结合:

  • 惰性删除: 访问 Key 时才检查是否过期,过期则删 → 保证用到的 Key 一定正确

  • 定期删除: Redis 周期性随机抽查一批 Key,删掉过期的 → 回收没人访问的过期 Key

举例: 一个过期 Key 一直没人访问,不会立刻被删,直到定期删除抽到它或被访问时惰性删除。

5.4 内存淘汰策略(maxmemory-policy)

当内存满了,按策略删除 Key 腾空间:

策略 说明
noeviction 不淘汰,写命令报错
allkeys-lru 从所有 Key 中淘汰最久未使用的(缓存场景首选
allkeys-lfu 淘汰使用频率最低的
volatile-lru 仅淘汰带过期时间的 Key
volatile-ttl 优先淘汰 TTL 更短的

六、持久化

6.1 RDB(快照)

原理: 定时把内存数据"拍一张照片"保存到 dump.rdb 文件。

触发方式:

  • SAVE:阻塞主进程(生产不用)

  • BGSAVE:fork 子进程后台写,主进程继续服务

  • 配置自动:save 900 1(900秒内1次写就触发)

优点: 文件紧凑,恢复快。缺点: 两次快照之间的数据可能丢失。

6.2 AOF(追加日志)

原理: 每条写命令追加到 appendonly.aof 文件,重启时重放命令恢复数据。

三种刷盘策略:

策略 说明 数据安全 性能
always 每条命令都刷盘 最高 最慢
everysec 每秒刷一次(默认推荐 最多丢 1 秒 平衡
no 交给 OS 决定 可能丢很多 最快

AOF 重写(瘦身): 对同一 Key 多次修改,只保留最终结果,生成更精简的 AOF。

6.3 混合持久化(Redis 4.0+)

AOF 文件前半段 = RDB 快照(紧凑),后半段 = 增量 AOF 命令 → 兼顾恢复速度与数据安全

6.4 选择建议

场景 方案
能接受丢几分钟 纯 RDB
几乎不能丢 AOF everysec
生产最佳实践 RDB + AOF 都开,或混合持久化
纯缓存(丢了可重建) 可关持久化

七、缓存三大经典问题

7.1 缓存穿透

问题: 查的数据数据库里也没有 → 每次穿透缓存打 DB。

解决:

  • 缓存空值: 查不到也缓存一个占位(短 TTL),下次直接返回"不存在"

  • 布隆过滤器: 先判断"可能存在吗",不可能直接返回

举例: 恶意请求查 id=-1 的商品,数据库没有 → 缓存空值 SET product:-1 "__NULL__" EX 60,60 秒内不再打库。

7.2 缓存击穿

问题: 热点 Key 过期瞬间,大量并发同时查库(单个热点问题)。

解决:

  • 互斥锁: 只有一个线程去查库回写,其他等待(SET lock:key uuid NX EX 10

  • 逻辑过期: 值里带过期时间,过期后返回旧值 + 异步刷新

举例: 秒杀商品缓存过期 → 1000 个请求同时来 → 只有 1 个拿到锁去查库,999 个等缓存更新后直接读。

7.3 缓存雪崩

问题: 大量 Key 同时失效 或 Redis 挂了 → 请求全打数据库(面的问题)。

解决:

  • 随机 TTL: TTL = 3600 + random(0, 300) 避免集体过期

  • 高可用: 主从 + 哨兵 / 集群

  • 限流降级: 保护数据库

举例: 100 个商品缓存都设了 1 小时过期 → 到点全部同时失效 → 加随机抖动后,过期时间分散在 60~65 分钟。

异常场景 核心问题 通俗理解 终极解决方案
缓存穿透 根本不存在的数据 有人恶意狂刷 id=-999 的商品 1. 缓存空对象 (短TTL) 2. 布隆过滤器 (Bloom Filter)拦截
缓存击穿 某一个热点数据突然过期 微博热搜第一名突然过期,请求全砸向 DB 1. 加互斥锁 (Redis SETNX) 2. 逻辑过期 (后台异步更新)
缓存雪崩 大批量数据同时过期 / 节点宕机 早上 8 点大批缓存同时失效,DB 瞬间被压垮 1. 过期时间加随机数 (打散) 2. Redis 部署高可用集群 (防宕机)

八、分布式锁

核心命令:

复制代码
SET lock:order:123 <唯一UUID> NX EX 30
  • NX:Key 不存在才成功(互斥)

  • EX 30:30 秒自动过期,防止死锁

  • 唯一UUID:释放时校验 value,防止误删别人的锁

释放锁(Lua 保证原子性):

复制代码
if redis.call("GET", KEYS[1]) == ARGV[1] then
    return redis.call("DEL", KEYS[1])
else
    return 0
end

要点: 过期时间 > 业务执行时间;释放必须校验 value + Lua 原子操作。

九、分布式 Session 共享

问题: 负载均衡下,用户登录在 A 机,下次请求到 B 机 → Session 丢失。

解决: Session 外置到 Redis,所有应用服务器共享读写。

举例(Spring Boot):

复制代码
// 引入 spring-session-data-redis,配置 Redis 连接
// 框架自动代理 HttpSession 读写到 Redis
session.setAttribute("user", userInfo);  // 写入 Redis
session.getAttribute("user");            // 从 Redis 读取

十、接口限流(滑动窗口)

思路: 用 ZSet,score 存请求时间戳,每次请求:

  1. 删除窗口外的旧记录

  2. 统计窗口内记录数

  3. 超限则拒绝,否则加入当前请求

举例: 用户每分钟最多 5 次请求

复制代码
ZREMRANGEBYSCORE rate:user:1001 0 <now-60>   # 删60秒前的
ZCARD rate:user:1001                           # 看当前窗口内次数
# 若 < 5 → ZADD rate:user:1001 <now> <uuid>    # 放行并记录

十一、事务与 Lua 脚本

11.1 Redis 事务

命令 作用
MULTI 开启事务,后续命令入队
EXEC 一次性执行队列中所有命令
DISCARD 放弃队列
WATCH key 监视 Key,若在 EXEC 前被改 → EXEC 失败(乐观锁)

关键区别:Redis 事务不支持回滚! 某条命令失败,其他命令照常执行。

举例:

复制代码
WATCH inventory:sku001
GET inventory:sku001       # 读到库存 10
MULTI
DECRBY inventory:sku001 1  # 入队
EXEC                       # 若期间库存被改过 → 返回空 → 重试

11.2 Lua 脚本

什么时候用 Lua: 需要"读 → 判断 → 写"原子完成,中间不能被打断。

复制代码
EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 mykey "hello"

Lua vs 事务:

维度 事务 Lua 脚本
条件分支 不能(无法根据结果分支) 可以 if/while
原子性 EXEC 时连续执行 整段脚本一次跑完
适用场景 简单批量 锁、限流、库存校验

十二、发布订阅(Pub/Sub)

模式: 发布者 → 频道 → 所有订阅者(广播)

命令 作用
SUBSCRIBE channel 订阅频道
PUBLISH channel msg 向频道发消息
PSUBSCRIBE pattern 模式匹配订阅(如 news.*

举例:

复制代码
# 终端1(订阅者)
SUBSCRIBE chat.room1
​
# 终端2(发布者)
PUBLISH chat.room1 "大家好"   # 返回 1(1个订阅者收到)

局限:

  • 消息不持久化,离线收不到

  • 无 ACK 确认,可能丢消息

  • 只适合实时广播,不适合可靠消息传递

Pub/Sub vs Stream: 要广播选 Pub/Sub,要可靠队列选 Stream。

十三、主从复制与高可用

13.1 主从复制

架构: 一个 Master(写)→ 多个 Replica(读,只读)

同步原理:

  • 全量同步: 初次连接,主把 RDB 快照发给从

  • 增量同步: 后续主上每条写命令持续同步到从

配置: REPLICAOF <master-ip> <port>(从节点执行)

注意: 主从复制是异步的,读从可能读到略微过期的数据。

13.2 哨兵模式(Sentinel)

解决什么问题: 主挂了,自动把从提升为新主,不需要人工改配置。

三大功能:

  • 监控: 周期性检查主、从、哨兵状态

  • 通知: 异常时通知管理员

  • 自动故障转移: 主不可用时选举新主

部署: 多个哨兵(奇数个,如 3 个)投票决策,避免单哨兵误判。

13.3 集群模式(Cluster)

解决什么问题: 数据量大到单机内存装不下,需要分片。

原理: 16384 个哈希槽分布在多个主节点上,每个 Key 通过 CRC16(key) % 16384 定位到对应槽/节点。

限制: 跨槽的多 Key 操作受限(如事务、Lua、交集运算需同槽)。

13.4 选择建议

场景 方案
单机够用,需自动故障转移 主从 + 哨兵
数据量大或单主写瓶颈 Redis Cluster
学习测试 单机
相关推荐
暴躁小师兄数据学院1 小时前
【AI大数据工程师特训笔记】第11讲:正则表达式与正则函数
数据库·mysql
garmin Chen1 小时前
LeetcodeHot100打卡(14、合并空间,15、轮转数组,16、除了自身以外数组乘积,17.缺失的第一个整数)
java·笔记·学习·算法
IT龟苓膏1 小时前
MySQL InnoDB 内存结构与性能调优:Buffer Pool、脏页、刷盘、临时表和 filesort 一篇讲清
数据库·mysql
城数派1 小时前
2026年500米分辨率DEM地形数据(全球/全国/分省/分市)
数据库·arcgis·信息可视化·数据分析·excel
AAA大运重卡何师傅(专跑国道)1 小时前
力扣hot100
服务器·前端·数据库
加号32 小时前
【MySQL】 审计功能深度解析:从原理到落地实践
数据库·mysql
不羁的木木2 小时前
ArkWeb实战学习笔记04-JavaScript与Native通信
笔记·学习·harmonyos
KaMeidebaby2 小时前
卡梅德生物技术快报|Western Blot 实验应用:肺肠轴机制研究全流程技术解析
前端·数据库·人工智能·算法·百度
雨辰AI2 小时前
MySQL 迁移至达梦 DM9 完整改造指南|99% SQL 零改动
java·开发语言·数据库·sql·mysql·政务