之前章节首次介绍multi和exec的时候讨论过它们的”事务“性质:被multi和exec包裹的命令在执行时不会被其他客户端打扰。而使用事务的其中一个好处就是底层的客户端会通过使用流水线来提高事务执行的性能。本节将介绍如何在不使用事务的情况下,通过使用流水线来进一步提升命令的执行性能。

创建redis实例,连接数据库

  • StrictRedis
from redis import StrictRedis

# 使用默认方式连接到数据库
conn = StrictRedis(host='localhost', port=6379, db=0) # 使用url方式连接到数据库
conn = StrictRedis.from_url('redis://@localhost:6379/1')
  • ConnectionPool
from redis import StrictRedis,ConnectionPool

# 使用默认方式连接到数据库
pool = ConnectionPool(host='localhost', port=6379, db=0)
conn = StrictRedis(connection_pool=pool) # 使用url方式连接到数据库
pool = ConnectionPool.from_url('redis://@localhost:6379/1')
conn = StrictRedis(connection_pool=pool)

构造url方式连接到数据库,有以下三种模式:

redis://[:password]@host:port/db    # TCP连接
rediss://[:password]@host:port/db # Redis TCP+SSL 连接
unix://[:password]@/path/to/socket.sock?db=db # Redis Unix Socket 连接

之前章节曾经介绍过一些可以接受多个参数的添加命令和更新命令,如mget、mset、hmget、hmset、rpush、lpush、sadd、zadd等。这些命令简化了那些需要重复执行相同命令的操作,并且极大地提升了性能。尽管效果可能没有以上提到的命令那么显著,但使用非事务型流水线同样可以获得相似的性能提升,并且可以让用户同时执行多个不同的命令。

在需要执行大量命令的情况下,即使命令实际上并不需要放在事务里面执行,但是为了通过一次发送所有命令来减少通信次数并降低延迟值,用户也可能会将命令包裹在multi和exec里面执行。遗憾的是,multi和exec并不是免费的:它们也会消耗资源,并且可能会导致其他重要的命令被延迟执行。不过好消息是,我们实际上可以在不使用multi和exec的情况下,获得流水线代理的所有好处。之前章节使用了一下语句来在Python中执行multi和exec命令:

pipe=conn.pipeline()

如果用户在执行pipeline()时传入True作为参数,或者不传入任何参数,那么客户端将使用multi和exec包裹起用户要执行的所有命令。另一方面,如果用户在执行pipeline()时传入False为参数,那么客户端同样会像执行事务那样收集起用户要执行的所有命令,只是不再使用multi和exec包裹这些命令。如果用户需要向Redis发送多个命令,并且对于这些命令来说,一个命令的执行结果并不会影响另一个命令的输入,而且这些命令也不需要以实物的方式来执行的话,那么我们可以通过向pipeline()方法传入False来进一步提升Redis的整体性能。让我们来看一个这方面的例子。

前面章节曾经编写并更新过一个名为update_token()函数,它负责记录用户最近浏览过的商品以及用户最近访问过的页面,并更新用户的登录cookie。下面代码是之前展示过得更新版update_token()函数,这个函数每次执行都会调用2个或者5个Redis命令,使得客户端和Redis之间产生2次或5次通信往返。

import time

def update_token(conn,token,user,item=None):
#获取时间戳
timestamp=time.time()
#创建令牌和已登陆用户之间的映射
conn.hset('login:',token,user)
#记录令牌最后一次出现的时间
conn.zadd('recent:',token,timestamp)
if item:
#把用户浏览过的商品记录起来
conn.zadd('viewed:'+token,item,timestamp)
#移除旧商品,只记录最新浏览的25件商品
conn.zremrangebyrank('viewed:'+token,0,-26)
#更新给定商品的被浏览慈善
conn.zincrby('viewed:',item,-1)

如果Redis和Web服务器通过局域网进行连接,那么他们之前的每次通信往返大概需要耗费一两毫秒,因此需要进行2次或者5次通信往返的update_token()函数大概需要花费2~10毫秒来执行,按照这个速度计算,单个Web服务器线程每秒可以处理100~500个请求,尽管这种速度已经非常可观了,但是我们还可以在这个速度的基础上更新一步:通过修改update_token()函数,让它创建一个非事务型流水线,然后使用这个流水线来发送所有请求,这样我们就的带了下面代码:

import time

