亿级任务中台设计实战 【策略模型+Saturn+Mycat】

背景:用户群体不断增加,部门不断扩展,产品经理提出越来越多的活动运营需求,程序员不堪重负。但是这些活动做多做久了,很容易发现这些活动往往都有相同的触达体系、任务体系及奖励体系。
      为加快产品迭代,减轻技术人员负担,提示系统安全稳定性。独立活动中台、任务中台、营销触达中心迫在眉睫。本文主要阐述任务中台搭建流程。
 

解决问题:

          1 构建更易扩展的任务场景数据库设计及代码设计;
          2 庞大会员体系(亿+)快速支撑任务开启、任务校验所涉及的并发及压力问题;
          3 依靠服务器及数据库水平扩容保证更大压力下的任务中台支撑能力;
技术方案:

          1 采用策略模式设计任务中台,校验不同任务完成状况,并更新用户任务记录表。
            数据库设计见下图:
          2 使用redis并发锁,通过锁定用户userID 3秒解决并发问题。使用hystrix熔断降级,保证服务能力。
          3 任务调度系统采用SaturnJob分布式调度系统,数据库为Mycat+mysql集群,通过Saturn分片参数,执行不同分片数据库数据,支撑服务能力水平提升。
实施:

       1 数据库设计见下图:

2 依靠策略模式核心代码:

/**
* 策略类
*/
public abstract class QuestStrategy {

    @Autowired
    private TaskRecordManager taskRecordManager;

    /**
     * 对接关联系统校验任务是否完成
     *
     * @return
     */
    public abstract boolean checkQuestIsComplete(TaskRecord record);


}
public enum QuestHandlerEnum {

    //等级提升
    MEMBER_GRADE_QUEST("0502", ServiceBean.getSpringContext().getBean(MemGradeStrategy.class)),
    //商品兑换
    MEMBER_EXCHANGE_QUEST("0503", ServiceBean.getSpringContext().getBean(MemExchangeStrategy.class)),

    //页面浏览
    BROWSE_QUEST("06", ServiceBean.getSpringContext().getBean(PageStrategy.class)),

    //页面点击
    CLICK_QUEST("07", ServiceBean.getSpringContext().getBean(PageStrategy.class));

    private String type;

    private QuestStrategy questStrategy;

    QuestHandlerEnum(String type, QuestStrategy quest) {
        this.type = type;
        this.questStrategy = quest;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public QuestStrategy getQuestStrategy() {
        return questStrategy;
    }

}
public class QuestHandler {

    private static final Logger logger = LoggerFactory.getLogger(LoggerType.COMMON);

    static Map<String, QuestStrategy> strategyMap = new HashMap<>();

    static {
        for(QuestHandlerEnum questEnum : QuestHandlerEnum.values()){
            strategyMap.put(questEnum.getType(),questEnum.getQuestStrategy());
        }
    }

    public static QuestStrategy getStrategy(String type) {
        return strategyMap.get(type);
    }

    /**
     * 校验任务是否完成
     * @return
     */
    public static TaskRecord checkQuest(String type, TaskRecord taskRecord,boolean isJob){
        try {
            QuestStrategy questStrategy = getStrategy(type);

            //校验任务完成状态
            boolean isFinish;

            if(isJob){
                isFinish = questStrategy.checkQuestIsCompleteJob(taskRecord);
            }else{
                isFinish = questStrategy.checkQuestIsComplete(taskRecord);
            }
            //未完成 将任务列表返回
            if(!isFinish){
                return taskRecord;
            }

            //任务完成  通过userId 和ID更新任务状态
            taskRecord.setStatus(Constant.RecordStatus.FINISHED);
            questStrategy.insertOrUpdateTask(taskRecord);
            return taskRecord;

        } catch (Exception e) {
            logger.error("checkQuest error ", e);
        }
        return taskRecord;
    }
}
/**
* 等级提升实现类
*/
@Service
public class MemGradeStrategy extends QuestStrategy {

    @Autowired
    private IntegralServiceImpl integralService;

    private static final Logger logger = LoggerFactory.getLogger(LoggerType.COMMON);

