IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。
在前五篇文章里,我们一直在用 redis-cli 和命令行探索 Redis 的五大数据结构外加三个高级类型。你可能会问:在 Python 项目里怎么操作 Redis?怎么管理连接?怎么提升性能?
从本文开始,我们将全面切换到 Python 视角 ,用 redis-py 这个官方推荐的客户端库来武装我们的 Python 应用。本文将手把手教你安装、建立连接、操作五大基础数据结构,并深入讲解连接池和 Pipeline 两大工程化利器。读完你会发现,用 Python 调用 Redis 原来可以这么简洁优雅。
1. 环境准备:安装 redis-py
redis-py 是 Redis 官方推荐的 Python 客户端,纯 Python 实现,API 简洁且紧跟 Redis 最新命令。安装只需一条命令:
预期输出:
bash
Collecting redis
Downloading redis-5.0.0-py3-none-any.whl (252 kB)
... 省略 ...
Installing collected packages: redis
Successfully installed redis-5.0.0
⚠️ 本文以
redis-py5.x 版本为例,部分 API 可能与 4.x 略有差异。建议使用 4.0 及以上版本,以获取完整类型提示和更好的异步支持。检查版本:pip show redis。
2. 创建连接:从简单到规范
2.1 最简单的连接
bash
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
# Ping 测试
print(r.ping()) # True
参数解释:
-
host:Redis 服务器地址,默认localhost -
port:端口,默认6379 -
decode_responses=True:强烈建议开启 ,让返回的数据自动解码为 Python 字符串,而不是 bytes。否则你得到的是b'value',需要自己.decode()
2.2 带密码和 db 的连接
bash
r = redis.Redis(
host='192.168.1.100',
port=6379,
password='your_password',
db=1,
decode_responses=True
)
db 参数指定数据库编号(Redis 默认有 0~15 共 16 个数据库,可通过配置修改)。
2.3 使用连接 URL(更现代)
redis-py 支持从 URL 字符串创建连接,适合从配置文件读取:
bash
# 格式: redis://[:password@]host:port/db
r = redis.from_url('redis://:mypassword@localhost:6379/0', decode_responses=True)
print(r.ping())
💡 最佳实践 :永远不要在代码中硬编码密码!使用环境变量:
redis.from_url(os.getenv('REDIS_URL'))
3. 五大基础数据类型 API 速查
redis-py 的 API 与 redis-cli 命令几乎一一对应,方法名小写,参数顺序一致。下面我们快速过一遍每种数据类型的 Python 调用方式,并附上输出。
3.1 String 操作
bash
import redis
r = redis.Redis(decode_responses=True)
# SET / GET / MSET / MGET
r.set('name', 'Alice')
print(r.get('name')) # Alice
r.mset({'age': 30, 'city': 'Beijing'})
print(r.mget(['age', 'city'])) # ['30', 'Beijing']
# SET 可选参数(ex, nx, px 等)
r.set('token', 'abc', ex=10) # 10秒过期
r.set('lock', '1', nx=True, ex=30) # 仅在不存在时设置
# 数字操作
r.set('counter', 0)
r.incr('counter')
r.incrby('counter', 5)
print(r.get('counter')) # 6
# 字符串操作
r.set('greet', 'Hello')
r.append('greet', ' Redis')
print(r.get('greet')) # Hello Redis
print(r.strlen('greet')) # 11
print(r.getrange('greet', 0, 4)) # Hello
3.2 Hash 操作
bash
# HSET / HGET / HMSET (已废弃,直接用 hset)
r.hset('user:1001', mapping={'name': 'Bob', 'age': '25', 'city': 'Shanghai'})
print(r.hgetall('user:1001')) # {'name': 'Bob', 'age': '25', 'city': 'Shanghai'}
# 获取单个字段
print(r.hget('user:1001', 'name')) # Bob
# 批量获取
print(r.hmget('user:1001', ['name', 'age'])) # ['Bob', '25']
# 自增
r.hincrby('user:1001', 'age', 1)
print(r.hget('user:1001', 'age')) # 26
# 字段是否存在/删除/长度
print(r.hexists('user:1001', 'city')) # True
r.hdel('user:1001', 'city')
print(r.hlen('user:1001')) # 2
3.3 List 操作
bash
# 左右插入
r.lpush('queue', 'task1', 'task2') # task2, task1
r.rpush('queue', 'task3') # task2, task1, task3
# 范围查看
print(r.lrange('queue', 0, -1)) # ['task2', 'task1', 'task3']
# 弹出
print(r.lpop('queue')) # task2
print(r.rpop('queue')) # task3
# 阻塞弹出
# r.brpop('queue', timeout=5) # 返回 (queue_name, value) 或 None
# 长度与索引
print(r.llen('queue')) # 1
print(r.lindex('queue', 0)) # task1
# 修剪
r.lpush('log', *['a','b','c','d','e'])
r.ltrim('log', 0, 2)
print(r.lrange('log', 0, -1)) # ['e', 'd', 'c']
3.4 Set 操作
bash
# 添加 / 查看 / 删除
r.sadd('tags:python', 'redis', 'django', 'flask')
print(r.smembers('tags:python')) # {'flask', 'django', 'redis'} 无序
print(r.sismember('tags:python', 'django')) # True
print(r.scard('tags:python')) # 3
r.srem('tags:python', 'flask')
# 集合运算
r.sadd('tags:web', 'django', 'fastapi')
print(r.sinter('tags:python', 'tags:web')) # {'django'}
print(r.sunion('tags:python', 'tags:web')) # {'redis', 'django', 'fastapi'}
print(r.sdiff('tags:python', 'tags:web')) # {'redis'}
# 随机弹出
print(r.spop('tags:python')) # 随机一个元素
3.5 Sorted Set 操作
bash
# 添加 / 查看
r.zadd('leaderboard', {'Alice': 100, 'Bob': 85, 'Charlie': 92})
print(r.zrange('leaderboard', 0, -1, withscores=True))
# [('Bob', 85.0), ('Charlie', 92.0), ('Alice', 100.0)]
# 反向获取 Top N
print(r.zrevrange('leaderboard', 0, 1, withscores=True))
# [('Alice', 100.0), ('Charlie', 92.0)]
# 增减分数
r.zincrby('leaderboard', 10, 'Bob')
print(r.zscore('leaderboard', 'Bob')) # 95.0
# 排名
print(r.zrank('leaderboard', 'Bob')) # 0 (从低到高)
print(r.zrevrank('leaderboard', 'Bob')) # 2 (从高到低)
# 按分数范围查询
print(r.zrangebyscore('leaderboard', 90, 100))
# ['Charlie', 'Alice']
🧪 动手试试 :把上面的代码逐段复制到你的
python交互环境(或脚本)运行,对比输出是否与预期一致。试着修改几个参数,比如给 String 加ex=5,然后用ttl观察倒计时。
4. 连接池管理:告别频繁握手
4.1 为什么要用连接池?
Redis 是基于 TCP 的,每次创建新连接都需要三次握手、认证等开销。如果你的应用在高并发场景下每次请求都 redis.Redis() 新建连接,不仅浪费性能,还可能耗尽 Redis 的 maxclients 限制。
连接池(ConnectionPool) 就是提前创建好一定数量的连接,放在池子里,用时借、用完还,连接不断开。
4.2 创建连接池
bash
import redis
# 显式创建连接池
pool = redis.ConnectionPool(
host='localhost',
port=6379,
decode_responses=True,
max_connections=20, # 最大连接数
# 更多参数如 password, db 等
)
# 多个 Redis 实例共享一个连接池
r1 = redis.Redis(connection_pool=pool)
r2 = redis.Redis(connection_pool=pool)
# 验证共享
print(r1.ping()) # True
print(r2.ping()) # True
# 使用完可以断开所有连接(应用退出时)
pool.disconnect()
max_connections 设定了池中最多保持多少个连接。当并发请求超过此数时,获取连接的请求会阻塞,直到有连接归还。
4.3 生产环境配置建议
bash
import redis
import os
def get_redis_client():
"""生产级 Redis 客户端工厂函数"""
pool = redis.ConnectionPool(
host=os.getenv('REDIS_HOST', 'localhost'),
port=int(os.getenv('REDIS_PORT', 6379)),
password=os.getenv('REDIS_PASSWORD', None),
db=int(os.getenv('REDIS_DB', 0)),
decode_responses=True,
max_connections=int(os.getenv('REDIS_MAX_CONNS', 50)),
socket_timeout=5, # 连接超时
socket_connect_timeout=5, # 连接建立超时
health_check_interval=30, # 健康检查间隔(秒)
)
return redis.Redis(connection_pool=pool)
client = get_redis_client()
⚠️ 常见误区:在多线程环境中,每个线程反复创建
redis.Redis()而不共享连接池。正确做法是全局创建一个连接池,所有线程共享同一个redis.Redis实例(线程安全)。在多进程(如 gunicorn)中,每个 worker 进程应创建自己的连接池。
5. Pipeline:批量命令的加速器
5.1 为什么需要 Pipeline?
Redis 的命令执行是 C/S 模式,每一次命令都要经历:客户端发送命令 → 网络传输 → 服务端处理 → 网络返回 → 客户端接收。如果我们有 N 个命令,就有 N 次网络往返(RTT)。
Pipeline(管道) 允许我们把多个命令打包,一次性发给 Redis 服务端,服务端批量执行后一次性返回结果。这样 N 个命令只花费 1 次 RTT,性能提升非常显著。
5.2 Pipeline 基础用法
bash
import redis
import time
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
# 不使用 Pipeline
start = time.perf_counter()
for i in range(1000):
r.set(f'key:{i}', f'value:{i}')
print(f'无 Pipeline 耗时: {time.perf_counter() - start:.3f}s')
# 使用 Pipeline
r.flushdb() # 清空测试库
start = time.perf_counter()
pipe = r.pipeline()
for i in range(1000):
pipe.set(f'key:{i}', f'value:{i}')
pipe.execute() # 一次性发送所有命令
print(f'使用 Pipeline 耗时: {time.perf_counter() - start:.3f}s')
输出示例(本地测试):
bash
无 Pipeline 耗时: 0.532s
使用 Pipeline 耗时: 0.018s
性能差距可达 30 倍以上!在跨机房、高延迟网络环境下差距更明显。
5.3 Pipeline 的高级用法
Pipeline 也可以链式调用,并且支持获取每个命令的返回值:
bash
pipe = r.pipeline()
# 链式写入
pipe.set('name', 'Alice').incr('counter').get('name')
# execute() 返回每个命令的结果列表
results = pipe.execute()
print(results) # [True, 1, 'Alice']
Pipeline + 事务 :
Pipeline 默认不是事务,命令只是打包发送,不会保证原子性。如果要事务,使用 transaction=True(下一章会深入讲解 Lua 和事务)。
bash
pipe = r.pipeline(transaction=True)
pipe.set('balance', 100)
pipe.incrby('balance', 50)
pipe.execute() # 在 MULTI/EXEC 中执行
5.4 最佳实践
-
批量读写 :循环中的
GET/SET应改为Pipeline或MGET/MSET。 -
批量操作但不同键 :
Pipeline比MSET更灵活,可以混合不同类型命令。 -
控制批量大小:一次 Pipeline 发送的命令数不要过大(建议 1000 以内),避免客户端内存爆涨和服务端长时间阻塞(虽然 Redis 单线程会一次性执行完这批命令,但命令过多会阻塞其他请求)。
-
pipeline 配合连接池:Pipeline 从连接池获取一个连接并独占,执行完毕后归还,确保连接被正确复用。
6. 异常处理与重试
网络抖动不可避免,健壮的应用需要容错机制:
bash
from redis.exceptions import ConnectionError, TimeoutError
import time
def safe_incr(r, key, retries=3):
for attempt in range(retries):
try:
return r.incr(key)
except (ConnectionError, TimeoutError) as e:
print(f'第 {attempt+1} 次重试,错误: {e}')
time.sleep(0.1 * (attempt + 1)) # 退避
raise Exception(f'操作失败,已重试 {retries} 次')
更完善的方案可使用 retry 库或 redis-py 内置的 retry_on_timeout 支持(4.x+ 版本):
bash
r = redis.Redis(
host='localhost',
retry_on_timeout=True,
retry_on_error=[ConnectionError, TimeoutError],
health_check_interval=30,
)
7. 动手试试
在本地完成以下实战练习:
-
连接池挑战 :创建一个
max_connections=5的连接池,启动 10 个线程同时执行GET操作,观察是否会阻塞?用time.sleep模拟长操作,验证连接池行为。 -
Pipeline 性能对决 :准备 5000 个键值对,分别用
for循环SET和Pipeline写入,打印耗时对比。然后将数据用两种方式读取出来(GET循环 vsMGET),观察性能。 -
模拟缓存读写 :编写一个函数
get_cached_data(key),先从 Redis 读取,不存在时模拟从 DB 查询(返回字符串),然后写入 Redis 并设 TTL,返回数据。验证第二次调用时是否命中缓存。
预期效果:Pipeline 写入速度是循环写入的数十倍;
MGET读取同样秒杀逐条GET;缓存命中时无需调用 DB 函数。
8. 总结
本文是 Python 操作 Redis 的开篇基石,我们掌握了:
-
安装与连接 :
pip install redis、redis.Redis()、连接 URL。 -
五大数据类型 API :方法与命令行一一对应,
decode_responses自动转字符串。 -
连接池:避免频繁 TCP 握手,全局共享,线程安全。
-
Pipeline:批量打包命令,大幅降低网络 RTT,性能提升数十倍。
-
异常处理与重试:让应用在网络抖动下依然可靠。
你已经可以从 redis-cli 无缝切换到 Python 代码,并能写出工程级别的 Redis 交互逻辑。下一篇,我们将继续深入 Python 操作 Redis 的进阶技巧:事务、Lua 脚本、序列化方案、异步 redis.asyncio 以及工具类封装,真正把 Redis 用出"高级感"。
想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维 !