
后端缓存数据库客户端消息队列【免费下载链接】StackExchange.RedisThe Redis client for .NET项目地址https://gitcode.com/gh_mirrors/st/StackExchange.Redis点击查看免费下载导读本文是一份面向 StackExchange.Redis 贡献者的完整实操指南讲解如何在src/StackExchange.Redis客户端中端到端地新增一个 Redis/RESP 命令或重载从RedisCommand枚举注册、IDatabase/IDatabaseAsync接口声明、RedisDatabase实现、ResultProcessor结果解析到公共 API 追踪文件、ResultProcessor RoundTrip 两层免服务器单元测试以及当新命令可以替代MULTI/WATCH事务时如何扩展TransactionAnalyzer规则。读完本文你将掌握一套可复制的九步流水线并能理解每一步背后仓库源码的强制约束如IsPrimaryOnly的穷举 switch、可加性重载避免歧义的技巧、PublicAPI.Shipped.txt的二进制兼容红线。前置先读懂 AGENTS.md 的两条铁律仓库根目录的 AGENTS.md 是任何命令实现工作的必读起点其中两条约束直接决定了实现方式Public API tracking → Backwards compatibility is paramount该库在 .NET 生态中被广泛引用对已发布公共 API 的硬性破坏被极度反对——尤其是以MissingMethodException/MissingFieldException形式暴露的二进制破坏。注意“源码兼容的改动仍可能是二进制破坏”给已存在方法追加可选参数会改变其 IL 签名从而破坏已编译的调用方。因此优先采用可加性模式新增重载而不是修改既有方法签名确需淘汰时用[Obsolete(...)]标记而非删除。ArchitectureConnectionMultiplexer→IDatabase/RedisDatabase→ServerEndPoint/PhysicalBridge→PhysicalConnection→ RESPite 的请求链路中Message.cs与ResultProcessor.cs是理解命令实现的两个枢纽——新增或修改一个命令本质就是“创建 Message请求字节 挑选/扩展一个 ResultProcessor应答解析”。同时仓库采用严格构建TreatWarningsAsErrorstrue、FeaturesstrictStyleCop 与分析器含 PublicApiAnalyzers随构建运行任何警告都会让构建失败。构建与 API 分析器会在漏接线的第一时间大声报错但真正证明命令“能用”的是测试。第一步先拿到命令的权威规范Spec First动手写任何代码之前必须先确定命令的精确参数顺序与应答形状——这两者分别决定Message请求字节与ResultProcessor应答解析怎么写而往返RoundTrip测试会对两者做逐字节断言。规范来源按优先级排列服务器源码 JSON 规范最精确例如https://github.com/redis/redis/blob/unstable/src/commands/xdelex.json包含参数 token/顺序/可选性、arity、键规范以及**write/readonly命令标志**——这直接告诉你IsPrimaryOnly该如何分类多数情况下还附有reply_schema。HTML 文档例如https://redis.io/docs/latest/commands/xdelex/可读性更好带应答示例。非 Redis 目标Valkey/Garnet 等使用各自源码与文档但线缆命令通常一致。模块命令RediSearchFT.*、RedisJSONJSON.*、RedisTimeSeriesTS.*、RedisBloom 等在各模块自己的仓库中通常是一份聚合的commands.json如 RediSearch 的commands.json。但模块命令通常由独立配套库如 NRedisStack处理一般不进入 StackExchange.Redis 核心临时使用走通用Execute/ExecuteAsync(string command, …)→RedisResultAPI。若确实要一线接入注意线缆 token 带点号FT.SEARCH而 C# 枚举成员名不能含.——成员名非法标识符时通过[AsciiHash(FT.SEARCH)]覆盖提供真实 token详见 eng/StackExchange.Redis.Build/AsciiHash.md。一线接入前先确认确实需要类型化绑定。未发布的新命令两处都查不到时向用户索取规范——精确参数顺序与一份具体的示例请求/应答最好有 RESP 原始字节不要猜RoundTrip 与 ResultProcessor 测试的正确性完全取决于这份样本。RESP2 vs RESP3应答偶尔包括参数处理在两个协议下可能微妙不同——例如 map/%与扁平*数组之别、double/,与 bulk-string 数字之别、或附加属性。JSONreply_schema有时能区分二者。两种形态都要捕获在ResultProcessor中分别处理并双双覆盖进单元测试。九步实现流水线第 1 步注册RedisCommand枚举并完成IsPrimaryOnly分类命令名加入 src/StackExchange.Redis/Enums/RedisCommand.cs 的internal enum RedisCommand。枚举成员名就是线缆 tokenCommandMap通过command.ToString()序列化它所以必须与 Redis 期望的拼写完全一致如GETEX、XAUTOCLAIM并保持既有字母序分组。从源码结构可以确认两点枚举第一个成员是带[AsciiHash()]的NONE“not a command; must be first for zero reasons”当枚举名不是合法线缆标识符时用[AsciiHash(...)]显式覆盖例如[AsciiHash(BITFIELD)] BITFIELD、[AsciiHash(EVAL_RO)] EVAL_RO、[AsciiHash(BITFIELD_RO)] BITFIELD_RO等——这是仓库中已存在的真实模式。随后必须在同一文件的IsPrimaryOnly扩展方法中完成分类。这个switch是穷举的其default分支在运行时抛出ArgumentOutOfRangeException错误信息形如Every RedisCommand must be defined in Message.IsPrimaryOnly, unknown command {command} encountered.位于 RedisCommand.cs 附近所以这一步不可省略。分类规则见源码注释写操作/变更类命令放入主库专用primary-only列表纯读命令落入可副本replica-eligible分支。分错会错误路由命令——例如把写命令发往副本。源码注释还给出了一个反直觉的判定准则“只要命令可能在 100% 场景下于只读副本上失败就进列表”而“可能可写”的命令如 EVAL 脚本不应标为 primary-only否则会阻断调用方通过.DemandReplica在副本上跑只读脚本的合法场景。第 2 步在接口上声明方法同步 异步注意可加性重载方法声明写入 src/StackExchange.Redis/Interfaces/IDatabase.cs与IDatabaseAsync.cs相关时也写在.Arrays.cs/.VectorSets.cs等分部文件里。同步与异步必须成对提供。Back-compat 红线永远不要给已发布方法追加可选参数二进制破坏 → 运行时MissingMethodException。应新增一个重载。可加性重载技巧避免歧义如果已发布方法尾部参数全可选例如Foo(key, int? count null, CommandFlags flags CommandFlags.None)再追加一个仅多带几个可选参数的新重载Foo(key, int? count null, int? extra null, CommandFlags flags ...)会让既有调用Foo(key, 5)产生歧义——两个候选都替换默认值没有“更优”者编译器报CS0121分析器还会标记RS0026。修复办法把既有重载的参数改为非可选去掉 ...默认值让新重载承载全部可选参数。去掉默认值在二进制上是安全的IL 签名不变源码上也是安全的旧调用自然重新绑定到功能等价的新全可选重载。这个改动必须在接口与每一个实现者RedisDatabase、KeyPrefixed/KeyPrefixedDatabase间保持一致并同步更新 PublicAPI.Shipped.txtoptional→required 属于就地编辑新全可选重载进入 PublicAPI.Unshipped.txt。新重载需要用#pragma warning disable RS0026包裹——因为即使重载不再歧义分析器仍会标记竞争的可选参数重载。注意“最富”的既有重载可能藏在兄弟分部文件里例如IDatabaseAsync.VectorSets.cs不要漏看。实现所有IDatabase/IDatabaseAsync实现者否则构建失败。重点是 KeyspaceIsolation/KeyPrefixedDatabase.cs——它必须通过ToInner(key)给键加前缀一个“转发但不加前缀”的桩可以编译通过却会静默破坏新命令的键空间隔离。若命令还应支持批处理/事务把它一并加到IBatch/ITransaction及其实现RedisBatch/RedisTransaction/KeyPrefixedBatch。第 3 步在RedisDatabase.cs实现Message 构建实现写入 src/StackExchange.Redis/RedisDatabase.cs紧挨着你选的模板方法。标准形态public RedisValue StringGet(RedisKey key, CommandFlags flags CommandFlags.None) { var msg Message.Create(Database, flags, RedisCommand.GET, key); return ExecuteSync(msg, ResultProcessor.RedisValue); } public TaskRedisValue StringGetAsync(RedisKey key, CommandFlags flags CommandFlags.None) { var msg Message.Create(Database, flags, RedisCommand.GET, key); return ExecuteAsync(msg, ResultProcessor.RedisValue); }这是贯穿整个文件的真实模式——源码中大量命令正是Message.Create(Database, flags, RedisCommand.X, key, args)的一行式构造如GEOSEARCHSTORE、HDEL、HINCRBY、HSET/HSETNX、LPUSH、SPOP、ZPOPMIN/ZPOPMAX等随后交给ExecuteSync/ExecuteAsync配一个ResultProcessor。当Message.Create覆盖不了参数形状可选 token、变长参数、多次往返时需要编写私有Message子类重写WriteImpl。在RedisDatabase.cs中搜索: Message与GetStringGetExMessage可找到现成范例private sealed class KeyMigrateCommandMessage : Message.CommandKeyBase——因为MIGRATE参数形态特殊源码注释明说 “MIGRATE is atypical”GetStringGetExMessage帮助方法——StringGet/StringGetEx家族用它把Expiration编译进GETEX的参数序列见 RedisDatabase.cs 一带的调用。必要时也可以使用IMultiMessage组合多条消息。第 4 步挑选或编写ResultProcessorT结果解析位于 src/StackExchange.Redis/ResultProcessor.cs。应答形状匹配时优先复用既有处理器RedisValue、RedisValueArray、Int64、Boolean、Lease等文件中有一批public static readonly ResultProcessor...字段如Int32、ExpireResultArray等均为internal sealed class XProcessor : ResultProcessorT的公开只读实例。否则新增一个嵌套类internal sealed class XProcessor : ResultProcessorT { public override bool SetResult(PhysicalConnection connection, Message message, ref RespReader reader) { // 用 RespReader 解析应答 } }并以public static readonly字段对外暴露。RESP2 与 RESP3 的差异、以及旧版本服务器的应答变体都要在这个类里处理——例如StreamAutoClaim处理器就要同时接受 Redis 7.0 的 3 元素应答含已删除 ID 数组与 Redis 6.2 的 2 元素旧格式。第 5 步新结果类型放APITypes/新的结果类型不是简单标量时放入 src/StackExchange.Redis/APITypes/以StreamAutoClaimResult等为模板——该目录已汇集GeoEntry、HashEntry、SortedSetEntry、StreamEntry、LCSMatchResult、RedisValueWithExpiry等公共结果类型。第 6 步更新公共 API 追踪文件把每个新公共成员加入 PublicAPI.Unshipped.txt以及仅在新 TFM 上存在的 API 对应的net6.0/子目录。构建错误会精确告诉你该加哪一行——漏加则PublicApiAnalyzers直接让构建失败。第 7 步编写两层单元测试免服务器、快速可靠详见下文“两个关键测试层”一节。这两层不需要任何外部服务器是正确性最快速可靠的证明——即使你打算再补实时集成测试也请先写它们。第 8 步按需为预发布服务端特性加实验门控面向未发布/预发布服务端的特性用[Experimental(Experiments.Server_8_x)]等诊断 ID 门控。诊断 ID 定义在 src/RESPite/Shared/Experiments.csSER001–SER006例如Respite SER004、按版本门控的服务端特性Server_8_4/8_6/8_8。这些 ID 位于根NoWarn列表因此库内部使用不会报错配套文档在docs/exp/目录。第 9 步自问“这是不是原子组合”一个令人惊讶的常见情形是新命令用一次往返做到了调用方目前需要MULTI/WATCH事务或多条排队命令才能完成的事——GETDEL、GETEX、HGETDEL、SMOVE、SET ... NX/GET/IFEQ、SMISMEMBER、所有M*/变长形式都是典型。如果是就必须教会TransactionAnalyzer认识它否则最可能受益的人永远不会发现它的存在。见下一节。当新命令替代一个事务时扩展 TransactionAnalyzereng/StackExchange.Redis.Build/TransactionAnalyzer.cs随包发布会在消费者写了一条“现在可以是一条命令”的事务时给出提示。一条新增的原子命令若没进这张表对消费者就是隐形的——分析器恰好对你写这条命令想要替代的代码保持沉默。现在补成本最低没人会以后回来补。先判断新命令替代的是哪种形态然后在 TransactionAnalyzer.cs 中对应表格加一行它替代的事务表规则一个AddCondition 一次写命令现在把该条件作为参数Mapfamily ASER300一个AddCondition 一次写由一条更新的命令同时吞掉两者Mapfamily BSER301一个AddCondition 一次写写的返回值本身已回答该条件Mapfamily CSER302两条不同的排队命令MapPairSER303同一条命令排队 N 次现可用变长重载MapVariadicSER304从源码看这些规则在 TransactionAnalyzer.cs 中对应Rule枚举ConditionalArgumentSER300、NewerAtomicOperationSER301、RedundantConditionSER302、CompoundCommandSER303、VariadicOverloadSER304映射逻辑集中在MapPair、MapVariadic等私有方法中。Rule与Rewrite.MinVersion有意保持分离ID 描述“修复种类”消费者据此配置严重级别、阅读文档页版本是单个映射的数据随服务器版本演进而移动。除建议文本外一行映射还要尽可能声明以下内容取决于所在表的列建议所需的服务端版本——不是被标记代码所需的版本。使用与实时集成测试同一RedisFeatures常量形态早于任何实际服役版本时用ServerVersion.Any提示“需要 2.6”毫无意义。这能让声明了RedisMinServerVersion的项目只看到自己可执行的建议。覆盖集coverage set——建议仍携带的参数名。调用方写出的任何不在此集合内的东西都会让规则保持沉默因为静默丢掉一个参数的改写比不提示更糟N 次StringSet(key, value, expiry)不等于MSET“好心”地折叠会把键变成永不过期。只声明保留的名字、绝不声明丢弃的名字这样以后给重载加参数也能安全失败fail safe。CommandFlags全局豁免。Family C 传null意为“全部”——它保留原命令、只删除条件。是否要求同一成员/字段匹配Map的SameMember、MapVariadic的RequiresMember关于成员a的条件对成员b的写毫无意义折叠两者会丢掉真实的防护。顺序不可交换的命令MapPairSET ... GET返回写入前的值SET会清除任何 TTL所以StringSetKeyExpire是SET ... EX而反向则不是。映射一个方向用负向测试钉死另一个。键的走向MapVariadic的ManyKeysSADD是一个键多个值N 次调用必须落在同一个键上MSET/DEL是多个键必须落在不同键上。方向搞反说明建议的是完全不同的命令。把“决定不映射什么”及其理由写下来。近失配才是最危险的部分表格里的注释是承重的ListRightPopListLeftPush不是LMOVE事务内 pop 的结果是未解析的Taskpush 进的是另一个值N 次ListLeftPop不是LMPOPLMPOP从第一个非空键弹出而不是从每个键弹出。如果你说服自己不要某个映射把推理留在下一个接手的人会撞上的地方。随后还需三件事测试tests/StackExchange.Redis.Build.Tests/下——正向测试放SER30x.cs如 SER300.cs重要的负向测试放 DetectionShape.cs。负向测试才是重点它们是“正确代码”却会被一个过于激进的分析器建议改坏而这条诊断会发给每一位消费者。如果你的映射要求同键、同成员、特定顺序或某参数缺失就必须为每一条写一个测试否则该约束就不是真的。文档docs/rules/SER30x.md 加一行——每条消息都链接到该页获取自身无法承载的注意事项。新规则 ID而不是在既有表里加行还需在 eng/StackExchange.Redis.Build/Diagnostics.cs 加描述符并在 eng/StackExchange.Redis.Build/AnalyzerReleases.Unshipped.md 登记——ID 一旦发布就是公共契约因为消费者会把它们写进NoWarn。两个关键测试层第一层ResultProcessor 单元测试纯解析证明你的ResultProcessor能把原始 RESP 字节变成正确的类型化值。在tests/StackExchange.Redis.Tests/ResultProcessorUnitTests/下新增一个派生自ResultProcessorUnitTest的类用手工构造的 RESP 线缆字节喂给Execute(resp, ResultProcessor.X)并对结果断言用ExecuteUnexpected(resp, ...)覆盖必须失败的应答。以 ResultProcessorUnitTests/StreamAutoClaim.cs 为模板——该文件真实展示了如何用手拼 RESP 字符串如*3\r\n$3\r\n0-0\r\n*1\r\n...验证XAUTOCLAIM的解析并分别覆盖 3 元素新版应答、2 元素旧版应答、空数组、null 数组等形态public class MyCommand(ITestOutputHelper log) : ResultProcessorUnitTest(log) { [Fact] public void Basic_Success() { var resp *2\r\n$3\r\n0-0\r\n*0\r\n; // hand-built RESP reply var result Execute(resp, ResultProcessor.MyCommand); Assert.Equal(0-0, result.NextStartId.ToString()); } [Fact] public void WrongShape_Failure() ExecuteUnexpected($5\r\nhello\r\n, ResultProcessor.MyCommand); }务必覆盖真正咬人的情形RESP2与RESP3 两种形态、空数组、null$-1/*-1、旧版本服务器应答形状如跨版本的 2 元素 vs 3 元素应答、以及至少一个畸形应答ExecuteUnexpected。第二层RoundTrip 单元测试完整写入 读取依然无服务器证明命令序列化为 Redis 期望的确切字节并能正确解析回来锻炼Message.WriteTo 命令映射。加入tests/StackExchange.Redis.Tests/RoundTripUnitTests/用TestConnection.ExecuteAsync(message, processor, requestResp, responseResp, ...)——它会断言出站 RESP 恰好等于requestResp再把responseResp喂回处理器。参见 RoundTripUnitTests/AdhocMessageRoundTrip.cs[Theory(Timeout 1000)] [InlineData(hello, *2\r\n$4\r\nECHO\r\n$5\r\nhello\r\n)] public async Task MyCommand_RoundTrips(string payload, string requestResp) { var msg /* build the Message exactly as RedisDatabase does */; var result await TestConnection.ExecuteAsync(msg, ResultProcessor.MyCommand, requestResp, :5\r\n, log: log); Assert.Equal(5, result.AsInt32()); }验证精确的出站字节长度前缀也要对并理想地验证命令映射的重命名与禁用行为——AdhocMessageRoundTrip.cs中的MapMode枚举Null/Default/Disabled/Renamed就是为此设计的真实模式Disabled模式断言抛RedisCommandException“This operation has been disabled in the command-map and cannot be used: echo”Renamed模式断言出站 token 变为新名字如ECHO2。可选实时集成测试需要真实服务器只有需要向真实服务器证明行为时才写——它们依赖 docker Redis 拓扑见 AGENTS.md 的 Testing topology或 tests/RedisConfigs/docker-compose.yml。服务器缺席时测试基建会自动跳过无需为此写代码。新命令真正需要处理的是服务端版本多数新命令是新服务端特性测试必须在过老服务器上以“无结论”跳过。用require:参数创建连接——它会连接并在实时服务器低于阈值时自动跳过await using var conn Create(require: RedisFeatures.v7_4_0_rc1); var db conn.GetDatabase(); // ... exercise the command ...挑选与引入该命令的版本匹配的RedisFeatures.vX_Y_Z常量模式见HashFieldTests.cs/CopyTests.cs。若所需版本阈值尚不存在把常量加入 src/StackExchange.Redis/RedisFeatures.cs。这样套件在 CI 与贡献者运行的各种服务器版本上都保持绿色。另外进程内托管服务器toys/StackExchange.Redis.Server/RedisServer.cs在集成测试针对它运行时可能也需要加一个处理器。完成前的验证清单提交前必须跑通以下命令均以仓库根目录为工作目录# 1. 全量构建分析器 TreatWarningsAsErrors 必须通过会抓漏掉的 PublicAPI.Unshipped.txt 条目 dotnet build Build.csproj -c Release /p:CItrue # 2. 只跑新命令的单元测试无需任何服务器 dotnet test tests/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj -f net10.0 --filter FullyQualifiedName~MyCommand # 3. 若改过 TransactionAnalyzer同样免服务器秒级完成 dotnet test tests/StackExchange.Redis.Build.Tests/StackExchange.Redis.Build.Tests.csproj最后双重检查没有任何已发布签名被改动back-compat。签名一旦发布任何变更都会变成消费者运行时的一声MissingMethodException。快速对照常见踩坑一览环节常见错误后果正确做法IsPrimaryOnly漏加新命令运行时ArgumentOutOfRangeException穷举 switch 的 default 抛异常写操作进 primary-only 列表读操作落到副本分支接口重载给已发布方法加可选参数二进制破坏 →MissingMethodException去默认值 新全可选重载#pragma warning disable RS0026键空间隔离KeyPrefixedDatabase桩转发不加ToInner(key)编译通过但静默破坏前缀隔离每个实现者都ToInner(key)RESP 变体只处理 RESP3旧服务器/协议下解析错乱两种形态 旧版本形状全覆盖进单测TransactionAnalyzer不登记原子组合命令消费者永远看不到建议对照五类表加行 负向测试 SER30x.md文档公共 API漏更新PublicAPI.Unshipped.txt构建失败PublicApiAnalyzers按构建错误提示精确补行掌握这条九步流水线后从枚举注册到两层免服务器测试、再到事务替换分析器联动任何新 RESP 命令都能以“构建通过 测试证明 不破坏兼容”的方式合入 StackExchange.Redis。赞分享后端缓存数据库客户端消息队列【免费下载链接】StackExchange.RedisThe Redis client for .NET项目地址https://gitcode.com/gh_mirrors/st/StackExchange.Redis点击查看免费下载相关推荐node-redis 命令实现完全指南从 parseCommand 到 transformReply 的端到端工作流node redis 命令实现完全指南从 parseCommand 到 transformReply 的端到端工作流 导读 node redis redi后端数据库客户端缓存go-redis 新增 Redis 命令完整实战指南从命令规范、Cmder 类型到 RESP 解析与测试go redis 新增 Redis 命令完整实战指南从命令规范、Cmder 类型到 RESP 解析与测试 本篇技术指南以 go redis 仓库Redis后端数据库客户端缓存RIOT OS USB HID 回显测试全指南从设备枚举到 hidraw 通信的端到端验证RIOT OS USB HID 回显测试全指南从设备枚举到 hidraw 通信的端到端验证 导读 本文围绕 RIOT OS 测试套件中的 usbus_hid物联网嵌入式操作系统实时系统上一篇如何让老款Mac焕然一新OpenCore Legacy Patcher终极指南下一篇JointJS 动态状态图标实战基于 Highlighter List 为元素实时挂载任意 SVG 状态指示器创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考