Lettuce 异步Get操作源码分析
1、如何基于异步操作Lettuce
Lettuce官网中,针对于异步操作,存在如下描述
lettuce futures can be used for initial and chaining operations. When using lettuce futures, you will notice the non-blocking behavior. This is because all I/O and command processing are handled asynchronously using the netty EventLoop. The lettuce RedisFuture extends a CompletionStage so all methods of the base type are available.
通过如下代码,进行异步操作
RedisAdvancedClusterAsyncCommands<String, String> asyncCommands = connection.async();
RedisFuture<String> redisFuture = asyncCommands.get("zhangsan");
redisFuture.thenAccept(System.out::println);
LettuceFutures.awaitAll(1, TimeUnit.MINUTES, redisFuture);
到此,整个异步操作就算完成了,是不是很简单。那么,Lettuce内部是如何操作的呢?
2、异步操作代码分析
首先,我们通过asyncCommands.get方法,发现其内部通过调用dispatch方法,实现的数据处理,现看一下dispatch方法的定义。
public <T> AsyncCommand<K, V, T> dispatch(RedisCommand<K, V, T> cmd) {
AsyncCommand<K, V, T> asyncCommand = new AsyncCommand<>(cmd);
RedisCommand<K, V, T> dispatched = connection.dispatch(asyncCommand);
if (dispatched instanceof AsyncCommand) {
return (AsyncCommand<K, V, T>) dispatched;
}
return asyncCommand;
}
可以发现,get方法内部,首先将get(key)操作,构造成一个command,然后将其作为入参,进行dispatch方法调用。现,我们具体看一下command命令如何构造。
Command<K, V, V> get(K key) {
notNullKey(key);
return createCommand(GET, new ValueOutput<>(codec), key);
}
protected <T> Command<K, V, T> createCommand(CommandType type, CommandOutput<K, V, T> output, K key) {
CommandArgs<K, V> args = new CommandArgs<K, V>(codec).addKey(key);
return createCommand(type, output, args);
}
protected <T> Command<K, V, T> createCommand(CommandType type, CommandOutput<K, V, T> output, CommandArgs<K, V> args) {
return new Command<K, V, T>(type, output, args);
}
发现,其内部分别通过创建通过构造CommandOutput以及CommandArgs对象,Command对象,来创建命令对象。到此,与Redis操作相关的命令、入参、返回值对象,已全部存在在Command对象。
接下来,让我们一起看一下dispatch方法实现
public <T> AsyncCommand<K, V, T> dispatch(RedisCommand<K, V, T> cmd) {
AsyncCommand<K, V, T> asyncCommand = new AsyncCommand<>(cmd);
RedisCommand<K, V, T> dispatched = connection.dispatch(asyncCommand);
if (dispatched instanceof AsyncCommand) {
return (AsyncCommand<K, V, T>) dispatched;
}
return asyncCommand;
}
内部通过connection进行的dispatch操作。
@Override
public <T> RedisCommand<K, V, T> dispatch(RedisCommand<K, V, T> command) {
return super.dispatch(preProcessCommand(command));
}
protected <T> RedisCommand<K, V, T> dispatch(RedisCommand<K, V, T> cmd) {
if (debugEnabled) {
logger.debug("dispatching command {}", cmd);
}
if (tracingEnabled) {
RedisCommand<K, V, T> commandToSend = cmd;
TraceContextProvider provider = CommandWrapper.unwrap(cmd, TraceContextProvider.class);
if (provider == null) {
commandToSend = new TracedCommand<>(cmd, clientResources.tracing()
.initialTraceContextProvider().getTraceContext());
}
return channelWriter.write(commandToSend);
}
return channelWriter.write(cmd);
}
内部,通过channelWriter的write方法,进行Command对象操作。
private <K, V, T> RedisCommand<K, V, T> doWrite(RedisCommand<K, V, T> command) {
if (command instanceof ClusterCommand && !command.isDone()) {
ClusterCommand<K, V, T> clusterCommand = (ClusterCommand<K, V, T>) command;
if (clusterCommand.isMoved() || clusterCommand.isAsk()) {
HostAndPort target;
boolean asking;
if (clusterCommand.isMoved()) {
target = getMoveTarget(clusterCommand.getError());
clusterEventListener.onMovedRedirection();
asking = false;
} else {
target = getAskTarget(clusterCommand.getError());
asking = true;
clusterEventListener.onAskRedirection();
}
command.getOutput().setError((String) null);
CompletableFuture<StatefulRedisConnection<K, V>> connectFuture = asyncClusterConnectionProvider
.getConnectionAsync(Intent.WRITE, target.getHostText(), target.getPort());
if (isSuccessfullyCompleted(connectFuture)) {
writeCommand(command, asking, connectFuture.join(), null);
} else {
connectFuture.whenComplete((connection, throwable) -> writeCommand(command, asking, connection, throwable));
}
return command;
}
}
ClusterCommand<K, V, T> commandToSend = getCommandToSend(command);
CommandArgs<K, V> args = command.getArgs();
// exclude CLIENT commands from cluster routing
if (args != null && !CommandType.CLIENT.equals(commandToSend.getType())) {
ByteBuffer encodedKey = args.getFirstEncodedKey();
if (encodedKey != null) {
int hash = getSlot(encodedKey);
Intent intent = getIntent(command.getType());
CompletableFuture<StatefulRedisConnection<K, V>> connectFuture = ((AsyncClusterConnectionProvider) clusterConnectionProvider)
.getConnectionAsync(intent, hash);
if (isSuccessfullyCompleted(connectFuture)) {
writeCommand(commandToSend, false, connectFuture.join(), null);
} else {
connectFuture.whenComplete((connection, throwable) -> writeCommand(commandToSend, false, connection,
throwable));
}
return commandToSend;
}
}
writeCommand(commandToSend, defaultWriter);
return commandToSend;
}
其中,特别需要注意如下代码
int hash = getSlot(encodedKey);
Intent intent = getIntent(command.getType());
CompletableFuture<StatefulRedisConnection<K, V>> connectFuture = ((AsyncClusterConnectionProvider) clusterConnectionProvider)
.getConnectionAsync(intent, hash);
通过key,获取其对应的槽位值,以及对应的Intent。在getConnectionAsync方法内部
RedisClusterNode partition = partitions.getPartitionBySlot(slot);
if (partition == null) {
throw new PartitionSelectorException("Cannot determine a partition for slot " + slot + ".", partitions.clone());
}
// Use always host and port for slot-oriented operations. We don't want to get reconnected on a different
// host because the nodeId can be handled by a different host.
RedisURI uri = partition.getUri();
ConnectionKey key = new ConnectionKey(Intent.WRITE, uri.getHost(), uri.getPort());
ConnectionFuture<StatefulRedisConnection<K, V>> future = getConnectionAsync(key);
通过槽位,确定其对应的节点信息,从而获取其对应的host以及port信息,从而得到对应连接。
接下来,看起writeCommand方法
private static <K, V> void writeCommand(RedisCommand<K, V, ?> command, boolean asking,
StatefulRedisConnection<K, V> connection, Throwable throwable) {
if (throwable != null) {
command.completeExceptionally(throwable);
return;
}
try {
if (asking) { // set asking bit
connection.async().asking();
}
writeCommand(command, ((RedisChannelHandler<K, V>) connection).getChannelWriter());
} catch (Exception e) {
command.completeExceptionally(e);
}
}
private static <K, V> void writeCommand(RedisCommand<K, V, ?> command, RedisChannelWriter writer) {
try {
getWriterToUse(writer).write(command);
} catch (Exception e) {
command.completeExceptionally(e);
}
}
此处的write方法,对应于DefaultEndpoint
public <K, V, T> RedisCommand<K, V, T> write(RedisCommand<K, V, T> command) {
LettuceAssert.notNull(command, "Command must not be null");
try {
sharedLock.incrementWriters();
validateWrite(1);
if (autoFlushCommands) {
if (isConnected()) {
writeToChannelAndFlush(command);
} else {
writeToDisconnectedBuffer(command);
}
} else {
writeToBuffer(command);
}
} finally {
sharedLock.decrementWriters();
if (debugEnabled) {
logger.debug("{} write() done", logPrefix());
}
}
return command;
}
到此,发现,通过writeToChannelAndFlush,从而实现数据的命令的发送以及刷新。
现总结如下
- 首先通过调用的方法以及入参,创建对应的Command对象。
- 根据传递的key,确定操作的槽位信息,从而确定所要操作的节点。
- 通过发送命令,确定操作结果。
到此,整个分析过程已完成,不足之处,请各位大佬不吝指教。