redis-py 命令体系完全指南:CoreCommands、Sentinel 与 Redis Cluster 命令的架构与实战
【免费下载链接】redis-pyRedis Python client项目地址: https://gitcode.com/GitHub_Trending/re/redis-py
导读
本文以 docs/commands.rst 为骨架,系统讲解 redis-py(当前仓库GitHub_Trending/re/redis-py,项目定位为 Redis Python client)中全部 Redis 命令的暴露方式与使用范式:标准单机客户端如何通过CoreCommands混入获得全套命令、Sentinel 客户端如何获得哨兵专属命令、Redis Cluster 客户端如何通过RedisClusterCommands获得集群命令并按哈希槽自动路由。读完本文,你将掌握同步/异步两套命令 API 的完整调用链、set/get等核心命令的参数语义、Sentinel 与 Cluster 的接入方式,以及从命令定义到协议执行再到响应解析的底层实现路径。
一、命令文档的定位:一份“命令门面”指南
docs/commands.rst是 redis-py 官方文档中的命令索引页,全文只做三件事:
- 用一段话说明:文档中列出的函数用于复刻等价的 Redis 命令,通常可以直接作为 redis 连接对象的方法来调用;
- 给出最简单的
set/get示例; - 通过 Sphinx 的
autoclass指令,把三类核心命令类(CoreCommands、SentinelCommands、RedisClusterCommands)的完整方法签名直接嵌入文档。
因此,这份文档的“正文”其实是由文档构建工具从源码自动生成的——它指向的类定义才是真正的内容主体。也就是说,要读懂这份命令文档,就必须读懂它引用的三个命令类。这正是本文接下来要展开的源码级解读。
命令文档还说明了两个关键事实:
- 命令即方法:Redis 命令在 redis-py 中被封装成客户端对象的同名方法,方法名即命令名(小写化),例如
SET→r.set(...)、GET→r.get(...); - 接口按部署形态划分:单机(含 Sentinel 托管的主从)走
CoreCommands,哨兵管理面走SentinelCommands,集群走RedisClusterCommands。
文档原文还提供了一个可以直接运行的入门片段(docs/commands.rst):
import redis r = redis.Redis(decode_responses=True) r.set('mykey', 'thevalueofmykey') r.get('mykey')其中decode_responses=True会让服务端返回的bytes自动解码为str,日常业务开发中几乎必开。
二、CoreCommands:单机客户端的命令全集
2.1 类定义与混入结构
CoreCommands定义在 redis/commands/core.py#L12523-L12536,是一个“混入(mixin)”类,通过多继承聚合了 8 组功能命令:
class CoreCommands( ACLCommands, # 访问控制列表:acl_cat / acl_setuser / acl_whoami ... ClusterCommands, # CLUSTER 子命令:cluster_info / cluster_slots ... DataAccessCommands, # 数据访问:String / Hash / List / Set / ZSet / Stream / Geo / Bitmap ... ManagementCommands, # 管理命令:info / config_get / client_list / slowlog_get ... ModuleCommands, # 模块加载:module_load / module_loadex / module_list ... PubSubCommands, # 发布订阅:publish / pubsub_channels / pubsub_numsub ... ScriptCommands, # Lua 脚本:eval / evalsha / script_load ... FunctionCommands, # Redis Functions:function_load / fcall / function_stats ... ): """A class containing all of the implemented redis commands. This class is to be used as a mixin for synchronous Redis clients."""其中DataAccessCommands本身又是一个聚合类(redis/commands/core.py#L12487-L12498),它把BasicKeyCommands(get/set/expire/ttl…)、HashCommands、ListCommands、SetCommands、SortedSetCommands、StreamCommands、GeoCommands、ScanCommands、HyperlogCommands、ArrayCommands全部打包在一起。
2.2 命令类如何“长”到客户端上
CoreCommands不是独立使用的对象,而是作为redis.Redis的基类被继承。在 redis/client.py#L157:
class Redis(RedisModuleCommands, CoreCommands, SentinelCommands): """Implementation of the Redis protocol..."""也就是说,你在文档中看到的“这些函数可以作为 redis 连接上的函数使用”,其实现机制就是 Python 的多继承:r = redis.Redis(...)之后,r身上同时拥有CoreCommands的全部方法(数据读写、管理、脚本、模块……)、RedisModuleCommands提供的 RediSearch/RedisJSON/TimeSeries 等模块门面(r.ft()、r.json()、r.ts()),以及SentinelCommands的哨兵命令。
从源码结构看,CoreCommands的每个子命令类(如ACLCommands)都实现了成对出现的三份方法签名(redis/commands/core.py#L116-L135 展示了ACLCommands的写法):
@overload def xxx(self: SyncClientProtocol, ...)—— 同步客户端的类型签名;@overload def xxx(self: AsyncClientProtocol, ...)—— 异步客户端的类型签名(返回Awaitable[...]);- 一个不带
@overload的真正实现,返回类型写成同步返回值 | Awaitable[同步返回值]的联合类型。
这种“一方法三签名”的模式贯穿整个 redis/commands/core.py(该文件共 12552 行),使得同步Redis与异步redis.asyncio.Redis(继承AsyncCoreCommands,见 redis/commands/core.py#L12539-L12552)可以共享同一套命令定义,同时保留 IDE 的类型提示。
2.3 深入set:参数语义与命令构造
set是命令文档入门示例的主角,它的完整签名定义在 redis/commands/core.py#L4329-L4346,下面是在同步/异步双类型下都生效的实现签名:
def set( self, name: KeyT, value: EncodableT, ex: ExpiryT | None = None, # 秒级过期时间 px: ExpiryT | None = None, # 毫秒级过期时间 nx: bool = False, # 仅当键不存在时写入(SET NX) xx: bool = False, # 仅当键已存在时写入(SET XX) keepttl: bool = False, # 保留原有 TTL get: bool = False, # 返回旧值(SET GET) exat: AbsExpiryT | None = None,# 绝对过期时间(秒级时间戳) pxat: AbsExpiryT | None = None,# 绝对过期时间(毫秒级时间戳) ifeq: bytes | str | None = None, # 实验性:仅当当前值等于该值时写入 ifne: bytes | str | None = None, # 实验性:仅当当前值不等于该值时写入 ifdeq: str | None = None, # 实验性:当前值 SHA1/hex 摘要相等时写入 ifdne: str | None = None, # 实验性:当前值 SHA1/hex 摘要不等时写入 ) -> bool | str | bytes | None:语义要点(源码 docstring,redis/commands/core.py#L4347-L4366):
ex/px/exat/pxat四者互斥,同时传入会抛错;nx与xx互斥;- 返回值:普通成功返回
True,get=True时返回旧值(键不存在返回None); ifeq/ifne/ifdeq/ifdne自 Redis 7.1 起标记为实验性,源码中通过@experimental_args([...])装饰器标注(redis/commands/core.py#L4329),API 可能在后续版本调整,生产环境需谨慎。
在实现层,set内部会把上述关键字参数逐项拼接成SET name value [EX s] [PX ms] [NX|XX] [KEEPTTL] [GET] [EXAT ts] [PXAT ts] ...形式的参数元组,最终调用self.execute_command("SET", name, value, *pieces)。
2.4 命令执行链路:从方法到协议
无论哪种命令,最终都会汇入Redis.execute_command(redis/client.py#L890-L896):
def execute_command(self, *args, **options): return self._execute_command(*args, **options) def _execute_command(self, *args, **options): """Execute a command and return a parsed response""" pool = self.connection_pool command_name = args[0] ...调用链可归纳为:
- 命令方法(如
r.get('mykey'))构造参数并调用execute_command; execute_command从connection_pool取连接,命令经编码器(Encoder)序列化后由连接对象写出;- 响应由解析器(RESP2/RESP3,见 redis/_parsers 目录)按命令注册的回调(response callbacks)转换成 Python 对象(
dict/list/bool/int…)返回。
Pipeline(管道)场景下,Pipeline类同样继承自Redis(redis/client.py#L1820),并重写execute_command(redis/client.py#L1919-L1922):非事务模式下命令被排队到pipeline_execute_command,最后一次性发送,减少网络往返;transaction=True(默认)时所有命令以 MULTI/EXEC 原子执行。客户端入口为r.pipeline(transaction=True)(redis/client.py#L628-L634)。
三、SentinelCommands:哨兵管理命令
3.1 类定义
SentinelCommands定义在 redis/commands/sentinel.py#L12-L16:
class SentinelCommands: """A class containing the commands specific to redis sentinel. This class is to be used as a mixin."""注意它与CoreCommands的差异:Sentinel 命令只能在哨兵节点上执行,因此它没有继承CoreCommands,而是独立的一层。异步版本AsyncSentinelCommands继承自SentinelCommands(redis/commands/sentinel.py#L255-L258)。
3.2 方法清单(对应 SENTINEL 子命令)
该类把SENTINEL <subcommand>拆解为一个个 Python 方法(redis/commands/sentinel.py):
| 方法 | 对应命令 | 说明 |
|---|---|---|
sentinel_get_master_addr_by_name(service_name, return_responses=False) | SENTINEL GET-MASTER-ADDR-BY-NAME | 返回 master 的 (host, port),可用于自定义主从发现 |
sentinel_master(service_name) | SENTINEL MASTER | 返回单个 master 的详细信息字典 |
sentinel_masters() | SENTINEL MASTERS | 返回所有被监控 master 的信息 |
sentinel_monitor(name, ip, port, quorum) | SENTINEL MONITOR | 动态添加一个被监控的 master |
sentinel_remove(name) | SENTINEL REMOVE | 移除监控 |
sentinel_sentinels(service_name) | SENTINEL SENTINELS | 返回监控同一 master 的其他哨兵列表 |
sentinel_slaves(service_name) | SENTINEL SLAVES | 返回该 master 的从节点列表 |
sentinel_set(name, option, value) | SENTINEL SET | 修改 master 配置 |
sentinel_reset(pattern) | SENTINEL RESET | 重置匹配的 master 状态 |
sentinel_failover(new_master_name) | SENTINEL FAILOVER | 强制发起一次故障切换 |
sentinel_ckquorum(new_master_name) | SENTINEL CKQUORUM | 检查法定人数是否满足 |
sentinel_flushconfig() | SENTINEL FLUSHCONFIG | 将配置刷写到哨兵配置文件 |
此外还有一个通用方法sentinel(*args),但源码中已用DeprecationWarning标记废弃(redis/commands/sentinel.py#L18-L20),官方建议改用上述sentinel_*方法。所有方法都支持return_responses=True以拿到哨兵的原始响应(而不是统一的布尔值)。
3.3 Sentinel 高层客户端
命令文档里提到的“连接上的函数”,在哨兵场景下有两层含义:
- 低层:普通
Redis客户端本身就继承了SentinelCommands(redis/client.py#L157),所以redis.Redis(host='sentinel1', port=26379)实例可以直接调用sentinel_masters()等命令; - 高层:redis/sentinel.py#L224-L251 的
Sentinel类封装了节点发现、主从切换与连接池管理,典型用法如下(出自该类 docstring):
from redis.sentinel import Sentinel sentinel = Sentinel([('localhost', 26379)], socket_timeout=0.1) master = sentinel.master_for('mymaster', socket_timeout=0.1) master.set('foo', 'bar') slave = sentinel.slave_for('mymaster', socket_timeout=0.1) slave.get('foo')关键参数:
sentinels:哨兵节点(host, port)列表,至少一个;min_other_sentinels:哨兵被认定为有效所需的最少对等节点数,默认 0;sentinel_kwargs:连接哨兵时的参数;缺省时自动复用connection_kwargs中以socket_开头的选项(redis/sentinel.py#L261-L267);connection_kwargs:连接真实 Redis 主从时的参数(如password、socket_timeout)。
Sentinel.execute_command默认向所有哨兵节点广播命令并返回all(responses),传入once=True则随机挑一个节点执行(redis/sentinel.py#L277-L303)。master_for/slave_for返回的客户端通过SentinelConnectionPool自动感知故障切换,切换后无需重启应用。
四、RedisClusterCommands:集群命令与哈希槽路由
4.1 类定义
RedisClusterCommands定义在 redis/commands/cluster.py#L1487-L1497:
class RedisClusterCommands( ClusterMultiKeyCommands, # 多 key 命令(MGET/MSET/EXISTS/DELETE...)按槽拆分 ClusterManagementCommands,# CLUSTER 管理命令(cluster_addslots / cluster_failover ...) ACLCommands, PubSubCommands, ClusterDataAccessCommands, ScriptCommands, FunctionCommands, ModuleCommands, RedisModuleCommands, ): """A class for all Redis Cluster commands For key-based commands, the target node(s) will be internally determined by the keys' hash slot."""docstring 中的关键句:“对于基于 key 的命令,目标节点由 key 的哈希槽在内部自动确定。”这正是集群客户端与单机客户端最大的区别。异步版本AsyncRedisClusterCommands位于 redis/commands/cluster.py#L1518-L1528。
4.2 集群专属命令
ClusterManagementCommands提供了仅在集群中有效的命令,例如:
cluster_addslots(target_node, *slots)/cluster_delslots(*slots):分配/释放槽;cluster_setslot(target_node, node_id, slot_id, state):迁移槽位状态;cluster_failover(target_node, option=None):手动故障转移;cluster_info(target_nodes=None):集群信息;cluster_nodes():节点拓扑(返回dict[str, ClusterNodeDetail]);cluster_keyslot(key):计算某个 key 所属的槽;cluster_get_keys_in_slot(slot, num_keys):取槽内 key;cluster_shards()/cluster_myid()/cluster_myshardid()等较新命令。
这些命令在 redis/commands/cluster.py 中同样采用“同步 overload + 异步 overload + 联合实现”三签名模式,多数还支持target_node/target_nodes参数以指定在哪个节点上执行。
4.3 多 key 命令的槽感知实现
ClusterMultiKeyCommands是集群客户端最有特色的部分:当一条命令涉及多个 key 时,它会按 key 的哈希槽进行分组。
以mget/mset为例,源码提供了两种策略(redis/commands/cluster.py):
mget(keys)/mset(mapping):要求所有 key 落在同一槽,否则抛RedisClusterException——这是为了保持原子性;mget_nonatomic(keys)/mset_nonatomic(mapping):允许 key 分散在不同槽,内部通过_partition_keys_by_slot按槽分组、_execute_pipeline_by_slot逐槽执行管道,再用_reorder_keys_by_command按原始 key 顺序重组结果(见 redis/commands/cluster.py 中_partition_keys_by_slot、_partition_pairs_by_slot、_execute_pipeline_by_slot、_reorder_keys_by_command等私有方法)。
同样,exists/delete/touch/unlink的多 key 变体会调用_split_command_across_slots把命令拆到多个槽分别执行后汇总计数。
4.4 集群客户端接入
RedisCluster类定义在 redis/cluster.py#L653-L657:
class RedisCluster( AbstractRedisCluster, MaintNotificationsAbstractRedisCluster, RedisClusterCommands ): _is_async_client: Literal[False] = False典型用法(官方文档示例风格):
from redis.cluster import RedisCluster # 连接任意一个节点即可,客户端会通过 CLUSTER SLOTS 自动发现拓扑 rc = RedisCluster(host="127.0.0.1", port=7000) rc.set("foo", "bar") rc.get("foo") rc.close()注意:slaveof、replicaof、swapdb等在集群模式下没有意义,RedisClusterCommands中已将这三者实现为直接抛错的方法(见 redis/commands/cluster.py)。异步场景使用redis.asyncio.cluster.RedisCluster(继承AsyncRedisClusterCommands)。
五、命令类的发布入口与模块扩展
5.1redis.commands包的统一出口
上述所有命令类都通过 redis/commands/init.py 统一导出:
from .cluster import READ_COMMANDS, AsyncRedisClusterCommands, RedisClusterCommands from .core import AsyncCoreCommands, CoreCommands from .helpers import list_or_args from .redismodules import AsyncRedisModuleCommands, RedisModuleCommands from .sentinel import AsyncSentinelCommands, SentinelCommands __all__ = [ "AsyncCoreCommands", "AsyncRedisClusterCommands", "AsyncRedisModuleCommands", "AsyncSentinelCommands", "CoreCommands", "READ_COMMANDS", "RedisClusterCommands", "RedisModuleCommands", "SentinelCommands", "list_or_args", ]因此你可以直接from redis.commands import CoreCommands, SentinelCommands, RedisClusterCommands做类型标注或自定义客户端组装,这也呼应了命令文档中autoclass指向的正是这三个类的行为。
5.2 Redis 模块命令:命令文档之外的门面
虽然docs/commands.rst只列出三类命令,但CoreCommands体系并未止步于原生 Redis 命令:RedisModuleCommands(redis/commands/redismodules.py)通过json()/ft()/ts()/bf()/vset()等方法把 RediSearch、RedisJSON、TimeSeries、Bloom 等模块客户端挂载到Redis实例上,使r.json().set(...)、r.ft("idx").search(...)成为可能。这解释了为何Redis类的基类列表中同时出现RedisModuleCommands, CoreCommands, SentinelCommands(redis/client.py#L157)。
六、同步与异步的命令一致性
命令文档中autoclass生成的 API 同时服务同步与异步客户端。从源码看,一致性由两点保证:
- 命令定义共享:
AsyncCoreCommands与CoreCommands的 8 个构成命令类共享底层实现,异步类只是把方法包装为async def并返回Awaitable[...](redis/commands/core.py#L12539-L12552); - 同一套调用约定:同步写法
r.set(...)与异步写法await r.set(...)参数完全一致。
异步示例(对应redis/asyncio客户端):
import asyncio from redis.asyncio import Redis async def main(): r = Redis(decode_responses=True) await r.set('mykey', 'thevalueofmykey') value = await r.get('mykey') print(value) # thevalueofmykey await r.aclose() asyncio.run(main())同步客户端入口为 redis/client.py,异步客户端入口为 redis/asyncio/client.py,两者命令集对齐。
七、结合测试与示例验证命令用法
仓库中针对各命令族的测试覆盖了本文介绍的全部路径,可作为查阅参数语义和返回值的权威参考:
- 核心数据命令:
tests/test_commands.py(String/Hash/List/Set/ZSet/Stream/Geo 等); - 集群命令:
tests/test_cluster.py、tests/test_asyncio/test_cluster.py; - Sentinel 命令:
tests/test_sentinel.py、tests/test_asyncio/test_sentinel.py; - 管道:
tests/test_pipeline.py、tests/test_asyncio/test_pipeline.py; - 连接与响应解析:
tests/test_connection.py、tests/test_parsers/。
入门示例还可见于 docs/examples 下的 notebook(如set_and_get_examples.ipynb、connection_examples.ipynb、pipeline_examples.ipynb),以及 doctests 目录中可实际运行的脚本(如 doctests/string_set_get.py、doctests/trans_pipe.py)。
八、速查:三种命令入口对比
| 维度 | CoreCommands | SentinelCommands | RedisClusterCommands |
|---|---|---|---|
| 定义位置 | redis/commands/core.py#L12523 | redis/commands/sentinel.py#L12 | redis/commands/cluster.py#L1487 |
| 覆盖范围 | 全部原生命令(ACL/数据/管理/PubSub/Script/Function/Module…) | 仅 SENTINEL 子命令 | 集群管理 + 原生命令 + 多 key 槽拆分 |
| 使用对象 | Redis(redis/client.py#L157) | Redis实例或Sentinel(redis/sentinel.py#L224) | RedisCluster(redis/cluster.py#L653) |
| 异步版本 | AsyncCoreCommands | AsyncSentinelCommands | AsyncRedisClusterCommands |
| 典型方法 | set/get/hset/zadd/xadd/eval… | sentinel_masters/sentinel_failover… | cluster_info/cluster_keyslot/mget_nonatomic… |
结语
docs/commands.rst虽然篇幅短小,但它定义的是 redis-py 的“命令门面”设计:所有命令方法都是可复用的混入类,按部署形态(单机 / Sentinel / Cluster)与功能域(数据 / 管理 / 脚本 / 模块)两个维度组织,同步与异步共享同一套定义。理解了这个架构,无论是阅读 redis/commands/core.py、redis/commands/sentinel.py 还是 redis/commands/cluster.py,你都能快速定位任意命令的实现、参数与返回类型;在实际开发中,也能根据“单机选redis.Redis、高可用选Sentinel、水平扩展选RedisCluster”的原则,准确选择接入方式,并善用mget_nonatomic、scan_iter、Pipeline 等客户端侧能力写出更高效的代码。
【免费下载链接】redis-pyRedis Python client项目地址: https://gitcode.com/GitHub_Trending/re/redis-py
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考