6.snail-job的MapReduce任务

前言

​ 上一节《5.snail-job的Map任务》中已经对Map任务有了大致的了解。并且上节中已经预告了本节介绍的任务类型-MapReduce。在Map任务的基础上再加上汇总的方法,就是MapReduce任务了。按照小学语文老师交给我们的写作技巧:总分总的关系来看。Map任务仅仅是总分关系,而MapReduce就是总分总关系了。

​ 可能你对上面的描述还有所疑惑,不过咱们通过上节的例子,和本节例子的结合对比,就能有所共鸣了。

本节目标

​ 上一节的例子是:200个数字,切成4个片,每个片中有50个数,对每个切片进行汇总计算。本节在这个基础上最后再多一步计算最终汇总值。从本例中了解的知识点如下:

  • 客户端采用继承类方式实现MapReduce任务
  • 客户端采用注解方式实现MapReduce任务
  • 服务器端配置分片数的作用

客户端代码1

这里针对的是reduce分片数设置 = 1的情况,后面会解释这个分片数的作用。

开发环境

  • JDK版本:openjdk-21.0.2
  • snail-job版本:1.2.0

Maven依赖

xml 复制代码
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- snail-job 客户端依赖 -->
        <dependency>
            <groupId>com.aizuda</groupId>
            <artifactId>snail-job-client-starter</artifactId>
            <version>1.2.0</version>
        </dependency>
        <!-- snail-job 重试相关依赖 -->
        <dependency>
            <groupId>com.aizuda</groupId>
            <artifactId>snail-job-client-retry-core</artifactId>
            <version>1.2.0</version>
        </dependency>
        <!-- snail-job 客户端核心依赖 -->
        <dependency>
            <groupId>com.aizuda</groupId>
            <artifactId>snail-job-client-job-core</artifactId>
            <version>1.2.0</version>
        </dependency>
    </dependencies>

继承类方式

java 复制代码
@Component
public class TestMapReduce1 extends AbstractMapReduceExecutor {

    @Override
    public ExecuteResult doJobMapExecute(MapArgs mapArgs, MapHandler mapHandler) {
        return switch (mapArgs.getTaskName()) {
            case SystemConstants.ROOT_MAP -> {
                // 生成1~200数值并分片
                int partitionSize = 50;
                List<List<Integer>> partition = IntStream.rangeClosed(1, 200)
                        .boxed()
                        .collect(Collectors.groupingBy(i -> (i - 1) / partitionSize))
                        .values()
                        .stream()
                        .toList();
                SnailJobLog.REMOTE.info("端口:{}完成分配任务", SpringUtil.getProperty("server.port"));
                yield mapHandler.doMap(partition, "doCalc");
            }
            case "doCalc" -> {
                List<Integer> sourceList = (List<Integer>) mapArgs.getMapResult();
                // 遍历sourceList的每一个元素,计算出一个累加值partitionTotal
                int partitionTotal = sourceList.stream().mapToInt(i -> i).sum();
                // 打印日志到服务器
                ThreadUtil.sleep(3, TimeUnit.SECONDS);
                SnailJobLog.REMOTE.info("端口:{},partitionTotal:{}", SpringUtil.getProperty("server.port"), partitionTotal);
                yield ExecuteResult.success(partitionTotal);
            }
            default -> ExecuteResult.failure();
        };
    }

    @Override
    protected ExecuteResult doReduceExecute(ReduceArgs reduceArgs) {
        // 数据进行累加计算
        int reduceTotal = reduceArgs.getMapResult().stream().mapToInt(i -> Integer.parseInt((String) i)).sum();
        SnailJobLog.REMOTE.info("端口:{},reduceTotal:{}", SpringUtil.getProperty("server.port"), reduceTotal);
        return ExecuteResult.success(reduceTotal);
    }

    @Override
    protected ExecuteResult doMergeReduceExecute(MergeReduceArgs mergeReduceArgs) {
        return null;
    }
}

