利用异步任务执行数据库信息存储

自定义异步线程池和异步任务异常捕获器

/**
 * ProjectName xyc-commerce-springcloud
 * 自定义异步任务线程池,异步任务异常捕获处理器
 * @author xieyucan
 * <br>CreateDate 2022/9/8 16:40
 */
@Slf4j
@EnableAsync //开启spring异步任务支持
@Configuration
public class AsyncPoolConfig implements AsyncConfigurer {

    /**
     * 将自定义的线程池注入到spring容器中
     * @return
     */
    @Bean
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        //阻塞队列
        executor.setQueueCapacity(20);
        //保存线程存活时间
        executor.setKeepAliveSeconds(60);
        //线程名称前缀
        executor.setThreadNamePrefix("XieYuCan-Async-");
        //等待所有任务执行结束在关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //设置线程池任务等待时间,确保任务可以关闭
        executor.setAwaitTerminationSeconds(60);
        //定义饱和策略  当线程池线程数量到达max 阻塞队列也满了,此时触发拒绝策略
        executor.setRejectedExecutionHandler(
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
        //初始化线程池 初始化core线程
        executor.initialize();

        return executor;

    }


    /**
     * 指定系统中异步任务出现异常时使用到的处理器
     * @return
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncExceptionHandler();
    }

    /**
     * 异步任务异常捕获处理器
     */
    @SuppressWarnings("all")
    class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler{

        @Override
        public void handleUncaughtException(Throwable throwable, 
        Method method, Object... objects) {
            throwable.printStackTrace();
            log.error("Async error:[{}],method:[{}],param:[{}]",
                    throwable.getMessage(),method.getName(), JSON.toJSONString(objects));

            //TODO 发送邮件或者短信,做进一步的报警处理
        }
    }
}

定义异步任务接口

/**
 * ProjectName xyc-commerce-springcloud
 * 异步服务接口定义
 * @author xieyucan
 * <br>CreateDate 2022/9/8 16:30
 */
public interface IAsyncService {

    /**
     * 异步将商品信息保存下来
     * @param goodsInfos
     * @param taskId
     */
    void asyncImportGoods(List<GoodsInfo> goodsInfos,String taskId);
}

定义异步接口实现类

/**
 * ProjectName xyc-commerce-springcloud
 * 异步任务接口实现
 * @author xieyucan
 * <br>CreateDate 2022/9/8 17:08
 */