    @Override
    public boolean checkQuestIsComplete(TaskRecord record) {
        //查询会员等级变化情况
        QueryGradeChangeDTO queryGradeChangeDTO = new QueryGradeChangeDTO();
        queryGradeChangeDTO.setFlag("1");
        queryGradeChangeDTO.setUserId(record.getUserId());
        queryGradeChangeDTO.setActivityStartTm(record.getStartTm());
        MemberGradeChangeVO memberGradeChange = integralService.getChangeInfo(queryGradeChangeDTO);
        if(null == memberGradeChange) {
            return false;
        }

        updateTaskProcess(record,String.valueOf(memberGradeChange.getGradeCode()));

        String value = getTargetString(record.getRule());
       return memberGradeChange.getGradeCode().compareTo(value)>=0 && memberGradeChange.getGradeCode().compareTo(memberGradeChange.getLstGradeCode())>0 && value.compareTo(memberGradeChange.getLstGradeCode())>0;
    }

}
/**
* 分布式调度任务执行
*/
@Component
public class UpdateTaskStatusJob extends AbstractSaturnJavaJob {

   private static final int mysqlNodeCount = 128;

    private static final Logger logger = LoggerFactory.getLogger(LoggerType.COMMON);

    ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);

    private final static List<Integer> slicingList = new ArrayList<>();

    @Autowired
    TaskRecordManager taskRecordManager;

    @Autowired
    TaskRecordService taskRecordService;

    static {
        for (int i = 1; i <= mysqlNodeCount; i++) {
            slicingList.add(i);
        }
    }

    /**
     * Java作业处理入口
     *
     * @param jobName         作业名
     * @param shardItem       分片项
     * @param shardParam      分片参数
     * @param shardingContext 其它参数信息
     * @return 返回执行结果
     * @throws InterruptedException 注意处理中断异常
     */
    @Override
    public SaturnJobReturn handleJavaJob(String jobName, Integer shardItem, String shardParam,
                                         SaturnJobExecutionContext shardingContext) {
        logger.info("UpdateTaskStatusJob定时任务执行--开始");
        long start = System.currentTimeMillis();
        SaturnJobReturn jr = new SaturnJobReturn();
        try {
            List<Integer> shardIds;
            if (StringUtils.isBlank(shardParam)) {
                shardIds = slicingList;
            } else {
                Integer piece = Integer.valueOf(shardParam);
                shardIds = slicingList.stream().collect(Collectors.groupingBy(e->e%shardingContext.getShardingTotalCount())).get(piece);
            }
            for (Integer shardId : shardIds) {
                processShard(shardId);
            }
            jr.setReturnCode(SaturnSystemReturnCode.SUCCESS);
            logger.info("UpdateTaskStatusJob定时任务执行--结束 耗时:{}ms", System.currentTimeMillis() - start);
        }catch (Exception e){
            logger.error("UpdateTaskStatusJob定时任务执行 error:{}",slicingList,e);
            jr.setReturnCode(SaturnSystemReturnCode.SYSTEM_FAIL);
        }
        return jr;
    }

    private void processShard(Integer shardId) {
        try {
            logger.info("开始处理分片数据 分片id:{}",shardId);
            long start = System.currentTimeMillis();
            long total = 0;
            Integer lastId = 0;
            List<TaskRecord> taskRecordList = taskRecordManager.findTaskRecordByShard(shardId, lastId, 1000);
            while (!taskRecordList.isEmpty()) {
                total += taskRecordList.size();
                processData(taskRecordList);
                lastId = taskRecordList.get(taskRecordList.size() - 1).getId();
                taskRecordList = taskRecordManager.findTaskRecordByShard(shardId, lastId, 1000);
            }
            logger.info("处理分片数据结束 分片id:{} total:{} 耗时:{}ms", shardId,total, System.currentTimeMillis() - start);
        } catch (Exception e) {
            logger.error("定时任务处理分片数据失败,分片id:{}", shardId, e);
        }
    }

    private void processData(List<TaskRecord> taskRecordList) {
        executorService.submit(() -> {
            if (!CollectionUtils.isEmpty(taskRecordList)) {
                TaskCheck taskCheck;
                for (TaskRecord taskRecord : taskRecordList) {
                    taskCheck = new TaskCheck();
                    if(taskRecordService.checkInValid(taskRecord)){
                        logger.info("任务已经失效。record:{}", JSONObject.toJSONString(taskRecord));
                        continue;
                    }
                    taskCheck.setMobile(taskRecord.getMobile());
                    taskCheck.setIds(taskRecord.getTaskCode());
                    taskCheck.setScene(taskRecord.getScene());
                    taskCheck.setUserId(taskRecord.getUserId());
                    taskRecordService.checkTaskJob(taskCheck);
                }
            }
        });
    }

}

3 部分运行截图: 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值