Redis相关

9 minute read

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 应用场景

  1. 缓存:热点数据、查询结果缓存,是最常见用途。
  2. 分布式锁:SETNX / RedLock 解决多节点互斥。
  3. 计数器 / 限流:INCR、滑动窗口、Lua 脚本。
  4. 排行榜 / 排序集合:Sorted Set 是天然排行榜。
  5. 消息队列:Stream(5.0+)/ List + BRPOP 实现轻量队列。
  6. 会话存储:Session 共享、Token 黑名单。
  7. 去重 / 签到:Bitmap、Set。
  8. Geo 位置:附近的人/车,GEO 数据类型。
  9. 延迟队列:Sorted Set 按时间戳排。

二、安装与启动

2.1 Windows 安装

  1. 到 GitHub redis-windows 或 MSOpenTech Redis 下载 zip 包。
  2. 解压到如 D:\redis-7.2,目录加入 PATH。
  3. 启动服务端:
    redis-server.exe
    
  4. 新开 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 工具