def update_token_pipeline(conn,token,user,item=None):
#获取时间戳
#设置流水线
pipe=conn.pipeline(False)
timestamp=time.time()
#创建令牌和已登陆用户之间的映射
conn.hset('login:',token,user)
#记录令牌最后一次出现的时间
conn.zadd('recent:',token,timestamp)
if item:
#把用户浏览过的商品记录起来
conn.zadd('viewed:'+token,item,timestamp)
#移除旧商品,只记录最新浏览的25件商品
conn.zremrangebyrank('viewed:'+token,0,-26)
#更新给定商品的被浏览慈善
conn.zincrby('viewed:',item,-1)
pipe.execute()

通过将标准的Redis连接替换成流水线连接,程序可以将通信往返的次数减少至原来的1/2到1/5,并将update_token_pipeline()函数的预期执行时间降低1~2毫秒。按照这个速度来计算的话,如果一个Web服务器只需要执行update_token_pipeline()来更新商品的浏览信息,那么这个Web服务器每秒可以处理500~1000个请求。从理论上来看,update_token_pipeline() 函数的效果非常棒,但是它的实际运行速度又是怎样的呢?

为了回答这个问题,我们将对update_token()函数和update_token_pipeline()函数进行一些简单的测试。我们将分别通过快速低延迟网络和慢速高延迟网络来访问同一台机器,并测试运行在机器上面的Redis每秒可以处理的请求数量。下面代码展示了性能测试函数,这个函数会在给定的时限内重复执行update_token()函数或者update_token_pipeline()函数,然后计算被测试的函数每秒执行了多少次。

import time

def benchmark_update_token(conn,duration):
#测试会分别执行update_token函数和update_token_pipeline函数
for function in (update_token,update_token_pipeline):
#设置计数器以及测试结束的条件
count=0
start=time.time()
end=start+duration
while time.time()<end:
count+=1
#调用两个函数的其中一个
function(conn,'token','user','item')
#计算函数的执行时长
delta=time.time()-start
#打印测试结果
print(function.__name__+":"+str(count)+","+str(delta)+","+str(count/delta))

下面展示了在不同宽带以及不同延迟值的网络上执行性能测试函数所得到的数据。

本地服务器,Unix域套接字大于1Gb(gigabit)0.015ms37616394
本地服务器,本地连接大于1Gb0.015ms32575991
远程服务器,共享交换机1Gb0.271ms7392841
远程服务器,通过VPN连接1.8Mb(megabit)48ms3.6718.2

根据上表数据显示,高延迟网络使用流水线时的速度要比不使用流水线时的速度快5倍,低延迟网络使用流水线也可以带来接近4倍的速度提升,而本地网络的测试结果实际上已经达到了Python在单核环境下使用Redis协议发送和接受短命令序列的性能极限了。

现在我们已经知道如何在不使用事务的情况下,通过使用流水线来提示Redis的性能了,那么除了流水线之外,还有其他可以提升Redis性能的常规方法吗?

阅读原文

使用python来操作redis用法详解

Python操作Redis数据库

Pyhton--Redis实战:Mac brew安装redis

Python--Redis实战:第一章:初识Redis:第一节:Redis简介

Python--Redis实战:第一章:初识Redis:第二节:Redis数据结构简介

Python--Redis实战:第一章:初识Redis:第三节:你好Redis-文章投票试炼

Python--Redis实战:第二章:使用Redis构建Web应用:第一节:登录和cookie缓存

Python--Redis实战:第二章:使用Redis构建Web应用:第二节:使用Redis实现购物车

Python--Redis实战:第二章:使用Redis构建Web应用:第三节:网页缓存

Python--Redis实战:第二章:使用Redis构建Web应用:第四节:数据行缓存

Python--Redis实战:第二章:使用Redis构建Web应用:第五节:网页分析

Python--Redis实战:第三章:Redis命令:第一节:字符串

Python--Redis实战:第三章:Redis命令:第二节:列表

Python--Redis实战:第三章:Redis命令:第三节:集合

Python--Redis实战:第三章:Redis命令:第四节:散列

Python--Redis实战:第三章:Redis命令:第五节:有序集合

Python--Redis实战:第三章:Redis命令:第六节:发布与订阅

Python--Redis实战:第三章:Redis命令:第七节:其他命令

Python--Redis实战:第四章:数据安全与性能保障:第1节:持久化选项

Python--Redis实战:第四章:数据安全与性能保障:第2节:快照持久化

Python--Redis实战:第四章:数据安全与性能保障:第3节:AOF持久化

Python--Redis实战:第四章:数据安全与性能保障:第4节:复制

Python--Redis实战:第四章:数据安全与性能保障:第5节:处理系统故障

Python--Redis实战:第四章:数据安全与性能保障:第6节:Redis事务

Python--Redis实战:第四章:数据安全与性能保障:第7节:非事务型流水线

Python--Redis实战:第四章:数据安全与性能保障:第8节:关于性能方面的注意事项

05-04 00:59