多机器并行处理的业务方案

2019-08-22  本文已影响0人  西5d

最近遇到的问题,可以总结是一个递进深入的过程,觉得有价值记录整理下。简单介绍下需求背景:有个定时执行的任务,每天早上执行,从数据库获取所有的分组id,给每个id发送消息。在开始设计的时候,就预料到如果是单机执行,随着上线后分组数逐渐增多,任务执行的延迟会逐渐增大。但开始限于某些原因,还是采用了单机的方案。定时器在固定时间触发任务执行,随机一个机器执行对应任务。采用的是消息通知的方式,只需要发送一条消息就可以了。下面说下后来遇到的问题和解决方案。

功能开始上线后,分组开始为20000多个,然后以每天5000个左右的数目增长,单机多线程执行的情况下(线程10个),任务耗时逐渐增长。从开始8分钟,长到了14分钟。可以预见在往后会更加变长。当时考虑了两种方案,分别是。

1、全量消息

在任务触发端,即查询到所有分组id后,全部作为消息发送给任务执行端。同时执行端可以多机器在各自线程池中执行。注意这里分组都是去重的。这种方式优点是编码量非常小,只需要实现全量查询,发送和接收,处理三个功能就可以。但问题消息量级比较大,比如最后会是5-6万的消息量,而且还会继续增长。虽然不多,但限于这边用消息量作为成本计算,所以没有采用,有点迫于形势。&-&

2、id分组

所谓的id分组,就是在任务触发端查询到全量的分组id之后,先排序,然后按照固定的范围切割,得到一些id的分组,再把分组按照消息发送出去,这样就减少了消息量,也实现了多机器并行执行。比如id总量是50000,排序后,按照每100个一组,1-100,101-200...类似,消息量就从10^5 降2个量级,到500。在采用这个方案后,目前接近6万的量,每天不到2分钟就可以执行完成,效果还是比较明显。
下面是分组的实现代码。

 /**
     * 分组发送id范围,闭区间
     */
    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    private class IdRange {
        /**
         * 表示数据范围
         */
        private long start;
        private long end;
    }

    private List<IdRange> buildRangeList(List<Long> ids) {
        int rangeSize = getRangeSizeConfig();
        if (rangeSize <= 0) {
            rangeSize = DEFAULT_RANGE_SIZE;
        }
        ids = ids.stream().distinct().sorted().collect(Collectors.toList());
        List<IdRange> idRanges = Lists.newArrayList();

        int size = ids.size();
        int start = 0;
        int end = 0;
        while (end < size - 1) {
            end += rangeSize;
            if (end >= size) {
                end = size - 1;
            }
            IdRange range = new IdRange(ids.get(start), ids.get(end));
            idRanges.add(range);
            start = end + 1;
        }

        return idRanges;
    }

以上就是两种简单方案的介绍。补充说点,这里任务要求每天仅执行一次,因此开始全量方案用的日期作为标记,表示是否执行过。 在改为切割的方案后,就改为日期+范围左右区间作为key去重了。

总结

首先一点是,简洁高效的技术方案本该是推荐的,但有时候就是有各种情形的限制,没法采用技术上较为合理的方案。随着以后功能越来越多,估计也会经常碰到,就得考虑如何取舍了。其次,很多时候方案就是逐渐推进改造的,不同的应用场景下,就得考虑更加适当的方案来解决问题。有的人可能说,可以刚开始就采用完善的方案,但这样往往提高了整体的复杂度,不利于扩展,改造。而且谁都难以预料后期需求会发生怎样的变动,因此归纳说应该“简而高效,合理完善”

上一篇下一篇

猜你喜欢

热点阅读