【问题标题】:How do I properly use connection pools in redis?如何在 redis 中正确使用连接池?
【发布时间】:2015-10-18 05:46:17
【问题描述】:

我不清楚连接池是如何工作的,以及如何正确使用它们。我希望有人可以详细说明。我在下面勾勒出我的用例:

settings.py:

import redis

def get_redis_connection():
    return redis.StrictRedis(host='localhost', port=6379, db=0)

task1.py

import settings

connection = settings.get_redis_connection()

def do_something1():
    return connection.hgetall(...)

task2.py

import settings

connection = settings.get_redis_connection()

def do_something1():
    return connection.hgetall(...)

等等


基本上我有一个setting.py 文件,它返回redis 连接,以及几个不同的任务文件,它们获取redis 连接,然后运行操作。所以每个任务文件都有自己的redis实例(估计很贵)。优化此过程的最佳方法是什么。这个例子可以使用连接池吗?有没有更有效的方法来设置这种模式?

对于我们的系统,我们有十几个遵循相同模式的任务文件,我注意到我们的请求速度变慢了。

谢谢

【问题讨论】:

    标签: python python-2.7 redis


    【解决方案1】:

    Redis-py 为您提供了一个连接池,您可以从中检索连接。连接池创建一组连接,您可以根据需要使用它们(完成后 - 连接将返回到连接池以供进一步重用)。尝试在不丢弃它们的情况下即时创建连接(即不使用池或未正确使用池)将使您与 redis 的连接太多(直到您达到连接限制)。

    您可以选择在 init 方法中设置连接池并使连接池全局化(如果对全局不满意,您可以查看其他选项)。

    redis_pool = None
    
    def init():
        global redis_pool
        print("PID %d: initializing redis pool..." % os.getpid())
        redis_pool = redis.ConnectionPool(host='10.0.0.1', port=6379, db=0)
    

    然后您可以像这样从池中检索连接:

    redis_conn = redis.Redis(connection_pool=redis_pool)
    

    另外,我假设您正在使用hiredis 和redis-py,因为它应该在某些情况下提高性能。您是否还检查了使用现有设置打开到 redis 服务器的连接数,因为它很可能非常高?您可以使用 INFO 命令获取该信息:

    redis-cli info
    

    检查 Clients 部分,您将在其中看到“connected_clients”字段,该字段将告诉您在该时刻您打开了多少个与 redis 服务器的连接。

    【讨论】:

    • 完全同意您的评论。就我而言,每个任务都会导入自己的 settings.py 文件,因此“全局”是模棱两可的。我很乐意重构我们的方法,但我只是想知道是否有更简单的解决方案。如果我们有 12 个任务文件在运行,每个任务文件都有自己的池,那么 redis 会很慢。
    • 您可以在 python 中查看工厂模式和单例模式的组合,尽管您可能会发现使用全局路由在 python 中实现起来更简单。使用全局路由将阻止您为每个模块生成新的连接池 - 您将使用来自连接池的连接
    • 也许我理解错了,如果每个任务实例都导入 settings.py (因此内存中同时运行两个 settings.py 文件),不是每个都有自己的全局吗?
    • 您打开了多少个连接(您是否尝试过使用 INFO)?如果连接数超出您应有的数量,您能否确认您是否正在释放连接(在使用连接池时,您可以调用释放,而不是断开连接 - 前面提到过)。您可以尝试使用内置模块,但我认为这种方法最容易实现(即保持代码相同,可能显式使用连接池,以便在读取代码时清晰,从池中获取连接,在不再需要时释放连接所以它会回到游泳池)。
    • 我对您提到的“init 方法”感到困惑。这是我不知道的 Python 的某些功能,还是您假设使用了某些框架?
    【解决方案2】:

    你应该使用一个基于单例(borg 模式)的包装器,它写在 redis-py 上,它将为你的所有文件提供一个公共连接池。 每当您使用此包装类的对象时, 它将使用相同的连接池。

    REDIS_SERVER_CONF = {
        'servers' : {
          'main_server': {
            'HOST' : 'X.X.X.X',
            'PORT' : 6379 ,
            'DATABASE':0
        }
      }
    }
    
    import redis
    class RedisWrapper(object):
        shared_state = {}
    
        def __init__(self):
            self.__dict__ = self.shared_state
    
        def redis_connect(self, server_key):
            redis_server_conf = settings.REDIS_SERVER_CONF['servers'][server_key]
            connection_pool = redis.ConnectionPool(host=redis_server_conf['HOST'], port=redis_server_conf['PORT'],
                                                   db=redis_server_conf['DATABASE'])
            return redis.StrictRedis(connection_pool=connection_pool)
    

    用法:

    r_server = RedisWrapper().redis_connect(server_key='main_server')
    r_server.ping()
    

    更新

    如果您的文件作为不同的进程运行,您将不得不使用 redis 代理来为您汇集连接,而不是直接连接到 redis,您将不得不使用代理。 一个非常稳定的 redis(和 memcached)代理是 twitter 创建的twemproxy,主要目的是减少打开的连接。

    【讨论】:

    • 我同意单例模式是正确的,但我不清楚每个任务文件如何共享相同的 RedisWrapper。例如,如果每个任务文件都导入 redis_wrapper,那么它们就不会共享同一个对象。如果包装器在单独的线程中运行实例,那将是有意义的,但这是另一个蠕虫罐
    • @vgoklani 我假设所有这些模块都是同一个过程的一部分。如果它们作为单独的进程运行,您可以使用连接池代理。已经更新了我的答案。
    • 似乎我们将同化这个例子。我们会将您的创意单身昵称添加到我们自己的昵称中。反抗是徒劳的。
    • 这里有什么shared_state?
    • 如果您希望 Borg 模式不实例化新的连接池,RedisWrapper 的代码应该将连接池分配给实例变量。即self.pool = redis.ConnectionPool(....)
    【解决方案3】:

    这是奶酪店 page 的报价。

    在后台,redis-py 使用连接池来管理与 Redis 服务器的连接。 默认情况下,您创建的每个 Redis 实例都会依次创建自己的连接池。您可以通过将已创建的连接池实例传递给 Redis 类的 connection_pool 参数来覆盖此行为并使用现有的连接池。您可以选择执行此操作以实现客户端分片或对连接的管理方式进行更精细的控制。

    pool = redis.ConnectionPool(host='localhost', port=6379, db=0)
    r = redis.Redis(connection_pool=pool)
    

    另外,实例are thread-safe:

    Redis 客户端实例可以安全地在线程之间共享。在内部,连接实例仅在命令执行期间从连接池中检索,之后直接返回池中。命令执行永远不会修改客户端实例上的状态。

    你说:

    所以每个任务文件都有自己的 redis 实例(估计很贵)。 ...对于我们的系统,我们有十几个遵循相同模式的任务文件,我注意到我们的请求速度变慢了。

    几十个连接不太可能减慢 Redis 服务器的速度。但是因为您的代码在幕后使用连接池,所以问题出在连接本身的某个地方。 Redis 是内存存储,因此在大多数可以想象的情况下都非常快。所以我宁愿在任务中寻找问题。

    更新

    来自@user3813256 的评论。是的,他在任务级别使用连接池。使用redis包的内置连接池的正常方法是共享连接。以最简单的方式,您的settings.py 可能如下所示:

    import redis
    
    connection = None
    
    def connect_to_redis():
        global connection
        connection = redis.StrictRedis(host='localhost', port=6379, db=0)
    

    然后在引导您的应用程序调用connect_to_redis 的某个地方。然后在任务模块中使用 import connection。

    【讨论】:

    • OP 没有使用连接池!他正在从每个模块生成一个新连接(并且他没有显示他是否在任何地方关闭连接)。是的,问题可能出在其他地方,但连接(基于共享的代码)不是来自池,也不是被关闭。
    • @user3813256 严格来说,从我在引文中强调的内容来看,你错了。实例化 redis.StrictRedis 创建一个池,OP 确实在使用它。你是对的,他是按任务执行的,这可能不是最理想的。我已经更新了答案。
    • 棘手的是每个“任务”都导入自己的settings.py文件,所以没有全局状态。
    猜你喜欢
    • 2015-02-09
    • 1970-01-01
    • 1970-01-01
    • 2020-06-20
    • 2010-12-27
    • 2021-01-01
    • 1970-01-01
    • 2014-03-25
    • 1970-01-01
    相关资源
    最近更新 更多