命令界面:使用Java中的动态API处理Redis

Redis是一个数据存储,支持190多个已记录命令和450多个命令排列。 社区积极支持Redis开发; 每个主要的Redis版本都附带新命令。 今年,Redis向第三方供应商开放,以开发可扩展Redis功能的模块。 对于客户端开发人员和Redis用户而言,命令的增长和对即将发布的模块的跟踪是一项挑战。

命令增长

对于客户端库,Redis中的命令增长是一项具有挑战性的业务。 多个客户端公开一个类型化的API,该API为每个Redis API调用声明一个方法(函数)签名。 静态声明对使用很有帮助,但是大量的Redis命令使用大量方法签名污染了客户端。 某些命令可能以不同的方式执行,从而影响需要附加签名的响应类型( ZREVRANGEZREVRANGE … WITHSCORES )。 让我们仔细看看一些方法签名:

重新发行

# Get the values of all the given hash fields.
# 
# @example
#   redis.hmget("hash", "f1", "f2")
  def hmget(key, *fields, &blk)

杰迪斯

public List<String> hmget(final String key, final String... fields)

生菜

List<V>
public List<K> hmget(K key, K... fields)

声明的方法可为开发人员提供类型安全性和文档,但它们同时是静态的。 Redis引入新命令后,客户端供应商必须更改API,否则新命令将不可用。 大多数Redis客户端公开客户端调用API来执行自定义命令来解决此问题:

重新发行

client.call([:hmget, key] + fields)

杰迪斯

final byte[][] params = …;
jedis.sendCommand(HMGET, params);

生菜

lettuce.dispatch(CommandType.HMGET, new ValueListOutput<>(codec),
new CommandArgs<>(codec)
   .addKey(key)
   .addKey(field));

吉迪普斯

rce.accept(client -> client.sendCmd(Cmds.HMGET, "hash", "field1", "field2", …));

