Redis 模块热升级指南:用 redis-py 多数据库故障转移做到零停机
【免费下载链接】redis-pyRedis Python client项目地址: https://gitcode.com/GitHub_Trending/re/redis-py
周五晚八点,你要给线上 Redis 升一个模块,手指悬在重启键上不敢按——一重启,几百条连接当场断掉。其实不用停。redis-py 的多数据库(multidb)故障转移机制,能让这次 Redis 模块热升级做到零停机:让备用节点顶上去、原主节点慢慢换,业务全程无感。
先给你一张地图:主备节点和流量是怎么切的
一句话讲清:平时只有权重最高的那个健康节点在扛流量,另一个节点闲着待命;升级时把流量切到备节点,主节点腾出手来换模块,换完再切回来。整个切换由客户端在应用进程内完成,不依赖你改 DNS、不依赖外部编排。
下面这张图里,左侧的入口对应你的业务进程,右侧多个"DB"对应 redis-py 配置的多台 Redis。平时请求只落到其中一台;一旦这台不可用,客户端在本地就把指向换到另一台,上层代码完全不用改。
机制拆解:故障转移怎么配
它解决什么问题?当正在扛流量的节点挂了(或你主动要把它摘下来升级),谁来顶?故障转移策略就是"挑替补的人"。redis-py 默认用基于权重的策略:把节点按权重从高到低排好,从头找第一个"健康"的(熔断器处于 CLOSED 状态)就选中它。权重高的优先,权重低的当备胎。
class WeightBasedFailoverStrategy(FailoverStrategy): def database(self) -> SyncDatabase: for database, _ in self._databases: if database.circuit.state == CBState.CLOSED: return database raise NoValidDatabaseException("No valid database available")选中之前的"挑"只是第一步。挑完之后还有一层执行器在兜底:如果一次没挑到健康节点,它会按failover_delay(默认 12 秒)反复重试failover_attempts(默认 10 次),期间对外抛"临时不可用"而不是直接报错。这意味着一次性的网络抖动不会立刻把你打崩。源码在 redis/multidb/failover.py。
一个容易忽略的细节:每个节点背后挂着一个熔断器,状态在 CLOSED(健康)→ OPEN(熔断)→ HALF_OPEN(试探恢复)之间流转。节点被判 OPEN 后,会先等一个grace_period(默认 60 秒)再进入 HALF_OPEN 试探,避免刚恢复又被打死。这正是"为什么备节点能安全接管"的底层保证。
机制拆解:健康检查策略怎么选
它解决什么问题?故障转移的前提是"这个节点到底还能不能用"。健康检查就是在给每个节点做体检,并把结果翻译成熔断器的开/合。redis-py 后台每隔health_check_interval(默认 5 秒)对所有节点做一轮探测,默认用PING探活。
真正可调的是判定策略——它决定"几次探测里失败几次才算不健康":
| 策略 | 判定规则 | 适用场景 |
|---|---|---|
HEALTHY_ALL | 全部探测成功才算健康(最严) | 关键业务,宁可误切也不带病运行 |
HEALTHY_MAJORITY | 多数成功即可(3 探 2 过) | 默认平衡点,容忍偶发抖动 |
HEALTHY_ANY | 只要一次成功就算健康(最松) | 可用性优先,能扛就扛 |
默认是HEALTHY_ALL。以"全部成功"策略为例,它连续发health_check_probes(默认 3)次探测,中间失败一次就立刻判为不健康,把熔断器打开:
async def _execute(self, health_check, database): client = await self.get_client(database) for attempt in range(health_check.health_check_probes): if not await health_check.check_health(database, client): return False if attempt < health_check.health_check_probes - 1: await asyncio.sleep(health_check.health_check_delay) return True探测次数、间隔(health_check_delay,默认 0.5 秒)、超时都能调。策略与探测项都定义在 redis/asyncio/multidb/healthcheck.py,配置入口在 redis/multidb/config.py。
实操走查:一次真实的模块热升级
前提先说死:两台节点数据必须保持同步(备节点是主节点的副本,或至少模块数据一致)。否则流量切过去读到的是旧数据,"无缝"就成空话。下面按时间线走一遍,配置一个双节点客户端长这样:
from redis.multidb.client import MultiDBClient from redis.multidb.config import MultiDbConfig, DatabaseConfig from redis.asyncio.multidb.healthcheck import HealthCheckPolicies config = MultiDbConfig( databases_config=[ DatabaseConfig(weight=10, client_kwargs={"host": "db-primary", "port": 6379}), DatabaseConfig(weight=5, client_kwargs={"host": "db-backup", "port": 6379}), ], health_check_policy=HealthCheckPolicies.HEALTHY_ALL, ) client = MultiDBClient(config)第一步:给备节点换模块。主节点(weight=10)继续扛全部流量,备节点(weight=5)此时不处理业务。你只在备节点上升级 Redis 模块、重启它。为什么安全:备节点本来就没流量,崩了也没人看见。要盯:模块是否加载成功、副本同步是否追平(master_link_status、主从偏移量归零)。
第二步:把流量切到备节点。备节点升完、健康检查转绿后,用权重把它"扶正"——调高备节点权重,让它超过主节点,客户端的故障转移策略就会自动把活跃数据库切过去。也可以直接调client.set_active_database(backup_db)手动晋升。为什么安全:切换前客户端会先对该节点做一轮健康检查,不健康会直接拒绝。要盯:切换日志里活跃数据库从 primary 变成 backup,以及旧连接的关闭、pub/sub 自动重订阅是否完成。
第三步:给原主节点补升级。现在主节点变成空闲的备胎了。对它升级模块、重启,跟第一步同样的动作。为什么安全:它已经不在扛流量,随便折腾。要盯:升级后它作为备节点能否正常跟上同步。
第四步:回切 / 回挂。原主节点升完,把权重调回(或调update_database_weight),让流量回到原本的"主人"身上,整个集群完成一代模块升级。此时备节点又回到待命状态,随时应对下一轮。要盯:回切后活跃数据库指向是否恢复、是否有报错率回升。
避坑清单
- 健康检查策略别一把梭
HEALTHY_ALL。它最严格,节点稍有抖动就切。升级窗口内你不想被一次偶发超时打回原形,可按业务容忍度降到HEALTHY_MAJORITY;但关键路径(如带缓存扣款)请保持严格,宁可切过去也不带病。 - 转移参数别设得太激进。
failover_delay默认 12 秒、重试 10 次,是为"宁可多等也别反复横跳"设计的。别为了"快"把它改成秒级——网络抖一下你就来回切,反而放大故障面。同理grace_period(默认 60 秒)是给恢复节点留的冷静期,别关。 - 盯监控,别盯日志肉眼。切换动作客户端会打出"unreachable / reachable again"的告警日志,但真正该看的是可观测指标:活跃数据库指向、错误率、以及 geo failover 事件记录(源码见 redis/multidb/command_executor.py)。给每次切换配个告警,别等用户投诉才发现切错了。
- 上线前先把故障演练跑一遍。别等到周五晚上才第一次见它。参考 tests/test_multidb/ 里的用例,主动把主节点拉黑,验证"摘主→切备→回挂"整条链路都按预期走,再谈零停机。
写在最后
把"换模块"拆成"备节点先换、流量切走、原节点补换、回切"四步,redis-py 用故障转移和健康检查把每一步的切换都自动化,你只需要按时间线操作并盯住监控。下次升级,手指可以不用悬在重启键上了。
下期想聊聊这套机制之外的坑:pub/sub 在切换时怎么自动重订阅、以及事务跨节点时为什么需要小心。
【免费下载链接】redis-pyRedis Python client项目地址: https://gitcode.com/GitHub_Trending/re/redis-py
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考