纯干货·手把手带敲实训课

Python 与 Redis 的第一次握手:
从基础操作到高可用初探

敲出你的第一行缓存代码,让数据飞起来!

今日上机闯关路线

1

环境配置基石

在 Windows 安家落户
安装 Redis 与 Python 驱动

2

代码连接测试

打破次元壁
用 Python 连通 Redis 数据库

3

核心数据实战

边学边练增删改查
String / List / Hash 实操

4

架构进阶探秘 & 终极挑战

主从/哨兵模式的 Python 玩法
电商场景综合大练兵

第一阶段

工欲善其事,必先利其器

在你的 Windows 电脑上把 Redis 跑起来

步骤1:Windows 版 Redis 下载与安装

绿色免安装版

官方 Redis 主要支持 Linux。但在学习阶段,我们可以使用微软提供的 Windows 移植版。

  • 获取方式:老师已将 Redis-x64-3.2.100.zip 压缩包下发到大家的桌面上。
  • 解压即安装:右键解压到 D盘 根目录,将文件夹重命名为 Redis
  • 重要提醒:路径中绝对不能包含中文

启动服务端

进入 D:\Redis 文件夹,在这个文件夹的空白处:

  1. 在顶部路径栏输入 cmd 并回车,打开黑窗口。
  2. 输入启动命令:redis-server.exe redis.windows.conf
  3. 看到一个方形的 Redis 蛋糕图标,说明数据库启动成功!

注意:这个黑窗口绝对不能关!关了数据库就停了!

步骤2:安装 Python 翻译官 (Redis驱动)

数据库跑起来了,接下来要让我们的 Python 具备和 Redis 沟通的能力。

打开 PyCharm 的终端 (Terminal)

在 PyCharm 底部找到 Terminal 选项卡,输入以下命令安装 redis-py 库:

PyCharm Terminal
(venv) D:\PythonProjects\RedisTest> pip install redis
Collecting redis
Downloading redis-5.x.x-py3-none-any.whl
Successfully installed redis-5.x.x

大家动起手来,看到 Successfully installed 就代表安装成功了!

第二阶段

打通代码与数据的桥梁

测试连接与连接池配置实战

理论讲解:如何“拨号”给 Redis?

在 Python 中,我们使用 redis.StrictRedis 类来创建连接对象。你需要告诉它四大关键参数:

host

目标 IP 地址。连我们自己电脑,固定写 '127.0.0.1' 或者 'localhost'

port

端口号。Redis 的默认大门永远是 6379。(必须是数字格式)

db

数据库编号。Redis 默认自带 16 个房间(0-15),我们今天都在 0 号房间玩。

decode_responses

必须设为 True!这样取出来的数据才是正常的中文/英文,而不是乱码一样的字节码(bytes)。

动手实操 1:Ping 一下试试连通性

新建一个 Python 文件叫 demo01_connect.py,跟着老师敲下这几行代码:

demo01_connect.py
import redis

# 1. 创建连接对象
client = redis.StrictRedis(host='127.0.0.1', port=6379, db=0, decode_responses=True)

# 2. 发送 ping 指令测试心跳
result = client.ping()
print(f"连接测试结果: {result}")

预期结果:控制台如果打印出 连接测试结果: True,恭喜你,桥搭好了!

理论讲解:为什么要用连接池?

上一步我们是“直连”。在企业开发中,我们绝对不会用直连!

共享单车的智慧

如果 1000 个用户同时访问网页,每个请求都去执行一次连接操作,就像 1000 个人同时买新自行车出门,太慢且浪费内存。

连接池(ConnectionPool):一次性买好 10 辆自行车放在池子里。

  • 用的时候,从池子里一辆(获取连接)。
  • 用完之后,回池子里(释放连接)。
  • 极大提升了程序的运行速度!

Python 连接池工作流

1. 创建 Pool (准备10个连接)
2. client = redis.StrictRedis(pool) 借用
3. 自动归还

动手实操 2:配置企业级连接池

