redis

您所在的位置:网站首页 redis-sentinel新增slaves redis

redis

2024-07-12 01:49| 来源: 网络整理| 查看: 265

redis-py(Sentinel)实战 基本介绍

Sentinel集群是服务于redis主从模式的,最小的集群组成包括:一个master节点,两个slave节点,三个sentinel守护进程。sentinel节点监控master和slave节点,在master挂掉时,执行主从切换。

master节点用于管理数据,可以支持读写,所有写操作都通过master节点。

slave节点只支持读操作。

特点: 集群监控,即时刻监控着redis的master和slave进程是否是在正常工作。消息通知,就是说当它发现有redis实例有故障的话,就会发送消息给管理员故障自动转移,如果redis master 节点宕机了的话,它就会将请求转到slave 节点上,slave升为master。充当配置中心,如果发生了故障转移,它会通知将master的新地址写在配置中心告诉客户端。 日常运维命令 PING:返回PONG。SENTINEL master :用于查看监控的某个Redis Master信息,包括配置和状态等。SENTINEL slaves :列出给定主服务器的所有从服务器,以及这些从服务器的当前状态。SENTINEL sentinels :查看给定主服务器的Sentinel实例列表及其状态。SENTINEL get-master-addr-by-name :返回给定名字的主服务器的IP地址和端口号。 如果这个主服务器正在执行故障转移操作,或者针对这个主服务器的故障转移操作已经完成,那么这个命令返回新的主服务器的IP地址和端口号。SENTINEL reset :重置所有名字和给定模式pattern相匹配的主服务器。pattern 参数是一个Glob风格的模式。重置操作清除主服务器目前的所有状态,包括正在执行中的故障转移,并移除目前已经发现和关联的,主服务器的所有从服务器和Sentinel。SENTINEL failover :当主服务器失效时, 在不询问其他Sentinel意见的情况下, 强制开始一次自动故障迁移(不过发起故障转移的Sentinel会向其他Sentinel发送一个新的配置,其他Sentinel会根据这个配置进行相应的更新)。SENTINEL reset :强制重设所有监控的Master状态,清除已知的Slave和Sentinel实例信息,重新获取并生成配置文件。SENTINEL failover :强制发起一次某个Master的failover,如果该Master不可访问的话。SENTINEL ckquorum :检测Sentinel配置是否合理,failover的条件是否可能满足,主要用来检测你的Sentinel配置是否正常。SENTINEL flushconfig:强制Sentinel重写所有配置信息到配置文件。SENTINEL is-master-down-by-addr :一个Sentinel可以通过向另一个Sentinel发送SENTINEL is-master-down-by-addr命令来询问对方是否认为给定的服务器已下线。 基本使用 from redis.sentinel import Sentinel if __name__ == '__main__': sentinel = Sentinel(['localhost', 26379], socket_timeout=0.1) print(sentinel.discover_master('mymaster')) # 根据哨兵获取集群中的master节点信息 print(sentinel.discover_slaves('mymaster')) # 根据哨兵获取集群中的slave节点信息 master = sentinel.master_for('mymaster', socket_timeout=0.1) # 从连接池获取一个与maser的连接 slave = sentinel.slave_for('mymaster', socket_timeout=0.1) # 从连接池获取一个与slave的连接 master.set('follow', 'test') # 赋值 follow = slave.get('follow') # 取值 print(follow) 源码学习

代码基于redis-py 3.5.2。

连接

初始化Redis实例过程中,通常我们会传入一些连接参数,但是实例化后Redis对象并不会马上建立socket连接,该连接是在使用Redis对象操作时创建的。

class Redis(object): """ Implementation of the Redis protocol. """ def __init__(self, host='localhost', port=6379, db=0, password=None, socket_timeout=None, socket_connect_timeout=None, socket_keepalive=None, socket_keepalive_options=None, connection_pool=None, unix_socket_path=None, encoding='utf-8', encoding_errors='strict', charset=None, errors=None, decode_responses=False, retry_on_timeout=False, ssl=False, ssl_keyfile=None, ssl_certfile=None, ssl_cert_reqs='required', ssl_ca_certs=None, ssl_check_hostname=False, max_connections=None, single_connection_client=False, health_check_interval=0, client_name=None, username=None):

以get操作为例:

def get(self, name): """ Return the value at key ``name``, or None if the key doesn't exist """ return self.execute_command('GET', name)

阅读redis-py源码可知,实际的redis操作都是通过该execute_command函数来执行的。该函数做了什么呢?

def execute_command(self, *args, **options): "Execute a command and return a parsed response" pool = self.connection_pool # Redis默认使用连接池 command_name = args[0] connection = pool.get_connection(command_name, **options) # 从连接池里拿到一个连接 try: 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) # 将连接放回到连接池

函数执行流程:

从连接池中获取连接通过连接发送命令解析响应结果将连接放回连接池

注意:

redis根据timeout参数决定连接的空闲时间(默认配置0,不主动断开连接),解析响应结果后,会将连接释放回连接池(pool.release(connection))。连接或交互中发生异常情况时,会抛出异常,执行disconnect()断开当前连接。重新建立连接时,如果是SentinelConnectionPool中的slave连接池,是随机获取其中的一个节点建立连接的。 长连接

redis-py版本>=2.10.0 版本开始引入socket-keepalive参数。

Redis版本>=3.2

添加完以上keepalive配置后,客户端会以 block 方式等待事件,也会在需要的时候发送keepalive探测包(probe packet)判断连接是否有效:

