1.redis.Redis与redis.StrictRedis
redis-py
提供两个类Redis
和StrictRedis
用于实现Redis
的命令,StrictRedis
用于实现大部分官方的命令,并使用官方的语法和命令(比如,SET
命令对应与StrictRedis.set
方法)。Redis
是StrictRedis
的子类,用于向后兼容旧版本的redis-py
。 简单说,官方推荐使用StrictRedis
方法。
1.不推荐Redis类
不推荐Redis
类,原因是和咱们在redis-cli
操作有些不一样,主要不一样是下面这三个方面。
·LREM:参数‘num’和‘value’的顺序交换了一下,cli是lrem queueName 0 ‘string’。
这里的0时所有的意思。 但是`Redis`这个类,把控制和`string`调换了。
·ZADD:实现时score和value的顺序不小心弄反了,后来有人用了,就这样了
·SETEX: time 和 value 的顺序反了
.Pool: 连接池
2.redis数据分层展示
from redis import StrictRedis
redis = StrictRedis(host='localhost', port=6379, db=1,)
redis.zadd('name:a:b:c', 100, 'Will', 100, 'William')
s = redis.zrangebyscore('name:a:b:c', 100, 100)
print(s, s[0])
控制台打印结果
[b'Will', b'William'] b'Will'
[图片上传失败...(image-d3b271-1592213823202)]
3.连接池
(1)Redis
连接池
pool = redis.ConnectionPool(host=‘localhost‘, port=6379, db=0)
r = redis.Redis(connection_pool=pool)
(2)StrictRedis连接池
pool = redis.ConnectionPool(host=‘127.0.0.1‘, port=6379)
r = redis.StrictRedis(connection_pool=pool)
4.redis 事物
1.基础知识
redis
事务是通过MULTI
,EXEC
,DISCARD
和WATCH
四个原语实现的。
(1)MULTI
用于开启一个事务,它总是返回OK
。
MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中.
(2)EXEC
被调用时,所有队列中的命令会被执行。
(3)DISCARD
通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务。
(4)watch
WATCH
命令可以为 Redis
事务提供 check-and-set
(CAS)行为。
被 WATCH
的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 EXEC
执行之前被修改了, 那么整个事务都会被取消, EXEC
返回空多条批量回复(null multi-bulk reply
)来表示事务已经失败。
2.几种事务场景
(1)正常执行
l@l ~ $ redis-cli
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET key1 1
QUEUED
127.0.0.1:6379> HSET key2 field1 1
QUEUED
127.0.0.1:6379> SADD key3 1
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) (integer) 0
3) (integer) 0
EXEC 命令的回复是一个数组,数组中的每个元素都是执行事务中的命令所产生的回复。 其中,回复元素的先后顺序和命令发送的先后顺序一致。
当客户端处于事务状态时,所有传入的命令都会返回一个内容为 QUEUED 的状态回复(status reply),这些被入队的命令将在 EXEC命令被调用时执行.
(2)放弃事务
当执行 DISCARD 命令时,事务会被放弃,事务队列会被清空,并且客户端会从事务状态中退出
127.0.0.1:6379> SET key1 1
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> EXEC
(error) ERR EXEC without MULTI
127.0.0.1:6379>
(3)入队错误回滚
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set key1 1
QUEUED
127.0.0.1:6379> HSET key2 1
(error) ERR wrong number of arguments for 'hset' command
127.0.0.1:6379> SADD key3 1
QUEUED
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
对于入队错误,redis 2.6.5
版本后,会记录这种错误,并且在执行EXEC的时候,报错并回滚事务中所有的命令,并且终止事务
(4)执行错误放过
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> HSET key1 field1 1
QUEUED
127.0.0.1:6379> HSET key2 field1 1
QUEUED
127.0.0.1:6379> EXEC
1) (error) WRONGTYPE Operation against a key holding the wrong kind of value
2) (integer) 0
注意:
当遇到执行错误时,redis放过这种错误,保证事务执行完成。
这里要注意此问题,与mysql中事务不同,在redis事务遇到执行错误的时候,不会进行回滚,而是简单的放过了,并保证其他的命令正常执行。这个区别在实现业务的时候,需要自己保证逻辑符合预期。
(5)redis使用watch
127.0.0.1:6379> WATCH key1
OK
127.0.0.1:6379> set key1 2
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set key1 3
QUEUED
127.0.0.1:6379> set key2 3
QUEUED
127.0.0.1:6379> EXEC
(nil)
3.乐观锁
使用上面的代码, 如果在 WATCH 执行之后, EXEC 执行之前, 有其他客户端修改了 key1 的值, 那么当前客户端的事务就会失败。 程序需要做的, 就是不断重试这个操作, 直到没有发生碰撞为止。
这种形式的锁被称作乐观锁, 它是一种非常强大的锁机制。 并且因为大多数情况下, 不同的客户端会访问不同的键, 碰撞的情况一般都很少, 所以通常并不需要进行重试。
5.pipeline读写redis
用了很久的redis了。随着业务的要求越来越高。对redis的读写速度要求也越来越高。正好最近有个需求(需要在秒级取值1000+的数据),如果对于传统的单词取值,循环取值,消耗实在是大,有小伙伴可能考虑到多线程,但这并不是最好的解决方案,这里考虑到了redis特有的功能pipeline管道功能。下面就更大家演示一下pipeline在python环境下的使用情况。
1.插入数据
>>> import redis
>>> conn = redis.Redis(host='localhost', port=6379)
>>> pipe = conn.pipeline()
>>> pipe.hset("hash_key", "leizhu900516",8)
Pipeline<ConnectionPool<Connection<host=localhost,port=6379,db=0>>>
>>> pipe.hset("hash_key", "chenhuachao",9)
Pipeline<ConnectionPool<Connection<host=localhost,port=6379,db=0>>>
>>> pipe.hset("hash_key", "wanger",10)
Pipeline<ConnectionPool<Connection<host=localhost,port=6379,db=0>>>
>>> pipe.execute()
[0, 0, 0]
打印结果:
l@l:~$ redis-cli
127.0.0.1:6379> keys *
127.0.0.1:6379> hgetall hash_key
1) "leizhu900516"
2) "8"
3) "chenhuachao"
4) "9"
5) "wanger"
6) "10"
127.0.0.1:6379>
2.批量读取数据
>>> pipe.hget("hash_key","leizhu900516")
Pipeline<ConnectionPool<Connection<host=localhost,port=6379,db=0>>>
>>> pipe.hget("hash_key","chenhuachao")
Pipeline<ConnectionPool<Connection<host=localhost,port=6379,db=0>>>
>>> pipe.hget("hash_key","wanger")
Pipeline<ConnectionPool<Connection<host=localhost,port=6379,db=0>>>
>>> result = pipe.execute()
>>> print(result)
[ b'8', b'9', b'10']
注意:
redis
的pipeline就是这么简单,实际生产环境,根据需要去编写相应的代码。思路同理。线上的redis
一般都是集群模式,集群模式下使用pipeline的时候,在创建pipeline的对象时,需要指定
pipe =conn.pipeline(transaction=False)
经过线上实测,利用pipeline
取值3500
条数据,大约需要900ms
,如果配合线程or协程来使用,每秒返回1W
数据是没有问题的,基本能满足大部分业务。
6.一个redis事务的demo
这里展示一个用python实现对key计数减一的原子操作。
如果在watch后值被修改,在执行pipe.execute()的时候会报异常WatchError: Watched variable changed.
import redis
from redis import WatchError
from concurrent.futures import ProcessPoolExecutor
r = redis.Redis(host='127.0.0.1', port=6379)
# 减库存函数, 循环直到减库存完成
# 库存充足, 减库存成功, 返回True
# 库存不足, 减库存失败, 返回False
def decr_stock():
# python中redis事务是通过pipeline的封装实现的
with r.pipeline() as pipe:
while True:
try:
# watch库存键, multi后如果该key被其他客户端改变, 事务操作会抛出WatchError异常
pipe.watch('stock:count')
count = int(pipe.get('stock:count'))
if count > 0: # 有库存
# 事务开始
pipe.multi()
pipe.decr('stock:count')
# 把命令推送过去
# execute返回命令执行结果列表, 这里只有一个decr返回当前值
print(pipe.execute()[0])
return True
else:
return False
except WatchError as e:
# 打印WatchError异常, 观察被watch锁住的情况
print(e)
pipe.unwatch()
def worker():
while True:
# 没有库存就退出
if not decr_stock():
break
# 实验开始
# 设置库存为100
r.set("stock:count", 100)
# 多进程模拟多个客户端提交
with ProcessPoolExecutor(max_workers=2) as pool:
for _ in range(10):
pool.submit(worker)
控制台打印结果
99
Watched variable changed.
98
Watched variable changed.
97
Watched variable changed.
96
95
Watched variable changed.
94
Watched variable changed.
93
Watched variable changed.
92
Watched variable changed.
91
90
Watched variable changed.
89
Watched variable changed.
88
Watched variable changed.
87
Watched variable changed.
86
85
Watched variable changed.
84
83
Watched variable changed.
82
Watched variable changed.
81
Watched variable changed.
80
Watched variable changed.
79
Watched variable changed.
78
Watched variable changed.
77
Watched variable changed.
76
Watched variable changed.
75
Watched variable changed.
74
Watched variable changed.
73
Watched variable changed.
72
Watched variable changed.
71
Watched variable changed.
70
Watched variable changed.
69
Watched variable changed.
68
Watched variable changed.
67
Watched variable changed.
66
Watched variable changed.
65
Watched variable changed.
64
Watched variable changed.
63
Watched variable changed.
62
Watched variable changed.
61
Watched variable changed.
60
Watched variable changed.
59
Watched variable changed.
58
Watched variable changed.
57
Watched variable changed.
56
Watched variable changed.
55
Watched variable changed.
54
Watched variable changed.
53
Watched variable changed.
52
Watched variable changed.
51
Watched variable changed.
50
Watched variable changed.
49
Watched variable changed.
48
Watched variable changed.
47
46
Watched variable changed.
45
Watched variable changed.
44
Watched variable changed.
43
Watched variable changed.
42
Watched variable changed.
41
Watched variable changed.
40
Watched variable changed.
39
Watched variable changed.
38
Watched variable changed.
37
Watched variable changed.
36
Watched variable changed.
35
Watched variable changed.
34
Watched variable changed.
33
Watched variable changed.
32
Watched variable changed.
31
Watched variable changed.
30
Watched variable changed.
29
Watched variable changed.
28
27
Watched variable changed.
26
Watched variable changed.
25
Watched variable changed.
24
Watched variable changed.
23
Watched variable changed.
22
21
Watched variable changed.
Watched variable changed.
20
19
Watched variable changed.
18
Watched variable changed.
17
Watched variable changed.
16
Watched variable changed.
15
14
Watched variable changed.
13
Watched variable changed.
12
Watched variable changed.
11
Watched variable changed.
10
9
Watched variable changed.
8
Watched variable changed.
7
Watched variable changed.
6
Watched variable changed.
5
Watched variable changed.
4
3
Watched variable changed.
2
Watched variable changed.
1
Watched variable changed.
0
网友评论