@SuppressWarnings("all")
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class AsyncServiceImpl implements IAsyncService{

    private final EcommerceGoodsDao ecommerceGoodsDao;

    private final StringRedisTemplate stringRedisTemplate;

    public AsyncServiceImpl(EcommerceGoodsDao ecommerceGoodsDao,
     StringRedisTemplate stringRedisTemplate) {
        this.ecommerceGoodsDao = ecommerceGoodsDao;
        this.stringRedisTemplate = stringRedisTemplate;
    }


    /**
     * 异步任务需要加上注解,并且指定使用的线程池
     * 异步任务处理两个事情:
     * 1:将商品信息保存到数据库表
     * 2:更新商品缓存
     * @param goodsInfos
     * @param taskId
     */
    @Async("getAsyncExecutor")
    @Override
    public void asyncImportGoods(List<GoodsInfo> goodsInfos, String taskId) {

        log.info("async task running taskId:[{}]",taskId);

        /**
         * 开始计时
         */
        StopWatch watch = StopWatch.createStarted();
        //1 如果goodsInfo 中存在重复的商品,不保存,直接返回,记录错误日志
        //请求数据是否符合的标记
        boolean isIllegal=false;

        //将商品信息字段 join 在一起,用来判断是否重复
        Set<String> goodsJointInfos=new HashSet<>(goodsInfos.size());
        //过滤出来的,可以入库的商品信息(规则按照自己的业务需求自定义即可)
        List<GoodsInfo> filteredGoodsInfo=new ArrayList<>(goodsInfos.size());
        //走一遍循环,过滤非法参数去判定当前请求是否合法
        for(GoodsInfo goodsInfo:goodsInfos)
        {
            //基本条件不满足,直接过滤掉
            if(goodsInfo.getPrice()<=0||goodsInfo.getSupply()<=0)
            {
                log.info("goods info is invalid:[{]]", JSON.toJSONString(goodsInfo));
                continue;
            }
            //组合每一个商品信息
            String jointInfo=String.format(
                    "%s,%s,%s",
                    goodsInfo.getGoodsCategory(),
                    goodsInfo.getBrandCategory(),
                    goodsInfo.getGoodsName());

            if(goodsJointInfos.contains(jointInfo))
            {
                isIllegal=true;
            }
            //加入到两个容器中
            goodsJointInfos.add(jointInfo);
            filteredGoodsInfo.add(goodsInfo);
        }
        //如果存在重复商品或者没有需要入库的商品,直接打印日志返回
        if(isIllegal|| CollectionUtils.isEmpty(filteredGoodsInfo))
        {
            watch.stop();
            log.warn("import nothing:[{}]",JSON.toJSONString(filteredGoodsInfo));
            log.info("check and import goods done:[{}ms]",
            watch.getTime(TimeUnit.MILLISECONDS));
            return;
        }

        List<EcommerceGoods> ecommerceGoods=filteredGoodsInfo
                .stream()
                .map(EcommerceGoods::to)
                .collect(Collectors.toList());

        List<EcommerceGoods> targetGoods=new ArrayList<>(ecommerceGoods.size());

        //保存goodsInfo之前判断下是否存在重复商品
        ecommerceGoods.forEach(g->{
            if(null!=ecommerceGoodsDao
                    .findFirst1ByGoodsCategoryAndBrandCategoryAndGoodsName(
                            g.getGoodsCategory(),g.getBrandCategory(),g.getGoodsName())
                            .orElse(null)) {
                return;
            }
            targetGoods.add(g);
        });
        //商品信息入库
        List<EcommerceGoods> saveGoods= IterableUtils.toList(
                ecommerceGoodsDao.saveAll(targetGoods)
        );

        // 将入库的商品同步到redis中
        saveNewGoodsInfo(saveGoods);
        log.info("save goodsInfo to db and redis:[{}]",saveGoods.size());
        watch.stop();
        log.info("check and import goods success:[{}]",
        watch.getTime(TimeUnit.MILLISECONDS));
    }


    /**
     * 将保存到数据表中的的数据缓存到redis中
     * dict:key-><id,simpleGoodsInfo(json)>
     * @param saveGoods
     */
    private void saveNewGoodsInfo(List<EcommerceGoods> saveGoods)
    {
        //由于redis是内存存储,所以只存储简单信息
        List<SimpleGoodsInfo> simpleGoodsInfos = saveGoods
                                                    .stream()
                                                    .map(EcommerceGoods::toSimple)
                                                    .collect(Collectors.toList());

        Map<String,String> id2JsonObject=new HashMap<>(simpleGoodsInfos.size());
        simpleGoodsInfos.forEach(
        g->id2JsonObject.put(g.getId().toString(),JSON.toJSONString(g)));
        //保存到redis中
        stringRedisTemplate.opsForHash()
        .putAll(GoodsConstant.ECOMMERCE_GOODS_DICT_KEY,id2JsonObject);
    }
}

异步任务执行信息类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AsyncTaskInfo {

    /**
     * 异步任务id
     */
    private String taskId;

    /**
     * 异步任务执行状态
     */
    private AsyncTaskStatusEnum status;

    /**
     * 异步任务执行开始时间
     */
    private Date startTime;

    /**
     * 异步任务执行结束时间
     */
    private Date endTime;

    /**
     * 异步任务总耗时
     */
    private String totalTime;
}

异步任务执行器(提交任务,并且查看任务执行情况)

/**
 * ProjectName xyc-commerce-springcloud
 * 异步任务执行管理器,对异步任务进行包装管理,
 * 记录并塞入异步任务执行信息
 * @author xieyucan
 * <br>CreateDate 2022/9/9 9:26
 */
@Slf4j
@Component
public class AsyncTaskManager {

    /**
     * 异步任务执行的容器(简单记录)
     */
    private final Map<String, AsyncTaskInfo> taskContainer=new HashMap<>(16);

    private final IAsyncService asyncService;

    public AsyncTaskManager(IAsyncService asyncService) {
        this.asyncService = asyncService;
    }


    /**
     * 初始化异步任务
     * @return
     */
    public AsyncTaskInfo initTask()
    {
        AsyncTaskInfo taskInfo = new AsyncTaskInfo();
        //设置一个唯一的异步id
        taskInfo.setTaskId(UUID.randomUUID().toString());
        taskInfo.setStatus(AsyncTaskStatusEnum.STARTED);
        taskInfo.setStartTime(new Date());

        //初始化的时候就要将异步任务执行信息放入到存储容器中
        taskContainer.put(taskInfo.getTaskId(),taskInfo);

        return taskInfo;
    }

