一 概述
通常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)

某个夜晚
11 声望1 粉丝

心之所向,素履以往