【大批量数据处理方式】monggoDB+xxxJob+rabbitMQ逐步提升查询性能

需求背景

当需要从monggoDB查询除符合条件的数据,把数据进行统计汇总,展示到前端

处理方式

从简单到难依次的处理方式:
1.直接从monggoDB中拿出来数据,对数据进行处理展示;(适合少批量且不容易修改的数据)
2.分批从monggoDB中拿出来数据,对数据进行处理展示;(适合中批量的数据)
3.分批从monggoDB中拿出来数据,对数据进行清理,组建成具体的sql,插入到新的表中,由于数据是每天实时更新的,所以需要对这个数据清理进行定时任务xxx-job的操作,让每天自动进行数据处理,这里处理时间的掌控,最好是前一天的最后时刻(比如定时任务00:00执行,就拿数据库里面统计的最后时间到前一天最后一秒来进行处理)
4.把这个数据处理的的操作放入到RabbitMQ中,让单线程的数据处理,改成多线程的方式执行

疑问

实际上,我们在第3种处理方式上,可以在具体的数据清理的方法内,加入java自带的多线程处理,但是由于对生产环境的一些配置:比如最大线程数之类的不太清楚,所以就想到了用MQ的方式处理,JAVA自带的多线程处理方式如下面这位SakamotoLz大佬的的文中所述:

SpringBoot + MongoDB 大容量数据多线程分批处理(示例:抽取字段构建新表)

大批量数据处理的不同实现方式

monggoDB 数据获取方式

太详细的东西咱们就不说了,咱们就直接看具体的代码实现

普通查和分批查

首先注入对应的对象

@Autowired
private MongoTemplate mongoTemplate;

普通查询

 @Transactional
    public void findDate() {
 		//查询mongodb工单数据(工单 && 已完成)
        List<Criteria> andCriteriaList = Lists.newArrayList();
        // 基本条件
        Criteria baseCriteria = Criteria
                .where("具体的字段").is("对应的值");
        andCriteriaList.add(baseCriteria);
        //这里可以是多个条件,并加入到查询list里
         Query query = new Query();
         query.addCriteria(new Criteria().andOperator(andCriteriaList));
         List<"对应的monggoDB表对象"> mongodbList= mongoTemplate.find(query,  "对应的monggoDB表对象".class ,"对应的monggoDB表名");
         }

分批查询

@Getter
@Setter
@ToString
@Accessors(chain = true)
@ApiModel(value = "计数结果", description = "计数结果")
public class CountResultD {
    private Long count;

    public long getCount() {
        return count;
    }

    public void setCount(long count) {
        this.count = count;
    }
}
@Transactional
    public void findDate() {
 		//查询mongodb工单数据(工单 && 已完成)
        List<Criteria> andCriteriaList = Lists.newArrayList();
        // 基本条件
        Criteria baseCriteria = Criteria
                .where("具体的字段").is("对应的值");
        andCriteriaList.add(baseCriteria);
        //这里可以是多个条件,并加入到查询list里
        AggregationOptions aggregationOptions = AggregationOptions.builder().allowDiskUse(true).build();
            CountOperation countOperation = Aggregation.count().as("count");
            Aggregation countAggregation = Aggregation.newAggregation(
                    Aggregation.match(new Criteria().andOperator(andCriteriaList)),
                    countOperation
            ).withOptions(aggregationOptions);
            AggregationResults<CountResultD> countResult = mongoTemplate.aggregate(countAggregation, "对应的monggoDB表名", CountResultD.class);
            CountResultD countResultD = countResult.getUniqueMappedResult();
            long totalCount = countResultD!=null ? countResultD.getCount() : 0;
            log.info("总数:{}", totalCount);
            int pageSize=1000;
            //这个数值可以自己调整,意义是一次查询多少个数据进行处理;
            long totalPage= (totalCount % pageSize == 0) ? (totalCount / pageSize) : (totalCount / pageSize + 1);
            List<"对应的monggoDB表对象"> mongodbList = new ArrayList<>();
            for(long i=1;i<=totalPage;i++){
                Aggregation aggregation = Aggregation.newAggregation(
                        Aggregation.match(new Criteria().andOperator(andCriteriaList))
                ).withOptions(aggregationOptions);
                aggregation.getPipeline()
                        .add(Aggregation.skip((i - 1) * pageSize))
                        .add(Aggregation.limit(pageSize));
                List<"对应的monggoDB表对象"> dataEList = mongoTemplate.aggregate(aggregation, "对应的monggoDB表名", "对应的monggoDB表对象".class).getMappedResults();
                mongodbList.addAll(dataEList);
            }
            //数据进行处理
            dateHand(mongodbList);
}