其他客户端(例如node_redis基于Redis命令创建函数原型。 这是对静态API的改进,因为它使API具有一定的灵活性。

构造Redis命令需要有关其请求和响应结构的知识。 这些知识被记录在调用代码内部的某个位置。 这很方便,因为您将其放在需要代码的地方,但是它也有一些缺点。 由于自定义命令是从方法内部运行的,因此自定义命令需要额外的精力才能重用。 不需要在许多客户端上找到的典型方法签名。 如果不遵循API组件方法,这种方法会使自省更具挑战性。 这是因为所有自定义命令仅使用不同的参数来调用同一方法。

具有固定参数列表的静态方法声明的性质仅限于接受所提供的参数。 方法调用的上下文控件不能通过该方法应用。 例如,Lettuce提供了一个同步API,该API可以控制所有命令的命令超时,但不能控制命令调用级别。

让我们通过动态API处理Redis。

动态API

动态API是编程接口,因为它们遵循约定,因此具有一定的灵活性。 从Resteasy客户端代理Spring Data的查询派生中可以知道动态API。 两者都是生活在用户区代码中的接口。 Resteasy / Spring Data检查接口并通过提供Java代理来实现这些接口。 这些接口(代理)上的方法调用将被拦截,检查并转换为相应的调用。 让我们看一下这对于Java和Redis如何工作:

一个简单的命令界面

public interface MyRedisCommands {

  List<String> hmget(String key, String... values);

}

上面的接口声明了一个方法: List<String > hmget(String key, String... fields) 。 我们可以从该声明中得出某些信息:

  • 它应该同步执行-结果类型中没有声明异步或反应性包装器
  • Redis命令方法返回一个String List -告诉我们有关命令结果的期望,因此我们期望使用Redis数组并将每个项目转换为字符串
  • 该方法名为hmget 。 因为这是唯一可用的详细信息,所以我们假设命令名为hmget
  • 定义了两个参数: String keyString... values 。 这告诉我们参数的顺序及其类型。 尽管Redis除了批量字符串外不接受任何其他参数类型,我们仍然可以对参数进行转换-我们可以从声明的类型中推断出它们的序列化。

从上面调用的命令如下所示:

commands.hmget("key", "field1", "field2");

并转换为Redis命令:

HMGET key field1 field2

接口上的声明带有两个有趣的属性:

  1. 有一个方法签名。 尽管这是一个显而易见的事实,但这是一个常见的可执行文件。 它允许通过搜索此方法的引用来快速分析呼叫者。
  2. 在方法签名上方有一个空格,理想用于文档目的。

多种执行模型

public interface MyRedisCommands {

  List<String> hmget(Timeout timeout, String key, String... values);

  RedisFuture<List<String>> mget(String... keys);

  Flux<String> smembers(String key);

}

动态API允许返回类型的差异。 让我们看看这如何影响我们可以从其返回类型派生的事物。

  • 您已经知道hmget以阻塞方式执行。 但是,等等,那Timeout参数是什么? 这是一个自己的参数类型,可以在调用级别上声明超时。 基础执行将从参数应用超时,而不再是在连接级别设置的默认值。
  • mget声明一个RedisFuture返回类型,该返回类型包装StringListRedisFuture是用于异步执行的包装器类型,它返回一个句柄以在稍后阶段执行同步或方法链接。 该方法可以异步执行。
  • smembers使用String Flux 。 基于返回类型,我们可以期待两个属性: Flux是一个反应式执行包装器,它将延迟执行直到订阅者订阅Flux为止。 List类型消失了,因为Flux可以发出0..N项目,因此我们可以决定进行流式响应执行。

命令结构

public interface MyRedisCommands {

  List<String> mget(String... keys);

  @Command("MGET")
  RedisFuture<List<String>> mgetAsync(String... keys);

  @CommandNaming(strategy = DOT)
  double nrRun(String key, int... indexes)

  @Command("NR.OBSERVE ?0 ?1 -> ?2 TRAIN")
  List<Integer> nrObserve(String key, int[] in, int... out)
}

Java要求方法的名称或参数类型有所不同。 字节码级别仅支持返回类型的差异,但在代码中编写方法时则不支持。 如果要声明一个同步执行的方法和一个采用相同参数异步执行的方法,该怎么办? 您需要指定其他名称。 但是,这是否与先前解释的名称派生冲突? 是的

  • 仔细看看mgetmgetAsync 。 两种方法均旨在同步和异步执行MGET命令。 mgetAsync带有@Command注释,该命令为命令提供命令名称,并覆盖该方法否则将被命名为MGETASYNC的假设。
  • Redis对模块开放。 每个模块都可以通过提供新命令来扩展Redis,其中命令模式遵循<PREFIX>。<COMMAND>准则。 但是,Java方法名称中不允许使用点。 让我们使用@CommandNaming(strategy = DOT)将不同的命名策略应用于nrRun 。 骆驼驼峰(字母大小写的变化)通过在各个命令段之间放置一个点来表示,我们很NR.RUNNeural Redis运行NR.RUN
  • 某些命令带有更复杂的语法,该语法不允许仅串联参数。 看一下NR.OBSERVE 。 它由三个静态部分组成,中间部分之间有参数。 该命令结构以类似命令的语言表示。 NR.OBSERVE ?0 ?1 -> ?2 TRAIN将命令描述为字符串,并为参数添加索引引用。 命令中的所有字符串部分都是常量,参数引用将替换为实际参数。

结论

将动态API应用于Redis可以将视图转移到新的视角。 它可以为用户提供简化的自定义命令方法,而不会牺牲可重用性。 方法声明的性质为调用者的文档记录和自省创造了条件。

动态API也对使用RESP的其他应用程序(例如DisqueTile38)也有利

可从Sonatype的OSS Snapshot存储库https://oss.sonatype.org/content/repositories/snapshots/通过生菜获得实验性实现:

<dependency>
     <groupId>biz.paluch.redis</groupId>
     <artifactId>lettuce</artifactId>
     <version>5.0.0-dynamic-api-SNAPSHOT</version>
</dependency>

使用RedisCommandFactory

RedisCommandFactory factory = new RedisCommandFactory(connection);

TestInterface api = factory.getCommands(TestInterface.class);
String value = api.get("key");

public interface TestInterface {

  String get(String key);

  @Command("GET")
  byte[] getAsBytes(String key);
}

参考

  • @Command :命令注释通过使用类似命令的语言来指定命令名称或整个命令结构。
  • @CommandNaming :用于指定命令命名策略的注释。
  • Timeout :包含超时的值对象。
  • RedisFuture :未来结果句柄。
  • FluxProject Reactor发布者,用于执行0..N项目的反应式执行。

翻译自: https://www.javacodegeeks.com/2016/10/command-interfaces-approaching-redis-dynamic-apis-java.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值