解释说明:

  • 通过继承AbstractMapReduceExecutor类实现MapReduce任务
  • 其中的doJobMapExecute和上一节的作用一样,都是通过任务名称,来区分是ROOT_MAP任务还是分片后的处理。
  • doReduceExecute方法,如果配置的reduce分片数是1的话,那么会随机调用1个客户端进行reduce【汇总】操作。
  • doMergeReduceExecute方法是在配置reduce分片数>1的时候才会去执行。也是随机调用一个存活的客户端去执行。本例采用的reduce分片数=1,故这里没有任何代码。详细配置见服务端配置1

注解方式

java 复制代码
@Component
@JobExecutor(name = "testMapReduceAnnotation1")
public class TestMapReduceAnnotation1 {

    @MapExecutor
    public ExecuteResult rootMapExecute(MapArgs mapArgs, MapHandler mapHandler) {
        int partitionSize = 50;
        List<List<Integer>> partition = IntStream.rangeClosed(1, 200)
                .boxed()
                .collect(Collectors.groupingBy(i -> (i - 1) / partitionSize))
                .values()
                .stream()
                .toList();
        SnailJobLog.REMOTE.info("端口:{}完成分配任务", SpringUtil.getProperty("server.port"));
        return mapHandler.doMap(partition, "doCalc");
    }

    @MapExecutor(taskName = "doCalc")
    public ExecuteResult doCalc(MapArgs mapArgs) {
        List<Integer> sourceList = (List<Integer>) mapArgs.getMapResult();
        // 遍历sourceList的每一个元素,计算出一个累加值partitionTotal
        int partitionTotal = sourceList.stream().mapToInt(i -> i).sum();
        // 打印日志到服务器
        ThreadUtil.sleep(3, TimeUnit.SECONDS);
        SnailJobLog.REMOTE.info("端口:{},partitionTotal:{}", SpringUtil.getProperty("server.port"), partitionTotal);
        return ExecuteResult.success(partitionTotal);
    }

    @ReduceExecutor
    public ExecuteResult reduceExecute(ReduceArgs reduceArgs) {
        int reduceTotal = reduceArgs.getMapResult().stream().mapToInt(i -> Integer.parseInt((String) i)).sum();
        SnailJobLog.REMOTE.info("端口:{},reduceTotal:{}", SpringUtil.getProperty("server.port"), reduceTotal);
        return ExecuteResult.success(reduceTotal);
    }
}

解释说明:

  • 通过@JobExecutor注解标识该类是一个定时任务
  • 通过@MapExecutor注解来来处理分片、分片后的任务处理
  • 通过@ReduceExecutor注解来处理汇总任务
  • 由于配置的reduce分片数是1,所以这里用到把上面@ReduceExecutor汇总任务执行结果再次进行合并操作的注解

示意图

服务端配置1

继承类方式

配置项 配置内容
任务名称 MapReduce任务1-继承类
状态 禁用
任务类型 MapReduce
自定义执行器 com.mayuanfei.test.TestMapReduce1
reduce分片数 1
并行数 1

说明:

  • 状态:状态设置为禁用,是想通过手动执行来触发
  • 任务类型:要选本节介绍的任务类型MapReduce
  • 自定义执行器:继承类的方式要写全路径【复习】
  • 并行数:指客户端每台机器的线程数【复习】
  • reduce分片数:它设置的数量决定的doReduceExecute由几台客户端来执行。当它设置的数>1时,还会随机抽取一台客户端执行doMergeReduceExecute方法

注解方式

配置项 配置内容
任务名称 MapReduce任务1-注解
状态 禁用
任务类型 MapReduce
自定义执行器 testMapReduceAnnotation1
reduce分片数 1
并行数 1

说明:

  • 自定义执行器 : 与注解@JobExecutor(name = "testMapReduceAnnotation1")名称一致【复习】

客户端代码1测试

这里不管是继承类方式,还是注解方式测试的结果都是一样的。

测试前提

web端口 snail-job的客户端端口
9100 1900
9200 2900

可以参考《3.snail-job广播任务》的本机两个客户端启动章节的介绍。idea中配置如下:

shell 复制代码
ver.port=9100 -Dsnail-job.port=1900
-Dserver.port=9200 -Dsnail-job.port=2900

测试MapReduce任务

  • 9100Web端口

  • 9200Web端口

服务端管理页面