如果在120s(TCP_KEEPIDLE)内没有收到任何网络数据则会每5s(TCP_KEEPINTVL)发送一个空包如果连续3次(TCP_KEEPCNT)都没有收到 ACK 回复,则认为连接已经失效,抛出异常业务端捕获异常,然后重试订阅

代码样例:

sentinel = Sentinel(sentinels=[('1.1.1.1', 26379)], socket_keepalive=True, socket_keepalive_options={ socket.TCP_KEEPIDLE: 60, # 空闲60秒则发送心跳探针 socket.TCP_KEEPINTVL: 30, # 两次探测的时间间隔 socket.TCP_KEEPCNT: 3 # 最大重试次数 }) Sentinel连接池

查看sentinel.py文件可以看到,新增了5个class:

MasterNotFoundErrorSlaveNotFoundErrorSentinelManagedConnectionSentinelConnectionPool:连接池Sentinel:Redis Sentinel集群客户端实例

在Sentinel主从模式下,连接池是其作为客户端很重要的部分。上述描述中,我们可以看到Redis默认使用了连接池,而对于Sentinel的主从模式下,连接池又有什么不同呢?

SentinelConnectionPool

继承自ConnectionPool,它的默认连接是SentinelManagedConnection:

class SentinelConnectionPool(ConnectionPool): def __init__(self, service_name, sentinel_manager, **kwargs): kwargs['connection_class'] = kwargs.get( 'connection_class', SentinelManagedConnection) # 默认连接方式是SentinelManagedConnection # 如果check_connection标志为True(默认为False),每次建立连接后会发送PING指令。 # 该连接方式会根据is_master标志去自动获取到master或slave的连接 self.is_master = kwargs.pop('is_master', True) # 用来标识是master还是slave的连接池 #... # 省略其他同步类的代码 def get_master_address(self): master_address = self.sentinel_manager.discover_master( self.service_name) # 通过discover_master获取master的地址,执行:SENTINEL MASTERS if self.is_master: if self.master_address is None: self.master_address = master_address elif master_address != self.master_address: # 这次获取的地址和上次不一样,就断开连接池的所有连接,解释:问题1第一小点 # Master address changed, disconnect all clients in this pool self.disconnect() return master_address def rotate_slaves(self): "Round-robin slave balancer" # 轮询salve slaves = self.sentinel_manager.discover_slaves(self.service_name)# 通过discover_slaves获取slaves的地址,执行:SENTINEL SLAVES if slaves: if self.slave_rr_counter is None: self.slave_rr_counter = random.randint(0, len(slaves) - 1) # 获取随机数 for _ in xrange(len(slaves)): self.slave_rr_counter = ( self.slave_rr_counter + 1) % len(slaves) slave = slaves[self.slave_rr_counter] yield slave # 从随机数为起点,轮询返回salve节点信息 # Fallback to the master connection try: yield self.get_master_address() except MasterNotFoundError: pass raise SlaveNotFoundError('No slave found for %r' % (self.service_name))

问题1:当redis发生了主从切换时,客户端如何知道地址已经变更了呢?

1)redis在创建一个新的连接时,会调用get_master_address方法来获取主节点地址。get_master_address方法中,客户端先查询主节点地址,然后与内存中的地址进行比较。如果不一致,则会断开连接,然后使用新的地址重新进行连接。

2)如果主节点没有挂,而Sentinel主动进行了主从切换,对于这种情况redis-py也做了处理。当尝试执行写操作语句时(execute_command),parse_response方法会调用SentinelManagedConnection类的read_response方法,该方法可以捕获一个ReadOnlyError的异常,然后断开连接,后续指令都需要重新连接,又通过第一点的方式重新获取master地址,创建新的连接。

所以,如果Redis服务器发生了主从切换,不需要重新执行discover_master和master_for的流程。

问题2:每次查询前,是不是都要走一遍discover_master和master_for的流程?

查看master_for或者slave_for源码可以看到,这两个方法返回的都是一个Redis客户端实例对象,根据上文介绍,每一个这样的对象内部都维护着自己的线程池。所以当需要使用时,不应该重复执行这两个流程。

def master_for(self, service_name, redis_class=Redis, connection_pool_class=SentinelConnectionPool, **kwargs): """ Returns a redis client instance for the ``service_name`` master. """ 回调

使用Redis客户端操作Redis时,命令的执行返回结果会被一个默认的回调函数处理,称这个回调函数为Response Callback。Redis 客户端中定义了对于每个命令默认的回调函数。

例如SET命令默认的回调函数为bool_ok。其内容如下:

def bool_ok(response): return nativestr(response) == 'OK'

回调函数的作用是将操作Redis的返回值转换为Python相对容易处理的数据类型,如:bool、dict、list等。

通常情况下默认的回调函数可以满足使用需求,但是在某些特殊需求下,可以通过set_response_callback方法修改某个命令的回调。

这里修改了SET命令的默认回调函数,使其返回0或1:

import redis from redis._compat import nativestr def int_ok(response): return int(nativestr(response) == 'OK') r = redis.Redis(host='localhost') r.set_response_callback('SET', int_ok) print(r.set('foo', 'bar')) print(r.get('foo'))

执行后输出的结果为:

1 b'bar'

回调部分参考链接:https://www.jianshu.com/p/e5071138a99d

参考:

https://huangzhw.github.io/2019/03/23/how-redis-py-sentinel-work/

https://www.jianshu.com/p/a79fc17ebdf4



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3