Redis:核心数据结构与应用场景
Redis(Remote Dictionary Server)是由 Salvatore Sanfilippo 于 2009 年开发的开源内存数据库,以极高性能、丰富数据结构、原子操作著称。 单线程命令处理模型(6.0+ 支持多线程 IO),读写性能可达 10 万+ QPS,广泛用于缓存、分布式锁、消息队列、排行榜等场景。
⚠️ 版本说明:本文以 Redis 7.x 为基准。7.0 引入了 Redis Functions、Listpack 优化等特性。
目录
| 章节 | 说明 |
|---|---|
| 核心特性 | 单线程模型、与 Memcached 对比、适用场景 |
| 安装与配置 | 安装、redis.conf 常用配置、CLI 操作 |
| 数据类型 | String、List、Hash、Set、ZSet 及命令 |
| 高级数据类型 | Bitmap、HyperLogLog、Geo、Stream |
| 过期与淘汰 | TTL 设置、过期策略、内存淘汰策略 |
| 持久化 | RDB、AOF、混合持久化 |
| 高可用 | 主从复制、哨兵模式、Cluster 集群 |
| 常用场景 | 缓存、分布式锁、限流、排行榜、消息队列 |
| 快速参考卡 | 数据类型速查、常用命令速查 |
核心特性
为什么 Redis 这么快
| 原因 | 说明 |
|---|---|
| 纯内存操作 | 数据存储在内存,读写无磁盘 IO |
| 单线程命令处理 | 避免多线程上下文切换和锁竞争 |
| IO 多路复用 | epoll/kqueue 处理大量并发连接 |
| 高效数据结构 | SDS、跳表、压缩列表等专为内存优化 |
| 6.0+ 多线程 IO | 网络读写多线程,命令执行仍单线程 |
与 Memcached 对比
| 维度 | Redis | Memcached |
|---|---|---|
| 数据结构 | 丰富(5 种基础 + 扩展) | 只有 String |
| 持久化 | RDB + AOF | 不支持 |
| 集群 | 原生 Cluster | 客户端分片 |
| 事务 | 支持(MULTI/EXEC) | 不支持 |
| Lua 脚本 | 支持 | 不支持 |
| 发布订阅 | 支持 | 不支持 |
| 内存效率 | 略低 | 略高 |
安装与配置
安装
# macOS
brew install redis
brew services start redis
# 验证
redis-cli ping # 返回 PONG
# Docker
docker run -d --name redis -p 6379:6379 redis:7-alpine
docker exec -it redis redis-cli
redis.conf 常用配置
# 网络
bind 127.0.0.1 # 绑定地址(生产环境不要 bind 0.0.0.0 且无密码)
port 6379
protected-mode yes # 保护模式,无密码时禁止外网访问
# 安全
requirepass yourpassword # 设置密码
# 内存
maxmemory 2gb # 最大内存限制
maxmemory-policy allkeys-lru # 内存淘汰策略
# 持久化
save 900 1 # 900 秒内有 1 次修改则 RDB 快照
save 300 10 # 300 秒内有 10 次修改
save 60 10000 # 60 秒内有 10000 次修改
appendonly yes # 开启 AOF
appendfsync everysec # AOF 每秒刷盘(推荐)
# 日志
loglevel notice
logfile /var/log/redis/redis.log
# 慢查询
slowlog-log-slower-than 10000 # 超过 10ms 记录慢查询(单位微秒)
slowlog-max-len 128
redis-cli 常用操作
# 连接
redis-cli -h 127.0.0.1 -p 6379 -a yourpassword
# 基础命令
redis-cli ping # 测试连通性
redis-cli info # 查看服务器信息
redis-cli info memory # 内存信息
redis-cli info replication # 主从信息
redis-cli dbsize # 当前 DB 的 key 数量
redis-cli flushdb # 清空当前 DB(⚠️ 谨慎)
redis-cli flushall # 清空所有 DB(⚠️ 危险)
# 慢查询
redis-cli slowlog get 10 # 查看最近 10 条慢查询
redis-cli slowlog len # 慢查询日志条数
# 监控
redis-cli monitor # 实时打印所有命令(调试用,生产慎用)
redis-cli --latency # 延迟测试
数据类型
String(字符串)
最基础的类型,底层是 SDS(Simple Dynamic String),可存字符串、整数、二进制数据,最大 512MB。
# 基本操作
SET key "hello"
GET key # "hello"
DEL key
EXISTS key # 1(存在)/ 0(不存在)
# 带过期时间
SET key "value" EX 60 # 60 秒后过期
SET key "value" PX 60000 # 60000 毫秒后过期
SETEX key 60 "value" # 等价于 SET ... EX
TTL key # 查看剩余秒数,-1=永不过期,-2=不存在
# 原子操作(不存在时才设置,用于分布式锁)
SET key "value" NX EX 30 # NX = Not eXists,原子性
SETNX key "value" # 旧写法,不推荐(无法同时设置过期时间)
# 计数器(整数值原子递增/递减)
SET counter 0
INCR counter # 1
INCRBY counter 5 # 6
DECR counter # 5
DECRBY counter 2 # 3
# 批量操作(减少网络往返)
MSET k1 v1 k2 v2 k3 v3
MGET k1 k2 k3 # ["v1", "v2", "v3"]
# 追加
APPEND key " world" # 追加到末尾,返回新长度
STRLEN key # 字符串长度
# 获取并设置
GETSET key "new_value" # 返回旧值并设置新值
GETEX key EX 60 # 获取值同时刷新过期时间(Redis 6.2+)
List(列表)
双端链表(元素少时用 Listpack 压缩),支持从两端推入/弹出,天然适合队列和栈。
# 推入
LPUSH list a b c # 从左推入,结果:[c, b, a]
RPUSH list x y z # 从右推入,结果:[c, b, a, x, y, z]
# 弹出
LPOP list # 弹出左端:c
RPOP list # 弹出右端:z
LPOP list 3 # 弹出左端 3 个(Redis 6.2+)
# 阻塞弹出(用于消费者等待队列,timeout=0 永久阻塞)
BLPOP list 10 # 阻塞等待 10 秒,有元素立即返回
BRPOP list 10
# 查看
LRANGE list 0 -1 # 查看所有元素(-1 表示最后一个)
LRANGE list 0 9 # 查看前 10 个
LLEN list # 列表长度
LINDEX list 0 # 获取指定索引的元素
# 修改与删除
LSET list 0 "new_val" # 设置指定索引的值
LREM list 2 "a" # 删除 2 个值为 "a" 的元素(正数从头,负数从尾)
LTRIM list 0 99 # 只保留索引 0~99 的元素(常用于限制列表长度)
Hash(哈希)
键值对集合,底层用 Listpack(元素少)或 HashTable,适合存储对象。
# 设置
HSET user:1 name "Alice" age 30 email "alice@example.com"
HSET user:1 city "Beijing" # 新增字段
HSETNX user:1 name "Bob" # 字段不存在时才设置
# 获取
HGET user:1 name # "Alice"
HMGET user:1 name age # ["Alice", "30"]
HGETALL user:1 # 所有字段和值
# 查看
HKEYS user:1 # 所有字段名
HVALS user:1 # 所有字段值
HLEN user:1 # 字段数量
HEXISTS user:1 email # 字段是否存在
# 删除
HDEL user:1 city
# 计数
HINCRBY user:1 age 1 # age 加 1,返回新值 31
HINCRBYFLOAT user:1 score 0.5 # 浮点递增
Set(集合)
无序、不重复的字符串集合,支持集合运算,底层用 Listpack 或 HashTable。
# 添加与删除
SADD tags "java" "redis" "mysql"
SREM tags "mysql"
# 查询
SMEMBERS tags # 所有成员(无序)
SCARD tags # 成员数量
SISMEMBER tags "java" # 是否存在:1/0
SMISMEMBER tags "java" "go" # 批量判断(Redis 6.2+)
# 随机操作
SRANDMEMBER tags 2 # 随机获取 2 个(不删除)
SPOP tags # 随机弹出一个(删除)
# 集合运算
SADD set1 "a" "b" "c"
SADD set2 "b" "c" "d"
SUNION set1 set2 # 并集:[a, b, c, d]
SINTER set1 set2 # 交集:[b, c]
SDIFF set1 set2 # 差集(set1 有 set2 没有):[a]
# 存储运算结果
SUNIONSTORE dest set1 set2
SINTERSTORE dest set1 set2
ZSet(有序集合)
每个成员关联一个 score(浮点分数),按 score 排序,底层用 Listpack 或跳表(SkipList)。
# 添加
ZADD leaderboard 100 "Alice"
ZADD leaderboard 95 "Bob" 88 "Charlie"
ZADD leaderboard NX 120 "Dave" # 不存在时才添加
ZADD leaderboard XX 110 "Alice" # 只更新已存在的成员
# 查询(按排名)
ZRANGE leaderboard 0 -1 # 升序所有成员
ZRANGE leaderboard 0 -1 WITHSCORES # 带分数
ZREVRANGE leaderboard 0 2 # 降序前 3 名(旧写法)
ZRANGE leaderboard 0 2 REV WITHSCORES # 降序带分数(Redis 6.2+)
# 查询(按分数)
ZRANGEBYSCORE leaderboard 90 120 # score 在 90~120 的成员
ZRANGEBYSCORE leaderboard -inf +inf # 所有成员
ZRANGEBYSCORE leaderboard 90 +inf LIMIT 0 10 # 分页
# 排名
ZRANK leaderboard "Alice" # 升序排名(0 开始)
ZREVRANK leaderboard "Alice" # 降序排名
ZSCORE leaderboard "Alice" # 获取分数
# 修改分数
ZINCRBY leaderboard 10 "Bob" # Bob 分数加 10
# 删除
ZREM leaderboard "Charlie"
ZREMRANGEBYRANK leaderboard 0 1 # 删除排名 0~1 的成员
ZREMRANGEBYSCORE leaderboard 0 60 # 删除分数 0~60 的成员
# 数量
ZCARD leaderboard # 成员总数
ZCOUNT leaderboard 90 100 # score 在 90~100 的成员数
高级数据类型
Bitmap(位图)
# 用 bit 记录用户签到(节省内存:1 亿用户只需 12.5MB)
SETBIT sign:user:1001 20240101 1 # 用户 1001 在第 20240101 天签到
GETBIT sign:user:1001 20240101 # 查询:1
BITCOUNT sign:user:1001 # 统计签到总天数
BITCOUNT sign:user:1001 0 6 # 统计第 0~6 字节范围内的 1 的数量
BITPOS sign:user:1001 1 # 第一个签到的 bit 位置
HyperLogLog(基数统计)
# 近似去重计数,误差约 0.81%,每个 key 最多 12KB
PFADD uv:page1 "user1" "user2" "user3"
PFADD uv:page1 "user1" # 重复,不计入
PFCOUNT uv:page1 # 约等于 3
# 合并多个 HyperLogLog
PFMERGE uv:total uv:page1 uv:page2
PFCOUNT uv:total
Geo(地理位置)
# 添加地理位置(底层用 ZSet 存储)
GEOADD locations 116.397128 39.916527 "北京"
GEOADD locations 121.480539 31.235929 "上海"
GEOADD locations 113.264385 23.129112 "广州"
# 获取坐标
GEOPOS locations "北京"
# 计算两点距离
GEODIST locations "北京" "上海" km # 单位:m/km/mi/ft
# 查找附近的点(6.2+ 推荐 GEOSEARCH)
GEOSEARCH locations FROMMEMBER "北京" BYRADIUS 1500 km ASC COUNT 5
Stream(消息流)
# 生产者:添加消息(* 表示自动生成 ID)
XADD orders * user_id 1001 product_id 2001 amount 99.9
XADD orders * user_id 1002 product_id 3001 amount 199.9
# 消费者:读取消息
XRANGE orders - + # 读取所有消息
XRANGE orders - + COUNT 10 # 读取最多 10 条
XLEN orders # 消息数量
# 消费者组(保证消息不重复消费)
XGROUP CREATE orders order-group $ MKSTREAM
XREADGROUP GROUP order-group consumer1 COUNT 10 STREAMS orders >
# > 表示读取未投递给该组的新消息
# 确认消费
XACK orders order-group <message-id>
过期与淘汰
设置过期时间
EXPIRE key 60 # 60 秒后过期
PEXPIRE key 60000 # 60000 毫秒后过期
EXPIREAT key 1735689600 # Unix 时间戳过期
PEXPIREAT key 1735689600000
TTL key # 剩余秒数(-1=永不过期,-2=key不存在)
PTTL key # 剩余毫秒数
PERSIST key # 移除过期时间,变为永久
过期删除策略
| 策略 | 说明 |
|---|---|
| 惰性删除 | 访问时检查是否过期,过期则删除;节省 CPU,可能内存泄漏 |
| 定期删除 | 每 100ms 随机抽取一批 key 检查删除;两种策略配合使用 |
内存淘汰策略(maxmemory-policy)
| 策略 | 说明 | 适用场景 |
|---|---|---|
noeviction | 内存满时写入报错(默认) | 不允许丢数据 |
allkeys-lru | 所有 key 中淘汰最近最少使用 | 缓存场景首选 |
allkeys-lfu | 所有 key 中淘汰最不常用(7.0+ 优化) | 热点数据明显时 |
volatile-lru | 只淘汰有过期时间的 key(LRU) | 混合存储场景 |
volatile-lfu | 只淘汰有过期时间的 key(LFU) | 混合存储场景 |
volatile-ttl | 淘汰剩余 TTL 最短的 key | — |
allkeys-random | 随机淘汰所有 key | — |
持久化
RDB(快照)
# 手动触发
SAVE # 同步(阻塞主线程,生产慎用)
BGSAVE # 异步(fork 子进程,推荐)
# 查看最后一次 RDB 保存时间
LASTSAVE # 返回 Unix 时间戳
# 配置(redis.conf)
save 900 1 # 900 秒内有 1 次修改
save 300 10
save 60 10000
dbfilename dump.rdb
dir /var/lib/redis
| 优点 | 缺点 |
|---|---|
| 文件紧凑,恢复速度快 | 可能丢失最后一次快照后的数据 |
| fork 子进程,不影响主线程 | 数据量大时 fork 耗时 |
AOF(追加日志)
# 配置
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec # always(最安全)/ everysec(推荐)/ no(最快)
# 手动触发 AOF 重写(压缩 AOF 文件)
BGREWRITEAOF
# 自动重写配置
auto-aof-rewrite-percentage 100 # AOF 文件比上次重写后增长 100% 时触发
auto-aof-rewrite-min-size 64mb # AOF 文件最小 64MB 才触发重写
| 优点 | 缺点 |
|---|---|
| 数据最多丢 1 秒(everysec) | 文件比 RDB 大 |
| 可读性强,便于数据恢复 | 恢复速度比 RDB 慢 |
混合持久化(推荐,4.0+)
# redis.conf
aof-use-rdb-preamble yes # AOF 文件头部嵌入 RDB 快照,尾部追加 AOF 日志
兼顾 RDB 的快速恢复和 AOF 的数据安全,生产环境推荐开启。
高可用
主从复制
# 从库配置(redis.conf)
replicaof 192.168.1.100 6379
masterauth yourpassword
# 查看复制状态
INFO replication
# 关注:role、master_link_status、master_last_io_seconds_ago
哨兵模式(Sentinel)
# sentinel.conf
sentinel monitor mymaster 192.168.1.100 6379 2 # 2 个哨兵同意才故障转移
sentinel auth-pass mymaster yourpassword
sentinel down-after-milliseconds mymaster 30000 # 30 秒无响应则认为主库下线
sentinel failover-timeout mymaster 180000
# 启动哨兵
redis-sentinel /etc/redis/sentinel.conf
# 查询主库地址(客户端通过哨兵发现主库)
redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster
Cluster 集群(生产推荐)
16384 个 slot(哈希槽)分配给各节点
key 通过 CRC16(key) % 16384 映射到对应 slot
每个主节点负责一段 slot,配置至少 3 主 3 从
# 创建集群(redis-cli 方式)
redis-cli --cluster create \
127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 \
127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 \
--cluster-replicas 1
# 查看集群状态
redis-cli -p 7001 CLUSTER INFO
redis-cli -p 7001 CLUSTER NODES
# 连接集群(-c 自动重定向)
redis-cli -c -p 7001
常用场景
缓存(最常见)
# 缓存穿透防护:缓存空值
SET cache:user:999 "NULL" EX 60 # 不存在的 key 也缓存短时间
# 缓存击穿防护:热点 key 永不过期 + 后台异步刷新
SET cache:hot:product:1 "{...}" EX 3600
# 缓存雪崩防护:过期时间加随机值
# Java 伪代码:ttl = baseTtl + random.nextInt(300)
分布式锁
# 加锁(原子性:SET NX EX)
SET lock:order:1001 "uuid-xxx" NX EX 30
# NX:不存在才设置(互斥)
# EX 30:30 秒自动释放(防止死锁)
# value 用唯一值(防止误删其他线程的锁)
# 解锁(Lua 脚本保证原子性:判断 + 删除)
# 伪代码等价:
# if GET lock:order:1001 == "uuid-xxx" then DEL lock:order:1001 end
EVAL "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 lock:order:1001 uuid-xxx
# 查看锁状态
GET lock:order:1001
TTL lock:order:1001
限流(滑动窗口)
# 固定窗口限流:1 分钟内最多 100 次
INCR rate:user:1001:202401011200 # key 包含分钟时间戳
EXPIRE rate:user:1001:202401011200 60
# 使用 Lua 脚本保证原子性
EVAL "
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local current = redis.call('incr', key)
if current == 1 then
redis.call('expire', key, ARGV[2])
end
return current
" 1 rate:user:1001 100 60
排行榜
# 实时排行榜(ZSet 天然支持)
ZADD rank:daily 1500 "user:1001"
ZADD rank:daily 2000 "user:1002"
ZADD rank:daily 1800 "user:1003"
# 获取前 10 名(降序)
ZRANGE rank:daily 0 9 REV WITHSCORES
# 获取用户排名
ZREVRANK rank:daily "user:1001" # 降序排名
# 增加积分
ZINCRBY rank:daily 100 "user:1001"
消息队列
# 简单队列(List)
RPUSH queue:email "msg1" # 生产者
BLPOP queue:email 0 # 消费者(阻塞等待)
# 可靠队列(Stream,支持消费者组、ACK)
XADD stream:order * order_id 1001 amount 99
XGROUP CREATE stream:order grp1 0
XREADGROUP GROUP grp1 c1 COUNT 10 STREAMS stream:order >
XACK stream:order grp1 <msg-id>
快速参考卡
数据类型选型
| 场景 | 数据类型 | 理由 |
|---|---|---|
| 缓存对象(JSON) | String | 简单直接 |
| 缓存对象(字段级访问) | Hash | 可单独读写字段,节省内存 |
| 消息队列 | List / Stream | List 简单,Stream 可靠 |
| 标签/关注关系 | Set | 去重 + 集合运算 |
| 排行榜/积分 | ZSet | 自动排序 |
| 签到/在线状态 | Bitmap | 极省内存 |
| UV 统计(近似) | HyperLogLog | 12KB 统计亿级基数 |
| 附近的人 | Geo | 内置距离计算 |
| 分布式锁 | String(SET NX EX) | 原子性 + 自动释放 |
| 计数器 | String(INCR) | 原子递增 |
常用命令速查
| 命令 | 说明 |
|---|---|
KEYS pattern | 查找匹配的 key(⚠️ 生产禁用,用 SCAN) |
SCAN 0 MATCH * COUNT 100 | 安全迭代 key |
TYPE key | 查看 key 的数据类型 |
OBJECT ENCODING key | 查看底层编码 |
DEBUG OBJECT key | 查看 key 的详细信息 |
DUMP key + RESTORE | 序列化与反序列化 |
OBJECT IDLETIME key | key 的空闲时间(秒) |
MEMORY USAGE key | key 占用的内存字节数 |
WAIT 1 0 | 等待至少 1 个从库同步完成 |
DEBUG SLEEP 5 | 让 Redis 睡眠 5 秒(测试用) |
性能排查命令
redis-cli INFO stats # 命令统计、命中率
redis-cli INFO memory # 内存使用详情
redis-cli INFO clients # 连接数
redis-cli SLOWLOG GET 10 # 慢查询
redis-cli --bigkeys # 扫描大 key(⚠️ 对生产有影响)
redis-cli --hotkeys # 热 key 统计(需 maxmemory-policy 为 LFU)
redis-cli OBJECT FREQ key # key 的访问频率(LFU 模式)
IO 多路复用与单线程模型
Redis 用一个主线程处理所有命令执行,靠 IO 多路复用(epoll)处理大量并发连接:
客户端连接 → epoll 事件循环 → 命令队列 → 单线程顺序执行 → 返回结果
Redis 6.0 多线程 IO:
- 网络读写(解析请求、写回响应)使用多线程
- 命令执行仍然是单线程(保证原子性)
- 配置:
io-threads 4、io-threads-do-reads yes
参考资料
⚠️ 深度专题笔记:
- 底层数据结构原理 →
02 Redis 底层数据结构.md- 持久化与主从复制 →
03 Redis 持久化与主从复制.md- 缓存设计与问题排查 →
04 Redis 缓存设计与问题处理.md- 高可用与集群 →
05 Redis 高可用与集群.md
评论 (0)