Redis 从入门到实战:Python操作全攻略

📌 摘要

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. 概述

RedisRe 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 事务处理 🔒
  • 通过 MULTIEXECDISCARDWATCH 命令实现事务
  • 特性:事务中的命令会按顺序串行执行,支持乐观锁
  • 注意: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):支持存储经纬度,计算距离、查找附近的人/门店
  • 命令示例:GEOADDGEODISTGEORADIUS
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. 性能最佳实践

实践建议 说明
使用连接池 生产环境务必使用连接池,避免频繁创建/关闭连接
设置过期时间 对缓存数据使用 EXPIRESETEX 设置 TTL,有效管理内存
善用批量操作 使用 MSET/MGETHMGET、管道等减少网络往返
避免 KEYS * 生产环境使用 SCAN 替代 KEYS,避免阻塞
选择合适数据结构 根据业务场景选择最合适的数据类型,节约内存
使用 Pipeline 批量执行命令时使用管道,可大幅提升性能
注意大 Key 避免存储过大 Value,可能导致内存问题和性能下降

3. 常见问题与注意事项

  1. Redis 事务不支持回滚:事务中的命令如果执行失败,不会自动回滚
  2. SETNX 分布式锁:需要配合 Lua 脚本释放,确保原子性
  3. 有序集合元素唯一:如果重复添加相同元素,会更新分数而非新增
  4. 列表索引从 0 开始:负数表示从尾部开始计数
  5. Hash 字段数量较多时 :使用 HGETALL 可能产生性能问题,建议分批获取或使用 HSCAN

📢 结语

本文从 Redis 的核心特性、应用场景出发,详细讲解了五种核心数据类型在命令行和 Python 中的完整操作方法,并提供了丰富的业务场景示例。希望这篇实战指南能帮助你快速上手 Redis,并在实际项目中灵活运用。

如果本文对你有所帮助,欢迎点赞、收藏、评论三连支持!你的互动是我持续分享优质技术文章的最大动力。

相关推荐
️学习的小王2 小时前
智能文档助手:基于RAG的本地化文档问答系统实战指南
人工智能·python·机器学习
不瘦80斤不改名2 小时前
05-vibe-coding-向agentic-engineering演进
人工智能·笔记·python·prompt
笨鸟先飞,勤能补拙2 小时前
AI 安全的下一个 5 年:从 Agent 到 AGI 的攻防博弈
人工智能·python·安全·web安全·网络安全·github·agi
隔窗听雨眠3 小时前
OceanBase旁路导入与自增主键冲突深度解析:原理、排查与解决方案
java·服务器·数据库
ZCBUS实时计算3 小时前
政务海量分库分表汇聚实战:基于 3 节点 ZCBUS 集群完成医保 10TB 数据实时整合
大数据·数据库·数据仓库·sql·dba·etl·政务
名不经传的养虾人3 小时前
从0到1:企业级AI项目迭代日记 Vol.81|工具调用前,先过一道审批门
数据库·人工智能·ai编程·ai工作流·企业ai
Databend3 小时前
Databend 产品更新:从 Spatial Index Join 到 Eval 数据管道
大数据·数据库·sql
Logintern093 小时前
LangSmith如何根据id关系,在服务端把扁平列表重建为树形结构,用于页面渲染Trace视图
python