五、Python操作redis

一、python对redis基本操作

(1)连接redis

# 方式1
import redis r = redis.Redis(host='127.0.0.1', port=6379)
r.set('foo', 'Bar')
print(r.get('foo')) # 方式2
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) # (1)字符串操作:不允许对已经存在的键设置值
ret = r.setnx("name", "yuan")
print(ret) # False
# (2)字符串操作:设置键有效期
r.setex("good_1001", 10, "2")
# (3)字符串操作:自增自减
r.set("age", 20)
r.incrby("age", 2)
print(r.get("age")) # b'22' # (4)hash操作:设置hash
r.hset("info", "name", "rain")
print(r.hget("info", "name")) # b'rain'
r.hset("info", "gender", "male", {"age": 22})
print(r.hgetall("info")) # {b'name': b'rain', b'gender': b'male', b'age': b'22'} # (5)list操作:设置list
r.rpush("scores", "100", "90", "80")
r.rpush("scores", "70")
r.lpush("scores", "120")
print(r.lrange("scores", 0, -1)) # ['120', '100', '90', '80', '70']
r.linsert("scores", "AFTER", "100", 95)
print(r.lrange("scores", 0, -1)) # ['120', '100', '95', '90', '80', '70']
print(r.lpop("scores")) # 120
print(r.rpop("scores")) # 70
print(r.lindex("scores", 1)) # '95' # (6)集合操作
# key对应的集合中添加元素
r.sadd("name_set", "zhangsan", "lisi", "wangwu")
# 获取key对应的集合的所有成员
print(r.smembers("name_set")) # {'lisi', 'zhangsan', 'wangwu'}
# 从key对应的集合中随机获取 numbers 个元素
print(r.srandmember("name_set", 2))
r.srem("name_set", "lisi")
print(r.smembers("name_set")) # {'wangwu', 'zhangsan'} # (7)有序集合操作
# 在key对应的有序集合中添加元素
r.zadd("jifenbang", {"yuan": 78, "rain": 20, "alvin": 89, "eric": 45})
# 按照索引范围获取key对应的有序集合的元素
# zrange( name, start, end, desc=False, withscores=False, score_cast_func=float)
print(r.zrange("jifenbang", 0, -1)) # ['rain', 'eric', 'yuan', 'alvin']
print(r.zrange("jifenbang", 0, -1, withscores=True)) # ['rain', 'eric', 'yuan', 'alvin']
print(r.zrevrange("jifenbang", 0, -1, withscores=True)) # ['rain', 'eric', 'yuan', 'alvin'] print(r.zrangebyscore("jifenbang", 0, 100))
print(r.zrangebyscore("jifenbang", 0, 100, start=0, num=1)) # 删除key对应的有序集合中值是values的成员
print(r.zrem("jifenbang", "yuan")) # 删除成功返回1
print(r.zrange("jifenbang", 0, -1)) # ['rain', 'eric', 'alvin'] # (8)键操作
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的分布式锁,很多小伙伴马上就会想到setnxexpire命令。即先用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("抢购失败!")

但是这个方案中,setnxexpire两个命令分开了,「不是原子操作」。如果执行完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)

# 监听所有事件
# pubsub = redis.pubsub()
# pubsub.psubscribe('__keyspace@0__:*')
#
# print('Starting message loop')
# while True:
# message = pubsub.get_message()
# if message:
# print(message) # 监听过期key
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):
# 保证value唯一
task_id = task_name + str(uuid.uuid4())
print()
# 5s后重试
retry_ts = time.time() + delay_time
r.zadd("delay-queue", {task_id: retry_ts}) def loop():
print("循环监听中...")
while True:
# 最多取1条
task_list = r.zrangebyscore("delay-queue", 0, time.time(), start=0, num=1) if not task_list:
# 延时队列空的,休息1s
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()