新建 demo02_pool.py,以后我们所有的操作,都基于这个连接池模板来写。

demo02_pool.py
import redis

# 1. 制造一个共享单车停放点 (连接池),最大允许10个人同时骑
pool = redis.ConnectionPool(
    host='127.0.0.1', port=6379, db=0,
    max_connections=10, decode_responses=True
)

# 2. 从池子里借一辆车出来
client = redis.StrictRedis(connection_pool=pool)

# 3. 随便测试一下
client.set('test_pool', '池子搭建成功!')
print(client.get('test_pool'))

第三阶段

数据结构编程实操 (核心重点)

把理论课学过的增删改查,结合真实业务场景全部敲出来!

理论讲解 1:字符串 (String) 基础操作

String 是 Redis 最基础的数据结构,可以存文本、数字、或者 JSON 格式的字符串数据。

Python 中的常用方法:

方法名 作用 举例 (Python代码)
set(name, value) 增 / 改:存入一个键值对 client.set('age', 18)
get(name) 查:获取指定键的值 client.get('age')
setex(name, time, val) 带过期时间的存入(常用于验证码) client.setex('code', 60, '1234')

动手实操 3-1:验证码生成与自动删除

场景:用户注册时,下发一个 10 秒过期的验证码。新建 demo03_string.py,先复制之前的连接池代码作为开头。

在 client = ... 之后继续写
import time

# 1. 存入验证码,设置 10 秒过期 (为了课堂演示快一点)
client.setex('sms:13800138000', 10, '8888')
print("验证码已存入,有效期 10 秒。")

# 2. 马上查询一次
v1 = client.get('sms:13800138000')
print(f"第一次查询结果: {v1}")

# 3. 模拟程序等待 11 秒
print("等待 11 秒中...")
time.sleep(11)

# 4. 再次查询,看看还会不会有数据?
v2 = client.get('sms:13800138000')
print(f"过期后查询结果: {v2}") # 猜猜打印出什么?(None)

进阶操作:用 String 做高速计数器

高并发下的计数

如果我们要统计一篇文章的浏览量,每次有人看,数字就 +1。如果用传统数据库,几万人同时点开,数据库会卡死。

但在 Redis 中,String 提供了专门的 自增 (INCR)自减 (DECR) 命令,速度极快,绝不会算错!

demo_counter.py
# 初始化一篇文章的浏览量为 0
client.set('article:100:views', 0)

# 模拟 3 个用户连续点击了文章 (增)
client.incr('article:100:views')
client.incr('article:100:views')
client.incr('article:100:views')

# 查询最终的浏览量 (查)
views = client.get('article:100:views')
print(f"文章总浏览量: {views}") # 输出 3

理论讲解 2:键 (Key) 的通用管理大法

不管你存的是 String、List 还是 Hash,它们都有一个“键名 (Key)”。我们要学会如何管理这些键。

四大通用键管理指令:

方法名 作用说明 实战场景
exists(name) 查:判断这个键存不存在(返回 1 存在,0 不存在) 防重提交、判断用户是否已登录
ttl(name) 查:查看这个键还有多少秒过期 给用户显示“支付倒计时还剩 X 秒”
expire(name, time) 改:给一个已经存在的键,强行加上过期时间 用户操作后,延长他的登录状态
delete(*names) 删:终极删除术,直接抹除数据 用户注销、清空购物车缓存

理论讲解 3:列表 (List) 操作指令

List 是一个有顺序的队伍。最适合用来做“排队抢购”、“消息队列”、“最新文章列表”。

Python 中的常用方法:

方法名 作用 举例
lpush(name, *values) 增:从左边(队头)塞入数据 client.lpush('queue', 'A', 'B')
rpop(name) 删/查:从右边(队尾)弹出一个数据 client.rpop('queue')
lrange(name, start, end) 查:获取指定范围的元素集合 client.lrange('queue', 0, -1) (0到-1表示全部)
llen(name) 查:看队伍里还有几个人 client.llen('queue')