客户端代码2

这里针对reduce分片数设置>1的情况,会有reduce分片数设置的数量的客户端执行reduce方法。最后通过合并汇总方法来执行最后的运算。

继承类方式

java 复制代码
@Component
public class TestMapReduce2 extends AbstractMapReduceExecutor {

    @Override
    public ExecuteResult doJobMapExecute(MapArgs mapArgs, MapHandler mapHandler) {
        return switch (mapArgs.getTaskName()) {
            case SystemConstants.ROOT_MAP -> {
                // 生成1~200数值并分片
                int partitionSize = 50;
                List<List<Integer>> partition = IntStream.rangeClosed(1, 200)
                        .boxed()
                        .collect(Collectors.groupingBy(i -> (i - 1) / partitionSize))
                        .values()
                        .stream()
                        .toList();
                SnailJobLog.REMOTE.info("端口:{}完成分配任务", SpringUtil.getProperty("server.port"));
                yield mapHandler.doMap(partition, "doCalc");
            }
            case "doCalc" -> {
                List<Integer> sourceList = (List<Integer>) mapArgs.getMapResult();
                // 遍历sourceList的每一个元素,计算出一个累加值partitionTotal
                int partitionTotal = sourceList.stream().mapToInt(i -> i).sum();
                // 打印日志到服务器
                ThreadUtil.sleep(3, TimeUnit.SECONDS);
                SnailJobLog.REMOTE.info("端口:{},partitionTotal:{}", SpringUtil.getProperty("server.port"), partitionTotal);
                yield ExecuteResult.success(partitionTotal);
            }
            default -> ExecuteResult.failure();
        };
    }

    @Override
    protected ExecuteResult doReduceExecute(ReduceArgs reduceArgs) {
        // 数据进行累加计算
        int reduceTotal = reduceArgs.getMapResult().stream().mapToInt(i -> Integer.parseInt((String) i)).sum();
        SnailJobLog.REMOTE.info("端口:{},reduceTotal:{}", SpringUtil.getProperty("server.port"), reduceTotal);
        return ExecuteResult.success(reduceTotal);
    }

    @Override
    protected ExecuteResult doMergeReduceExecute(MergeReduceArgs mergeReduceArgs) {
        // 把reduce的结果进行累加计算
        int mergeReduceTotal = mergeReduceArgs.getReduces().stream().mapToInt(i -> Integer.parseInt((String) i)).sum();
        SnailJobLog.REMOTE.info("端口:{},mergeReduceTotal:{}", SpringUtil.getProperty("server.port"), mergeReduceTotal);
        return ExecuteResult.success(mergeReduceTotal);
    }
}

说明:

  • 只有reduce分片数>1时,doMergeReduceExecute方法才会被触发执行
  • 并且仅一个客户端节点执行doMergeReduceExecute方法

注解方式

java 复制代码
@Component
@JobExecutor(name = "testMapReduceAnnotation2")
public class TestMapReduceAnnotation2 {

    @MapExecutor
    public ExecuteResult rootMapExecute(MapArgs mapArgs, MapHandler mapHandler) {
        int partitionSize = 50;
        List<List<Integer>> partition = IntStream.rangeClosed(1, 200)
                .boxed()
                .collect(Collectors.groupingBy(i -> (i - 1) / partitionSize))
                .values()
                .stream()
                .toList();
        SnailJobLog.REMOTE.info("端口:{}完成分配任务", SpringUtil.getProperty("server.port"));
        return mapHandler.doMap(partition, "doCalc");
    }

    @MapExecutor(taskName = "doCalc")
    public ExecuteResult doCalc(MapArgs mapArgs) {
        List<Integer> sourceList = (List<Integer>) mapArgs.getMapResult();
        // 遍历sourceList的每一个元素,计算出一个累加值partitionTotal
        int partitionTotal = sourceList.stream().mapToInt(i -> i).sum();
        // 打印日志到服务器
        ThreadUtil.sleep(3, TimeUnit.SECONDS);
        SnailJobLog.REMOTE.info("端口:{},partitionTotal:{}", SpringUtil.getProperty("server.port"), partitionTotal);
        return ExecuteResult.success(partitionTotal);
    }

