一 概述
通常python 连接redis数据库用的redis-py 这个三方库,最近研究了一下它的源码
·
使用方式:
import redis
# 下面两步只是实例化创建了连接池,并未进行实际连接
conn_pool = redis.ConnectionPool(host='1.2.2.2', port=63, password='xxx', db=0)
conn = redis.StrictRedis(connection_pool=conn_pool)
# 第一次执行命令时才会去连接
conn.ping()
下面我们一步一步去了解
执行ping命令时 在client.py 模块,会执行 StrictRedis 类的方法 self.execute_command
# COMMAND EXECUTION AND PROTOCOL PARSING
def execute_command(self, *args, **options):
"Execute a command and return a parsed response"
pool = self.connection_pool
command_name = args[0]
# 从连接池获取连接
connection = pool.get_connection(command_name, **options)
try:
# 这里开始去连接redis服务
connection.send_command(*args)
# 返回解析的结果
return self.parse_response(connection, command_name, **options)
except (ConnectionError, TimeoutError) as e:
connection.disconnect()
if not connection.retry_on_timeout and isinstance(e, TimeoutError):
raise
connection.send_command(*args)
return self.parse_response(connection, command_name, **options)
finally:
pool.release(connection)
send_command 会调下面的方法
def send_packed_command(self, command):
"Send an already packed command to the Redis server"
if not self._sock:
# 开始连接
self.connect()
try:
if isinstance(command, str):
command = [command]
for item in command:
self._sock.sendall(item)
except socket.timeout:
self.disconnect()
raise TimeoutError("Timeout writing to socket")
except socket.error:
e = sys.exc_info()[1]
self.disconnect()
if len(e.args) == 1:
errno, errmsg = 'UNKNOWN', e.args[0]
else:
errno = e.args[0]
errmsg = e.args[1]
raise ConnectionError("Error %s while writing to socket. %s." %
(errno, errmsg))
except:
self.disconnect()
raise
它连接服务器的函数是这个 _connect 函数
def _connect(self****):
"""创建一个tcp socket 连接"""
# we want to mimic wha****t socket.create_connection does to support
# ipv4/ipv6, but we want to set options prior to calling
# socket.connect()
err = None
for res in socket.getaddrinfo(self.host, self.port, 0,
socket.SOCK_STREAM):
family, socktype, proto, canonname, socket_address = res
sock = None
try:
sock = socket.socket(family, socktype, proto)
# TCP_NODELAY
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
# TCP 长连接
if self.socket_keepalive:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
for k, v in iteritems(self.socket_keepalive_options):
sock.setsockopt(socket.SOL_TCP, k, v)
# 我们连接之前设置socket_connect_timeout超时时间
sock.settimeout(self.socket_connect_timeout)
# 连接
sock.connect(socket_address)
# 连接成功后设置 socket_timeout 时间
sock.settimeout(self.socket_timeout)
return sock
except socket.error as _:
err = _
if sock is not None:
sock.close()
if err is not None:
raise err
raise socket.error("socket.getaddrinfo returned an empty list")
on_connect 函数,实列化连接,有密码则发送密码,选择数据表
def on_connect(self):
"Initialize the connection, authenticate and select a database"
self._parser.on_connect(self)
# if a password is specified, authenticate
if self.password:
# 如果redis服务做了进一步的安全加固,也是在这里进行鉴权
self.send_command('AUTH', self.password)
if nativestr(self.read_response()) != 'OK':
raise AuthenticationError('Invalid Password')
# if a database is specified, switch to it
if self.db:
self.send_command('SELECT', self.db)
if nativestr(self.read_response()) != 'OK':
raise ConnectionError('Invalid Database')
它的连接池使用一个list来做的
def reset(self):
self.pid = os.getpid()
# 创建的连接数
self._created_connections = 0
# 存放可用的连接
self._available_connections = []
# 存放已经再用的连接
self._in_use_connections = set()
# 线程锁
self._check_lock = threading.Lock()
获取连接
def get_connection(self, command_name, *keys, **options):
"Get a connection from the pool"
self._checkpid()
try:
# 从list获取连接
connection = self._available_connections.pop()
except IndexError:
# 不存在测创建连接
connection = self.make_connection()
# 并添加到可用连接集合
self._in_use_connections.add(connection)
return connection
创建新的连接
def make_connection(self):
"Create a new connection"
if self._created_connections >= self.max_connections:
raise ConnectionError("Too many connections")
self._created_connections += 1
return self.connection_class(**self.connection_kwargs)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。