Python中redis-py连接池导致服务器大量CLOSE_WAIT问题的再讨论与解决方案

今天服务器突然提示一台 redis 无法连接了 登上去一看 发现到这个 redis 服务器的连接基本都是 CLOSE_WAIT 状态 有十几万条 导致没法再建立新的连接

至于 redis 的用法也是 很传统的用法 就是服务启动的时候会初始化一个 redis.StrictRedis 的连接 然后就直接用
至于连接池 其实 StrictRedis 内部的实现就是用连接池的

后来查了 google 发现这个
http://russellluo.com/2017/10/redis-py-pool-cannot-handle-idle-close-wait-connections.html
v2 上也有人发过讨论: https://www.v2ex.com/t/237326 但没有提供解决方案

参考了第一个的方案 改进了一下 redis 连接池的 get_connection 方法 更新上去之后 效果很明显 确保了一个进程一条连接 失效了重新建立新的连接
看起来很完美的样子

我的疑问就是 既然都有这个问题 也有解决方案在那里 为什么这个 issue 都没人关注呢 还是大家都没遇到过这个问题吗
Python中redis-py连接池导致服务器大量CLOSE_WAIT问题的再讨论与解决方案


9 回复

这叫我想起来之前遇到过的数据库连接未释放的问题了,关注生命周期是关键


这个问题我遇到过,redis-py的连接池管理不当确实容易导致CLOSE_WAIT堆积。核心原因是连接没有正确释放,导致TCP连接处于半关闭状态。

先看一个典型的问题代码:

import redis
import time

# 错误示例:每次请求都创建新连接池
def get_data_bad():
    pool = redis.ConnectionPool(host='localhost', port=6379)
    r = redis.Redis(connection_pool=pool)
    return r.get('key')

# 正确做法:复用全局连接池
pool = redis.ConnectionPool(host='localhost', port=6379, max_connections=10)

def get_data_good():
    r = redis.Redis(connection_pool=pool)
    try:
        return r.get('key')
    finally:
        r.connection_pool.release(r.connection_pool.get_connection('localhost', 6379))

关键点:

  1. 连接池复用:不要每次操作都创建新连接池,应该全局共享一个
  2. 连接释放:使用完连接后要显式释放回连接池
  3. 连接池配置:合理设置max_connections,避免连接数过多

更推荐的做法是使用上下文管理器:

from contextlib import contextmanager

@contextmanager
def get_redis_connection():
    conn = pool.get_connection('localhost', 6379)
    try:
        yield conn
    finally:
        pool.release(conn)

# 使用方式
with get_redis_connection() as conn:
    r = redis.Redis(connection=conn)
    result = r.get('key')

如果已经出现CLOSE_WAIT,可以用ss -tannetstat命令查看状态,然后重启应用强制释放连接。

总结:确保连接池单例且正确释放连接。

redis-py 真是想各种吐槽

- 连接池不能负载均衡
- 连接池不能读写分离
- zadd 不支持 nx 等参数
- zadd 那个坑死无数新人的参数顺序。

你们用都不看源码的么 那个链接池简陋得连心跳都没 你们也直接用

#3 源码看了 确实简陋 有别的选择吗 基本都在用这个库的吧

#2 确实 有更好的库可以选择吗

当然自己重新封装 自己写心跳啊

如果发现已经有 Pull Request,+1 求合并就好了,求合并的人多了,自然受重视

如果只是默默吐槽,那就不怪别人了

没用的 对比下 kombu 的代码 就知道了 这玩意就是个基本接口根本没打算做全功能

就算是 kombu openstack 还再封装过一遍

回到顶部