    @ReduceExecutor
    public ExecuteResult reduceExecute(ReduceArgs reduceArgs) {
        int reduceTotal = reduceArgs.getMapResult().stream().mapToInt(i -> Integer.parseInt((String) i)).sum();
        SnailJobLog.REMOTE.info("端口:{},reduceTotal:{}", SpringUtil.getProperty("server.port"), reduceTotal);
        return ExecuteResult.success(reduceTotal);
    }

    @MergeReduceExecutor
    public ExecuteResult mergeReduceExecute(MergeReduceArgs mergeReduceArgs) {
        // 把reduce的结果进行累加计算
        int mergeReduceTotal = mergeReduceArgs.getReduces().stream().mapToInt(i -> Integer.parseInt((String) i)).sum();
        SnailJobLog.REMOTE.info("端口:{},mergeReduceTotal:{}", SpringUtil.getProperty("server.port"), mergeReduceTotal);
        return ExecuteResult.success(mergeReduceTotal);
    }
}

说明:

和继承类方式完全一致,只不过这里采用注解方式实现。同理@MergeReduceExecutor只有reduce分片数>1时才有意义。

示意图

服务端配置2

继承类方式

配置项 配置内容
任务名称 MapReduce任务1-继承类
状态 禁用
任务类型 MapReduce
自定义执行器 com.mayuanfei.test.TestMapReduce2
reduce分片数 2
并行数 1

说明:

这里reduce分片数=2

注解方式

配置项 配置内容
任务名称 MapReduce任务2-注解
状态 禁用
任务类型 MapReduce
自定义执行器 testMapReduceAnnotation2
reduce分片数 2
并行数 1

说明:

这里reduce分片数=2

客户端代码2测试

测试前提

web端口 snail-job的客户端端口
9100 1900
9200 2900

可以参考《3.snail-job广播任务》的本机两个客户端启动章节的介绍。idea中配置如下:

shell 复制代码
-Dserver.port=9100 -Dsnail-job.port=1900
-Dserver.port=9200 -Dsnail-job.port=2900

测试MapReduce任务

  • 9100Web端口

  • 9200Web端口

服务端管理页面

总结

  • MapReduce任务就是一个总分总的过程。
    • @MapExecutor【总】
    • @MapExecutor(taskName="doCalc") 【分】
    • @ReduceExecutor【总】
  • MapReduce任务用注解方式似乎更容易理解。
  • reduce分片数的作用:
    • 等于1时:@ReduceExecutor会随机客户端执行,@MergeReduceExecutor无效。
    • 大于1时:@ReduceExecutor按照指定配置数量随机找出后执行,@MergeReduceExecutor会随机找一台客户端执行
  • 分片数量不建议过多(大于200时会提示分片过多,最多不能超过500个分片)
相关推荐
MrZhangBaby3 分钟前
SQL-leetcode—1158. 市场分析 I
java·sql·leetcode
一只淡水鱼6617 分钟前
【spring原理】Bean的作用域与生命周期
java·spring boot·spring原理
五味香23 分钟前
Java学习,查找List最大最小值
android·java·开发语言·python·学习·golang·kotlin
jerry-8937 分钟前
Centos类型服务器等保测评整/etc/pam.d/system-auth
java·前端·github
Jerry Lau38 分钟前
大模型-本地化部署调用--基于ollama+openWebUI+springBoot
java·spring boot·后端·llama
工业甲酰苯胺39 分钟前
深入解析 Spring AI 系列:解析返回参数处理
javascript·windows·spring
小白的一叶扁舟42 分钟前
Kafka 入门与应用实战:吞吐量优化与与 RabbitMQ、RocketMQ 的对比
java·spring boot·kafka·rabbitmq·rocketmq
幼儿园老大*43 分钟前
【系统架构】如何设计一个秒杀系统?
java·经验分享·后端·微服务·系统架构
言之。1 小时前
【Java】面试中遇到的两个排序
java·面试·排序算法
计算机-秋大田1 小时前
基于SSM的家庭记账本小程序设计与实现(LW+源码+讲解)
java·前端·后端·微信小程序·小程序·课程设计