五、Python操作redis
一、python对redis基本操作
(1)连接redis
import redis
r = redis.Redis(host='127.0.0.1' , port=6379 )
r.set ('foo' , 'Bar' )
print (r.get('foo' ))
import redis
pool = redis.ConnectionPool(host='127.0.0.1' , port=6379 )
r = redis.Redis(connection_pool=pool)
r.set ('bar' , 'Foo' )
print (r.get('bar' ))
通常情况下, 当我们需要做redis操作时, 会创建一个连接, 并基于这个连接进行redis操作, 操作完成后, 释放连接,一般情况下, 这是没问题的, 但当并发量比较高的时候, 频繁的连接创建和释放对性能会有较高的影响。于是, 连接池就发挥作用了。连接池的原理是, 通过预先创建多个连接, 当进行redis操作时, 直接获取已经创建的连接进行操作, 而且操作完成后, 不会释放, 用于后续的其他redis操作。这样就达到了避免频繁的redis连接创建和释放的目的, 从而提高性能。
(2)数据类型操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import redis
pool = redis.ConnectionPool(host='127.0.0.1' , port=6379 , db=0 , decode_responses=True )
r = redis.Redis(connection_pool=pool)
ret = r.setnx("name" , "yuan" )
print (ret)
r.setex("good_1001" , 10 , "2" )
r.set ("age" , 20 )
r.incrby("age" , 2 )
print (r.get("age" ))
r.hset("info" , "name" , "rain" )
print (r.hget("info" , "name" ))
r.hset("info" , "gender" , "male" , {"age" : 22 })
print (r.hgetall("info" ))
r.rpush("scores" , "100" , "90" , "80" )
r.rpush("scores" , "70" )
r.lpush("scores" , "120" )
print (r.lrange("scores" , 0 , -1 ))
r.linsert("scores" , "AFTER" , "100" , 95 )
print (r.lrange("scores" , 0 , -1 ))
print (r.lpop("scores" ))
print (r.rpop("scores" ))
print (r.lindex("scores" , 1 ))
r.sadd("name_set" , "zhangsan" , "lisi" , "wangwu" )
print (r.smembers("name_set" ))
print (r.srandmember("name_set" , 2 ))
r.srem("name_set" , "lisi" )
print (r.smembers("name_set" ))
r.zadd("jifenbang" , {"yuan" : 78 , "rain" : 20 , "alvin" : 89 , "eric" : 45 })
print (r.zrange("jifenbang" , 0 , -1 ))
print (r.zrange("jifenbang" , 0 , -1 , withscores=True ))
print (r.zrevrange("jifenbang" , 0 , -1 , withscores=True ))
print (r.zrangebyscore("jifenbang" , 0 , 100 ))
print (r.zrangebyscore("jifenbang" , 0 , 100 , start=0 , num=1 ))
print (r.zrem("jifenbang" , "yuan" ))
print (r.zrange("jifenbang" , 0 , -1 ))
r.delete("scores" )
print (r.exists("scores" ))
print (r.keys("*" ))
r.expire("name" ,10 )
二、关于redis的实战案例
(1)案例1:KV缓存
第1个是最基础也是最常?的就是KV功能,我们可以用Redis来缓存用户信息、会话信息、商品信息等等。下面这段代码就是通过缓存读取逻辑。
1
2
3
4
5
6
7
8
9
10
11
12
13
import redis
pool = redis.ConnectionPool(host='127.0.0.1' , port=6379 , db=6 , decode_responses=True )
r = redis.Redis(connection_pool=pool)
def get_user (user_id ):
user = r.get(user_id)
if not user:
user = UserInfo.objects.get(pk=user_id)
r.setex(user_id, 3600 , user)
return user
(2)案例2:分布式锁
什么是分布式锁
❝
分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。
❞
提到Redis的分布式锁,很多小伙伴马上就会想到setnx
+ expire
命令。即先用setnx
来抢锁,如果抢到之后,再用expire
给锁设置一个过期时间,防止锁忘记了释放。
❝
SETNX 是SET IF NOT EXISTS的简写.日常命令格式是SETNX key value,如果 key不存在,则SETNX成功返回1,如果这个key已经存在了,则返回0。
❞
假设某电商网站的某商品做秒杀活动,key可以设置为key_resource_id,value设置任意值,伪代码如下:
方案1
1
2
3
4
5
6
7
8
9
10
11
12
import redis
pool = redis.ConnectionPool(host='127.0.0.1' )
r = redis.Redis(connection_pool=pool)
ret = r.setnx("key_resource_id" , "ok" )
if ret:
r.expire("key_resource_id" , 5 )
print ("抢购成功!" )
r.delete("key_resource_id" )
else :
print ("抢购失败!" )
但是这个方案中,setnx
和expire
两个命令分开了,「不是原子操作」。如果执行完setnx
加锁,正要执行expire
设置过期时间时,进程crash或者要重启维护了,那么这个锁就“长生不老”了,「别的线程永远获取不到锁啦」。
方案2:SETNX + value值是(系统时间+过期时间)
为了解决方案一,「发生异常锁得不到释放的场景」,可以把过期时间放到setnx
的value值里面。如果加锁失败,再拿出value值校验一下即可。加锁代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time
def foo ():
expiresTime = time.time() + 10
ret = r.setnx("key_resource_id" , expiresTime)
if ret:
print ("当前锁不存在,加锁成功" )
return True
oldExpiresTime = r.get("key_resource_id" )
if float (oldExpiresTime) < time.time():
newExpiresTime = r.getset("key_resource_id" , expiresTime)
if oldExpiresTime == newExpiresTime:
return True
return False
foo()
方案3
实际上,我们还可以使用Py的redis模块中的set函数来保证原子性(包含setnx和expire两条指令)代码如下:
1
r.set ("key_resource_id" , "1" , nx=True , ex=10 )
(3)案例3:定时任务
利用 Redis 也能实现订单30分钟自动取消。
用户下单之后,在规定时间内如果不完成付款,订单自动取消,并且释放库存使用技术:Redis键空间通知(过期回调 )用户下单之后将订单id作为key,任意值作为值存入redis中,给这条数据设置过期时间,也就是订单超时的时间启用键空间通知
方式1:开启过期key监听
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from redis import StrictRedis
redis = StrictRedis(host='localhost' , port=6379 )
def event_handler (msg ):
print ("sss" ,msg)
thread.stop()
pubsub = redis.pubsub()
pubsub.psubscribe(**{'__keyevent@0__:expired' : event_handler})
thread = pubsub.run_in_thread(sleep_time=0.01 )
(4)案例4:延迟队列
延时队列可以通过Redis的zset(有序列表)来实现。我们将消息序列化为一个字符串作为zset的值。这个消息的到期时间处理时间作为score,然后用多个线程轮询zset获取到期的任务进行处理,多线程时为了保障可用性,万一挂了一个线程还有其他线程可以继续处理。因为有多个线程,所有需要考虑并发争抢任务,确保任务不能被多次执行。
import time
import uuid
import redis
pool = redis.ConnectionPool(host='127.0.0.1' , port=6379 , decode_responses=True )
r = redis.Redis(connection_pool=pool)
def delay_task (task_name, delay_time ):
task_id = task_name + str (uuid.uuid4())
print ()
retry_ts = time.time() + delay_time
r.zadd("delay-queue" , {task_id: retry_ts})
def loop ():
print ("循环监听中..." )
while True :
task_list = r.zrangebyscore("delay-queue" , 0 , time.time(), start=0 , num=1 )
if not task_list:
print ("cost 1秒钟" )
time.sleep(1 )
continue
task_id = task_list[0 ]
success = r.zrem("delay-queue" , task_id)
if success:
handle_msg(task_id)
def handle_msg (msg ):
"""消息处理逻辑"""
print (f"消息{msg} 已经被处理完成!" )
import threading
t = threading.Thread(target=loop)
t.start()
delay_task("任务1延迟5" , 5 )
delay_task("任务2延迟2" , 2 )
delay_task("任务3延迟3" , 3 )
delay_task("任务4延迟10" , 10 )
redis的zrem方法是对多线程争抢任务的关键,它的返回值决定了当前实例有没有抢到任务,因为loop方法可能会被多个线程、多个进程调用, 同一个任务可能会被多个进程线程抢到,通过zrem来决定唯一的属主。
同时,一定要对handle_msg进行异常捕获, 避免因为个别任务处理问题导致的循环异常退出。
(5)案例5:发布订阅
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import threading
import redis
r = redis.Redis(host='127.0.0.1' )
def recv_msg ():
pub = r.pubsub()
pub.subscribe("fm104.5" )
pub.parse_response()
while 1 :
msg = pub.parse_response()
print (msg)
def send_msg ():
msg = input (">>>" )
r.publish("fm104.5" , msg)
t = threading.Thread(target=send_msg)
t.start()
recv_msg()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!