ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Garnet 服务端 Raw String 自定义扩展开发指南:从基类方法到命令注册与客户端调用

Garnet 服务端 Raw String 自定义扩展开发指南:从基类方法到命令注册与客户端调用 Garnet 服务端 Raw String 自定义扩展开发指南从基类方法到命令注册与客户端调用【免费下载链接】garnetGarnet is a remote cache-store from Microsoft Research that offers strong performance (throughput and latency), scalability, storage, recovery, cluster sharding, key migration, and replication features. Garnet can work with existing Redis clients.项目地址: https://gitcode.com/GitHub_Trending/garnet4/garnet导读Garnet 允许开发者编写自定义的 Raw String原始字符串扩展函数在服务端注册后即可通过任意 Redis 客户端调用从而实现服务端数据操作逻辑的下沉。本文以CustomRawStringFunctions基类为核心完整讲解六个必选方法与两个可选方法的职责与实现约定、参数解析辅助工具、服务端RegisterApi.NewCommand注册流程并结合仓库中SETIFPM、DELIFM、SETWPIFPGT三个真实示例与测试用例给出可直接落地的开发与验证方案。读完本文你将能够独立实现、注册并测试自己的 Raw String 自定义命令。一、Raw String 扩展是什么Garnet 的 Raw String 扩展允许在服务端新增针对字符串值的函数并注册到服务器注册后的命令可以被任何 Garnet 客户端包括 StackExchange.Redis 等现有 Redis 客户端直接调用并在服务端执行。这属于 Garnet 自定义命令体系中自定义 Raw String 命令一类其他类型见 自定义命令开发指南。从源码结构看该能力由以下文件共同支撑基类定义CustomRawStringFunctions.cs命令载体CustomRawStringCommand.cs注册入口RegisterApi.cs参数解析工具CustomCommandUtils.cs一个自定义 Raw String 命令本质上是将一组针对字符串的**读取Read与读改写Read-Modify-WriteRMW**回调逻辑注入 Garnet 的存储引擎命令输入被封装为StringInput输出通过RespMemoryWriter以 RESP 协议格式写出。二、基类与六个必选方法开发一个新的自定义 Raw String 函数需要继承CustomRawStringFunctions基类并实现其中六个抽象方法分别覆盖 RMW 更新的三种路径初始更新、原地更新、拷贝更新和读取路径以及对应的长度预估方法职责返回值的语义GetInitialLength(ref StringInput input)返回使用给定input通过 RMW 填充新值时初始值的预期长度长度字节数GetLength(ReadOnlySpanbyte value, ref StringInput input)返回对value使用给定input做 RMW 修改后结果值的长度长度字节数InitialUpdater(ReadOnlySpanbyte key, ref StringInput input, Spanbyte value, ref RespMemoryWriter writer, ref RMWInfo rmwInfo)执行 RMW 的初始更新键不存在时根据key与input生成待写入的value并通过writer输出本次操作的结果返回true表示完成false表示取消本次更新InPlaceUpdater(ReadOnlySpanbyte key, ref StringInput input, Spanbyte value, ref int valueLength, ref RespMemoryWriter writer, ref RMWInfo rmwInfo)执行 RMW 的原地更新直接在value所在位置修改新长度通过valueLength写出返回true表示完成false表示原地空间不足CopyUpdater(ReadOnlySpanbyte key, ref StringInput input, ReadOnlySpanbyte oldValue, Spanbyte newValue, ref RespMemoryWriter writer, ref RMWInfo rmwInfo)执行 RMW 的拷贝更新基于oldValue与input计算newValue并写入新位置返回true表示完成false表示取消Reader(ReadOnlySpanbyte key, ref StringInput input, ReadOnlySpanbyte value, ref RespMemoryWriter writer, ref ReadInfo readInfo)执行记录读取根据key、input和记录的value计算输出并写入writer返回true表示完成false表示未找到这些方法的参数语义在基类 XML 注释中有明确说明见 CustomRawStringFunctions.cskey当前记录的键input客户端命令参数封装成的StringInputvalue/oldValue/newValue当前值 / 旧值 / 目标新值writer用于把本次操作结果按 RESP 格式写出的RespMemoryWriterrmwInfo当前记录的记录信息引用用于 RMW 阶段的锁与记录动作控制例如删除记录readInfo读取阶段的高级参数引用。实现约定从源码推断GetInitialLength/InitialUpdater与InPlaceUpdater/CopyUpdater/GetLength分别对应记录不存在与记录已存在两种 RMW 路径对于只读命令通常只有Reader会被实际调用其余方法可以抛InvalidOperationException下文示例中可见这种写法。基类还额外提供了一个默认方法NotFound(ReadOnlySpanbyte key, ref StringInput input, ref RespMemoryWriter writer)当读取命令未找到值时会调用它默认实现写出 nullRESP 3 下为_\r\nRESP 2 下为$-1\r\n可在子类中覆盖以自定义未命中响应见 CustomRawStringFunctions.cs。三、两个可选方法控制 RMW 更新路径除六个必选方法外基类还提供两个可选的虚方法用于在 RMW 流程中提前决策NeedInitialUpdate(ReadOnlySpanbyte key, ref StringInput input, ref RespMemoryWriter writer)决定当记录不存在时是否应触发InitialUpdater。默认返回true返回false表示跳过初始更新例如条件不满足时直接放弃写入。NeedCopyUpdate(ReadOnlySpanbyte key, ref StringInput input, ReadOnlySpanbyte oldValue, ref RespMemoryWriter writer)决定对已存在记录是否应执行拷贝更新CopyUpdater。默认返回true返回false可跳过拷贝更新。这两个方法既承担条件判断职责也可以借助writer.WriteError(...)在条件不满足时向客户端返回错误信息从而避免InPlaceUpdater/CopyUpdater中被重复执行校验。它们与InPlaceUpdater的关系可以推断为更新发生时存储引擎会优先尝试原地更新若空间不足或NeedCopyUpdate返回true则回退到拷贝更新而NeedInitialUpdate则决定键不存在时是否值得创建记录。四、参数解析辅助方法编写上述方法体时需要从StringInput中取出命令参数。基类提供了两个受保护的静态辅助方法封装自 CustomCommandUtils.csGetNextArg(ref StringInput input, scoped ref int offset)从input的当前offset处取出下一个参数返回ReadOnlySpanbyte方法内部通过指针 长度头的方式读取参数调用一次后offset会前进到下一个参数位置。GetFirstArg(ref StringInput input)直接取出offset为 0 处的第一个参数等价于对GetNextArg的便捷封装。注意GetNextArg的offset参数是ref传递多次取参时须维护同一个局部变量典型写法如下摘自 SetIfPM.csvar offset 0; var newVal GetNextArg(ref input, ref offset); var prefix GetNextArg(ref input, ref offset);五、服务端注册RegisterApi.NewCommand开发完成后需要在服务端把自定义函数注册为一条可被客户端调用的命令。注册入口是GarnetServer实例的RegisterApi.NewCommand完整签名见 RegisterApi.cspublic int NewCommand( string name, CommandType type, // CommandType.Read 或 CommandType.ReadModifyWrite CustomRawStringFunctions customFunctions, // 自定义函数实例 RespCommandsInfo commandInfo null, // 可选RESP 命令元信息 RespCommandDocs commandDocs null, // 可选RESP 命令文档 long expirationTicks 0) // 可选过期时间tick各参数要点name命令名注册时会被转为大写见 CustomRawStringCommand.cs因此setifpm与SETIFPM等价typeCommandType.Read只读命令或CommandType.ReadModifyWrite读改写命令expirationTicks值的过期时间语义如下源码注释见 RegisterApi.cs-1移除已有的过期元数据0保留当前过期设置新记录则不带过期——默认值0设置给定的过期时长单位 tick1 秒 10,000,000 ticks。5.1 带命令元信息的注册示例为了让新命令出现在客户端的COMMAND/COMMAND INFO输出中可同时提供RespCommandsInfo。仓库入口 Program.cs 中的SETIFPM注册示范了完整做法var setIfPmCmdInfo new RespCommandsInfo { Name SETIFPM, Arity 4, // 命令参数个数含命令名 FirstKey 1, // 第一个键的位置 LastKey 1, // 最后一个键的位置 Step 1, Flags RespCommandFlags.DenyOom | RespCommandFlags.Write, AclCategories RespAclCategories.String | RespAclCategories.Write, }; server.Register.NewCommand(SETIFPM, CommandType.ReadModifyWrite, new SetIfPMCustomCommand(), setIfPmCmdInfo);5.2 从客户端注册REGISTERCS除了服务端编程式注册还可以在客户端以管理命令方式执行REGISTERCS注册——前提是承载实现代码的程序集已经存在于服务器上。完整语法与注意事项见 自定义命令开发指南核心语法为REGISTERCS cmdType name numParams className [expTicks] ... [INFO path] SRC path [path ...]其中cmdType取READ、READMODIFYWRITE或RMWclassName为实现了CustomRawStringFunctions的具体类名expTicks即上文所述的过期 tick 语义。测试用例 RespCustomCommandTests.cs 展示了通过 REGISTERCS 同时注册事务与 Raw String 命令的写法var args new Listobject { TXN, READWRITETX, 3, ReadWriteTxn, RMW, SETIFPM, 2, SetIfPMCustomCommand, TimeSpan.FromSeconds(10).Ticks, SRC, }; args.AddRange(libraryPaths); var resp (string)db.Execute(REGISTERCS, [.. args]);需要注意客户端注册属于管理命令需要服务端启用EnableModuleCommand并配置ExtensionBinPaths允许加载程序集的路径详见 custom-commands.md。六、仓库实战示例剖析6.1 SETIFPM条件写入与原地更新优先的完整范式SETIFPM key value prefix的语义是仅当给定prefix与现有值前缀匹配时才把key更新为value否则不动作。完整实现见 SetIfPM.cs其中体现了几个通用设计模式NeedInitialUpdate返回false键不存在时不创建记录条件写入不满足前缀匹配的前提NeedCopyUpdate内做条件判断比较prefix与新值newVal的前缀是否一致只有一致才允许拷贝更新InPlaceUpdater中再次校验前缀并处理空间不足新值比现有值长时返回false让存储引擎回退到拷贝更新路径见 SetIfPM.csGetLength/GetInitialLength返回新值长度GetLength直接返回GetFirstArg(ref input).Length见 SetIfPM.cs只读路径不适用Reader抛出InvalidOperationException表示该命令不可用于读取。测试 RespCustomCommandTests.cs 验证了四条行为前缀匹配时写入成功、前缀不匹配时保持原值、新值更短与更长触发拷贝更新时均能正确写入。6.2 DELIFM通过 rmwInfo 删除记录DELIFM key valueDelete If Match用于值精确匹配时删除键典型用途是实现分布式锁的解锁操作。实现见 DeleteIfMatch.cs它的关键技巧是public override bool InPlaceUpdater(ReadOnlySpanbyte key, ref StringInput input, Spanbyte value, ref int valueLength, ref RespMemoryWriter writer, ref RMWInfo rmwInfo) { var expectedVal GetFirstArg(ref input); if (value.SequenceEqual(expectedVal)) { rmwInfo.Action RMWAction.ExpireAndStop; // 标记记录过期并停止后续处理 return false; } return true; // 不匹配时返回 true由框架输出默认的 OK }即通过给rmwInfo.Action赋值为RMWAction.ExpireAndStop来删除当前记录GetLength返回value.Length保持长度不变NeedCopyUpdate仅在新旧值匹配时返回true。Reader同样抛出异常以禁用只读路径。6.3 SETWPIFPGT8 字节前缀的比较写入与错误输出SETWPIFPGT key value 8-byte-prefix将键值更新为8 字节前缀 值且仅当给定前缀按 8 字节 long 解析大于现有值的前缀时才更新。实现见 SetWPIFPGT.cs其亮点是NeedInitialUpdate校验前缀长度前缀不是 8 字节时通过writer.WriteError(PrefixError)返回Invalid prefix length, should be 8 bytes并返回false放弃更新见 SetWPIFPGT.csGetInitialLength/GetLength返回newVal.Length 8为新值预留 8 字节前缀空间NeedCopyUpdate/InPlaceUpdater中使用BitConverter.ToUInt64比较 8 字节前缀的数值大小见 SetWPIFPGT.csInitialUpdater/CopyUpdater中分别把prefix与newVal拷贝到value的[0..8)与[8..)区间。该示例同时展示了自定义命令完全可以在回调中直接向writer写出 RESP 错误实现服务端参数校验。七、调用方式与验证7.1 客户端调用注册完成后命令即可像内置命令一样被调用。以 StackExchange.Redis 为例Program.cs 注释中的用法db.Execute(SETIFPM, key, value, prefix); // 自定义条件写入 db.Execute(DELIFM, key, expectedValue); // 条件删除 db.Execute(SETWPIFPGT, key, value, prefix8Bytes);7.2 测试验证仓库在 RespCustomCommandTests.cs 中为这些命令提供了完整测试可作为自己开发时的参考模板服务端注册后通过ConnectionMultiplexer连接并执行db.Execute(SETIFPM, ...)用StringGet断言结果L285-L328错误前缀长度触发异常的场景SETWPIFPGT传入 3 字节前缀应报错L330-L349通过REGISTERCS客户端注册后立即调用的端到端验证L999-L1058。八、开发要点小结明确命令类型只读逻辑实现Reader配合可选的NotFound写逻辑实现 RMW 三件套InitialUpdater/InPlaceUpdater/CopyUpdater与两个长度方法不适用的路径直接抛异常避免误用。善用两个 Need 方法在NeedInitialUpdate/NeedCopyUpdate中做条件判断与参数校验可减少不必要的数据写入并能借助writer.WriteError提前返回错误。长度预估必须准确GetInitialLength/GetLength决定存储引擎预分配的空间返回过小会导致更新失败或触发额外拷贝。原地更新失败要返回falseInPlaceUpdater中新值放不下时返回false引擎会自动走CopyUpdater路径。注册信息要完整提供RespCommandsInfoArity、FirstKey、LastKey、Flags、AclCategories可让COMMAND/COMMAND INFO正确展示新命令需要过期能力时通过expirationTicks控制-1/0/0。两种注册途径任选服务端RegisterApi.NewCommand适合随服务启动静态注册客户端REGISTERCS管理命令需EnableModuleCommand与ExtensionBinPaths配合适合在已运行的服务器上动态加载程序集注册。如需进一步了解 Raw String 命令与自定义对象命令、事务、过程等其他扩展类型的整体关系可继续阅读 自定义命令开发指南 与 主程序注册示例。【免费下载链接】garnetGarnet is a remote cache-store from Microsoft Research that offers strong performance (throughput and latency), scalability, storage, recovery, cluster sharding, key migration, and replication features. Garnet can work with existing Redis clients.项目地址: https://gitcode.com/GitHub_Trending/garnet4/garnet创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表