    /**
     * 提交异步任务
     * @param goodsInfos
     * @return
     */
    public AsyncTaskInfo submit(List<GoodsInfo> goodsInfos)
    {
        //初始化一个异步任务的监控信息
        AsyncTaskInfo taskInfo = initTask();
        asyncService.asyncImportGoods(goodsInfos,taskInfo.getTaskId());
        return taskInfo;
    }


    /**
     * 设置异步任务执行状态信息
     * @param taskInfo
     */
    public void setTaskInfo(AsyncTaskInfo taskInfo)
    {
        taskContainer.put(taskInfo.getTaskId(),taskInfo);
    }


    /**
     * 获取异步任务执行状态信息
     * @param taskId
     * @return
     */
    public AsyncTaskInfo getTaskInfo(String taskId)
    {
        return taskContainer.get(taskId);
    }
}

异步任务监控器(利用切面,补齐异步任务执行信息类信息内容)

/**
 * ProjectName xyc-commerce-springcloud
 * 异步任务执行监控切面
 * @author xieyucan
 * <br>CreateDate 2022/9/9 9:52
 */
@Slf4j
@Aspect
@Component
public class AsyncTaskMonitor {

    private final AsyncTaskManager asyncTaskManager;

    public AsyncTaskMonitor(AsyncTaskManager asyncTaskManager) {
        this.asyncTaskManager = asyncTaskManager;
    }

    /**
     * 异步任务执行的环绕切面
     * 环绕切面让我们可以在方法执行之前之后做一些额外的操作
     * @param joinPoint
     * @return
     */
    @Around("execution(* com.imooc.ecommerce.service.async.AsyncServiceImpl.*(..))")
    public Object taskHandler(ProceedingJoinPoint joinPoint)
    {
        //获取taskId调用异步任务传递的第二个参数
        String taskId=joinPoint.getArgs()[1].toString();
        //获取任务信息,在提交任务的时候,已经放入容器中了
        AsyncTaskInfo taskInfo = asyncTaskManager.getTaskInfo(taskId);
        log.info("AsyncTaskMonitor is monitor async task:[{}]",taskId);
        //设置为运行状态,并且重新放入容器中
        taskInfo.setStatus(AsyncTaskStatusEnum.RUNNING);
        asyncTaskManager.setTaskInfo(taskInfo);

        AsyncTaskStatusEnum status;
        Object result;

        try {
            result=joinPoint.proceed();
            status=AsyncTaskStatusEnum.SUCCESS;
        }catch (Throwable ex){
            result=null;
            status=AsyncTaskStatusEnum.FAILED;
            log.error("AsyncTaskMonitor:async task [{}] is failed,error info: [{}]",taskId,ex.getMessage(),ex);
        }
        //设置异步任务其他信息,再次重新放入容器中
        taskInfo.setEndTime(new Date());
        taskInfo.setStatus(status);
        taskInfo.setTotalTime(String.valueOf(taskInfo.getEndTime().getTime()
        -taskInfo.getStartTime().getTime()));
        asyncTaskManager.setTaskInfo(taskInfo);
        return result;
    }
}

最终controller调用

/**
 * ProjectName xyc-commerce-springcloud
 * 异步任务服务对外提供api接口
 * @author xieyucan
 * <br>CreateDate 2022/9/12 10:44
 */
@Api(tags = "商品异步入库服务")
@Slf4j
@RestController
@RequestMapping("/async-goods")
public class AsyncGoodsController {
	//异步任务执行器
    private final AsyncTaskManager asyncTaskManager;

    public AsyncGoodsController(AsyncTaskManager asyncTaskManager) {
        this.asyncTaskManager = asyncTaskManager;
    }


    @ApiOperation(value = "导入商品",notes = "导入商品到商品表",httpMethod = "POST")
    @PostMapping("/import-goods")
    public AsyncTaskInfo importGoods(@RequestBody List<GoodsInfo> goodsInfos)
    {
        return asyncTaskManager.submit(goodsInfos);
    }


    @ApiOperation(value = "查询状态",notes = "查询异步任务的执行状态",httpMethod = "GET")
    @GetMapping("/task-info")
    //taskId 为报错时,反馈给我们之前我们传递哪一个任务出错了,我们可以及时查看
    public AsyncTaskInfo getTaskInfo(@RequestParam String taskId)
    {
        return asyncTaskManager.getTaskInfo(taskId);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值