📌 摘要
Redis作为业界使用最广泛的内存键值数据库,凭借其卓越的性能、丰富的数据结构和灵活的部署方式,已成为现代分布式系统架构中的核心组件。本文将系统介绍Redis的核心特性、典型应用场景、命令行操作指令,并重点讲解在Python中使用redis-py库操作Redis五种核心数据类型的完整方法,包含大量代码示例和最佳实践建议。
📖 文章目录
- [一、Redis 介绍](#一、Redis 介绍)
- [1. 概述](#1. 概述)
- [2. 核心特性](#2. 核心特性)
- [3. 典型应用场景](#3. 典型应用场景)
- [二、Redis 基础指令](#二、Redis 基础指令)
- 三、数据操作
- [1. 数据类型总览](#1. 数据类型总览)
- [2. 字符串类型完整操作](#2. 字符串类型完整操作)
- [3. 哈希类型完整操作](#3. 哈希类型完整操作)
- [4. 列表类型完整操作](#4. 列表类型完整操作)
- [5. 集合类型完整操作](#5. 集合类型完整操作)
- [6. 有序集合类型完整操作](#6. 有序集合类型完整操作)
- [四、Python 操作 Redis(redis-py)](#四、Python 操作 Redis(redis-py))
- [1. 安装与连接](#1. 安装与连接)
- [2. 字符串操作](#2. 字符串操作)
- [3. 哈希操作](#3. 哈希操作)
- [4. 列表操作](#4. 列表操作)
- [5. 集合操作](#5. 集合操作)
- [6. 有序集合操作](#6. 有序集合操作)
- [7. 管道(Pipeline)进阶](#7. 管道(Pipeline)进阶)
- 五、总结与最佳实践
一、Redis 介绍
1. 概述
Redis (Re mote Di ctionary Server)是一个开源的、基于内存的高性能键值对(Key-Value)数据库系统。
- 作者:Salvatore Sanfilippo(antirez)
- 首次发布:2009年
- 开发语言:ANSI C
- 核心定位:内存中的数据结构存储系统,可用作数据库、缓存和消息中间件
Redis与传统关系型数据库(如MySQL)最大的区别在于:数据主要存储在内存中,读写速度极快,同时支持将数据异步持久化到磁盘,兼顾了高性能与数据安全。
2. 核心特性
2.1 卓越性能 ⚡
- 读写速度 :基于内存操作,读速度可达 11万次/秒 ,写速度可达 8万次/秒
- 单线程模型:采用单线程+多路I/O复用(epoll),避免了多线程竞争和上下文切换开销
- 低延迟:微秒级别的响应延迟
2.2 数据持久化 💾
支持两种持久化机制,可按需灵活配置:
| 持久化方式 | 原理 | 适用场景 |
|---|---|---|
| RDB(快照) | 在指定时间间隔将内存数据集生成快照(dump.rdb) | 适合灾难恢复,对数据完整性要求不高的场景 |
| AOF(日志) | 记录每个写操作命令,重启时重放恢复 | 适合对数据安全性要求较高的场景,可配置为每秒同步 |
2.3 高级数据结构 🗂️
Redis提供了远超普通键值存储的丰富数据类型,满足多样化业务需求:
- String(字符串)------ 基础键值
- Hash(哈希)------ 对象存储
- List(列表)------ 消息队列、最新消息
- Set(集合)------ 去重、标签系统
- Zset(有序集合)------ 排行榜、优先级队列
- 扩展类型:Bitmaps、HyperLogLog、GEO(地理空间)、Stream
2.4 消息系统 📨
- 支持 发布/订阅(Pub/Sub) 模式,实现轻量级的消息通信
- 支持 Stream 数据类型,提供更完善的消息队列能力
2.5 事务处理 🔒
- 通过
MULTI、EXEC、DISCARD、WATCH命令实现事务 - 特性:事务中的命令会按顺序串行执行,支持乐观锁
- 注意:Redis事务不支持回滚(Rollback),但保证了命令的原子性
2.6 高可用架构 🏗️
| 架构模式 | 说明 | 适用场景 |
|---|---|---|
| 主从复制 | 一主多从,数据实时同步,实现读写分离 | 读多写少,对可用性要求一般的场景 |
| 哨兵模式(Sentinel) | 自动故障检测和主从切换,实现高可用 | 生产环境标配,保证服务持续可用 |
| 集群模式(Cluster) | 数据分片存储(16384个槽位),线性扩展 | 海量数据、高吞吐量场景 |
3. 典型应用场景
3.1 缓存系统 🚀
- 页面缓存:缓存HTML片段、API响应结果,减轻后端数据库压力
- 数据缓存:缓存用户信息、商品详情、配置信息等热点数据
- 缓存策略 :配合
EXPIRE设置过期时间,实现LRU淘汰策略
3.2 实时应用 ⏱️
- 排行榜:利用Zset实现游戏积分榜、热榜文章排序
- 计数器 :利用
INCR实现网站PV/UV统计、点赞数、评论数 - 限流器 :基于
INCR+EXPIRE实现滑动窗口限流
3.3 消息服务 📬
- 任务队列 :利用List的
LPUSH/RPOP实现生产者-消费者模式 - 延迟队列:结合Zset的Score实现定时/延迟任务调度
- 发布订阅:实现实时通知、聊天室等轻量级消息场景
3.4 空间计算 🌍
- 地理位置(GEO):支持存储经纬度,计算距离、查找附近的人/门店
- 命令示例:
GEOADD、GEODIST、GEORADIUS
3.5 大数据处理 📊
- 去重统计:利用Set或HyperLogLog实现UV统计(百万级数据仅需几KB内存)
- 位图统计:利用Bitmaps实现签到系统、在线状态标记
二、Redis 基础指令
1. 进入Redis命令行
bash
# 启动Redis客户端(默认连接本地6379端口)
redis-cli
# 指定远程主机和端口
redis-cli -h 192.168.1.100 -p 6379
# 如果设置了密码,使用 -a 参数
redis-cli -a your_password
2. 服务测试与切换数据库
bash
# 测试服务是否正常,返回 PONG 表示连接成功
127.0.0.1:6379> ping
PONG
# 切换数据库(Redis默认有16个数据库,索引0-15)
127.0.0.1:6379> select 10
OK
127.0.0.1:6379[10]>
3. 通用键指令
| 命令 | 示例 | 说明 |
|---|---|---|
KEYS pattern |
KEYS * |
查看所有键(生产环境慎用,会阻塞) |
KEYS n* |
KEYS user* |
通配符匹配,查看以user开头的键 |
EXISTS key |
EXISTS name |
检查键是否存在(返回1/0) |
TYPE key |
TYPE age |
查看键对应的数据类型 |
DEL key1 key2 |
DEL name age |
删除一个或多个键 |
EXPIRE key seconds |
EXPIRE session 3600 |
设置键的过期时间(秒) |
TTL key |
TTL session |
查看键的剩余生存时间(-1表示永不过期) |
三、数据操作
1. 数据类型总览
在正式操作 Redis 之前,我们需要了解一下 Redis 数据库中常用的五种核心数据类型:
| 数据类型 | 英文名称 | 底层结构 | 特性说明 |
|---|---|---|---|
| 字符串 | String | SDS(简单动态字符串) | 最基本类型,二进制安全,最大512MB |
| 哈希 | Hash | 压缩列表/哈希表 | 适合存储对象,可单独操作每个字段 |
| 列表 | List | 双向链表/压缩列表 | 有序,支持两端操作,可实现队列/栈 |
| 集合 | Set | 哈希表/整数集合 | 无序,元素唯一,支持集合运算 |
| 有序集合 | Zset | 跳表+哈希表 | 每个元素关联分数,按分数排序 |
数据类型适用场景对照表
| 数据类型 | ✅ 适用场景 | ❌ 不适用场景 |
|---|---|---|
| String | 简单键值缓存、计数器、分布式锁、Session存储 | 存储复杂嵌套对象(需序列化) |
| Hash | 用户信息、商品详情、配置参数等对象存储 | 字段需要单独设置过期时间的场景 |
| List | 消息队列、最新消息列表、操作日志、时间线 | 需要随机访问中间元素的场景 |
| Set | 标签系统、好友关系、去重统计、共同关注 | 需要保持有序或需要排序的场景 |
| Zset | 排行榜、优先级队列、延迟任务、范围查询 | 仅需去重不需要排序的场景 |
2. 字符串类型完整操作
字符串是 Redis 最基础的数据类型,键和值都是字符串形式,值最大支持 512MB。
2.1 基础读写操作
bash
# ========== 设置与获取 ==========
# 设置键值对
127.0.0.1:6379> SET name tuling
OK
# 获取键对应的值
127.0.0.1:6379> GET name
"tuling"
# 如果键不存在,返回 (nil)
127.0.0.1:6379> GET address
(nil)
# ========== 设置并指定过期时间 ==========
# 设置 name 键的值为"小黑猪",3秒后自动删除
127.0.0.1:6379> SETEX name 3 小黑猪
OK
# 3秒后再次获取
127.0.0.1:6379> GET name
(nil)
# ========== 批量设置与批量获取 ==========
# 一次设置多个键值对(减少网络往返)
127.0.0.1:6379> MSET name 小孩 age 18 gender 女
OK
# 一次获取多个键的值
127.0.0.1:6379> MGET name age gender
1) "小孩"
2) "18"
3) "女"
2.2 数值操作(原子性)
bash
# ========== 计数器操作 ==========
# 初始化计数器
127.0.0.1:6379> SET page_view 100
OK
# 自增1(原子操作,线程安全)
127.0.0.1:6379> INCR page_view
(integer) 101
# 自增指定数值(如增加10)
127.0.0.1:6379> INCRBY page_view 10
(integer) 111
# 自减1
127.0.0.1:6379> DECR page_view
(integer) 110
# 自减指定数值
127.0.0.1:6379> DECRBY page_view 5
(integer) 105
# 浮点数增减
127.0.0.1:6379> SET score 88.5
OK
127.0.0.1:6379> INCRBYFLOAT score 1.5
"90.0"
2.3 字符串高级操作
bash
# ========== 字符串追加 ==========
127.0.0.1:6379> SET greeting Hello
OK
127.0.0.1:6379> APPEND greeting " World"
(integer) 11
127.0.0.1:6379> GET greeting
"Hello World"
# ========== 获取子字符串 ==========
# 获取索引0到4的字符(包含两端)
127.0.0.1:6379> GETRANGE greeting 0 4
"Hello"
# 获取字符串长度
127.0.0.1:6379> STRLEN greeting
(integer) 11
# ========== 设置新值并返回旧值 ==========
# 适用于需要"先读后写"的场景
127.0.0.1:6379> GETSET name "张三"
"tuling"
127.0.0.1:6379> GET name
"张三"
# ========== 仅当键不存在时设置(分布式锁) ==========
127.0.0.1:6379> SETNX lock_key "locked"
(integer) 1 # 设置成功
127.0.0.1:6379> SETNX lock_key "another"
(integer) 0 # 设置失败(键已存在)
# ========== 设置带过期时间的 NX(Redis 2.6.12+)==========
# 仅当键不存在时设置,并指定过期时间为10秒
127.0.0.1:6379> SET lock_key "locked" NX EX 10
OK
2.4 字符串业务场景示例
bash
# ========== 场景1:文章点赞数 ==========
127.0.0.1:6379> SET article:1001:likes 0
OK
127.0.0.1:6379> INCR article:1001:likes
(integer) 1
127.0.0.1:6379> INCR article:1001:likes
(integer) 2
# ========== 场景2:用户 Session 存储(带过期) ==========
127.0.0.1:6379> SETEX session:user:1001 3600 "{\"uid\":1001,\"name\":\"张三\"}"
OK
# 1小时后自动失效
# ========== 场景3:分布式锁(释放时校验值) ==========
127.0.0.1:6379> SET lock:order:1001 "uuid-xxxx-1234" NX EX 30
OK
3. 哈希类型完整操作
Hash 适合存储对象,每个对象有一个键名,内部包含多个字段-值对。
3.1 基础读写操作
bash
# ========== 设置字段 ==========
# 设置单个字段
127.0.0.1:6379> HSET user:1001 name tuling
(integer) 1
# 设置多个字段(推荐使用 HSET 批量)
127.0.0.1:6379> HSET user:1001 age 25 city "Beijing" email "tuling@test.com"
(integer) 3
# 或者使用 HMSET(旧版本兼容)
127.0.0.1:6379> HMSET user:1001 age 25 city Beijing email tuling@test.com
OK
# ========== 获取字段 ==========
# 获取单个字段
127.0.0.1:6379> HGET user:1001 name
"tuling"
# 获取多个字段
127.0.0.1:6379> HMGET user:1001 name age city
1) "tuling"
2) "25"
3) "Beijing"
# 获取所有字段和值
127.0.0.1:6379> HGETALL user:1001
1) "name"
2) "tuling"
3) "age"
4) "25"
5) "city"
6) "Beijing"
7) "email"
8) "tuling@test.com"
# ========== 获取所有字段名 / 所有值 ==========
127.0.0.1:6379> HKEYS user:1001
1) "name"
2) "age"
3) "city"
4) "email"
127.0.0.1:6379> HVALS user:1001
1) "tuling"
2) "25"
3) "Beijing"
4) "tuling@test.com"
3.2 哈希高级操作
bash
# ========== 字段数值操作 ==========
127.0.0.1:6379> HSET user:1001 points 100
(integer) 1
127.0.0.1:6379> HINCRBY user:1001 points 10
(integer) 110
127.0.0.1:6379> HINCRBYFLOAT user:1001 score 2.5
"92.5"
# ========== 字段删除 ==========
# 删除指定字段
127.0.0.1:6379> HDEL user:1001 email
(integer) 1
# 删除整个 Hash(使用 DEL)
127.0.0.1:6379> DEL user:1001
(integer) 1
# ========== 判断字段是否存在 ==========
127.0.0.1:6379> HEXISTS user:1001 name
(integer) 1
127.0.0.1:6379> HEXISTS user:1001 email
(integer) 0
# ========== 获取字段数量 ==========
127.0.0.1:6379> HLEN user:1001
(integer) 4
# ========== 仅当字段不存在时设置 ==========
127.0.0.1:6379> HSETNX user:1001 nickname "小图灵"
(integer) 1 # 设置成功
127.0.0.1:6379> HSETNX user:1001 nickname "图灵哥"
(integer) 0 # 设置失败(字段已存在)
3.3 哈希业务场景示例
bash
# ========== 场景1:用户个人信息存储 ==========
127.0.0.1:6379> HSET user:1001 username "zhangsan" realname "张三" age 28 gender "男" phone "13800138000"
(integer) 5
# 获取用户基本信息
127.0.0.1:6379> HMGET user:1001 username realname age
1) "zhangsan"
2) "张三"
3) "28"
# ========== 场景2:商品详情页缓存 ==========
127.0.0.1:6379> HMSET product:2001 name "iPhone 15 Pro" price 7999 stock 100 category "手机" rating 4.8
OK
# 更新库存(原子操作)
127.0.0.1:6379> HINCRBY product:2001 stock -1
(integer) 99
# ========== 场景3:购物车信息 ==========
# 用户1001的购物车:商品ID -> 数量
127.0.0.1:6379> HSET cart:1001 2001 2
(integer) 1
127.0.0.1:6379> HSET cart:1001 2003 1
(integer) 1
127.0.0.1:6379> HINCRBY cart:1001 2001 1 # 增加一件
(integer) 3
127.0.0.1:6379> HGETALL cart:1001
1) "2001"
2) "3"
3) "2003"
4) "1"
4. 列表类型完整操作
List 是有序的字符串集合,底层使用双向链表实现,支持两端操作。
4.1 基础推入与获取
bash
# ========== 左侧推入(LPUSH) ==========
# 依次从左侧推入 a、b、c,最终顺序:c -> b -> a
127.0.0.1:6379> LPUSH list1 a b c
(integer) 3
127.0.0.1:6379> LRANGE list1 0 -1
1) "c"
2) "b"
3) "a"
# ========== 右侧推入(RPUSH) ==========
# 依次从右侧推入 a、b、c,最终顺序:a -> b -> c
127.0.0.1:6379> RPUSH list2 a b c
(integer) 3
127.0.0.1:6379> LRANGE list2 0 -1
1) "a"
2) "b"
3) "c"
# ========== 获取列表范围 ==========
127.0.0.1:6379> LRANGE list2 0 1 # 获取前2个元素
1) "a"
2) "b"
127.0.0.1:6379> LRANGE list2 -2 -1 # 获取最后2个元素
1) "b"
2) "c"
4.2 列表弹出与删除操作
bash
# ========== 弹出元素 ==========
127.0.0.1:6379> LPUSH tasks task1 task2 task3
(integer) 3
# 从左侧弹出(移除并返回)
127.0.0.1:6379> LPOP tasks
"task3"
127.0.0.1:6379> LRANGE tasks 0 -1
1) "task2"
2) "task1"
# 从右侧弹出
127.0.0.1:6379> RPOP tasks
"task1"
127.0.0.1:6379> LRANGE tasks 0 -1
1) "task2"
# ========== 阻塞弹出(实现消息队列)==========
# 阻塞5秒从列表左侧弹出,超时返回 (nil)
127.0.0.1:6379> BLPOP tasks 5
1) "tasks"
2) "task2"
# 若列表为空,则阻塞等待5秒
# ========== 删除指定元素(LREM) ==========
127.0.0.1:6379> LPUSH test_list a b a b a b
(integer) 6
127.0.0.1:6379> LRANGE test_list 0 -1
1) "b"
2) "a"
3) "b"
4) "a"
5) "b"
6) "a"
# 从右侧删除2个 "b"
127.0.0.1:6379> LREM test_list -2 b
(integer) 2
127.0.0.1:6379> LRANGE test_list 0 -1
1) "b"
2) "a"
3) "a"
4) "a"
# 删除所有 "a"
127.0.0.1:6379> LREM test_list 0 a
(integer) 3
127.0.0.1:6379> LRANGE test_list 0 -1
1) "b"
4.3 列表高级操作
bash
# ========== 获取列表长度 ==========
127.0.0.1:6379> LLEN tasks
(integer) 0
# ========== 按索引获取/设置元素 ==========
127.0.0.1:6379> RPUSH fruits apple banana cherry date
(integer) 4
127.0.0.1:6379> LINDEX fruits 2
"cherry"
127.0.0.1:6379> LINDEX fruits -1
"date"
# 设置指定索引的值
127.0.0.1:6379> LSET fruits 1 "orange"
OK
127.0.0.1:6379> LRANGE fruits 0 -1
1) "apple"
2) "orange"
3) "cherry"
4) "date"
# ========== 修剪列表(保留指定范围) ==========
# 只保留索引1到2的元素
127.0.0.1:6379> LTRIM fruits 1 2
OK
127.0.0.1:6379> LRANGE fruits 0 -1
1) "orange"
2) "cherry"
# ========== 插入元素(在指定元素前后) ==========
127.0.0.1:6379> RPUSH letters a c d
(integer) 3
# 在元素 "c" 前面插入 "b"
127.0.0.1:6379> LINSERT letters BEFORE c b
(integer) 4
127.0.0.1:6379> LRANGE letters 0 -1
1) "a"
2) "b"
3) "c"
4) "d"
# 在元素 "c" 后面插入 "x"
127.0.0.1:6379> LINSERT letters AFTER c x
(integer) 5
127.0.0.1:6379> LRANGE letters 0 -1
1) "a"
2) "b"
3) "c"
4) "x"
5) "d"
4.4 列表业务场景示例
bash
# ========== 场景1:消息队列(生产者-消费者) ==========
# 生产者:推送任务到队列
127.0.0.1:6379> RPUSH task_queue "send_email:user:1001" "generate_report:2024-01"
(integer) 2
# 消费者:从队列取任务(阻塞式)
127.0.0.1:6379> BLPOP task_queue 0 # 0表示无限阻塞
1) "task_queue"
2) "send_email:user:1001"
# ========== 场景2:最新消息列表(如新闻、评论) ==========
# 发布新文章,使用 LPUSH 推入,最新文章在列表头部
127.0.0.1:6379> LPUSH news:list "新闻标题3" "新闻标题2" "新闻标题1"
(integer) 3
# 获取最新的5条新闻
127.0.0.1:6379> LRANGE news:list 0 4
1) "新闻标题1"
2) "新闻标题2"
3) "新闻标题3"
# ========== 场景3:操作日志记录 ==========
127.0.0.1:6379> RPUSH operation_log "2024-01-15 10:00:01 用户登录"
(integer) 1
127.0.0.1:6379> RPUSH operation_log "2024-01-15 10:00:05 修改密码"
(integer) 2
127.0.0.1:6379> RPUSH operation_log "2024-01-15 10:00:10 退出登录"
(integer) 3
# 查看最近10条日志
127.0.0.1:6379> LRANGE operation_log -10 -1
5. 集合类型完整操作
集合是无序、元素唯一的字符串集合,支持交集、并集、差集运算。
5.1 基础读写操作
bash
# ========== 添加元素(SADD) ==========
# 添加多个元素(重复元素自动忽略)
127.0.0.1:6379> SADD user_name_1 zhangsan lisi wangwu
(integer) 3
# 再次添加已存在的元素
127.0.0.1:6379> SADD user_name_1 zhangsan
(integer) 0 # 返回0表示没有新增
# ========== 获取所有元素(SMEMBERS) ==========
127.0.0.1:6379> SMEMBERS user_name_1
1) "wangwu"
2) "lisi"
3) "zhangsan"
# 注意:返回顺序是无序的!
# ========== 删除元素(SREM) ==========
127.0.0.1:6379> SREM user_name_1 wangwu
(integer) 1
127.0.0.1:6379> SMEMBERS user_name_1
1) "lisi"
2) "zhangsan"
5.2 集合判断与数量操作
bash
# ========== 判断元素是否在集合中 ==========
127.0.0.1:6379> SISMEMBER user_name_1 lisi
(integer) 1 # 存在
127.0.0.1:6379> SISMEMBER user_name_1 wangwu
(integer) 0 # 不存在
# ========== 获取集合元素数量 ==========
127.0.0.1:6379> SCARD user_name_1
(integer) 2
# ========== 随机获取/移除元素 ==========
127.0.0.1:6379> SADD fruits apple banana cherry date
(integer) 4
# 随机返回一个元素(不移除)
127.0.0.1:6379> SRANDMEMBER fruits
"banana"
# 随机移除并返回一个元素
127.0.0.1:6379> SPOP fruits
"cherry"
127.0.0.1:6379> SMEMBERS fruits
1) "apple"
2) "date"
3) "banana"
5.3 集合运算操作
bash
# ========== 准备测试数据 ==========
127.0.0.1:6379> SADD set1 a b c d
(integer) 4
127.0.0.1:6379> SADD set2 c d e f
(integer) 4
# ========== 并集(SUNION) ==========
127.0.0.1:6379> SUNION set1 set2
1) "b"
2) "c"
3) "a"
4) "e"
5) "f"
6) "d"
# ========== 交集(SINTER) ==========
127.0.0.1:6379> SINTER set1 set2
1) "c"
2) "d"
# ========== 差集(SDIFF) ==========
# set1 中有但 set2 中没有的元素
127.0.0.1:6379> SDIFF set1 set2
1) "a"
2) "b"
# set2 中有但 set1 中没有的元素
127.0.0.1:6379> SDIFF set2 set1
1) "e"
2) "f"
# ========== 将运算结果存储到新集合 ==========
# 将并集存储到 new_set
127.0.0.1:6379> SUNIONSTORE new_set set1 set2
(integer) 6
127.0.0.1:6379> SMEMBERS new_set
1) "b"
2) "c"
3) "a"
4) "e"
5) "f"
6) "d"
5.4 集合业务场景示例
bash
# ========== 场景1:用户标签系统 ==========
# 用户1的标签
127.0.0.1:6379> SADD user:1001:tags "科技" "数码" "摄影"
(integer) 3
# 用户2的标签
127.0.0.1:6379> SADD user:1002:tags "科技" "运动" "旅行"
(integer) 3
# 共同标签(交集)
127.0.0.1:6379> SINTER user:1001:tags user:1002:tags
1) "科技"
# 用户1独有的标签(差集)
127.0.0.1:6379> SDIFF user:1001:tags user:1002:tags
1) "数码"
2) "摄影"
# ========== 场景2:好友关系 ==========
# 用户A的关注
127.0.0.1:6379> SADD follow:A B C D
(integer) 4
# 用户B的关注
127.0.0.1:6379> SADD follow:B A C E
(integer) 3
# 共同关注
127.0.0.1:6379> SINTER follow:A follow:B
1) "C"
# A关注了但B没有关注
127.0.0.1:6379> SDIFF follow:A follow:B
1) "B"
2) "D"
# ========== 场景3:每日签到去重 ==========
# 2024-01-15 签到用户
127.0.0.1:6379> SADD sign:2024-01-15 user:1001 user:1002 user:1003
(integer) 3
# 2024-01-16 签到用户
127.0.0.1:6379> SADD sign:2024-01-16 user:1001 user:1003 user:1004
(integer) 3
# 连续两天签到的用户(交集)
127.0.0.1:6379> SINTER sign:2024-01-15 sign:2024-01-16
1) "user:1001"
2) "user:1003"
# 两天总共签到人数(并集数量)
127.0.0.1:6379> SUNION sign:2024-01-15 sign:2024-01-16
1) "user:1001"
2) "user:1002"
3) "user:1003"
4) "user:1004"
127.0.0.1:6379> SUNIONSTORE total_sign sign:2024-01-15 sign:2024-01-16
(integer) 4
6. 有序集合类型完整操作
Zset 是有序集合,每个元素关联一个 double 类型的 score(分数),根据分数从小到大排序。
6.1 基础读写操作
bash
# ========== 添加元素(ZADD) ==========
# 格式:ZADD key score1 member1 score2 member2 ...
127.0.0.1:6379> ZADD user_name_2 4 lisi 5 wangwu 6 zhaoliu 3 zhangsan
(integer) 4
# ========== 获取所有元素(ZRANGE) ==========
# 按分数从小到大排序
127.0.0.1:6379> ZRANGE user_name_2 0 -1
1) "zhangsan"
2) "lisi"
3) "wangwu"
4) "zhaoliu"
# 带分数返回
127.0.0.1:6379> ZRANGE user_name_2 0 -1 WITHSCORES
1) "zhangsan"
2) "3"
3) "lisi"
4) "4"
5) "wangwu"
6) "5"
7) "zhaoliu"
8) "6"
# ========== 反向获取(ZREVRANGE) ==========
# 按分数从大到小排序(从高到低)
127.0.0.1:6379> ZREVRANGE user_name_2 0 -1
1) "zhaoliu"
2) "wangwu"
3) "lisi"
4) "zhangsan"
# ========== 获取元素分数 ==========
127.0.0.1:6379> ZSCORE user_name_2 lisi
"4"
# ========== 删除元素(ZREM) ==========
127.0.0.1:6379> ZREM user_name_2 zhangsan
(integer) 1
127.0.0.1:6379> ZRANGE user_name_2 0 -1
1) "lisi"
2) "wangwu"
3) "zhaoliu"
6.2 有序集合高级操作
bash
# ========== 获取元素排名 ==========
# ZRANK:按分数从小到大排名(从0开始)
127.0.0.1:6379> ZRANK user_name_2 wangwu
(integer) 1 # wangwu 排第2
# ZREVRANK:按分数从大到小排名
127.0.0.1:6379> ZREVRANK user_name_2 wangwu
(integer) 1 # 从高到低排第2
# ========== 获取元素数量 ==========
127.0.0.1:6379> ZCARD user_name_2
(integer) 3
# ========== 获取指定分数范围的元素 ==========
127.0.0.1:6379> ZADD scores 85 "张三" 92 "李四" 78 "王五" 95 "赵六" 88 "孙七"
(integer) 5
# 获取分数在80到90之间的元素
127.0.0.1:6379> ZRANGEBYSCORE scores 80 90
1) "张三"
2) "孙七"
# 带分数返回
127.0.0.1:6379> ZRANGEBYSCORE scores 80 90 WITHSCORES
1) "张三"
2) "85"
3) "孙七"
4) "88"
# 获取分数大于等于90的元素
127.0.0.1:6379> ZRANGEBYSCORE scores 90 +inf
1) "李四"
2) "赵六"
# ========== 删除指定分数范围的元素 ==========
127.0.0.1:6379> ZREMRANGEBYSCORE scores 70 80
(integer) 1 # 删除了"王五"
127.0.0.1:6379> ZRANGE scores 0 -1
1) "张三"
2) "孙七"
3) "李四"
4) "赵六"
# ========== 增加元素分数 ==========
127.0.0.1:6379> ZINCRBY scores 5 "张三"
"90"
127.0.0.1:6379> ZSCORE scores "张三"
"90"
6.3 有序集合业务场景示例
bash
# ========== 场景1:游戏排行榜 ==========
# 记录玩家分数
127.0.0.1:6379> ZADD game:rank 1500 "player:1001"
(integer) 1
127.0.0.1:6379> ZADD game:rank 2800 "player:1002"
(integer) 1
127.0.0.1:6379> ZADD game:rank 3200 "player:1003"
(integer) 1
127.0.0.1:6379> ZADD game:rank 2100 "player:1004"
(integer) 1
# 更新玩家分数(增加100分)
127.0.0.1:6379> ZINCRBY game:rank 100 "player:1001"
"1600"
# 获取排行榜前3名(从高到低)
127.0.0.1:6379> ZREVRANGE game:rank 0 2 WITHSCORES
1) "player:1003"
2) "3200"
3) "player:1002"
4) "2800"
5) "player:1004"
6) "2100"
# 查看玩家排名(从高到低,从0开始)
127.0.0.1:6379> ZREVRANK game:rank "player:1001"
(integer) 3 # 排第4名
# ========== 场景2:延迟任务队列 ==========
# 添加任务,score为执行时间戳
127.0.0.1:6379> ZADD delay_queue 1705315200 "task:send_email:1001"
(integer) 1
127.0.0.1:6379> ZADD delay_queue 1705318800 "task:generate_report:2024-01"
(integer) 1
# 获取当前时间之前到期的任务(执行时间 <= 当前时间)
# 假设当前时间戳为 1705315000
127.0.0.1:6379> ZRANGEBYSCORE delay_queue 0 1705315000
1) "task:send_email:1001"
# 获取并删除已到期任务(原子操作,需配合Lua脚本)
# 实际生产中需使用Lua脚本保证原子性
# ========== 场景3:商品热销榜 ==========
# 记录商品销量
127.0.0.1:6379> ZINCRBY hot:products 10 "product:2001"
"10"
127.0.0.1:6379> ZINCRBY hot:products 5 "product:2002"
"5"
127.0.0.1:6379> ZINCRBY hot:products 20 "product:2003"
"20"
# 获取热销榜前3名
127.0.0.1:6379> ZREVRANGE hot:products 0 2 WITHSCORES
1) "product:2003"
2) "20"
3) "product:2001"
4) "10"
5) "product:2002"
5) "5"
# ========== 场景4:带权重的抽奖池 ==========
# 用户中奖权重(权重越高,中奖概率越大)
127.0.0.1:6379> ZADD lottery:pool 10 "user:1001"
(integer) 1
127.0.0.1:6379> ZADD lottery:pool 30 "user:1002"
(integer) 1
127.0.0.1:6379> ZADD lottery:pool 50 "user:1003"
(integer) 1
127.0.0.1:6379> ZADD lottery:pool 20 "user:1004"
(integer) 1
# 随机抽取(使用 ZRANDMEMBER)
# 注意:Redis 6.2+ 支持 ZRANDMEMBER
127.0.0.1:6379> ZRANDMEMBER lottery:pool
"user:1003"
四、Python 操作 Redis(redis-py)
1. 安装与连接
1.1 安装
bash
pip install redis
1.2 连接配置(推荐使用连接池)
python
import redis
# 创建连接池------生产环境必备
pool = redis.ConnectionPool(
host='localhost', # Redis服务器地址
port=6379, # 端口
db=0, # 数据库索引(0-15)
decode_responses=True, # 自动解码为字符串
max_connections=10 # 最大连接数
)
# 创建客户端对象
r = redis.Redis(connection_pool=pool)
# 测试连接
print(r.ping()) # True
⚠️ 参数说明:
decode_responses=True:返回结果自动转为Python字符串,否则返回bytes类型- 如需存储二进制数据(如图片),可设为
False
2. 字符串操作
python
# ========== 基本读写 ==========
r.set('name', 'Alice')
print(r.get('name')) # 'Alice'
# 键不存在时返回 None
print(r.get('not_exist')) # None
# ========== 设置过期时间 ==========
r.setex('temp_key', 60, 'value') # 60秒后过期
# ========== 批量操作 ==========
r.mset({'k1': 'v1', 'k2': 'v2'})
print(r.mget(['k1', 'k2'])) # ['v1', 'v2']
# ========== 数值操作 ==========
r.set('page_view', 100)
r.incr('page_view') # 自增1 → 101
r.incrby('page_view', 10) # 增加10 → 111
r.decr('page_view') # 自减1 → 110
# ========== 浮点数操作 ==========
r.set('score', 88.5)
r.incrbyfloat('score', 1.5) # 90.0
# ========== 字符串高级操作 ==========
r.set('greeting', 'Hello')
r.append('greeting', ' World') # 追加
print(r.get('greeting')) # 'Hello World'
print(r.strlen('greeting')) # 11
# ========== 存储Python对象(需序列化) ==========
import json
user_dict = {'name': 'Bob', 'age': 25, 'city': 'Beijing'}
r.set('user:1', json.dumps(user_dict))
data = json.loads(r.get('user:1'))
print(data['name']) # 'Bob'
# ========== SETNX(分布式锁场景) ==========
# 仅当键不存在时设置
result = r.setnx('lock_key', 'locked')
print(result) # True 或 False
# ========== SET 带 NX 和 EX 参数 ==========
# 仅当键不存在时设置,并指定10秒过期
result = r.set('lock_key', 'locked', nx=True, ex=10)
print(result) # True 或 None
3. 哈希操作
python
# ========== 设置字段 ==========
# 单个字段
r.hset('user:1001', 'name', 'tuling')
# 批量设置
r.hset('user:1001', mapping={
'age': 25,
'city': 'Beijing',
'email': 'tuling@test.com'
})
# ========== 获取字段 ==========
print(r.hget('user:1001', 'name')) # 'tuling'
print(r.hmget('user:1001', 'name', 'age')) # ['tuling', '25']
print(r.hgetall('user:1001')) # {'name':'tuling', 'age':'25', ...}
# ========== 获取字段名和值 ==========
print(r.hkeys('user:1001')) # ['name', 'age', 'city', 'email']
print(r.hvals('user:1001')) # ['tuling', '25', 'Beijing', 'tuling@test.com']
# ========== 数值操作 ==========
r.hset('user:1001', 'points', 100)
r.hincrby('user:1001', 'points', 10) # 110
# ========== 判断字段是否存在 ==========
print(r.hexists('user:1001', 'name')) # True
print(r.hexists('user:1001', 'phone')) # False
# ========== 删除字段 ==========
r.hdel('user:1001', 'email')
print(r.hgetall('user:1001')) # email 字段已删除
# 删除整个哈希
r.delete('user:1001')
4. 列表操作
python
# ========== 推入元素 ==========
r.lpush('tasks', 'task1', 'task2') # 从左侧推入
r.rpush('tasks', 'task3') # 从右侧推入
# ========== 获取元素 ==========
print(r.lrange('tasks', 0, -1)) # ['task2', 'task1', 'task3']
print(r.lindex('tasks', 0)) # 'task2'
print(r.llen('tasks')) # 3
# ========== 弹出元素 ==========
print(r.lpop('tasks')) # 'task2'
print(r.rpop('tasks')) # 'task3'
print(r.lrange('tasks', 0, -1)) # ['task1']
# ========== 阻塞弹出(消息队列) ==========
# 阻塞5秒从列表左侧弹出一个元素
result = r.blpop('tasks', timeout=5)
# result 为 (key, value) 元组,或 None(超时)
# ========== 删除元素(LREM) ==========
r.lpush('test_list', 'a', 'b', 'a', 'b', 'a', 'b')
# 从右侧删除2个 'b'
r.lrem('test_list', -2, 'b')
# 删除所有 'a'
r.lrem('test_list', 0, 'a')
# ========== 设置指定索引的值 ==========
r.rpush('fruits', 'apple', 'banana', 'cherry')
r.lset('fruits', 1, 'orange')
print(r.lrange('fruits', 0, -1)) # ['apple', 'orange', 'cherry']
# ========== 修剪列表 ==========
r.ltrim('fruits', 0, 1) # 只保留前2个元素
# ========== 插入元素 ==========
r.rpush('letters', 'a', 'c', 'd')
r.linsert('letters', 'BEFORE', 'c', 'b') # 在 'c' 前插入 'b'
print(r.lrange('letters', 0, -1)) # ['a', 'b', 'c', 'd']
5. 集合操作
python
# ========== 添加元素 ==========
r.sadd('tags', 'python', 'redis', 'django')
r.sadd('tags', 'python') # 重复添加,返回0
# ========== 获取所有元素 ==========
print(r.smembers('tags')) # {'django', 'python', 'redis'}
# ========== 删除元素 ==========
r.srem('tags', 'django')
# ========== 判断存在 ==========
print(r.sismember('tags', 'python')) # True
# ========== 获取元素数量 ==========
print(r.scard('tags')) # 2
# ========== 随机获取/移除 ==========
print(r.srandmember('tags')) # 随机返回一个元素
print(r.spop('tags')) # 随机移除并返回一个元素
# ========== 集合运算 ==========
r.sadd('set1', 'a', 'b', 'c', 'd')
r.sadd('set2', 'c', 'd', 'e', 'f')
print(r.sunion('set1', 'set2')) # 并集
print(r.sinter('set1', 'set2')) # 交集
print(r.sdiff('set1', 'set2')) # 差集
# ========== 集合运算并存储 ==========
r.sunionstore('new_set', 'set1', 'set2')
print(r.smembers('new_set'))
6. 有序集合操作
python
# ========== 添加元素 ==========
# 格式:zadd(key, {member1: score1, member2: score2})
r.zadd('rank', {'user1': 100, 'user2': 90, 'user3': 120})
# ========== 获取元素 ==========
# 按分数从小到大
print(r.zrange('rank', 0, -1)) # ['user2', 'user1', 'user3']
# 带分数
print(r.zrange('rank', 0, -1, withscores=True))
# 按分数从大到小
print(r.zrevrange('rank', 0, -1)) # ['user3', 'user1', 'user2']
# ========== 获取分数 ==========
print(r.zscore('rank', 'user1')) # 100.0
# ========== 获取排名 ==========
print(r.zrank('rank', 'user1')) # 1(从小到大,第2名)
print(r.zrevrank('rank', 'user1')) # 1(从大到小,第2名)
# ========== 获取元素数量 ==========
print(r.zcard('rank')) # 3
# ========== 按分数范围查询 ==========
print(r.zrangebyscore('rank', 90, 110)) # ['user2', 'user1']
print(r.zrangebyscore('rank', 90, 110, withscores=True))
# ========== 增加分数 ==========
r.zincrby('rank', 10, 'user1') # user1 加10分
# ========== 删除元素 ==========
r.zrem('rank', 'user3')
# ========== 删除指定分数范围的元素 ==========
r.zremrangebyscore('rank', 0, 50)
7. 管道(Pipeline)进阶
当需要一次性执行多个命令时,使用管道可以大幅减少网络往返时间。
python
# ========== 基本管道用法 ==========
pipe = r.pipeline()
# 将多个命令放入管道
pipe.set('key1', 'value1')
pipe.incr('counter')
pipe.hset('user:1', 'name', 'Alice')
pipe.zadd('rank', {'user1': 100})
# 执行所有命令,按顺序返回结果列表
results = pipe.execute()
print(results) # [True, 1, 1, 1]
# ========== 管道 + 事务模式 ==========
# 使用 MULTI/EXEC 包裹命令
pipe = r.pipeline(transaction=True)
pipe.set('key_a', 'value_a')
pipe.set('key_b', 'value_b')
results = pipe.execute() # 原子执行
# ========== 管道 + 监视(乐观锁) ==========
# 使用 WATCH 实现 CAS(检查并设置)
with r.pipeline() as pipe:
# 监视 key,如果在执行前被修改则事务失败
pipe.watch('counter')
current = int(pipe.get('counter') or 0)
pipe.multi()
pipe.set('counter', current + 1)
result = pipe.execute() # 如果 counter 被其他客户端修改,这里返回空列表
if result:
print('更新成功')
else:
print('更新失败,请重试')
五、总结与最佳实践
1. 数据类型选择指南
| 业务场景 | 推荐数据类型 | 理由 |
|---|---|---|
| 缓存简单值(Session、验证码) | String | 简单高效,支持过期时间 |
| 存储对象(用户信息、商品详情) | Hash | 可单独操作字段,节省内存 |
| 消息队列、任务队列 | List | 支持两端操作,阻塞弹出 |
| 标签系统、好友关系、去重 | Set | 自动去重,支持集合运算 |
| 排行榜、优先级队列 | Zset | 按分数自动排序,支持范围查询 |
2. 性能最佳实践
| 实践建议 | 说明 |
|---|---|
| 使用连接池 | 生产环境务必使用连接池,避免频繁创建/关闭连接 |
| 设置过期时间 | 对缓存数据使用 EXPIRE 或 SETEX 设置 TTL,有效管理内存 |
| 善用批量操作 | 使用 MSET/MGET、HMGET、管道等减少网络往返 |
| 避免 KEYS * | 生产环境使用 SCAN 替代 KEYS,避免阻塞 |
| 选择合适数据结构 | 根据业务场景选择最合适的数据类型,节约内存 |
| 使用 Pipeline | 批量执行命令时使用管道,可大幅提升性能 |
| 注意大 Key | 避免存储过大 Value,可能导致内存问题和性能下降 |
3. 常见问题与注意事项
- Redis 事务不支持回滚:事务中的命令如果执行失败,不会自动回滚
- SETNX 分布式锁:需要配合 Lua 脚本释放,确保原子性
- 有序集合元素唯一:如果重复添加相同元素,会更新分数而非新增
- 列表索引从 0 开始:负数表示从尾部开始计数
- Hash 字段数量较多时 :使用
HGETALL可能产生性能问题,建议分批获取或使用HSCAN
📢 结语
本文从 Redis 的核心特性、应用场景出发,详细讲解了五种核心数据类型在命令行和 Python 中的完整操作方法,并提供了丰富的业务场景示例。希望这篇实战指南能帮助你快速上手 Redis,并在实际项目中灵活运用。
如果本文对你有所帮助,欢迎点赞、收藏、评论三连支持!你的互动是我持续分享优质技术文章的最大动力。