一,Redis管道概念
-
redis-py默认执行每次连接都会创建和断开一次连接操作,如果一次请求中想要执行多个命令需要使用pipline
import redis conn = redis.StrictRedis(host='127.0.0.1',port=6379) #创建管道 pip = conn.pipeline(transaction=False) #缓冲多个命令 pip.keys('*').set('name','Max').sadd('sex','boy').incr('num') pip.execute()#返回一个列表 #也可组装一起写 # pip.keys('*').set('name','Max').sadd('sex','boy').incr('num').execute()
-
并且pipline默认是原子操作
#python可以使用管道来代替事务 try: pipe.multi() pipe.set('age1',22) pipe.set('age2',23) pipe.set('age3',24) time.sleep(5) pipe.execute() print(r.mget('age1','age2','age3')) except redis.exceptions.WatchError as e: print('Error')
-
redis中multi和pipline区别
- redis.multi()方法会将命令逐条发给redis服务端。只有在需要使用事物时才选择redis.multi()I方法,它可以保证发给redis的一系列命令以原子方式执行。但效率相应也是最低的
- redis.pipeline()方法,pipeline 只是把多个redis指令一起发出去,redis并没有保证这些指定的执行是原子的。如果只是为了一下执行多条redis命令,无需事物和原子性,那么应该选用redis.pipeline()方法。代码的性能会有大幅度提升!
- pipeline方式执行效率要比其他方式高10倍左右的速度,启用multi写入要比没有开启慢一点。
-
-
管道使redis的读写速度更加的快速。秒级取值1000+的数据
-
需要注意到是:
- 用pipeline方式打包命令发送,redis必须在处理完所有命令前先缓存起所有命令的处理结果。打包的命令越多,缓存消耗内存也越多。所以并是不是打包的命令越多越好。具体多少合适需要根据具体情况测试。
-
redis管道适用场景
- 批量将数据写入redis,允许一定比例的写入失败
- 比如10万条一下进入redis,可能失败了几条无所谓,后期有补偿机制就行了,比如短信群发这种场景,如果一下群发10万条,按照第一种模式去实现,那这个请求过来,要很久才能给客户端响应,这个延迟就太长了,如果客户端请求设置了超时时间5秒,那肯定就抛出异常了,而且本身群发短信要求实时性也没那么高,这时候用pipeline最好了。
- pipeline 底层是通过把所有的操作封装成流,redis有定义自己的出入输出流。在 sync() 方法执行操作,每次请求放在队列里面,解析响应包。
二,管道与连接池
-
使用pipeline(管道),通过缓冲多条命令,然后一次性执行的方法减少服务器–客户端之间TCP数据库包,从而提高效率
import redis # conn = redis.StrictRedis(host='127.0.0.1',port=6379) pool = redis.ConnectionPool(host='192.168.22.147',port=6379) conn = redis.StrictRedis(connection_pool=pool) #创建管道 pip = conn.pipeline(transaction=False)
- 利用pipeline取值3500条数据,大约需要900ms,如果配合线程or协程来使用,每秒返回1W数据是没有问题的,基本能满足大部分业务。
三,管道的优缺点
-
优点:
- 可以使用各种redis命令,使用更灵活
- 管道可以将多条命令打包一起发送,并且在一次回复中接收所有被执行命令的结果,使用这个功能可以有效地提升程序在执行多条命令时的效率
- 服务器端和客户端各一次
read()
和write()
系统调用,减少客户端与服务器之间的通信次数,节约时间
-
缺点:
- redis必须在处理完所有命令前先缓存起所有命令的处理结果会占用内存
- 打包的命令越多,缓存消耗内存也越多。最好以具有合理数量的批处理方式发送它们,例如10k命令,阅读答复,然后再次发送10k命令,依此类推。
- 因为管道通常是通过单个
read()
系统调用读取许多命令,而通过单个系统调用传递多个答复write()
。因此,每秒执行的总查询数最初会随着管道较长而线性增加,最后会趋近于不使用管道的10倍。
- 因为管道通常是通过单个
- Redis2.6版本以后,脚本在大部分场景中的表现要优于管道。
四,拓展
-
客户端和服务端都在同一物理机上,为什么还是很慢?
- 前面我们提到,为了解决网络开销带来的延迟问题,可以把客户端和服务器放到一台物理机上。但是有时用benchmark进行压测的时候发现这仍然很慢。
- 这时客户端和服务端实际是在一台物理机上的,所有的操作都在内存中进行,没有网络延迟,按理来说这样的操作应该是非常快的。为什么会出现上面的情况的呢?
- 实际上,这是由内核调度导致的。比如说,benchmark运行时,读取了服务器返回的结果,然后写了一个新的命令。这个命令就在回环接口的send buffer中了,如果要执行这个命令,内核需要唤醒Redis服务器进程。所以在某些情况下,本地接口也会出现类似于网络延迟的延迟。其实是内核特别繁忙,一直没有调度到Redis服务器进程。
-
深入理解Redis交互流程
- 图片解释
- 文字解释一个完整的交互流程如下:
- 客户端进程调用
write()
把消息写入到操作系统内核为Socket分配的send buffer中 - 操作系统会把send buffer中的内容写入网卡,网卡再通过网关路由把内容发送到服务器端的网卡
- 服务端网卡会把接收到的消息写入操作系统为Socket分配的recv buffer
- 服务器进程调用
read()
读取消息然后进行处理 - 处理完成后调用
write()
把返回结果写入到服务器端的send buffer - 服务器操作系统再将send buffer中的内容写入网卡,然后发送到客户端
- 客户端操作系统将网卡内容读到recv buffer中
- 客户端进程调用
read()
从recv buffer中读取消息并返回
- 客户端进程调用
- 命令执行的时间进一步细分:
- 命令的执行时间 = 客户端调用write并写网卡时间+一次网络开销的时间+服务读网卡并调用read时间++服务器处理数据时间+服务端调用write并写网卡时间+客户端读网卡并调用read时间
- 这其中除了网络开销,花费时间最长的就是进行系统调用
write()
和read()
了,这一过程需要操作系统由用户态切换到内核态,中间涉及到的上下文切换会浪费很多时间。 - 使用管道时,多个命令只会进行一次
read()
和wrtie()
系统调用,因此使用管道会提升Redis服务器处理命令的速度
五,区别
-
事务、管道和脚本的区别
- 事务和脚本(在Redis 2.6或更高版本中可用)都能满足原子性的要求,其区别在于脚本可借助Lua语言可在服务器端存储以最小的延迟读写数据,但脚本无法处理长耗时的操作。
- redis确保这条脚本**脚本执行期间,其它任何脚本或者命令都无法执行。**正是由于这种原子性,脚本才可以替代MULTI/EXEC作为事务使用。
- 应用程序可能还希望在管道中发送EVAL或EVALSHA命令,Redis通过SCRIPT LOAD命令明确支持它(它保证可以调用EVALSHA而不会失败)
- 官方文档也说了,正是由于脚本执行的原子性,所以我们不要在脚本中执行过长开销的程序,否则会验证影响其它请求的执行。
- 管道是无状态操作集合,使用管道可能在效率上比使用脚本要好,但是有的情况下只能使用脚本。
- 因为在执行后面的命令时,无法得到前面命令的结果,就像事务一样,所以如果需要在后面命令中使用前面命令的value等结果,则只能使用脚本或者事务+watch。
-
使用mset,管道,管道中加事务与事务发送命令的关系:
- mset和管道两者发送机制不同使用mset发送命令是发送一次返回一次结果(如发送四次返回四次即产生八次报文)但是管道是将所有命令一次性全部发送然后全部返回。(发送命令越多占的内存越多)管道节省资源。
- 如果for 循环 次数越多,其实,mset和mget更快
- mset性能最好,吞吐量最高,因为mset是作为单条命令执行,在命令解析和执行上都更有效率
- pipeline好于transaction in pipeline,因为事务会导致命令入列和出列会稍许浪费cpu时间
- transaction in pipeline微弱领先于transaction,但是几乎没有区别,可以理解为pipeline在命令传输上更有效率。
- 总得来说,在批量模式下,四种操作都比普通的get/set性能上有几大的提升。
- 在当前生产环境中使用较多的Redis Cluster环境中,上述四种批量操作的使用场景都比较有限,其中transaction不支持,pipeline建议仅用于单slot且目前支持的客户端很少,mget/mset也仅仅可以操作于单slot中的key。