摘要 :使用 redis-py 访问 Redis Sentinel 时,要随机打乱 Sentinel 地址,否则客户端所有连接都集中到相同 Sentinel,很容易触 > 发 maxclients 限制引起业务异常。

问题表现

  • 业务日志出现 raise MasterNotFoundError ("No master found for % r" % (service_name,)) 异常;
  • 使用 redis-cli 连接 Redis Sentinel 地址提示 reset by peer 错误。

查证过程

在 Sentinel 机器上执行ss -ant |grep 26379,发现有大量连接。通过查看 Redis 源码发现, 默认情况下 Redis Sentinel 限制最多接受 10000 个连接(maxclients配置), 而且无法使用 CONFIG SET 动态修改该配置,只能通过修改配置文件,然后重启进程的方式来修改。

问题原因

我们业务使用的是 Python redis-py 模块来操作 Redis,它支持传递多个 Sentinel 地址来初始化:

import redis.sentinel

sentinels_addrs = [('127.0.0.1', 26379), ('127.0.0.1', 26380), ('127.0.0.1', 26381)]
sentinel = redis.sentinel.Sentinel(sentinels_addrs, socket_timeout=1)
master = sentinel.master_for('test')

是什么原因导致 Sentinel 连接数过大呢?

通过查看 redis-py 源码,原来其内部在 Sentinel 获取 master 或 slave 时, 会按照给定的 Sentinel 列表顺序依次连接尝试获取,也就是说,几乎所有进程都只会连接到第一个可用的 Sentinel:

class Sentinel(object):

    def __init__(self, sentinels, min_other_sentinels=0, sentinel_kwargs=None,
                 **connection_kwargs):
        ...

        self.sentinels = [Redis(hostname, port, **self.sentinel_kwargs)
                          for hostname, port in sentinels]
        ...

    def discover_slaves(self, service_name):
        "Returns a list of alive slaves for service ``service_name``"
        for sentinel in self.sentinels:  # 按传入的Sentinel顺序依次尝试
            try:
                slaves = sentinel.sentinel_slaves(service_name)
            except (ConnectionError, ResponseError, TimeoutError):
                continue
            slaves = self.filter_slaves(slaves)
            if slaves:
                return slaves
        return []

这就会导致所有客户端都集中连接到第一个 Sentinel,进而触发了 Sentinelmaxclients=10000的默认限制,导致连接异常。

解决办法

随机打乱 Sentinel 列表地址

在业务层随机打乱 Sentinel 列表地址,避免所有客户端都连接到相同的 Sentinel:

import random
import redis.sentinel

sentinels_addrs = [('127.0.0.1', 26379), ('127.0.0.1', 26380), ('127.0.0.1', 26381)]
random.shuffle(sentinels_addrs)  # 打乱顺序,避免都连到第一个Sentinel
sentinel = redis.sentinel.Sentinel(sentinels_addrs, socket_timeout=1)
master = sentinel.master_for('test')

Sentinel 和 Redis-Server 开启 keepalive 配置

Redis 服务端也需要开启 keepalive 配置 (Redis>=3.2.1 已默认开启),避免网络异常情况下,一直保持大量无效的连接:

# TCP keepalive.
#
# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence
# of communication. This is useful for two reasons:
#
# 1) Detect dead peers.
# 2) Take the connection alive from the point of view of network
#    equipment in the middle.
#
# On Linux, the specified value (in seconds) is the period used to send ACKs.
# Note that to close the connection the double of the time is needed.
# On other kernels the period depends on the kernel configuration.
#
# A reasonable value for this option is 300 seconds, which is the new
# Redis default starting with Redis 3.2.1.
tcp-keepalive 300

完善 Sentinel Server 监控

之前线上只对 Redis Server 的连接数、CPU、内存、其他指标(可通过redis-cli info获取)进行了监控,需要 Sentinel Server 监控也补全。

扩展资料