动手实操 3-2:模拟排队打饭任务

场景:食堂排队,同学 A、B、C 依次排队,食堂大妈依次给队伍最前面的人打饭。新建 demo04_list.py

在 client = ... 之后继续写
# 先用我们刚学的通用删除指令,清空上次可能残留的数据
client.delete('canteen:queue')

# 1. 增:A, B, C 依次排队 (注意 lpush 是左侧推入,先进去的会被挤到右边)
client.lpush('canteen:queue', '同学A', '同学B', '同学C')

# 2. 查:查看当前队伍全貌 (应该返回 ['同学C', '同学B', '同学A'])
all_students = client.lrange('canteen:queue', 0, -1)
print(f"当前队伍: {all_students}")

# 3. 删/改:食堂大妈开始打饭,从队伍最前方 (右侧) 叫走一个人
lucky_guy = client.rpop('canteen:queue')
print(f"打饭完毕离开的人是: {lucky_guy}")
print(f"队伍还剩下 {client.llen('canteen:queue')} 人")

理论讲解 4:哈希 (Hash) 操作指令

Hash 特别像 Python 里的“字典(dict)”。一个键(key)里面,包着很多个属性(field)和值(value)。最适合存对象(比如一个商品的所有属性)。

Python 中的常用方法:

方法名 作用 举例
hset(name, mapping=dict) 增:一次存入对象内的多个属性。 client.hset('user1', mapping={'age':18})
hget(name, key) 查:只获取对象的某一个特定属性 client.hget('user1', 'age')
hgetall(name) 查:一口气获取对象的所有属性 client.hgetall('user1') (返回字典)

动手实操 3-3:存储与深度修改用户档案

场景:我们要存李四的档案,并且要在不影响名字的前提下,单独修改他的成绩,并删除不必要的属性。

demo05_hash.py
# 1. 增:一次性存入完整结构 (字典形式)
client.hset('student:002', mapping={'name': '李四', 'score': 80, 'hobby': '打游戏'})

# 2. 改:李四进步了,用 hincrby 让他的成绩增加 15 分!(不需要改名字)
client.hincrby('student:002', 'score', 15)

# 3. 删:用 hdel 单独删除 Hash 里面的某一个字段 (把爱好删掉)
client.hdel('student:002', 'hobby')

# 4. 查:获取全部档案验收成果
all_data = client.hgetall('student:002')
print(f"最新档案: {all_data}") # 输出应该没有 hobby,且分数为 95

理论讲解 5:集合 (Set) 去重与交友

什么是 Set?

Set 就像一个神奇的口袋,它有两个特点:
1. 无序(丢进去的东西顺序全乱);
2. 绝对不重复(相同的放进去也会自动变成一个)。

非常适合用来做:共同好友计算、用户画像打标签、或者抽奖盒子!

方法名 作用
sadd(name, *vals) 增:往口袋里扔元素
smembers(name) 查:把口袋底朝天,看所有元素
sismember(name, val) 查:判断某人是否在口袋里
srem(name, *vals) 删:把某个人从口袋里踢出去

动手实操 3-4:制作文章点赞去重系统

场景:用户给文章点赞,一个人只能点赞一次,重复点赞无效。用 Set 解决最完美!

demo06_set.py
# 清理旧数据
client.delete('article:88:likes')

# 1. 增:三位同学点赞,注意:'王五' 疯狂点击了三次!
client.sadd('article:88:likes', '张三', '李四', '王五', '王五', '王五')

# 2. 查:看看实际点赞列表里有几个人?(会自动去重)
likers = client.smembers('article:88:likes')
print(f"点赞用户列表: {likers}") # 只有张三、李四、王五

# 3. 查:快速判断 "赵六" 有没有点过赞?
has_liked = client.sismember('article:88:likes', '赵六')
print(f"赵六点过赞吗? {has_liked}") # 输出 False (0)

# 4. 删:张三觉得文章不好看,取消点赞
client.srem('article:88:likes', '张三')

