外观
Redis 笔记
约 4551 字大约 15 分钟
2025-08-16
一、Redis 简介
1.1 什么是 Redis?
Redis(Remote Dictionary Server)是一个开源的、基于内存的键值对存储数据库,通常被用作数据库、缓存和消息中间件。Redis 支持多种数据结构,如字符串、哈希、列表、集合、有序集合等,并提供了丰富的操作命令。
1.2 Redis 核心特点
- 高性能:数据存储在内存中,读写速度极快,每秒可处理 10 万+ 次读写操作
- 持久化:支持将内存中的数据持久化到磁盘,保证数据安全
- 丰富的数据结构:支持字符串、哈希、列表、集合、有序集合等多种数据结构
- 原子操作:所有操作都是原子的,支持事务
- 发布/订阅:内置消息发布/订阅功能
- 高可用性:支持主从复制、哨兵模式和集群模式
1.3 Redis 与传统数据库对比
| 特性 | Redis | 传统关系型数据库 |
|---|---|---|
| 存储位置 | 内存为主,可持久化到磁盘 | 磁盘 |
| 数据结构 | 多种丰富数据结构 | 表结构 |
| 查询语言 | 专用命令 | SQL |
| 事务支持 | 简单事务 | 完整 ACID 事务 |
| 适用场景 | 缓存、会话存储、实时分析 | 复杂查询、事务性操作 |
| 性能 | 极高 | 相对较低 |
1.4 典型应用场景
- 缓存:减轻数据库压力,提高应用响应速度
- 会话存储:存储用户会话信息
- 排行榜/计数器:利用有序集合实现
- 消息队列:利用列表结构实现简单的消息队列
- 实时系统:实时数据分析、实时排行榜
- 分布式锁:利用 SETNX 命令实现
二、Redis 数据结构
2.1 字符串(String)
字符串是 Redis 最基本的数据类型,可以存储文本、数字或二进制数据。
常用操作:
# 设置键值
SET name "John"
SET counter 100
# 获取值
GET name # 返回 "John"
# 数值操作
INCR counter # 递增,返回 101
INCRBY counter 5 # 按指定值递增,返回 106
DECR counter # 递减,返回 105
DECRBY counter 3 # 按指定值递减,返回 102
# 批量操作
MSET key1 "value1" key2 "value2"
MGET key1 key2 # 返回 ["value1", "value2"]
# 设置过期时间
SETEX key 60 "value" # 60秒后过期
SET key "value" EX 60 # 同上使用场景:
- 缓存简单数据
- 计数器(如页面访问量)
- 会话存储
2.2 哈希(Hash)
哈希是一个键值对集合,适合存储对象。
常用操作:
# 设置字段值
HSET user:1000 name "John" age 30 email "john@example.com"
# 获取字段值
HGET user:1000 name # 返回 "John"
HGETALL user:1000 # 返回所有字段和值
HMGET user:1000 name age # 返回 ["John", "30"]
# 字段操作
HKEYS user:1000 # 返回所有字段名
HVALS user:1000 # 返回所有字段值
HEXISTS user:1000 name # 检查字段是否存在,返回 1
# 数值操作
HINCRBY user:1000 age 1 # age 字段加1使用场景:
- 存储对象(如用户信息)
- 存储配置项
- 代替多个字符串键
2.3 列表(List)
列表是简单的字符串列表,按照插入顺序排序,可以在两端插入和删除元素。
常用操作:
# 从右端插入
RPUSH mylist "a" "b" "c" # mylist: ["a", "b", "c"]
# 从左端插入
LPUSH mylist "x" # mylist: ["x", "a", "b", "c"]
# 获取列表元素
LRANGE mylist 0 -1 # 获取所有元素,返回 ["x", "a", "b", "c"]
LINDEX mylist 1 # 获取索引1的元素,返回 "a"
# 从右端弹出
RPOP mylist # 返回 "c",mylist: ["x", "a", "b"]
# 从左端弹出
LPOP mylist # 返回 "x",mylist: ["a", "b"]
# 获取列表长度
LLEN mylist # 返回 2
# 修剪列表
LTRIM mylist 0 1 # 保留索引0-1的元素,mylist: ["a", "b"]使用场景:
- 消息队列(生产者-消费者模型)
- 最新消息展示(如朋友圈、微博)
- 任务队列
2.4 集合(Set)
集合是无序、不重复的字符串集合,支持集合运算。
常用操作:
# 添加元素
SADD myset "a" "b" "c" # myset: {"a", "b", "c"}
# 获取所有元素
SMEMBERS myset # 返回 ["a", "b", "c"](顺序不定)
# 检查元素是否存在
SISMEMBER myset "a" # 返回 1(存在)
SISMEMBER myset "d" # 返回 0(不存在)
# 集合运算
SADD set1 "a" "b" "c"
SADD set2 "b" "c" "d"
SINTER set1 set2 # 交集,返回 ["b", "c"]
SUNION set1 set2 # 并集,返回 ["a", "b", "c", "d"]
SDIFF set1 set2 # 差集,返回 ["a"]
# 随机获取元素
SRANDMEMBER myset # 随机返回一个元素
SPOP myset # 随机弹出并移除一个元素使用场景:
- 标签系统
- 共同好友/关注
- 去重(如访问过的文章ID)
- 随机推荐
2.5 有序集合(Sorted Set)
有序集合与集合类似,但每个元素都关联一个分数(score),元素按分数排序。
常用操作:
# 添加元素(格式:ZADD key score member)
ZADD myzset 1 "a" 2 "b" 3 "c" # myzset: {"a":1, "b":2, "c":3}
# 获取元素
ZRANGE myzset 0 -1 # 按分数升序返回所有元素,返回 ["a", "b", "c"]
ZREVRANGE myzset 0 -1 # 按分数降序返回所有元素,返回 ["c", "b", "a"]
ZRANGEBYSCORE myzset 1 2 # 返回分数在1-2之间的元素,返回 ["a", "b"]
# 获取元素分数
ZSCORE myzset "b" # 返回 "2"
# 获取排名
ZRANK myzset "b" # 返回 1(升序排名,从0开始)
ZREVRANK myzset "b" # 返回 1(降序排名)
# 元素计数
ZCOUNT myzset 1 2 # 返回 2(分数在1-2之间的元素数量)
# 更新分数
ZINCRBY myzset 1 "b" # b的分数加1,返回 "3"使用场景:
- 排行榜(如游戏积分榜)
- 带权重的任务队列
- 时间轴(使用时间戳作为分数)
- 范围查询(如查找特定价格区间的商品)
三、键操作与过期时间
3.1 键操作
# 设置键值
SET key value
# 检查键是否存在
EXISTS key # 存在返回1,不存在返回0
# 删除键
DEL key
# 重命名键
RENAME old_key new_key
# 查找键(模式匹配)
KEYS pattern # 例如 KEYS user:*,但生产环境慎用,可能阻塞
# 模糊查找(推荐)
SCAN cursor [MATCH pattern] [COUNT count]3.2 过期时间
# 设置过期时间(秒)
EXPIRE key 60 # 60秒后过期
EXPIREAT key timestamp # 在指定时间戳过期
# 设置过期时间(毫秒)
PEXPIRE key 60000 # 60秒后过期
PEXPIREAT key timestamp # 在指定时间戳过期
# 查看过期时间
TTL key # 返回剩余秒数,-2表示键不存在,-1表示永不过期
PTTL key # 以毫秒为单位返回剩余时间
# 移除过期时间
PERSIST key # 使键永不过期使用技巧:
- 缓存场景:为缓存数据设置合理的过期时间
- 会话管理:设置会话过期时间(如30分钟)
- 临时数据:使用过期时间自动清理临时数据
四、事务与管道
4.1 事务
Redis 事务提供了一种将多个命令打包执行的机制,但与传统数据库事务不同,Redis 事务不支持回滚。
# 开始事务
MULTI
# 添加命令到事务
SET key1 "value1"
SET key2 "value2"
# 执行事务
EXEC
# 取消事务
DISCARD事务特点:
- 事务中的命令会按顺序执行
- 事务执行期间不会被其他客户端命令打断
- 如果事务中的某个命令出错,其他命令仍会执行(Redis 不支持回滚)
- 事务是原子的,但不保证隔离性
4.2 管道(Pipeline)
管道允许客户端一次性发送多个命令,减少网络往返时间,显著提高性能。
# Python 示例
import redis
r = redis.Redis()
pipe = r.pipeline()
# 将多个命令添加到管道
pipe.set('key1', 'value1')
pipe.set('key2', 'value2')
pipe.get('key1')
pipe.get('key2')
# 执行所有命令
results = pipe.execute()
# results 将包含 [True, True, 'value1', 'value2']使用场景:
- 批量操作(如批量插入数据)
- 需要执行多个命令且对实时性要求不高的场景
- 减少网络开销,提高吞吐量
五、持久化机制
Redis 提供两种持久化方式,可根据需求选择或结合使用。
5.1 RDB(快照)
工作原理:
- 在指定时间间隔内,如果数据变化超过一定数量,Redis 会自动将内存中的数据快照写入磁盘
- 也可以通过
SAVE或BGSAVE命令手动触发
配置示例:
# 在900秒内至少有1个键改变时创建快照
save 900 1
# 在300秒内至少有10个键改变时创建快照
save 300 10
# 在60秒内至少有10000个键改变时创建快照
save 60 10000
# 快照文件名
dbfilename dump.rdb
# 快照文件存放路径
dir /var/lib/redis优点:
- 适合备份和灾难恢复
- 恢复大数据集时速度比 AOF 快
- 产生的文件紧凑,占用空间小
缺点:
- 可能丢失最后一次快照后的数据
- 大数据集时可能造成服务短暂停止
5.2 AOF(追加文件)
工作原理:
- 记录服务器接收到的每一个写操作命令
- Redis 重启时会重新执行这些命令来重建原始数据集
配置示例:
# 开启AOF
appendonly yes
# AOF文件名
appendfilename "appendonly.aof"
# 同步策略
# always - 每个命令都同步(最安全,性能最差)
# everysec - 每秒同步一次(推荐,平衡安全与性能)
# no - 由操作系统决定(最快,最不安全)
appendfsync everysec
# AOF重写配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb优点:
- 数据安全性更高,最多丢失1秒数据
- AOF文件可读,可手动修改
- AOF重写不会阻塞主进程
缺点:
- 文件通常比RDB大
- 恢复速度比RDB慢
- 根据同步策略可能影响性能
最佳实践:
- 一般情况下,建议同时启用 RDB 和 AOF
- 如果对数据安全性要求极高,使用 AOF 并设置
appendfsync always - 如果更关注性能,使用 RDB 并适当调整快照频率
六、发布/订阅模式
Redis 提供了简单的发布/订阅消息机制,适用于简单的消息传递场景。
# 订阅频道
SUBSCRIBE news
# 发布消息
PUBLISH news "Hello World"
# 模式订阅
PSUBSCRIBE news.* # 订阅所有以news.开头的频道使用示例:
# Python 发布者
import redis
r = redis.Redis()
r.publish('news', 'Breaking news: Redis is awesome!')
# Python 订阅者
import redis
r = redis.Redis()
p = r.pubsub()
p.subscribe('news')
for message in p.listen():
if message['type'] == 'message':
print(f"Received: {message['data'].decode()}")使用场景:
- 实时消息通知
- 简单的事件驱动架构
- 日志收集与分发
- 聊天应用
注意事项:
- Redis 发布/订阅是"即发即忘"的,不保证消息送达
- 不适合需要消息确认、持久化存储的场景
- 对于复杂的消息队列需求,建议使用专门的消息中间件(如 RabbitMQ、Kafka)
七、高可用与集群
7.1 主从复制
Redis 支持主从复制,实现数据冗余和读写分离。
配置方法:
- 配置主服务器(无需特殊配置)
- 配置从服务器:在 redis.conf 中添加
replicaof <masterip> <masterport> masterauth <master-password>
特点:
- 一个主服务器可以有多个从服务器
- 从服务器可以是其他从服务器的主服务器(级联复制)
- 主服务器写操作会异步复制到从服务器
- 从服务器默认为只读
使用场景:
- 数据冗余备份
- 读写分离(写操作在主服务器,读操作在从服务器)
- 扩展读性能
7.2 哨兵模式(Sentinel)
哨兵系统用于监控 Redis 主从架构,实现自动故障转移。
主要功能:
- 监控:持续检查主服务器和从服务器是否正常工作
- 通知:当监控对象出现问题时,通过API通知系统管理员
- 故障转移:当主服务器不能正常工作时,自动将一个从服务器升级为主服务器
- 配置提供者:客户端连接时,告知当前主服务器地址
配置示例:
# sentinel.conf
port 26379
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel auth-pass mymaster mypassword
sentinel down-after-milliseconds mymaster 30000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1启动哨兵:
redis-sentinel /path/to/sentinel.conf7.3 集群模式(Cluster)
Redis 集群提供自动分片和高可用性,适合大规模部署。
核心概念:
- 分片(Sharding):数据分布在多个节点上
- 槽(Slot):Redis 集群有 16384 个哈希槽,数据通过 key 计算分配到不同槽
- 主节点:处理写操作和部分读操作
- 从节点:复制主节点数据,主节点故障时可提升为主节点
集群特点:
- 自动分片,支持大规模数据集
- 高可用性,部分节点故障不影响整体服务
- 客户端可直接连接任一节点,由集群自动重定向
搭建集群:
# 创建6个节点(3主3从)
redis-cli --cluster create 127.0.0.1:7000 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 \
--cluster-replicas 1使用建议:
- 小型应用:单机或主从复制
- 中型应用:哨兵模式
- 大型应用:集群模式
八、性能优化
8.1 内存优化
数据结构选择:
- 小对象:使用哈希(Hash)代替多个字符串
- 小集合:使用整数集合(intset)代替普通集合
- 小列表:使用压缩列表(ziplist)代替普通列表
配置优化:
# 哈希、列表、集合、有序集合使用压缩列表的条件
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64其他技巧:
- 使用
SCAN替代KEYS避免阻塞 - 合理设置过期时间,避免内存无限增长
- 使用
MEMORY USAGE命令分析内存使用情况 - 对大键进行拆分
8.2 持久化优化
RDB 优化:
- 适当调整
save配置,平衡数据安全与性能 - 使用
BGSAVE代替SAVE避免阻塞 - 确保有足够的磁盘空间和I/O性能
AOF 优化:
- 推荐使用
appendfsync everysec - 启用 AOF 重写(
auto-aof-rewrite-percentage和auto-aof-rewrite-min-size) - 定期手动执行
BGREWRITEAOF优化 AOF 文件
8.3 网络与客户端优化
- 使用连接池管理 Redis 连接
- 合理使用管道(Pipeline)减少网络往返
- 避免在单个命令中处理过多数据
- 使用二进制安全的协议(如 RESP3)
- 适当调整 TCP 参数(如
tcp-keepalive)
九、安全性
9.1 访问控制
设置密码:
# redis.conf
requirepass your_strong_password使用示例:
# 连接时认证
redis-cli -a your_strong_password
# 运行时认证
127.0.0.1:6379> AUTH your_strong_password
OK9.2 网络安全
绑定IP:
# 只允许本地访问
bind 127.0.0.1
# 允许多个IP访问
bind 127.0.0.1 192.168.1.100禁用危险命令:
# 重命名或禁用危险命令
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command CONFIG ""
rename-command KEYS ""9.3 安全最佳实践
- 不要暴露在公网:Redis 服务器不应直接暴露在公网上
- 使用防火墙:限制只有特定IP可以访问 Redis
- 定期更新:保持 Redis 版本最新,修复已知漏洞
- 最小权限原则:只授予应用程序必要的权限
- 监控和审计:监控 Redis 访问日志,及时发现异常行为
- TLS 加密:在 Redis 6.0+ 中支持 TLS 加密传输
十、常用应用场景
10.1 缓存
实现方式:
def get_user(user_id):
# 先尝试从缓存获取
user = redis.get(f"user:{user_id}")
if user is None:
# 缓存未命中,从数据库获取
user = db.query("SELECT * FROM users WHERE id = %s", user_id)
# 将结果存入缓存,设置过期时间
redis.setex(f"user:{user_id}", 3600, user)
return user缓存策略:
- Cache-Aside(旁路缓存):应用直接与数据库和缓存交互
- Read-Through(读穿透):应用只与缓存交互,缓存负责与数据库交互
- Write-Through(写穿透):写操作同时更新缓存和数据库
- Write-Back(写回):先更新缓存,异步更新数据库
缓存问题处理:
- 缓存穿透:查询不存在的数据,解决方案:布隆过滤器、缓存空值
- 缓存击穿:热点数据过期瞬间大量请求,解决方案:互斥锁、永不过期
- 缓存雪崩:大量缓存同时过期,解决方案:随机过期时间、高可用架构
10.2 会话存储
实现方式:
# 用户登录
def login(username, password):
user = authenticate(username, password)
if user:
# 生成会话ID
session_id = generate_session_id()
# 存储会话数据
session_data = {
"user_id": user.id,
"username": user.username,
"login_time": time.time()
}
# 设置过期时间(30分钟)
redis.setex(f"session:{session_id}", 1800, json.dumps(session_data))
return session_id
return None
# 获取会话
def get_session(session_id):
session_data = redis.get(f"session:{session_id}")
if session_data:
# 延长会话有效期
redis.expire(f"session:{session_id}", 1800)
return json.loads(session_data)
return None10.3 排行榜
实现方式:
# 添加用户积分
ZINCRBY leaderboard 100 "user:1001"
ZINCRBY leaderboard 80 "user:1002"
ZINCRBY leaderboard 120 "user:1003"
# 获取Top 10
ZREVRANGE leaderboard 0 9 WITHSCORES
# 获取用户排名
ZRANK leaderboard "user:1002"10.4 限流器
令牌桶算法实现:
def is_allowed(user_id, limit=100, period=60):
"""
检查用户是否允许进行操作(每分钟最多100次)
"""
key = f"rate_limit:{user_id}"
now = time.time()
# 使用Redis事务确保原子性
with redis.pipeline() as pipe:
try:
pipe.watch(key)
# 获取当前计数
current = redis.get(key)
if current:
current = int(current)
# 检查是否超过限制
if current >= limit:
return False
else:
current = 0
# 在事务中更新计数
pipe.multi()
pipe.incr(key)
pipe.expire(key, period)
pipe.execute()
return True
except WatchError:
# 如果key被其他客户端修改,重试
return is_allowed(user_id, limit, period)十一、最佳实践
11.1 键设计规范
- 使用冒号分隔命名空间:
object-type:id:field,如user:1000:name - 保持键名简洁但有意义:避免过长的键名,但要保证可读性
- 使用一致的命名约定:如全部小写,使用连字符
- 避免特殊字符:只使用字母、数字和连字符
- 考虑键的生命周期:为临时数据设置合理的过期时间
11.2 避免常见陷阱
大键问题:
- 避免存储过大的字符串(>1MB)
- 避免集合、列表、哈希包含过多元素(>10,000)
- 使用
SCAN命令代替KEYS避免阻塞
热键问题:
- 识别并分散热点数据
- 使用本地缓存减轻 Redis 压力
- 考虑分片或复制热点数据
阻塞命令:
- 避免在生产环境使用
KEYS * - 谨慎使用
SORT、SINTER等可能阻塞的命令 - 使用
SCAN替代阻塞命令
- 避免在生产环境使用
11.3 监控与维护
关键指标监控:
- 内存使用率(
used_memory) - 命令处理速率(
total_commands_processed) - 连接数(
connected_clients) - 命令延迟(
latency) - 持久化状态(RDB/AOF)
- 内存使用率(
定期维护任务:
- 检查慢查询日志(
SLOWLOG GET) - 分析内存使用(
MEMORY USAGE、MEMORY STATS) - 监控主从复制延迟
- 定期进行故障转移演练
- 检查慢查询日志(
