redis-py驱动使用详解
说明: 本文基于3.3.8版本redis-py,代码基于python3.7。文章着重介绍redis-py的使用,试图从底层介绍redis-py的操作,不会过多介绍如何用redis-py如何使用Redis命令
安装
redis-py可以直接使用pip进行安装:
$ pip install redis
使用
基本使用
redis-py
的使用很简单,只需要用redis.Redis
建立连接,即可对Redis进行操作。例如:
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
r.set('foo', 'bar')
r.get('foo')
默认情况下,Python3返回bytes
,Python2返回str
。
多线程使用
redis-py
是线程安全的Redis客户端,可以放心的在多线程代码中使用。
import redis
import threading
class RedaisExexThread(threading.Thread):
def __init__(self, redis_client):
threading.Thread.__init__(self)
self.redis_client = redis_client
def run(self):
res = self.redis_client.get('foo')
print(res)
def main():
redis_client = redis.Redis(host='localhost', port=6379, db=0)
thread1 = RedaisExexThread(redis_client)
thread2 = RedaisExexThread(redis_client)
thread3 = RedaisExexThread(redis_client)
thread4 = RedaisExexThread(redis_client)
thread1.start()
thread2.start()
thread3.start()
thread4.start()
if __name__ == '__main__':
main()
关于SELECT操作
在redis-py
中,并没有为Redis客户端实例实现SELECT
操作,也就是如果要使用Redis其他库时,需要新建另一个Redis实例。这是由于Redis采用连接池维护Redis连接。在多线程时,若允许SELECT
操作可能导致这个连接已经切换到其他Redis DB上;并且切换后连接直接进入连接池,而没有销毁。这可能导致操作的DB不是预期的DB。因此为了保证线程安全,Redis客户端不支持SELECT
操作。
自定义连接池操作
使用Redis客户端时,支持直接传入一个Redis连接池,使用起来更加灵活。
import redis
def main():
p = redis.ConnectionPool(host='localhost', port=6379, db=0)
r = redis.Redis(connection_pool=p)
res = r.get('foo')
print(res)
if __name__ == '__main__':
main()
使用阻塞连接池
前面介绍了Redis客户端可以通过连接池方式初始化,在redis-py
中提供了多种连接池以满足实际需求,这里介绍使用阻塞连接池(BlockingConnectionPool
)操作Redis。
阻塞连接池的特点是:当连接池中没有空闲的连接时,会等待timeout
秒,直到获取到连接或超时报错。
import redis
import threading
class RedaisExexThread(threading.Thread):
def __init__(self, redis_client):
threading.Thread.__init__(self)
self.redis_client = redis_client
def run(self):
res = self.redis_client.get('foo')
print(res)
def main():
pool = redis.BlockingConnectionPool(host='localhost', port=6379, db=0, max_connections=2, timeout=5)
redis_client = redis.Redis(connection_pool=pool)
thread1 = RedaisExexThread(redis_client)
thread2 = RedaisExexThread(redis_client)
thread3 = RedaisExexThread(redis_client)
thread4 = RedaisExexThread(redis_client)
thread5 = RedaisExexThread(redis_client)
thread6 = RedaisExexThread(redis_client)
thread7 = RedaisExexThread(redis_client)
thread8 = RedaisExexThread(redis_client)
thread9 = RedaisExexThread(redis_client)
thread10 = RedaisExexThread(redis_client)
thread1.start()
thread2.start()
thread3.start()
thread4.start()
thread5.start()
thread6.start()
thread7.start()
thread8.start()
thread9.start()
thread10.start()
if __name__ == '__main__':
main()
这里创建了一个阻塞连接池,其容量为2,超时等待时间为5秒。用10个线程并发的方式测试阻塞连接池,可以正常使用。感兴趣的可以用普通连接池测试下,看会出现什么问题。
另外根据文档描述,这个阻塞连接池可以给多个客户端使用,实际测试也是可以的。感兴趣可以自己试下。
Redis客户端(Redis 类)
Redis
类是redis-py
中与Redis服务器交互的类,数据库的操作都Redis类来实现,其是Redis协议的一个实现,也是Redis连接池的管理者。
其提供两种连接方式:
- TCP连接:默认的连接方式
- 本地socket(unix domain socket)连接:通过本机的文件socket连接
redis-py
在与Redis数据库的连接层面采用连接池(Connection Pools)。Redis连接池由Redis客户端(即Redis
类)来管理,默认情况下每个Redis客户端实例都维护了一个独立的Redis连接池。除此之外也可以通过指定Redis客户端实例的connection_pool
参数的方式个性化定义Redis连接池,这样可以实现客户端分片,或者更好的管理连接等功能。
参数介绍
Redis客户端的参数较多,这里简单介绍下参数的默认值及功能。
参数 | 默认值 | 描述 |
---|---|---|
host | 'localhost' | 域名 |
port | 6379 | 端口 |
db | 0 | 连接数据库 |
password | None | 密码 |
socket_timeout | None | float类型。 None - 一直阻塞读取数据,不会超时; 0.0 - 非阻塞模式,会报错,不建议使用; 非0 socket阻塞时间,超时会报错 |
socket_connect_timeout | None | 建立socket连接的超时时间,默认为socket_timeout |
socket_keepalive | None | 是否启用TCP KEEPALIVE机制 |
socket_keepalive_options | None | TCP KEEPALIVE参数 |
connection_pool | None | Redis连接池,默认使用ConnectionPool |
unix_socket_path | None | 以本地socket方式连接时,socket文件路径 |
encoding | 'utf-8' | 编码方式 |
encoding_errors | 'strict' | 出现编解码错误时的处理方案 |
charset | None | 已废弃,作用等同encoding |
errors | None | 已废弃,作用等同encoding_errors |
decode_responses | False | 是否对返回解码 |
retry_on_timeout | False | 是否超时重试 |
ssl | False | 启用SSL |
ssl_keyfile | None | |
ssl_certfile | None | |
ssl_cert_reqs | 'required' | |
ssl_ca_certs | None | |
max_connections | None | 最大连接数 |
single_connection_client | False | 单连接模式,非连接池连接 |
health_check_interval | 0 | 健康检测间隔时间 |
客户端初始化
Redis客户端是操作Redis数据库的基础,Redis客户端提供多种初始化方式,以便灵活使用。下面介绍几种访问方式:
- 基本访问:最基本的访问方式为,通过指定
host, port, password, db
的方式访问。
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
print(r.set('foo', 'bar'))
print(r.get('foo'))
- 指定connect pool访问:通过实例化connect pool,将connect pool实例传入到Redis中实现访问。
import redis
pool = redis.ConnectionPool(host='localhost', port=6379, db=0)
r = redis.Redis(connection_pool=pool) # 注意这里使用的connection_pool
print(r.set('foo', 'bar'))
print(r.get('foo'))
- 通过文件socket方式连接:Reidis类支持通过Unix Domain Socket的方式连接Redis服务器
import redis
r = redis.Redis.from_url(unix_socket_path='/tmp/redis.sock')
print(r.set('foo', 'bar'))
print(r.get('foo'))
- 通过url访问:Redis类还支持通过URL直接访问。
import redis
redis_url = "redis://@localhost:6379/0"
r = redis.Redis.from_url(redis_url)
print(r.set('foo', 'bar'))
print(r.get('foo'))
这里的url格式可以为:
-
redis://[:password]@localhost:6379/0
普通TCP方式连接 -
rediss://[:password]@localhost:6379/0
SSL TCP方式连接 -
unix://[:password]@/path/to/socket.sock?db=0
本地文件socket方式连接
回调(Response Callback)
通过Redis客户端操作Redis数据库时,执行命令的返回结果都会被回调函数处理,称这个回调函数为Response Callback
。Redis
客户端中定义了对于每个命令默认的回调函数。
例如SET
命令默认的回调函数为bool_ok
。其内容如下:
def bool_ok(response):
return nativestr(response) == 'OK'
回调函数的作用是将操作Redis的返回值转换为Python相对容易处理的数据类型,如:bool、dict、list等。
通常情况下默认的回调函数可以满足使用需求,但是在某些特殊需求下,可以通过set_response_callback
方法修改某个命令的回调。
这里修改了SET
命令的默认回调函数,使其返回0
或1
:
import redis
from redis._compat import nativestr
def int_ok(response):
return int(nativestr(response) == 'OK')
r = redis.Redis(host='localhost')
r.set_response_callback('SET', int_ok)
print(r.set('foo', 'bar'))
print(r.get('foo'))
执行后输出的结果为:
1
b'bar'
单连接模式(single connection client)
使用Redis
客户端实现数据库交互时,默认是使用连接池的进行连接管理。
除此之外Redis
客户端也提供了单连接方式进行数据库交互。通过配置single_connection_client
参数实现单连接交互,但底层上也是通过维护单个连接的连接池实现。
连接池(ConnectionPool 类)
在redis-py
中所有与数据库交互的连接都通过ConnectionPool
维护。其实现的功能有:url访问串解析、连接维护(包括:分配连接、申请新连接、关闭连接等)、编解码器维护等。
除了标准的ConnectionPool连接池外,redis-py
也提供了非阻塞连接池BlockingConnectionPool
和哨兵连接池SentinelConnectionPool
。
参数介绍
参数 | 默认值 | 描述 |
---|---|---|
connection_class | Connection | 连接实例类,用于初始化数据库连接 |
max_connections | None | 连接池的容量,默认为 2 ** 32,即4294967296 |
**connection_kwargs | 连接参数,即Redis中大部分的参数 |
连接维护
ConnectionPool
类最重要的功能是其中维护了Redis连接池。其实现了连接获取(get_connection),释放连接(release),创建连接(make_connection),关闭连接(disconnect),重置(reset)等一系列工作。其使用Python中的list
作为连接池,通过list的pop
、push
等操作完成连接池的维护。
URL访问串解析
以URL方式访问数据库时,URL串的解析工作最终是由ConnectionPool类实现,解析函数为from_url
。
编解码器维护
在ConnectionPool
中提供get_encoder
函数,通过此函数可以根据客户端(Redis)或连接池(ConnectionPool)对应的编码参数生成编解码器(Encoder)。
非阻塞的连接池(BlockingConnectionPool 类)
BlockingConnectionPool
是一个线程安全的非阻塞连接池。其基本使用和普通的连接池一致,只不过在其内部重新实现了对连接池的管理。
其连接池中默认的连接数量为50;并且增加了一个timeout
参数,其默认值为20,当其设置为None
代表无限等待下去。
当连接池中的连接全部被使用时,其不会直接报错,而是会最多等待timeout
秒,若还是没有可用连接,则会报错。
在BlockingConnectionPool
类,连接默认采用LifoQueue
即队列维护,这个队列就是连接池。
连接(Connection 类)
Connection
类用于管理与Redis Server之间的TCP连接。连接在此层面进行维护。
除了基础的Connection
外,redis-py
也提供了SSL版本的SSLConnection
以及Unix Domain Socket版本的UnixDomainSocketConnection
。
redis-py
在此层面维护了TCP Socket连接、编解码器(Encoder)、Redis协议分析器(parser)、对命令封包(pack command)以及连接健康校验等功能。
连接管理
Connection
类实现了对TCP连接的管理。包括connect
、on_connect
、_connect
、disconnect
等方法的实现。
-
connect
:连接到Redis服务器。包括socket连接建立及连接初始化。 -
on_connect
:初始化连接,包括初始化Redis协议分析器、账号认证(AUTH)以及库切换(SELECT)。 -
_connect
:创建TCP Socket连接,并配置socket。 -
disconnect
:断开与Redis服务器之间的连接。
健康检查(check health)
健康检查的功能是测试当前的连接是否可用,检测通过PING/PONG
命令实现。
检查间隔(health_check_interval
)的含义是当连接在health_check_interval
秒内没有使用,下次使用时需要进行健康检查。
命令封包(pack command)
为了满足Redis协议,需要对待执行的命令进行封包,具体就是将命令串转换为Redis协议要求的格式。
编解码器(Encoder)
编解码器的功能是对输入的命令进行编码;对命令执行结果进行解码。redis-py
的编解码器维护在连接层面。每个连接有一个独立的编解码器。
协议分析器(parser)
协议分析器用来读取Rrdis服务器的返回、解析Redis服务器的响应。每个连接维护了一个独立的协议分析类的实例。
在redis-py
中有两个parser
可供选择,分别为PythonParser
和HiredisParser
。默认情况下redis-py
会优先使用HiredisParser
;若其没有安装,则会选用PythonParser
替代。
Hiredis
是Redis团队维护的C库。Pieter Noordhuis大神(Redis主要代码贡献者)提供了Python版本的实现。据说使用Hiredis
会将解析速度提升10倍,尤其是在使用LRANGE
或SMEMBERS
检索许多数据时效率极高。默认情况下,安装redis-py
不会安装Hiredis
,可以使用pip方式安装:
$ pip install hiredis