理论讲解 6:有序集合 (ZSet) 与排行榜

ZSet 是 Redis 最强大、最独特的数据结构!它不仅能像 Set 一样保证元素不重复,还能给每个元素关联一个分数 (Score),并自动按分数排好名次。

Python 中的常用方法:

方法名 作用 举例
zadd(name, mapping=dict) 增/改:添加成员及其分数。如果成员已存在,则更新分数。 client.zadd('rank', {'Alice': 90})
zrevrange(name, start, end) 查:从大到小获取排名范围内的成员。(例如前三名) client.zrevrange('rank', 0, 2)
zscore(name, value) 查:获取某个特定成员的具体分数。 client.zscore('rank', 'Alice')

动手实操 3-5:游戏战斗力排行榜

场景:王者荣耀大本营,我们需要实时更新玩家的战斗力,并立刻提取出全服前 3 名的大神!

demo07_zset.py
# 1. 增:录入 4 名玩家的战斗力分数
client.zadd('game:power_rank', mapping={
    '玩家_亚瑟': 3500,
    '玩家_李白': 5200,
    '玩家_后羿': 4800,
    '玩家_小乔': 4100
})

# 2. 改:亚瑟打赢了一局,战斗力提升到 4500 (直接用 zadd 覆盖即可)
client.zadd('game:power_rank', mapping={'玩家_亚瑟': 4500})

# 3. 查:获取全服排名前 3 的玩家 (从大到小排,取索引 0 到 2)
# 加上 withscores=True 可以顺便把分数也带出来
top3 = client.zrevrange('game:power_rank', 0, 2, withscores=True)
print(f"全服前三名排行榜: {top3}")
# 结果预估:李白(5200), 后羿(4800), 亚瑟(4500)

第四阶段

架构进阶:代码如何应对企业级集群?

理论结合实战:当 Redis 不是单机时,我们的代码该怎么变?

本地模拟:部署一个“从节点(Slave)”

理论课讲过“主从复制”,现在我们在 Windows 上自己开一个主从架构:1个老板(主),1个助理(从)。

跟着老师一步步做:

  1. 打开 D盘,把整个 Redis 文件夹复制并粘贴一份,改名叫 Redis-Slave
  2. 进入 Redis-Slave 文件夹,找到 redis.windows.conf 文件,用记事本打开。
  3. 按下 Ctrl+F 搜索 port 6379,把它改成 port 6380 (助理不能占老板的工位)。
  4. 再搜索 slaveof,在下方空白处加一行核心配置:
    replicaof 127.0.0.1 6379 (意思是:去认6379端口的做大哥)。
  5. 保存关闭文件。在这个文件夹里重新开一个 cmd,输入 redis-server.exe redis.windows.conf 启动从节点。

动手实操 6:用 Python 验证主从读写分离

新建 demo06_master_slave.py。我们测试一下:给老板发数据,能不能从助理那里读出来?如果往助理那里强行塞数据会怎样?

demo06_master_slave.py
import redis

# 1. 连上主节点 (老板端口 6379) 和 从节点 (助理端口 6380)
master = redis.StrictRedis(host='127.0.0.1', port=6379, decode_responses=True)
slave = redis.StrictRedis(host='127.0.0.1', port=6380, decode_responses=True)

# 2. 往主节点写数据
master.set('msg', '老板今天心情很好')

# 3. 从从节点读取数据 (这就是读写分离!)
print(f"从助理处打听到的消息: {slave.get('msg')}")

# 4. 强行往从节点(助理)写数据会怎样?
try:
    slave.set('msg', '我想篡改消息')
except redis.exceptions.ReadOnlyError as e:
    print(f"报错啦!原因: {e}") # 从节点默认只能读,不能写!

架构延展:Python 如何连接哨兵模式 (Sentinel)?

理论课讲过,哨兵负责在主节点死掉时,自动把从节点提拔为主节点。

