Command开发指南
Redis命令扩展开发指南 🚀
Section titled “Redis命令扩展开发指南 🚀”欢迎来到 Redisun 社区!本指南将帮助你了解如何为 Redisun 客户端贡献新的 Redis 命令实现。Redisun 是一个轻量级、高性能的 Redis 客户端,专为 Java 平台设计。我们热忱欢迎社区开发者参与贡献,一起完善 Redisun 的命令支持。
项目架构概览 🏗️
Section titled “项目架构概览 🏗️”在开始开发之前,让我们先了解一下 Redisun 的核心架构:
src/main/java/tech/smartboot/redisun/├── Command.java # 命令抽象基类├── SimpleCommand.java # 简单命令实现类(包含常用命令常量)├── Redisun.java # 客户端主类├── RedisunOptions.java # 客户端配置选项├── cmd/ # 复杂命令实现目录│ ├── SetCommand.java # SET 命令实现(支持多种选项)│ ├── ExpireCommand.java # EXPIRE 命令实现(支持多种选项)│ ├── ZRangeCommand.java # ZRANGE 命令实现(支持多种选项)│ └── ... # 其他复杂命令实现└── resp/ # RESP 协议解析模块 ├── RESP.java # RESP 协议基类 ├── BulkStrings.java # 批量字符串类型 ├── SimpleStrings.java # 简单字符串类型 ├── Integers.java # 整数类型 ├── Arrays.java # 数组类型 └── ... # 其他 RESP 类型命令实现基础 🎯
Section titled “命令实现基础 🎯”Command 抽象基类
Section titled “Command 抽象基类”所有 Redis 命令实现都必须继承 Command 抽象基类。该类定义了命令构建的通用接口:
public abstract class Command { /** * 构建Redis命令参数列表 * @return 包含命令参数的BulkStrings列表 */ protected List<BulkStrings> buildParams() { throw new UnsupportedOperationException("Not implemented"); }
/** * 将命令写入WriteBuffer * @param writeBuffer 写入缓冲区 */ public void writeTo(WriteBuffer writeBuffer) throws IOException { List params = buildParams(); RESP.ofArray(params).writeTo(writeBuffer); }}两种命令实现方式
Section titled “两种命令实现方式”Redisun 提供了两种命令实现方式:
方式一:使用 SimpleCommand(推荐用于简单命令)
Section titled “方式一:使用 SimpleCommand(推荐用于简单命令)”对于参数固定的简单命令,可以直接使用 SimpleCommand 类,在 SimpleCommand.java 中定义命令常量:
public class SimpleCommand extends Command { // 定义命令常量 public static final BulkStrings CONSTANTS_INCR = BulkStrings.of("INCR"); public static final BulkStrings CONSTANTS_INCRBY = BulkStrings.of("INCRBY"); public static final BulkStrings CONSTANTS_DECR = BulkStrings.of("DECR"); public static final BulkStrings CONSTANTS_DECRBY = BulkStrings.of("DECRBY"); // ... 其他常量
private final BulkStrings[] params;
public SimpleCommand(BulkStrings... params) { this.params = params; }
@Override protected List<BulkStrings> buildParams() { return Arrays.asList(params); }}方式二:创建专用命令类(用于复杂命令)
Section titled “方式二:创建专用命令类(用于复杂命令)”对于支持多种选项的复杂命令(如 SET、ZRANGE、EXPIRE),需要创建专门的命令类:
public class SetCommand extends Command { private static final BulkStrings CONSTANTS_SET = BulkStrings.of("SET"); private static final BulkStrings CONSTANTS_NX = BulkStrings.of("NX"); private static final BulkStrings CONSTANTS_XX = BulkStrings.of("XX"); private static final BulkStrings CONSTANTS_EX = BulkStrings.of("EX"); private static final BulkStrings CONSTANTS_PX = BulkStrings.of("PX"); private static final BulkStrings CONSTANTS_KEEPTTL = BulkStrings.of("KEEPTTL");
private final byte[] key; private final byte[] value; private BulkStrings exists; // NX/XX 选项 private Consumer<List<BulkStrings>> expire;
public SetCommand(String key, String value) { this.key = key.getBytes(); this.value = value.getBytes(); }
@Override protected List<BulkStrings> buildParams() { List<BulkStrings> param = new ArrayList<>(); param.add(CONSTANTS_SET); param.add(RESP.ofString(key)); param.add(RESP.ofString(value));
if (exists != null) { param.add(exists); }
if (expire != null) { expire.accept(param); } return param; }
// 链式调用方法设置选项 public SetCommand setIfNotExists() { exists = CONSTANTS_NX; return this; }
public SetCommand setIfExists() { exists = CONSTANTS_XX; return this; }
public SetCommand ex(int seconds) { expire = list -> { list.add(CONSTANTS_EX); list.add(RESP.ofString(String.valueOf(seconds))); }; return this; }}开发新命令的步骤 🛠️
Section titled “开发新命令的步骤 🛠️”1. 添加命令常量(使用 SimpleCommand 方式)
Section titled “1. 添加命令常量(使用 SimpleCommand 方式)”对于简单命令,在 SimpleCommand.java 中添加命令常量:
/** * Redis INCRBY 命令常量 * <p> * 将 key 所储存的值加上给定的增量值(increment)。 * </p> * * @see <a href="https://redis.io/commands/incrby/">Redis INCRBY Command</a> */public static final BulkStrings CONSTANTS_INCRBY = BulkStrings.of("INCRBY");2. 在客户端中添加方法
Section titled “2. 在客户端中添加方法”在 Redisun.java 中添加对应的方法。
响应处理函数常量
Section titled “响应处理函数常量”首先定义响应处理函数(在类顶部与其他函数常量一起定义):
private static final Function<RESP, Long> LONG_FUTURE = resp -> { if (resp instanceof Integers) { return ((Integers) resp).getValue().longValue(); } throw new RedisunException("invalid response:" + resp);};
private static final Function<RESP, String> BULK_STRING_FUTURE = resp -> { if (resp instanceof Nulls) { return null; } else if (resp instanceof BulkStrings) { return ((BulkStrings) resp).getValue(); } throw new RedisunException("invalid response:" + resp);};同步方法实现
Section titled “同步方法实现”/** * 将 key 所储存的值加上给定的增量值(increment) * * @param key 要增加的键 * @param increment 增量值 * @return 执行命令后 key 的值 */public long incrBy(String key, long increment) { return get(asyncIncrBy(key, increment));}异步方法实现
Section titled “异步方法实现”/** * 将 key 所储存的值加上给定的增量值(increment)(异步版本) * * @param key 要增加的键 * @param increment 增量值 * @return 执行命令后 key 的值的CompletableFuture */public CompletableFuture<Long> asyncIncrBy(String key, long increment) { return execute(new SimpleCommand(SimpleCommand.CONSTANTS_INCRBY, RESP.ofString(key), RESP.ofString(String.valueOf(increment))) ).thenApply(LONG_FUTURE);}3. 复杂命令实现示例
Section titled “3. 复杂命令实现示例”对于支持多种选项的复杂命令,需要创建专用命令类并在客户端中使用。
命令类实现(以 ExpireCommand 为例)
Section titled “命令类实现(以 ExpireCommand 为例)”public class ExpireCommand extends Command { private static final BulkStrings CONSTANTS_EXPIRE = BulkStrings.of("EXPIRE"); private static final BulkStrings CONSTANTS_NX = BulkStrings.of("NX"); private static final BulkStrings CONSTANTS_XX = BulkStrings.of("XX"); private static final BulkStrings CONSTANTS_GT = BulkStrings.of("GT"); private static final BulkStrings CONSTANTS_LT = BulkStrings.of("LT");
private final String key; private final int seconds; private BulkStrings option;
public ExpireCommand(String key, int seconds) { this.key = key; this.seconds = seconds; }
@Override protected List<BulkStrings> buildParams() { List<BulkStrings> param = new ArrayList<>(); param.add(CONSTANTS_EXPIRE); param.add(RESP.ofString(key)); param.add(RESP.ofString(String.valueOf(seconds))); if (option != null) { param.add(option); } return param; }
public ExpireCommand nx() { option = CONSTANTS_NX; return this; }
public ExpireCommand xx() { option = CONSTANTS_XX; return this; }
public ExpireCommand gt() { option = CONSTANTS_GT; return this; }
public ExpireCommand lt() { option = CONSTANTS_LT; return this; }}客户端方法实现
Section titled “客户端方法实现”/** * 为给定 key 设置过期时间,以秒计 * * @param key 要设置过期时间的键 * @param seconds 过期时间(秒) * @return 设置成功返回 1,否则返回 0 */public int expire(String key, int seconds) { return expire(key, seconds, null);}
/** * 为给定 key 设置过期时间,以秒计,并支持选项 * * @param key 要设置过期时间的键 * @param seconds 过期时间(秒) * @param options EXPIRE命令的额外选项配置函数 * @return 设置成功返回 1,否则返回 0 */public int expire(String key, int seconds, Consumer<ExpireCommand> options) { return get(asyncExpire(key, seconds, options));}
private CompletableFuture<Integer> asyncExpire(String key, int seconds, Consumer<ExpireCommand> options) { ExpireCommand cmd = new ExpireCommand(key, seconds); if (options != null) { options.accept(cmd); } return execute(cmd).thenApply(INTEGER_FUTURE);}4. 编写测试用例
Section titled “4. 编写测试用例”在测试文件中添加新命令的测试用例:
@Testpublic void testIncr() { String key = "test_incr_key"; // 先删除可能存在的键 redisun.del(key); // 首次调用应该返回1 Assert.assertEquals(1, redisun.incr(key)); // 再次调用应该返回2 Assert.assertEquals(2, redisun.incr(key)); // 验证GET命令能正确获取值 Assert.assertEquals("2", redisun.get(key));}
@Testpublic void testIncrBy() { String key = "test_incrby_key"; redisun.del(key); // 增加10 Assert.assertEquals(10, redisun.incrBy(key, 10)); // 再增加5 Assert.assertEquals(15, redisun.incrBy(key, 5));}命令实现最佳实践 ✅
Section titled “命令实现最佳实践 ✅”1. 常量优化
Section titled “1. 常量优化”对于命令中使用的固定字符串,建议定义为静态常量以提高性能:
private static final BulkStrings CONSTANTS_SET = BulkStrings.of("SET");private static final BulkStrings CONSTANTS_NX = BulkStrings.of("NX");2. 参数处理
Section titled “2. 参数处理”使用 RESP 工具类处理参数转换:
param.add(RESP.ofString(key));param.add(RESP.ofString(value));3. 响应处理
Section titled “3. 响应处理”使用预定义的函数常量处理响应:
// 整数响应private static final Function<RESP, Integer> INTEGER_FUTURE = resp -> { if (resp instanceof Integers) { return ((Integers) resp).getValue().intValue(); } throw new RedisunException("invalid response:" + resp);};
// 长整数响应private static final Function<RESP, Long> LONG_FUTURE = resp -> { if (resp instanceof Integers) { return ((Integers) resp).getValue().longValue(); } throw new RedisunException("invalid response:" + resp);};
// 字符串响应private static final Function<RESP, String> BULK_STRING_FUTURE = resp -> { if (resp instanceof Nulls) { return null; } else if (resp instanceof BulkStrings) { return ((BulkStrings) resp).getValue(); } throw new RedisunException("invalid response:" + resp);};
// 布尔响应(OK)private static final Function<RESP, Boolean> OK_FUTURE = resp -> { if (resp instanceof SimpleStrings) { return SimpleStrings.OK.equals(((SimpleStrings) resp).getValue()); } throw new RedisunException("invalid response:" + resp);};4. 同步与异步方法设计
Section titled “4. 同步与异步方法设计”同步和异步接口的设计原则:
- 同步方法名遵循
command格式,如set()、get()、del()、incr() - 异步方法名遵循
asyncCommand格式,如asyncSet()、asyncGet()、asyncDel()、asyncIncr() - 同步方法内部通过调用
get(asyncXxx())方式实现 - 异步方法通过调用
execute()方法返回CompletableFuture对象,允许非阻塞操作
5. 复杂命令的选项配置
Section titled “5. 复杂命令的选项配置”对于支持多种选项的命令,使用 Consumer<Command> 模式:
// 使用示例redisun.set("key", "value", cmd -> cmd.ex(60).nx());redisun.expire("key", 60, cmd -> cmd.nx());redisun.zrange("key", 0, -1, cmd -> cmd.withScores().rev());复杂命令示例 💡
Section titled “复杂命令示例 💡”ZRangeCommand 实现
Section titled “ZRangeCommand 实现”public class ZRangeCommand extends Command { private static final BulkStrings CONSTANTS_ZRANGE = BulkStrings.of("ZRANGE"); private static final BulkStrings CONSTANTS_BYSCORE = BulkStrings.of("BYSCORE"); private static final BulkStrings CONSTANTS_BYLEX = BulkStrings.of("BYLEX"); private static final BulkStrings CONSTANTS_REV = BulkStrings.of("REV"); private static final BulkStrings CONSTANTS_WITHSCORES = BulkStrings.of("WITHSCORES"); private static final BulkStrings CONSTANTS_LIMIT = BulkStrings.of("LIMIT");
private final String key; private final String start; private final String stop;
private boolean byScore = false; private boolean byLex = false; private boolean rev = false; private boolean withScores = false; private boolean hasLimit = false; private long limitOffset; private long limitCount;
public ZRangeCommand(String key, String start, String stop) { this.key = key; this.start = start; this.stop = stop; }
@Override protected List<BulkStrings> buildParams() { List<BulkStrings> param = new ArrayList<>(); param.add(CONSTANTS_ZRANGE); param.add(RESP.ofString(key)); param.add(RESP.ofString(start)); param.add(RESP.ofString(stop));
if (byScore) param.add(CONSTANTS_BYSCORE); if (byLex) param.add(CONSTANTS_BYLEX); if (rev) param.add(CONSTANTS_REV); if (hasLimit) { param.add(CONSTANTS_LIMIT); param.add(RESP.ofString(String.valueOf(limitOffset))); param.add(RESP.ofString(String.valueOf(limitCount))); } if (withScores) param.add(CONSTANTS_WITHSCORES);
return param; }
// 链式调用方法 public ZRangeCommand byScore() { this.byScore = true; return this; } public ZRangeCommand byLex() { this.byLex = true; return this; } public ZRangeCommand rev() { this.rev = true; return this; } public ZRangeCommand withScores() { this.withScores = true; return this; } public ZRangeCommand limit(long offset, long count) { this.hasLimit = true; this.limitOffset = offset; this.limitCount = count; return this; }
// 返回结果封装 public static class Tuple { private String member; private double score; // getters and setters }}客户端方法实现
Section titled “客户端方法实现”public List<String> zrange(String key, long start, long stop) { return get(asyncZrange(key, start, stop));}
public List<ZRangeCommand.Tuple> zrange(String key, long start, long stop, Consumer<ZRangeCommand> options) { return get(asyncZrange(key, start, stop, options));}
public CompletableFuture<List<String>> asyncZrange(String key, long start, long stop) { return asyncZrange(key, start, stop, null) .thenApply(list -> list.stream() .map(ZRangeCommand.Tuple::getMember) .collect(Collectors.toList()));}
public CompletableFuture<List<ZRangeCommand.Tuple>> asyncZrange(String key, long start, long stop, Consumer<ZRangeCommand> options) { ZRangeCommand cmd = new ZRangeCommand(key, String.valueOf(start), String.valueOf(stop)); if (options != null) { options.accept(cmd); } return execute(cmd).thenApply(resp -> { if (resp instanceof Arrays) { List<RESP> resps = ((Arrays) resp).getValue(); List<ZRangeCommand.Tuple> result = new ArrayList<>(resps.size()); for (RESP r : resps) { ZRangeCommand.Tuple tuple = new ZRangeCommand.Tuple(); if (r instanceof Arrays) { Arrays arrays = (Arrays) r; tuple.setMember(((BulkStrings) arrays.getValue().get(0)).getValue()); tuple.setScore(((Doubles) arrays.getValue().get(1)).getValue()); } else if (r instanceof BulkStrings) { tuple.setMember(((BulkStrings) r).getValue()); } result.add(tuple); } return result; } throw new RedisunException("invalid response:" + resp); });}性能基准测试用例开发 📊
Section titled “性能基准测试用例开发 📊”为了评估重要命令的性能表现,我们需要为其编写对应的基准测试用例。
基准测试框架介绍
Section titled “基准测试框架介绍”Redisun 使用 JUnit 编写基准测试用例,主要测试同步和异步两种操作模式的性能表现。基准测试位于 bench 包中:
src/test/java/tech/smartboot/redisun/bench/├── Bench.java # 基准测试配置类├── RedisunBenchmark.java # Redisun 基准测试类└── RedissonBenchmark.java # Redisson 对照组基准测试类添加新命令的基准测试
Section titled “添加新命令的基准测试”当你为 Redisun 实现了一个重要命令后,需要在 RedisunBenchmark.java 中添加相应的基准测试方法。
以 INCR 命令为例:
@Testpublic void incr() { String key = "bench_incr_key"; // 初始化键值 redisun.set(key, "0");
long start = System.currentTimeMillis(); for (int i = 0; i < SET_COUNT; i++) { redisun.incr(key); } System.out.println("redisun incr cost " + (System.currentTimeMillis() - start) + "ms");}
@Testpublic void incrConcurrent() throws InterruptedException { String key = "bench_incr_concurrent_key"; redisun.set(key, "0");
CountDownLatch latch = new CountDownLatch(SET_COUNT); long start = System.currentTimeMillis();
for (int i = 0; i < CONCURRENT_CLIENT_COUNT; i++) { Thread thread = new Thread(() -> { while (latch.getCount() > 0) { redisun.incr(key); latch.countDown(); } }); thread.setDaemon(true); thread.start(); }
latch.await(); System.out.println("redisun concurrent incr cost " + (System.currentTimeMillis() - start) + "ms");}基准测试最佳实践 ✅
Section titled “基准测试最佳实践 ✅”-
测试环境一致性
- 确保所有测试都在相同的硬件和网络环境下运行
- 每次测试前清理 Redis 数据库,避免历史数据影响测试结果
-
测试覆盖全面
- 同步操作性能测试
- 异步操作性能测试
- 并发场景性能测试
-
结果记录与对比
- 记录执行时间作为性能指标
- 与其他客户端(如 Redisson)进行对比测试
- 在不同负载条件下测试性能表现
贡献流程 🤝
Section titled “贡献流程 🤝”我们欢迎任何形式的贡献!请按照以下流程参与贡献:
- Fork 项目 - 在 Gitee 或 GitHub 上 Fork Redisun 项目
- 创建分支 - 为你的功能创建一个新的分支
- 实现命令 - 按照本指南实现新的 Redis 命令
- 编写测试 - 为新命令编写完整的测试用例
- 添加基准测试 - 为重要的新命令添加性能基准测试(非必需项)
- 编写文档 - 在 cmd 目录下为新命令编写相应的文档
- 提交代码 - 提交你的代码并推送至你的仓库
- 发起 PR - 创建 Pull Request,描述你的实现内容
文档编写规范 📝
Section titled “文档编写规范 📝”当你实现一个新的 Redis 命令后,需要为其编写相应的文档。 文档位于 /pages/src/content/docs/cmd 目录下,每个命令都有一个对应的 .mdx 文件。
请遵循以下文档编写规范:
每个命令文档应包含以下部分:
- 文件头部 - 包含title和description等元数据
- 命令简介 - 简要介绍该命令的作用
- Redis原生命令语法 - 展示Redis原生的命令语法
- 参数说明 - 详细说明每个参数的含义
- 详细说明 - 对命令的详细解释和使用场景
- Redisun使用方式 - 展示如何在Redisun中使用该命令
- 注意事项 - 使用该命令时需要注意的事项
- 参考资料 - 指向Redis官方文档的链接
1. 文件头部
Section titled “1. 文件头部”---title: 命令名称description: 简要描述该命令的功能sidebar: order: 数字(按字母顺序排列)---2. 命令简介
Section titled “2. 命令简介”在文件头部之后,应简要介绍该命令的作用,无需一级标题。
3. Redis原生命令语法
Section titled “3. Redis原生命令语法”使用三级标题”### Redis原生命令语法”,并使用Code组件展示语法:
### Redis原生命令语法
<Code code={`命令语法`} lang="bash" />4. 参数说明
Section titled “4. 参数说明”使用加粗格式列出参数说明,无需标题:
**参数说明**
- **参数名**: 参数说明5. 详细说明
Section titled “5. 详细说明”使用三级标题”### 详细说明”,可以包含子级标题来组织内容。
6. Redisun使用方式
Section titled “6. Redisun使用方式”使用二级标题”## Redisun使用方式”,展示如何在Redisun中使用该命令,并提供代码示例。
7. 注意事项
Section titled “7. 注意事项”列出使用该命令时需要注意的事项。
8. 参考资料
Section titled “8. 参考资料”提供指向Redis官方文档的链接。
社区支持 💬
Section titled “社区支持 💬”如果你在开发过程中遇到任何问题:
- 查看 Redis 官方文档 了解命令规范
- 在项目 Issues 中提问
- 联系项目维护者获取帮助
让我们一起构建更好的 Redisun!✨