五、Python操作redis的更多相关文章

  1. Python操作redis系列之 列表(list) (五)(转)

    # -*- coding: utf-8 -*- import redis r =redis.Redis(host=") 1. Lpush 命令将一个或多个值插入到列表头部. 如果 key 不 ...

  2. Python操作redis系列之 列表(list) (五)

    # -*- coding: utf- -*- import redis r =redis.Redis(host=,password=") 1. Lpush 命令将一个或多个值插入到列表头部. ...

  3. 第二百九十五节,python操作redis缓存-字符串类型

    python操作redis缓存-字符串类型 首先要安装redis-py模块 python连接redis方式,有两种连接方式,一种是直接连接,一张是通过连接池连接 注意:以后我们都用的连接池方式连接,直 ...

  4. Python学习笔记(五)之Python操作Redis、mysql、mongodb数据库

    操作数据库 一.数据库 数据库类型主要有关系型数据库和菲关系型数据库. 数据库:用来存储和管理数的仓库,数据库是通过依据“数据结构”将数据格式化,以记录->表->库的关系存储.因此数据查询 ...

  5. Python操作redis系列之 列表(list) (四)

    # -*- coding: utf- -*- import redis r =redis.Redis(host=,password="ZBHRwlb1608") 1. Lpush ...

  6. 数据库之redis篇(3)—— Python操作redis

    虽然前面两篇已经说了redis的一些配置安装什么的,篇幅有点长,可能看完了也不知道怎么操作,这里再浓缩一下: 什么是redis redis完全开源免费的,遵守BSD协议,是一个高性能的非关系型key- ...

  7. Python操作redis学习系列之(集合)set,redis set详解 (六)

    # -*- coding: utf-8 -*- import redis r = redis.Redis(host=") 1. Sadd 命令将一个或多个成员元素加入到集合中,已经存在于集合 ...

  8. redis学习 (key)键,Python操作redis 键 (二)

    # -*- coding: utf-8 -*- import redis #这个redis 连接不能用,请根据自己的需要修改 r =redis.Redis(host=") 1. delete ...

  9. edis 以及 Python操作Redis

    Redis 以及 Python操作Redis   Redis Redis是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库. Redis有以下特点: -- Redis支持数据的持 ...

随机推荐

  1. Git 10 IDEA撤销推送

    参考源 https://www.bilibili.com/video/BV1FE411P7B3?spm_id_from=333.999.0.0 版本 本文章基于 Git 2.35.1.2 如果推送了多 ...

  2. ceph 007 双向池同步 rgw对象网关配置 s3对象存储

    增量导入导出要基于快照 导出的过程当中害怕镜像被修改所以打快照.快照的数据是不会变化的 镜像级别的双向同步 镜像主到备,备到主.一对一 就算是池模式的双向同步,镜像也具有主备关系 双向同步,池模式 [ ...

  3. mybatis 14: 多对一关联查询

    业务背景 根据订单id查询订单的信息,以及该订单所属的客户的基本信息(不包括该客户自己的订单信息) 两张数据表 客户表 订单表 实体类 客户实体类:Customer private Integer i ...

  4. [HFCTF2020]EasyLogin-1|JWT身份伪造

    1.打开之后只有一个登陆界面和注册界面,右键检查发现app.js代码,结果如下: app.js代码如下: /** * 或许该用 koa-static 来处理静态文件 * 路径该怎么配置?不管了先填个根 ...

  5. linux下用docker安装redis

    docker安装redis方法: 1.用命令来查看可用版本: docker search redis 2.拉取官方的最新版本的镜像:docker pull redis:latest 3.查看镜像:do ...

  6. 刷题记录:Codeforces Round #739 (Div. 3)

    Codeforces Round #739 (Div. 3) 20210907.网址:https://codeforces.com/contest/1560. --(叹). A 不希望出现带" ...

  7. 青源Talk第8期|苗旺:因果推断,观察性研究和2021年诺贝尔经济学奖

    biobank 英国的基金数据因果推断和不同的研究互相论证,而非一个研究得到的接了就行.数据融合,data fusion,同一个因果问题不同数据不同结论,以及历史上的数据,来共同得到更稳健.更高效的推 ...

  8. 【HTML】学习路径2-设置文档类型、网页编码、文件注释

    第一章:设置文档类型 我们通常在html文件最前面写一行: <!DOCTYPE html> 这玩意有啥用? https://developer.mozilla.org/zh-CN/docs ...

  9. WinUI(WASDK)项目实践——优雅的开发上位机应用(新)

    摘要 这就是一个记录自己进行WinUI项目实践的博客,项目开源地址如下,觉得有帮助的可以去看看,因为项目都开源了,所以保姆级的讲解肯定不如直接看代码来的实在了. 电子脑壳项目地址 为什么叫新 因为之前 ...

  10. Docker 入门指南

    Docker 入门指南 目录 基础概念 安装教程 基本操作 常用安装 构建操作 容器编排 壹.基础概念 什么是Docker? Docker是基于Go开发的应用容器引擎,属于 Linux 容器的一种封装 ...