Redis HA部署&使用
Redis广泛应用于各种项目中,尤其是锁和Celery等应用场景。本文主要介绍Redis的HA部署&使用,如果没有水平扩展(大数据)的需求,建议使用哨兵模式。
目录
HA部署模式
Redis主要有以下三种部署模式
- 主从复制(Master-Slave Replication)
当主节点失效时,可以手动或通过某种自动方式将其中一个从节点提升为新的主节点。
- 哨兵模式(Sentinel)
- 基于主从复制,增加了监控、通知和自动故障转移功能。
- 当主节点失效时,哨兵会自动从现有的从节点中选举一个新的主节点
- 主从节点都可以读取,但只有主节点可以写入
- 最少2个Redis实例,3个Sentinel实例(选举)以保证其可用性和决策的正确性
- 客户端先访问哨兵获取最新的master节点,然后再进行操作
- 集群模式(Cluster)
- Redis Cluster 是 Redis 的分布式解决方案,提供了数据分片和高可用性。
- 每个分片都有一个主节点和0个或多个从节点。
- 主节点负责分片中的一部分键值对,从节点为其对应的主节点提供复制和故障恢复。
- 自动重定向,访问一个不在当前节点上的键,Redis会返回一个“MOVED”错误,告诉客户端正确的节点地址和端口(主节点),由客户端处理(透明)
- 主从节点都可以读取,但只有主节点可以写入
- 至少需要6个节点(3主 + 3从)
- 通过“哈希插槽”技术将数据分散在各个节点上。总共有16384个哈希插槽插槽
- 在最小集群配置中,会被均匀地分配给3个主节点。每个主节点负责大约5461个插槽
哨兵 VS 集群
*哨兵Sentinel | *集群Cluster | |
---|---|---|
部署 | ||
目标 | 高可用性 | 高可用性+水平扩展(大数据) |
节点数 | 3+(3+2) | 6+(3+3) |
部署复杂性 | 简单 | 复杂 |
水平扩展性 | 不可以(全量数据) | 可以(数据分片) |
从节点是否可写 | 否 | 否 |
开发 | ||
客户端 | 和哨兵返回的master节点交互 | 和任意节点交互,根据 `MOVED` 或 `ASK` 重定向到目标节点 |
Python | 支持 | 支持 |
Celery Broker | 支持 | 不支持(6.0 Milestone) |
Key是否需要额外处理 | 否 | 是(Hash Tag解决批量操作问题) |
Python+Redis+Websocket | 支持 | 不支持(AttributeError: 'RedisCluster' object has no attribute 'pubsub') |
功能限制 | 无 | 1.BY option of SORT denied in Cluster mode |
Cluster Hash Tag
Redis Cluster 使用一种散列技术将数据划分到不同的节点(shards)上。默认情况下,Redis 使用键的 CRC16 值的一部分(具体地说,是 CRC16 值的最后 4 位)来决定将其存储在哪个 slot 上。因为 Redis Cluster 有 16384 个 slot,所以这种方法可以均匀地将数据分布在不同的节点上。 但是,有时您可能希望确保某些键始终在同一个 slot(因此在同一个节点)上。这对于那些需要执行多键操作(例如 `MSET` 或 `MGET`)的键尤其有用,并且希望保证它们在同一个节点上。 Cluster hash tags 是 Redis Cluster 中的一个特性,允许您通过在键名中加入 `{...}` 来覆盖默认的分片行为。具体地说,如果键名中包含 `{...}`,则只有 `{...}` 内的部分用于计算 slot
`user:{123}:name` -123用于计算slog
`user:{123}:age` -123用于计算slog
`order:{456}:total` -456用于计算slog
`order:{456}:status` -456用于计算slog
如果一个键包含了多个`{...}`部分,只有**第一个**`{...}`部分会被考虑。例如,在`keyname:{hashtag1}:{hashtag2}`中,只有`hashtag1`被用来计算哈希slot。redisz一次操作多个键值,也需要使用Hash Tag
rdz.rename('test:{i}0', 'test:{i}1')
rdz.list_bpop(['test:{numbers}1', 'test:{numbers}2']
Redis in Celery(哨兵模式)
CELERY = {
'broker_url': 'sentinel://10.124.206.61:27001;10.124.206.62:27001;10.124.206.63:27001', # Broker Redis哨兵列表
'broker_transport_options': {
'master_name': 'mymaster',
'visibility_timeout': 3600,
'socket_timeout': 10,
},
'result_backend': 'sentinel://10.124.206.61:27001;10.124.206.62:27001;10.124.206.63:27001', # Result Redis哨兵列表
'result_backend_transport_options': {
'master_name': 'mymaster',
'visibility_timeout': 3600,
'socket_timeout': 10,
},
# ...
}
Redis +Websocket(哨兵模式)
# ws_redis = redis.asyncio.from_url(ws_config.get('REDIS_URL')) # 单点
sentinel = redis.asyncio.Sentinel([("10.124.206.61", 27001), ("10.124.206.62", 27001), ("10.124.206.63", 27001)]) # 哨兵
ws_redis = sentinel.master_for("mymaster")
ws_channel = ws_redis.pubsub()
flaskz_logger.debug('redis websocket channel=' + str(channel) + ' connect')
await ws_channel.subscribe(*channel) # 每个ws连接监听的channel可以不一样
try:
while True:
if websocket.closed:
break
redis_message = await ws_channel.get_message(ignore_subscribe_messages=True, timeout=timeout)
if redis_message:
redis_message = redis_message.get('data')
await websocket.send(redis_message.decode('utf-8'))
except websockets.WebSocketException as e:
flaskz_logger.debug('redis-ws websocket exception= ' + str(e))
except redis.exceptions.ConnectionError as e: # @2023-09-01 添加redis哨兵模式的逻辑处理
flaskz_logger.debug('redis-ws redis connection error= ' + str(e))
await ws_channel.unsubscribe(channel)
await websocket_handler(websocket, path) # reconnect redis
finally:
await ws_channel.unsubscribe(channel)
flaskz_logger.debug('redis websocket channel=' + str(channel) + ' clear resource')
安装&配置
哨兵模式
- 修改redis.conf配置
bind 0.0.0.0 # 绑定地址 port 6379 # 端口 databases 16 # 数据库数量,默认为16 daemonize yes # 是否后台运行 yes/no # 自动创建快照条件/满足一个即创建快照,注释掉所有的 save 配置行,并设置 save ""以禁用快照持久化 save 3600 1 save 300 100 # 300s内有100个键值变化 save 60 10000 # *仅Slave节点配置,启动时手动设置,后由哨兵维护 # slaveof 10.124.206.61 6379
- 修改哨兵sentinel.conf配置
port 26379 # 哨兵端口,默认=26379 sentinel announce-ip "10.124.206.61" # 哨兵地址(所在节点IP地址) sentinel monitor mymaster 10.124.206.61 6379 2 # Redis master地址,最小必要数量quorum= n/2+1=2 sentinel down-after-milliseconds mymaster 5000 # Redis master 5000毫秒不响应,则认为master不可达 sentinel failover-timeout mymaster 60000 # 如果故障转移在60000毫秒内没有完成,则视为失败 dir "/usr/local/redis/sentinel" # Sentinel元数据的存储目录,元数据可能包括哪个主服务器被认为是不可达的、选举的状态等 daemonize yes # 是否后台运行 yes/no # Generated by CONFIG REWRITE(自动生成) latency-tracking-info-percentiles 50 99 99.9 protected-mode no user default on nopass sanitize-payload ~* &* +@all sentinel myid f0d4d469ca27d91fc3822bd72b73830615c812de sentinel config-epoch mymaster 7 sentinel leader-epoch mymaster 7 sentinel current-epoch 7 sentinel known-sentinel mymaster 10.124.206.62 26379 c78f30077b1d733e2bdfa2b2d5149a16bc6b9ca4 # 其他哨兵1 sentinel known-sentinel mymaster 10.124.206.63 26379 46f8504b1d74c60094b0f59bd3b5302933c2e91c # 其他哨兵2 sentinel known-replica mymaster 10.124.206.63 6379 # Redis slave节点1 pidfile "/var/run/redis.pid" sentinel known-replica mymaster 10.124.206.62 6379 # Redis slave节点2
- 启动redis
redis-server /path/to/redis.conf
- 启动哨兵
redis-sentinel /path/to/sentinel.conf
集群模式
- 修改redis.conf配置
bind 0.0.0.0 # 绑定地址 port 6379 # 端口 databases 16 # 数据库数量,默认为16 daemonize yes # 是否后台运行 yes/no # 自动创建快照条件/满足一个即创建快照,注释掉所有的 save 配置行,并设置 save ""以禁用快照持久化 save 3600 1 save 300 100 # 300s内有100个键值变化 save 60 10000 cluster-enabled yes # 开启集群功能 cluster-config-file nodes.conf # 集群配置文件,用于保存集群的状态和配置(Redis自动维护) cluster-node-timeout 5000# 节点被认为不可达之前等待的毫秒数
- 启动redis
redis-server /path/to/redis.conf
- 初始化集群
Supervisor
[program:redis-server]
command=/path/to/redis-server /path/to/redis.conf
autostart=true
autorestart=true
stderr_logfile=/var/log/redis/redis.err.log
stdout_logfile=/var/log/redis/redis.out.log