Redis相关
Redis 内存数据库与缓存 完整系统学习大纲
本文面向测试工程师及后端开发初学者。Redis 在现代 Web 架构中无处不在:缓存加速、分布式锁、限流、计数器、排行榜、Session 共享…… 接口测试时验证”数据是否真的被缓存”、性能测试时排查”为什么缓存不命中”、排查 Bug 时看懂 Redis 数据结构,都是测试工程师的高频技能。 命令基于 Redis 7.x,可在 Windows PowerShell、Linux/macOS 终端中执行。
一、Redis 概述
1.1 什么是 Redis?
- 定义:Redis(Remote Dictionary Server)是一个基于内存的键值型NoSQL 数据库,同时支持持久化,常被用作缓存、消息队列、分布式锁等。
- 作者:Salvatore Sanfilippo(antirez),2009 年发布。
- 核心模型:单线程事件循环(IO 多路复用 epoll),所有命令串行执行,避免加锁开销,天然支持原子性操作。
- 网络协议:基于 TCP 的文本/二进制协议(
RESP),简单高效。
1.2 核心特点
| 特点 | 说明 |
|---|---|
| 超快 | 数据放内存,单次简单命令微秒级,官方可达 10 万+ QPS |
| 丰富的数据类型 | String、Hash、List、Set、Sorted Set、Bitmap、HyperLogLog、GEO、Stream |
| 持久化 | 支持 RDB 快照 + AOF 日志,可选开启 |
| 高可用 | 主从复制、哨兵、Cluster 集群 |
| 单线程 | IO 多路复用模型,避免锁竞争,简单命令原子执行 |
| 多语言客户端 | 几乎所有主流语言都有官方/第三方客户端 |
| Lua 脚本 | 服务端原子执行复杂逻辑 |
1.3 Redis vs Memcached
| 维度 | Redis | Memcached |
|---|---|---|
| 数据类型 | 丰富(5 种基础 + 4 种特殊) | 简单键值(字符串) |
| 持久化 | RDB / AOF | 不支持 |
| 集群 | 原生 Cluster / 哨兵 | 一致性哈希(需客户端) |
| 性能 | 极高(复杂命令也快) | 极高(纯 KV 更简单) |
| 内存管理 | 多种淘汰策略 | LRU 为主 |
| 适用场景 | 缓存 + 计算 + 队列 | 纯缓存 |
简单记:现在新项目几乎都直接用 Redis,Memcached 优势越来越小。
1.4 应用场景
- 缓存:热点数据、查询结果缓存,是最常见用途。
- 分布式锁:
SETNX/ RedLock 解决多节点互斥。 - 计数器 / 限流:INCR、滑动窗口、Lua 脚本。
- 排行榜 / 排序集合:Sorted Set 是天然排行榜。
- 消息队列:Stream(5.0+)/ List + BRPOP 实现轻量队列。
- 会话存储:Session 共享、Token 黑名单。
- 去重 / 签到:Bitmap、Set。
- Geo 位置:附近的人/车,GEO 数据类型。
- 延迟队列:Sorted Set 按时间戳排。
二、安装与启动
2.1 Windows 安装
- 到 GitHub redis-windows 或 MSOpenTech Redis 下载 zip 包。
- 解压到如
D:\redis-7.2,目录加入 PATH。 - 启动服务端:
redis-server.exe - 新开 PowerShell 连接测试:
redis-cli.exe 127.0.0.1:6379> PING PONG
2.2 Linux 安装
# Ubuntu / Debian
sudo apt update && sudo apt install -y redis-server
# CentOS / RHEL
sudo yum install -y redis
# 编译安装(可获得最新版本)
wget https://download.redis.io/releases/redis-7.2.4.tar.gz
tar -xzf redis-7.2.4.tar.gz && cd redis-7.2.4
make && sudo make install
2.3 启动与管理
# 前台启动(看日志方便调试)
redis-server
# 后台启动
redis-server --daemonize yes
# 或
redis-server /etc/redis/redis.conf &
# 通过 redis-cli 关闭
redis-cli shutdown
# Linux 用 systemd 管理
sudo systemctl enable redis-server --now
sudo systemctl status redis-server
2.4 核心配置文件(redis.conf 关键项)
# 后台运行
daemonize yes
# 监听地址(生产环境不要注释 bind 并设为 0.0.0.0!要限制内网)
bind 127.0.0.1 -::1
# 端口
port 6379
# 连接密码(生产必开)
requirepass YourStrongPassword
# 最大内存(推荐生产环境设置)
maxmemory 2gb
# 内存满时淘汰策略
maxmemory-policy allkeys-lru
# 持久化策略
save 900 1 # 900 秒内至少 1 个 key 变化 → 触发 RDB
save 300 10
save 60 10000
appendonly no # 关闭 AOF(开 AOF 用 yes)
appendfsync everysec # AOF 同步策略
# 慢查询日志(性能调优)
slowlog-log-slower-than 10000 # 超过 10ms 记录
slowlog-max-len 128
# 安全:禁用或重命名危险命令
rename-command FLUSHDB ""
rename-command CONFIG "CONFIG_xxx"
⚠️ 生产要点:
bind不要设为0.0.0.0、必须设置requirepass、建议设置maxmemory与淘汰策略、限制rename-command防误操作。
2.5 redis-cli 常用连接命令
# 本机连接
redis-cli -p 6379
# 远程 + 密码
redis-cli -h 192.168.1.10 -p 6379 -a YourPassword
# 指定数据库(0-15 默认 16 个)
redis-cli -n 1
# 执行单条命令退出
redis-cli SET name "zhangsan"
# 批量从文件执行
redis-cli < commands.txt
# 调试连接
redis-cli -h 127.0.0.1 -p 6379 PING
三、数据类型与基本命令
3.1 String(字符串,最常用)
键值对,值可以是字符串、数字、二进制(最大 512MB)。
SET name "zhangsan" # 设置
GET name # 获取
DEL name # 删除
EXISTS name # 是否存在
APPEND name " hello" # 追加
STRLEN name # 长度
INCR counter # 自增 1(不存在则初始化为 0)
INCRBY counter 10 # 自增 10
DECR counter
INCRBYFLOAT price 0.5 # 浮点自增
SETEX code 60 "123456" # 设置 + 60 秒过期(验证码常用)
SETNX lock_key 1 # 不存在才设置(分布式锁基础)
MSET k1 v1 k2 v2 # 批量设置
MGET k1 k2 # 批量获取
GETSET key new_value # 获取旧值并设置新值
💡 测试常用:
SETEX模拟验证码;INCR验证限流次数;SETNX验证分布式锁是否生效。
3.2 Hash(哈希,适合存储对象)
HSET user:1 name "zhangsan" age 20 city "Beijing"
HGET user:1 name
HMSET user:1 name "lisi" age 25 # 批量设置(已废弃,HSET 已支持多 field)
HGETALL user:1 # 获取所有 field-value
HMGET user:1 name age
HEXISTS user:1 name
HDEL user:1 age
HINCRBY user:1 age 1
HKEYS user:1 # 所有 field
HVALS user:1 # 所有 value
HLEN user:1 # field 数量
适用场景:用户对象(用户 ID → 多个属性),比 JSON 字符串更节省内存且可单字段更新。
3.3 List(列表,双向链表)
LPUSH news "headline1" # 左侧(头部)插入
RPUSH news "headline2" # 右侧(尾部)插入
LPOP news # 左侧弹出
RPOP news # 右侧弹出
LRANGE news 0 -1 # 取所有元素
LINDEX news 0 # 按下标取
LLEN news # 长度
LREM news 1 "headline1" # 删除 1 个匹配元素
LSET news 0 "newHead" # 按下标设置
LTRIM news 0 9 # 裁剪到只保留前 10 条
BLPOP news 30 # 阻塞式弹出(消息队列)
适用场景:最新消息列表(保留前 N 条)、任务队列(LPUSH + BRPOP)、栈 / 队列。
3.4 Set(集合,无序不重复)
SADD tags "java" "python" "go"
SMEMBERS tags # 所有元素
SISMEMBER tags "java" # 是否存在
SCARD tags # 元素数量
SREM tags "go" # 删除
SPOP tags # 随机弹出一个
SRANDMEMBER tags 2 # 随机取 2 个(不弹出)
SINTER set1 set2 # 交集(共同好友)
SUNION set1 set2 # 并集
SDIFF set1 set2 # 差集
SINTERSTORE result set1 set2 # 存到新 key
适用场景:标签、共同好友、抽奖(SPOP)、去重。
3.5 Sorted Set(有序集合,排行榜神器)
每个元素关联一个 score,按 score 升序排序,元素唯一。
ZADD rank 95 "alice" 88 "bob" 76 "carol"
ZRANGE rank 0 -1 # 升序所有
ZRANGE rank 0 -1 WITHSCORES # 带分数
ZREVRANGE rank 0 2 # 倒序前 3 名(排行榜)
ZSCORE rank "alice" # 查分数
ZINCRBY rank 5 "alice" # 加分
ZRANK rank "alice" # 升序排名
ZREVRANK rank "alice" # 降序排名
ZCARD rank # 元素数量
ZCOUNT rank 80 100 # 分数在 80~100 的数量
ZREMRANGEBYRANK rank 0 0 # 删除排名最低的 1 个
ZREMRANGEBYSCORE rank 0 60 # 删除分数 ≤ 60 的
适用场景:排行榜(销量榜、积分榜)、延迟队列(score = 执行时间戳)、滑动窗口限流。
3.6 特殊数据类型
| 类型 | 用途 | 示例 |
|---|---|---|
| Bitmap | 二值统计(签到、用户在线) | SETBIT user:1:sign 0 1 / BITCOUNT |
| HyperLogLog | 基数统计(UV),误差 < 1% | PFADD page:uv user1 user2 / PFCOUNT |
| GEO | 地理位置(附近的人/车) | GEOADD city 116.40 39.90 "Beijing" / GEOSEARCH |
| Stream | 消息流(5.0+ 轻量 MQ) | XADD / XREAD / XLEN |
# Bitmap 签到示例
SETBIT user:1001:sign 0 1 # 第 1 天签到
SETBIT user:1001:sign 5 1 # 第 6 天签到
BITCOUNT user:1001:sign # 总签到天数
GETBIT user:1001:sign 5 # 第 6 天是否签到
# GEO 附近的人
GEOADD shop 116.40 39.90 "shopA" 116.41 39.91 "shopB"
GEOSEARCH shop FROMLONLAT 116.40 39.90 BYRADIUS 5 km
四、键管理与通用命令
# 查看所有 key(生产慎用,会阻塞!)
KEYS *
# 渐进式遍历(推荐)
SCAN 0 MATCH user:* COUNT 100
# 循环到 cursor = 0 结束
EXISTS key # 是否存在(返回 0/1)
TYPE key # 类型
DEL key1 key2 # 删除
UNLINK key1 key2 # 异步删除(Redis 4+ 大 key 友好)
RENAME key new_key # 重命名
COPY key new_key # 复制
EXPIRE key 60 # 设置过期(秒)
PEXPIRE key 60000 # 毫秒
EXPIREAT key 1700000000 # 到指定时间戳过期
TTL key # 查看剩余生存时间(-1 永不过期,-2 不存在)
PERSIST key # 取消过期
RANDOMKEY # 随机一个 key
DBSIZE # 当前 db 的 key 数量
SELECT 0 # 切换 db(0-15)
FLUSHDB # 清空当前 db
FLUSHALL # 清空所有 db(危险!)
⚠️
KEYS在大 key 场景下会阻塞整个 Redis。生产排查 一律用SCAN。 ⚠️FLUSHDB / FLUSHALL一旦在生产执行后果严重。生产环境务必重命名或禁用:rename-command FLUSHDB ""。
五、持久化
5.1 RDB(Redis Database)
- 定时对内存全量快照,以二进制压缩保存到
dump.rdb。 - 触发方式:
save(同步阻塞)/bgsave(后台异步)/ 配置 save 规则。 - 优点:文件紧凑、恢复快、适合备份。
- 缺点:会丢失最后一次快照后的数据。
5.2 AOF(Append Only File)
- 记录每条写命令,追加到
appendonly.aof,通过重写压缩。 - 同步策略:
always(每条 fsync,最安全)/everysec(每秒,推荐)/no(操作系统决定)。 - 优点:数据更安全,最多丢 1 秒。
- 缺点:文件大、恢复慢、写入有一定性能损耗。
5.3 对比与混合持久化
| 维度 | RDB | AOF |
|---|---|---|
| 数据安全 | 可能丢分钟级 | 最多丢 1 秒 |
| 文件大小 | 小 | 较大(重写后) |
| 恢复速度 | 快 | 慢 |
| 写性能影响 | 很小 | everysec 时较小 |
| 适用场景 | 备份 / 容忍少量数据丢失 | 高安全要求 |
混合持久化(Redis 5+ 官方推荐):AOF 文件中包含 RDB 快照 + 后续 AOF 日志,恢复时先加载 RDB 再回放 AOF,兼顾速度与安全。
# 开启混合持久化
aof-use-rdb-preamble yes
六、Redis 事务
6.1 MULTI / EXEC / DISCARD
MULTI # 开启事务
SET balance 100
INCRBY balance 50
DECRBY balance 30
EXEC # 提交执行(所有命令一次执行,期间不会被插入)
MULTI
SET a 1
SET b 2
DISCARD # 放弃事务
重要:Redis 事务不支持回滚!命令语法错误时所有命令不执行,运行时错误(如对 String 做 INCR)只跳过错误命令。
6.2 WATCH / UNWATCH(乐观锁)
WATCH balance
GET balance # 读取
MULTI
SET balance 200 # 如果 balance 在 WATCH 期间被其他客户端改动,EXEC 返回 nil
EXEC
适用场景:库存扣减、计数器等”读-改-写”原子操作。
6.3 与关系型数据库事务对比
| 特性 | Redis 事务 | 关系型事务 |
|---|---|---|
| 原子性 | 命令序列化执行 | 完全原子(回滚) |
| 隔离 | 单线程串行 | 多级隔离级别 |
| 回滚 | ❌ 不支持 | ✅ 支持 |
| 使用 | 简单业务 | 复杂业务 |
复杂业务建议用 Lua 脚本(见下文)替代事务,更灵活且真正原子。
七、Pipeline
Pipeline 将多条命令一次发送、一次读取,减少网络 RTT,显著提升批量操作性能。
# 普通模式:5 次 RTT
for i in 1 2 3 4 5; do redis-cli SET "k$i" $i; done
# Pipeline 模式:1 次 RTT
(echo -e "SET k1 1\nSET k2 2\nSET k3 3\nSET k4 4\nSET k5 5\n"; sleep 0.1) | redis-cli --pipe
⚠️ Pipeline 不是事务:中途出错不会停止,且不在同一个 RTT 内被处理(但用
MULTI + EXEC包裹则可以)。
八、Lua 脚本
服务端原子执行,复杂逻辑无需”读-写”并发控制。
# 加载脚本
SCRIPT LOAD "return KEYS[1]"
# 返回 SHA: 532b8bc8b8b8...
# 用 SHA 调用(推荐,更省带宽)
EVALSHA 532b8bc8b8b8... 0 mykey
# 直接传脚本
EVAL "return KEYS[1]" 1 mykey
经典案例:滑动窗口限流
-- rate_limit.lua
-- KEYS[1] = 限流 key
-- ARGV[1] = 时间窗(毫秒)
-- ARGV[2] = 限制次数
-- ARGV[3] = 当前时间戳
local key = KEYS[1]
local window = tonumber(ARGV[1])
local limit = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
-- 清理窗口外记录
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
-- 当前窗口内请求数
local count = redis.call('ZCARD', key)
if count < limit then
redis.call('ZADD', key, now, now .. ':' .. math.random())
redis.call('PEXPIRE', key, window)
return 1
else
return 0
end
redis-cli --eval rate_limit.lua rate:user:1 , 60000 5 $(date +%s%3N)
# 限制用户 1 在 60 秒内最多 5 次
💡 测试场景:验证限流时,每次请求用
redis-cli ZCARD查看窗口内请求数;用 Lua 脚本能避免”读后写”的并发漏洞。
九、内存管理与淘汰策略
9.1 maxmemory 设置
maxmemory 2gb # 限制最大内存
maxmemory-policy allkeys-lru # 内存满时策略
9.2 八种淘汰策略
| 策略 | 说明 |
|---|---|
noeviction(默认) |
不淘汰,写入报错,适合数据不能丢的场景 |
allkeys-lru |
所有 key 中淘汰最久未使用的(最常用) |
allkeys-lfu |
所有 key 中淘汰最不经常使用的(Redis 4.0+) |
allkeys-random |
所有 key 中随机淘汰 |
volatile-lru |
只在设置了过期时间的 key 中 LRU |
volatile-lfu |
只在设置了过期时间的 key 中 LFU |
volatile-random |
只在设置了过期时间的 key 中随机淘汰 |
volatile-ttl |
淘汰即将过期的 key |
💡 通用推荐:
allkeys-lru(缓存场景首选);数据不能丢的场景用noeviction+ 业务层自行控制写入。
9.3 查看内存信息
INFO memory # 详细内存信息
MEMORY USAGE mykey # 单个 key 占用的内存(字节)
MEMORY DOCTOR # 给出优化建议
十、Spring Boot 集成(Java 测试场景)
10.1 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
10.2 application.yml
spring:
redis:
host: 127.0.0.1
port: 6379
password: 123456
database: 0
timeout: 2000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
10.3 基础操作
@Autowired
private StringRedisTemplate redisTemplate;
public void demo() {
ValueOperations<String, String> op = redisTemplate.opsForValue();
// String 操作
op.set("name", "zhangsan", 60, TimeUnit.SECONDS);
op.setIfAbsent("lock", "1", 10, TimeUnit.SECONDS); // 分布式锁
String name = op.get("name");
// Hash
HashOperations<String, String, String> hash = redisTemplate.opsForHash();
hash.put("user:1", "name", "zhangsan");
String userName = hash.get("user:1", "name");
// 通用
redisTemplate.expire("key", 60, TimeUnit.SECONDS);
redisTemplate.delete("key");
Boolean has = redisTemplate.hasKey("key");
}
十一、测试场景实战(重点)
11.1 用 Docker 快速搭建 Redis 测试环境
docker run -d --name test-redis -p 6379:6379 redis:7-alpine
docker run -d --name test-redis-auth -p 6380:6379 \
redis:7-alpine redis-server --requirepass 123456
搭配 Redis Desktop Manager / Another Redis Desktop Manager(开源)查看数据。
11.2 接口测试中的缓存验证
# 测试"商品详情"接口:第二次请求应命中缓存
import requests
url = "http://test.api/products/1"
# 第一次:清缓存
r = requests.delete("http://test.api/cache/products/1") # 业务提供的清缓存接口
# 第一次:查库 + 写缓存
t1 = requests.get(url).elapsed.total_seconds()
# 第二次:直接读缓存,应明显更快
t2 = requests.get(url).elapsed.total_seconds()
assert t2 < t1, f"缓存未生效: 第一次 {t1}s, 第二次 {t2}s"
直接验证 Redis 端:
# 接口被调用后,Redis 应有 product:1 这个 key
redis-cli EXISTS product:1
redis-cli TTL product:1
redis-cli GET product:1
11.3 排查缓存”三大问题”
| 问题 | 表现 | 原因 | 解决方案 |
|---|---|---|---|
| 缓存穿透 | 请求不存在的 key,每次都打到 DB | 恶意攻击/参数非法 | 缓存空值、布隆过滤器 |
| 缓存击穿 | 热点 key 过期瞬间,大量请求打到 DB | 过期瞬间并发 | 互斥锁、逻辑过期、永不过期+异步刷新 |
| 缓存雪崩 | 大批 key 同时过期,DB 压力骤增 | 集中过期 | 过期时间加随机值、多级缓存 |
# 验证是否雪崩:看 key 过期时间是否集中
redis-cli --scan --pattern "product:*" | xargs -I {} redis-cli TTL {} | sort | uniq -c
# 如果同一时间点大量 TTL 一致,就是雪崩隐患
11.4 排查常用命令
# 1. 服务状态与配置
redis-cli INFO # 全量信息
redis-cli INFO replication # 主从
redis-cli INFO clients # 客户端连接
redis-cli INFO stats # 统计(命中率等)
# 2. 实时命令监控(看哪些命令在跑、慢不慢)
redis-cli MONITOR # 实时输出所有命令(调试用,生产慎用)
# 3. 慢查询日志
redis-cli SLOWLOG GET 10 # 看最近 10 条慢查询
redis-cli SLOWLOG RESET # 清空
# 4. 客户端连接
redis-cli CLIENT LIST
redis-cli CLIENT KILL ID 123 # 强制断开某连接
# 5. 大 key 排查(使用 redis-cli --bigkeys)
redis-cli --bigkeys # 扫描所有 key 按类型给大小分布
redis-cli MEMORY USAGE mykey # 单 key 内存
# 6. 内存分析(memory usage 拉满往往是 bug 信号)
redis-cli MEMORY DOCTOR
11.5 常见问题排查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 连不上 Redis | 端口未开、密码错、bind 限制 | redis-cli PING、ss -lntp \| grep 6379 |
| 命令越来越慢 | 大 key、持久化阻塞、慢命令 | SLOWLOG GET、禁用 KEYS 与 FLUSHALL |
| 内存持续上涨 | 内存泄漏、淘汰策略失效 | INFO memory、MEMORY DOCTOR |
| 主从数据不一致 | 网络抖动、复制缓冲溢出 | INFO replication、监控 lag |
| OOM 报错 | maxmemory 满 + noeviction |
调大内存或改 maxmemory-policy |
| 业务报”lock 获取失败” | 锁未释放、过期时间过短 | redis-cli TTL lock_key |
十二、高可用与集群(简述)
| 方案 | 拓扑 | 高可用 | 写扩展 | 运维复杂度 | 适用场景 |
|---|---|---|---|---|---|
| 主从复制 | 1 主 N 从 | ❌ 主故障需手动切换 | ❌ | 低 | 备份、读扩展 |
| 哨兵 Sentinel | 1 主 N 从 + N 哨兵 | ✅ 自动故障转移 | ❌ | 中 | 中小规模高可用 |
| Cluster 集群 | 多主多从(16384 槽位) | ✅ | ✅ | 高 | 大规模、高 QPS |
主从复制快速搭建:
# 主节点(默认)
redis-server --port 6379
# 从节点
redis-server --port 6380 --replicaof 127.0.0.1 6379 --masterauth 123456
# 验证
redis-cli -p 6380 INFO replication
# role:slave / master_repl_offset 应持续增长
💡 测试场景:压测时主从配置不同,写都到主,读可分到从——避免误把所有流量打到从导致读不到最新数据。
十三、学习资源
| 资源 | 说明 |
|---|---|
| Redis 官方文档 | 最权威,新特性首发 |
| Redis 命令参考(中文) | 命令速查表 |
| Try Redis | 浏览器内交互式体验 |
| 《Redis 设计与实现》(黄健宏) | 进阶必读,从源码讲透数据结构与原理 |
| 《Redis 实战》 | 经典场景书 |
| Another Redis Desktop Manager | 开源 Redis GUI 工具 |