Lettuce 异步操作源码分析

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,从而实现数据的命令的发送以及刷新。
现总结如下

  1. 首先通过调用的方法以及入参,创建对应的Command对象。
  2. 根据传递的key,确定操作的槽位信息,从而确定所要操作的节点。
  3. 通过发送命令,确定操作结果。

到此,整个分析过程已完成,不足之处,请各位大佬不吝指教。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我要做个有钱人2020

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值