这样就直接拿出来所以的符合条件的数据并对数据进行处理了.

xxxJob实现自动任务执行

基础的配置就稍后再讲,我们看具体的实现配置

@Component("bpmReportPolicedOrderCollectJob")
@RequiredArgsConstructor
@Slf4j
public class ReportPolicedCollectJob {
	 @XxlJob("reportPolicedOrderCollectJob")
    public ReturnT<String> reportPolicedOrderCollectAllJob(String param) {
                long startTime = System.currentTimeMillis();
                log.info("执行定时任务开始-:{} : {}");
                try {
                    processTaskService.findDate();
                } catch (Exception e) {
                    log.error("执行定时任务失败" + e);
                    e.printStackTrace();
                }
                long endTime = System.currentTimeMillis();
                log.info("执行定时任务结束");
                log.info("执行时间:" + (endTime - startTime));

        return ReturnT.SUCCESS;
    }
}

下面是XXX-job具体的配置图,值得注意的是JobHandler的配置 要跟 @XxlJob(“reportPolicedOrderCollectJob”)保持一致,且如果任务执行失败,查看是否开启了任务,有时候不开启任务,执行任务是没作用的,也会失败的.
在这里插入图片描述

rabbitMQ将处理放到mq中进行处理消费

由于放到xxx-job是单线程执行任务,当配到执行的任务数量过于多,会占用大量的服务器资源,所以我们可以用rabbitMQ多线程并发处理,将我们的任务放入到mq中,持续的消费,就可以避免过多占用服务器资源的问题.
生产者:

@MessagingGateway
@SuppressWarnings("UnresolvedMessageChannel")
public interface ReportPolicedMsgGetWay {

    @Gateway(requestChannel = "REPORT_POLICED_TASK_EVENT_OUTPUT")
    void reportPolicedTaskMsg();

}

消费者:
这里的 consumer 指的是java.util.function里面的方法,是用于接收到具体的任务,进行消费

@Component(value = "reportPolicedCollectConsumer")
@Slf4j
public class ReportPolicedCollectConsumer implements Consumer<PolicedPointReportMsg> {

    @Setter(onMethod_ = {@Autowired})
    private  ProcessTaskService taskService;

    @Override
    public void accept() {
        log.info("接收到任务状态发改变消息{}");
        taskService.findDate();
    }
}

具体的mq配置类:
application-mq-bpm.properties
一定要注意配置是否跟代码里面书写的是否保持一致;

bpm.output-bindings=reportPolicedTaskMsg
bpm.definition=reportPolicedCollectConsumer


spring.cloud.stream.function.bindings.reportPolicedTaskMsg-out-0=REPORT_POLICED_TASK_EVENT_OUTPUT
spring.cloud.stream.bindings.REPORT_POLICED_TASK_EVENT_OUTPUT.destination=REPORT_POLICED_TASK_EVENT
spring.cloud.stream.rabbit.bindings.REPORT_POLICED_TASK_EVENT_OUTPUT.producer.routing-key-expression=''


spring.cloud.stream.function.bindings.reportPolicedCollectConsumer-in-0=reportPolicedCollectConsumer_input
spring.cloud.stream.bindings.reportPolicedCollectConsumer_input.destination=REPORT_POLICED_TASK_EVENT
spring.cloud.stream.bindings.reportPolicedCollectConsumer_input.group=REPORT_POLICED_TASK_CHANGE_GROUP

修改我们之前的xxx-job方法:

@Component("bpmReportPolicedOrderCollectJob")
@RequiredArgsConstructor
@Slf4j
public class ReportPolicedCollectJob {
 @Resource
    private ReportPolicedMsgGetWay reportPolicedMsgGetWay;
	 @XxlJob("reportPolicedOrderCollectJob")
    public ReturnT<String> reportPolicedOrderCollectAllJob(String param) {
                long startTime = System.currentTimeMillis();
                log.info("执行定时任务开始-:{} : {}");
                try {
                    reportPolicedMsgGetWay.reportPolicedTaskMsg();
                } catch (Exception e) {
                    log.error("执行定时任务失败" + e);
                    e.printStackTrace();
                }
                long endTime = System.currentTimeMillis();
                log.info("执行定时任务结束");
                log.info("执行时间:" + (endTime - startTime));

        return ReturnT.SUCCESS;
    }
}

结语

希望我的这次分享能给你带来一丝考虑,让我们一起慢慢成长,早日实现自己的理想!
在这里插入图片描述

  • 22
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值