上游系统(随机)发起批量(不固定数量)流程,处理完需要(及时通知成功失败),保证数据不丢失,自身服务稳定,可重试,可监控,时效性保证等
简单点说就是:数据落库->背压机制处理任务->回调(单次/批次)
业务简单的流程图如下:
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调度器
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 generateTask(BatchOperatorReq req) {
if (req instanceof BatchFallBackLastNodeReq) {}
return null;
}
@Override
public jj doHandle(BatchTaskEntity entity) {
sendMQMessage(resp,entity);
return jj;
}
}