批量任务后端如何平滑处理

1.背景:

    上游系统(随机)发起批量(不固定数量)流程,处理完需要(及时通知成功失败),保证数据不丢失,自身服务稳定,可重试,可监控,时效性保证等

简单点说就是:数据落库->背压机制处理任务->回调(单次/批次)

2.流程图:

 业务简单的流程图如下:

 3.问题分析

    3.1 上游系统如何批量提交?

         1:走同步接口,本地服务异步处理任务

            优点:任务入库操作放到事务,成功则返回,异步线程池处理,逻辑简单,数据落库

            缺点:依赖下游服务的稳定性,一次性保存万条数据接口响应稍慢,对于几十万的任务提交很有压力

         2:上游提交mq队列,本地服务处理mq消息

            优点:解耦,可以通过消费者的消费速率来解决流量整形问题,支持瞬间提交几十万的任务

            缺点:依赖mq的稳定性,持久化,数据丢失很难处理,任务的优先级难以实现,所有对接方都要接入你的mq集群

      3:上游提交任务,自己提交到mq,自己消费(优先考虑的方案!!)

           优点:天然的分片,可以限流,实现简单

           缺点:在mq积压情况下需要实现对应降级方案,动态优先级很难实现比如某条任务特别紧急(静态优先级可通过mq优先级队列实现)

实现参考:消息消费者降级告警应用_qq1076472549的博客-CSDN博客

走mq进行批量任务处理比较简单。然后业务需要(公司mq丢消息,rabbit和rocket都用,各业务集群隔离不方便第三方接入,作为开放平台支持http协议比只支持mq来说更重要,以及每次最多几千任务,任务有时候优先级会动态变化等原因)本文采用的是同步方式+定时任务补偿的方案来实现的,但不管哪种方案要考虑的东西很多。思考设计如下:

    3.2 集群部署+轮询负载均衡有哪些问题要考虑?

        1:热点问题:单台机器任务量过大处理不完

            解:阈值内任务直接当前进程起线程处理,其余的任务跑定时任务分散处理.但是这里有很多问题:定时任务跑的频次,任务重复执行,单个服务的任务积压(其实mq也有这种问题,只是mq自己解决了,比如prefetch限制消费者的速率,消费者组的设计来避免重复消费)

        2:流量整形:怎么保证批处理任务匀速执行解

           解: 1.redis集群令牌桶控制流量

                   优点:实现简单,严格速率控制;缺点:不动态无法感知服务处理能力动态适配

                   2.背压策略

                   优点:根据消费速率动态变化流量提交,任务时效性会更快,对服务保护性好;缺点:波动可能会较大,如果任务数据量大服务会一直高负荷运行,实现方式(通过单位时间内滑动窗口处理完任务来判断速率)

                  3:背压策略进行流量动态控制+令牌桶进行兜底(分布式防止数据库压力过大,单点问题) 两种方式结合

        3:流量监控:每台机器的处理任务监控

          解: 任务处理成功/失败进行分布式打点(cat等),任务入库可通过数据库查看积压情况

        4:负载机器变化后(kill/扩容)怎么自动切换

          解:xxjob 分片广播(使用消息队列就不必用xxjob,可以用死信队列来搞)

   3.3  任务生命周期怎么管控

       1.任务提交后怎么保证一定会执行

         解:任务状态机:任务状态 0-处理完成  1-待处理 2-处理中 3-处理失败

       2.执行时间的时效性怎么保证

         解:定义任务的优先级,优先级高的优先执行(如果使用rabbitmq,可以使用优先级队列更简单实现)

       3.僵尸任务怎么处理

         解:针对一直处理失败的任务设置最大重试次数,失败直接进日志,发送通知

       4.数据一致性怎么保证

         解:最终一致,定时任务补偿

       5.任务怎么加锁处理

         解:redis锁,或者mysql表的活锁

       6.不同类型任务的执行时间长短不一致怎么管理

        解:不同的mq队列/不同xxjob调度器

 

4.同步+xxjob方案设计

5.代码实现

1.xxjob分片

根据数据库的数据进行hash分片执行

2.任务执行代码:

事务1:批处理数据入库

             异步线程     

public void handle(){
           
   int maxTry = 最大获取令牌次数;
   Boolean isSuccess = true;
   try {
    while (maxTry > 0) {
        if (获取redis令牌) {
            if (CAS加当前任务锁) {
                业务处理
            } else {
                log.warn("任务加锁失败:{}", entity);
            }
            return;
        }
        maxTry--;
        sleep(10);
     }
   } catch (Exception e) {
    log.error("批量任务处理失败:{},{}", entity, e);
    entity.setErrorMessage(StrUtil.subWithLength(e.getMessage(), 0, 900));
    isSuccess = false;
  } finally {
    保存执行结果
    发送MQ消息通知
  }

 }


    public Boolean lock(xx entity) {
        UpdateWrapper uw = new UpdateWrapper();
        uw.eq("id", entity.getId());
        uw.eq("status", xxxx.READY);
        entity.setStatus(xxxx.ING.getValue());
        entity.setEffectiveTime(DateUtil.offset(entity.getCreateTime(), DateField.SECOND, 10));
        return xxxxx.update(entity, uw) > 0 ? true : false;
    }
@Service
public class xxhandle extends AbstractProcessTaskBatchOperator implements BatchOperatorService {

    @Override
    public List<BatchTaskEntity> generateTask(BatchOperatorReq req) {
        if (req instanceof BatchFallBackLastNodeReq) {}
         
        return null;
    }

    @Override
    public jj doHandle(BatchTaskEntity entity) {
        
        sendMQMessage(resp,entity);
        return jj;
    }
}

6.结果压测

7.可迭代优化点

8.是否可做成通用组件/服务

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值