问题来了:既然主节点IP随时可能因为故障而改变,我们的 Python 代码该连哪个 IP 呢?

连接哨兵的逻辑

代码不再直连 Redis,而是先去连哨兵集群
问哨兵:“现在的 Master 是谁?”,拿到最新的 IP 后,代码再去操作实际的 Redis 节点。

伪代码演示 (了解即可)
from redis.sentinel import Sentinel

# 1. 填入哨兵集群的 IP 和端口地址
sentinel = Sentinel([('192.168.1.10', 26379), ('192.168.1.11', 26379)])

# 2. 问哨兵要 master 的连接 (mymaster 是配置文件起的名字)
master = sentinel.master_for('mymaster', decode_responses=True)

# 3. 正常操作 (如果底层发生了主从切换,程序会自动重连新主节点)
master.set('key', 'value')

架构延展:Python 如何连接 Cluster 集群?

Cluster 模式下有 16384 个哈希槽分布在很多台服务器上。

引入 redis.cluster 模块

你不需要在代码里自己算哈希槽,Python 驱动已经非常聪明地把这一切封装好了。

你只要把集群里部分或全部的节点IP告诉它,它在内部就会自动管理连接和路由重定向(MOVED)。操作起来和单机版几乎一模一样。

伪代码演示 (了解即可)
from redis.cluster import RedisCluster

# 1. 随便指定集群中的一个或几个入口节点
startup_nodes = [
    {"host": "192.168.1.11", "port": 6379},
    {"host": "192.168.1.12", "port": 6379}
]

# 2. 创建集群连接客户端
rc = RedisCluster(startup_nodes=startup_nodes, decode_responses=True)

# 3. 正常存取!(它会自动把 foo 算好槽位,发给正确的机器)
rc.set("foo", "bar")
print(rc.get("foo"))

终极挑战环节

双十一电商后台模拟练兵

把前面学到的知识融会贯通,独立完成以下任务!

任务背景与要求

模拟场景:双十一大促倒计时

你是公司的大数据开发工程师,现在距离双十一还有10分钟,你需要编写一个完整的 Python 脚本 ecommerce_task.py,完成以下三个核心业务的数据缓存操作。请利用连接池来管理连接。

任务 A (考察 String): 首页有一个限时福利码,将键 coupon:code 的值设为 "VIP888",并设置 30秒 后过期。
任务 B (考察 List): 很多用户正在疯狂下单。请模拟一个订单队列 order:queue,依次从队尾(左侧)推入三个订单号:"OD-001", "OD-002", "OD-003"。然后模拟处理系统,从队头(右侧)弹出一个订单并打印出来。
任务 C (考察 Hash): 存储一部手机的商品详情。键为 product:1001,里面包含:品牌(brand)为"华为",库存(stock)为 500。存入后,读取出它的库存并打印。

挑战提示与脚手架代码

如果觉得无从下手,可以参考这个脚手架框架,把 # TODO 的部分补全即可!

ecommerce_task.py (参考框架)
import redis

# 初始化连接池
pool = redis.ConnectionPool(host='127.0.0.1', port=6379, db=0, decode_responses=True)
client = redis.StrictRedis(connection_pool=pool)

# ==== 任务A:福利码 ====
# TODO: 使用带有 ex 后缀的方法设置验证码并打印成功信息

# ==== 任务B:订单队列 ====
# TODO: 使用 l... 方法推入订单数据
# TODO: 使用 r... 方法弹出一个订单,并 print 出来

# ==== 任务C:商品详情 ====
# TODO: 准备一个字典数据
# TODO: 使用 h... 方法存入映射(mapping)
# TODO: 读取 stock 字段并 print 出来

实践课圆满结束!

恭喜大家!今天你们不仅在 Windows 上亲手搭建了 Redis,还用 Python 掌握了高频面试考点:连接池、字符串、列表、哈希,甚至感受了主从架构的读写分离操作。

记得把刚才挑战题的 `.py` 代码文件提交到学习通作为本节作业哦!下课!