水煮Redisson(二一)-批处理

前言

这一章介绍Redisson中的批处理类【RBatch】,功能上类似redis的管道操作,在同一个请求中,发送多条指令,节省网络I/O时间。

下面展示一个例子,测试用例中,一次发送了三条Map查询指令,然后同时返回。

scss 复制代码
    @Test
    public void testBatch() throws InterruptedException {
        // 创建redis链接
        RedissonClient redissonClient = create();
        // 创建批处理对象
        RBatch batch = redissonClient.createBatch(BatchOptions.defaults());
        // 批操作
        batch.getMap("TEST_MAP").getAsync("KEY_11");
        batch.getMap("TEST_MAP").getAsync("KEY_22");
        batch.getMap("TEST_MAP222").getAsync("KEY_33");
        // 异步执行
        RFuture<BatchResult<?>> executeAsync = batch.executeAsync();
        executeAsync.onComplete((r, e) -> {
            log.info("======================批处理结果:{}", r.getResponses());
        });
        // 等待结果
        executeAsync.await(5, TimeUnit.MINUTES);
    }

关键字

  • CommandBatchService:批处理的入口
  • RedisCommonBatchExecutor:批处理执行器,继承了【RedisExecutor】类,在上一章着重介绍过。
  • commands:指令集合,类型为ConcurrentMap,MasterSlaveEntry是一个slot的映射,表示相同slot的指令,都会存放到同一个Entry中。上面的测试用例,有两个map key,假设这两个key分布在不同的slot中,commands集合就会有两个元素,图示如下:

添加指令

添加指令的入口是CommandBatchService.async(...),判断操作对象是否在同一个slot,如果是的话,指令会放在一批发送,如果不是,则单独创建一个Entry,并放入到commands集合。后续执行和获取结果,都需要用到此集合。

ini 复制代码
protected final void addBatchCommandData(Object[] batchParams) {
    MasterSlaveEntry msEntry = getEntry(source);
    Entry entry = commands.get(msEntry);
	// 判断slot是否在批对象中存在,如果不存在,则需要创建
    if (entry == null) {
        commands.putIfAbsent(msEntry, new Entry());
    }
    Codec codecToUse = getCodec(codec);
	// 新建批指令
    BatchCommandData<V, R> commandData = new BatchCommandData<V, R>(mainPromise, codecToUse, command, batchParams, index.incrementAndGet());
	// 批指令加入到slot
    entry.getCommands().add(commandData);
}

批处理入口

执行方法的入口:CommandBatchService.executeAsync(...),方法中触发执行和结果监听回传。

ini 复制代码
public RFuture<BatchResult<?>> executeAsync() {
    RPromise<BatchResult<?>> promise = new RedissonPromise<>();
    RPromise<Void> voidPromise = new RedissonPromise<Void>();
    // 结果监听
    voidPromise.onComplete((res, ex) -> {
        executed.set(true);
        // 整理指令
        List<BatchCommandData> entries = new ArrayList<BatchCommandData>();
        // commands的类型,ConcurrentMap<MasterSlaveEntry, Entry>,如果是相同slot,那么放在一批执行
        for (Entry e : commands.values()) {
            entries.addAll(e.getCommands());
        }
        // 排序,这里不太明白为什么要排序
        Collections.sort(entries);
        // 接收批处理结果
        List<Object> responses = new ArrayList<Object>(entries.size());
        int syncedSlaves = 0;
        for (BatchCommandData<?, ?> commandEntry : entries) {
            Object entryResult = commandEntry.getPromise().getNow();
            responses.add(entryResult);
        }
        // 结果组装
        BatchResult<Object> result = new BatchResult<Object>(responses, syncedSlaves);
        // 触发单个指令的成功事件,如果此promise注册了监听,则会立即触发
        promise.trySuccess(result);
    });
    // 每个指令,用RedisCommonBatchExecutor类单独执行
    for (Map.Entry<MasterSlaveEntry, Entry> e : commands.entrySet()) {
        // 相同slot,放在一批执行
        RedisCommonBatchExecutor executor = new RedisCommonBatchExecutor(new NodeSource(e.getKey()), voidPromise,
                                                connectionManager, this.options, e.getValue(), slots);
        executor.execute();
    }
    return promise;
}

执行指令

RedisCommonBatchExecutor:批处理执行器,继承了【RedisExecutor】类,主要重写了方法【sendCommand】,因为这里发送的是一个指令集合。

less 复制代码
@Override
protected void sendCommand(RPromise<Void> attemptPromise, RedisConnection connection) {
    // 同一个slot中的指令集合
    List<CommandData<?, ?>> list = new ArrayList<>(entry.getCommands().size());
    // 集体发送到redis服务
    writeFuture = connection.send(new CommandsData(attemptPromise, list, options.isSkipResult(), isAtomic, isQueued, options.getSyncSlaves() > 0));
}

发送到redis服务

上面代码中,connection.send方法,后续会对指令进行编码【CommandBatchEncoder】。在下面展示的代码中,可以看到redisson中的批处理,没有使用redis服务的pipeline指令,而是通过遍历,将集合中的指令,分条进行编码,然后一起发送到redis服务。

csharp 复制代码
    @Override
    protected void encode(ChannelHandlerContext ctx, CommandsData msg, ByteBuf out) throws Exception {
        CommandEncoder encoder = ctx.pipeline().get(CommandEncoder.class);
        for (CommandData<?, ?> commandData : msg.getCommands()) {
			// 对单条指令进行编码
            encoder.encode(ctx, commandData, out);
        }
    }
相关推荐
Cyanto几秒前
深入MyBatis:CRUD操作与高级查询实战
java·数据库·mybatis
麦兜*39 分钟前
Spring Boot 集成Reactive Web 性能优化全栈技术方案,包含底层原理、压测方法论、参数调优
java·前端·spring boot·spring·spring cloud·性能优化·maven
天上掉下来个程小白42 分钟前
MybatisPlus-06.核心功能-自定义SQL
java·spring boot·后端·sql·微服务·mybatisplus
知了一笑1 小时前
独立开发第二周:构建、执行、规划
java·前端·后端
今天背单词了吗9802 小时前
算法学习笔记:17.蒙特卡洛算法 ——从原理到实战,涵盖 LeetCode 与考研 408 例题
java·笔记·考研·算法·蒙特卡洛算法
Dcs2 小时前
从 C 到 Rust:一位开发者的 `tmux` 全面移植之旅
java
Maybyy2 小时前
力扣242.有效的字母异位词
java·javascript·leetcode
小小寂寞的城3 小时前
JAVA观察者模式demo【设计模式系列】
java·观察者模式·设计模式
xinghunzhiye20103 小时前
redis升级
数据库·redis·缓存
探索java3 小时前
Java并发编程中的StampedLock详解:原理、实践与性能优化
java·stampedlock