单机条件下断电的情况下提交到线程池的任务该怎么办?

本文主要记录一下自己对于这个问题的想法和思路,由于时间匆忙有很多不完善的地方欢迎指正。
一般情况下,这样的场景线程的任务肯定全部没有了,程序重启的时候这些任务也没有办法重做了。因此我们需要借助其他的方式来重做这些丢失的任务。大体的思路是日志+定时器的方式。
处理的大体流程:

1) 提交线程池之前,记录数据库日志,设置标志位1(初始化状态)
2) 进入线程执行的时候,设置该日志的标志位2(运行中的状态)
3) 业务逻辑处理和删除日志信息,这两个步骤必须在一个事务中,必须同时成功。业务逻辑的处理需要设计成幂等操作,极端情况下任务可能被重复处理。
4) 定时器,每隔一分钟运行一次,首先查询日志记录中状态等于2并且当前时间大于超时时间(过期时间=设置状态为2的时间+两分钟,我们认为当前任务在两分钟内没有被处理完的时候,遇到了未知的情况,他本身不能正常处理,需要重新提交任务,正常情况下一个任务不大可能需要执行两分钟),将这些任务的状态设置为1。最好查询所有的状态=1并且当前时间大于超时时间的任务重新提交到线程池。

代码片

下面展示一些 内联代码片

package com.panda.open.api.gateway.controller;


import com.alibaba.fastjson.JSON;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.panda.open.api.gateway.pojo.TaskDO;
import com.panda.open.api.gateway.pojo.User;
import com.panda.open.api.gateway.service.TaskService;
import com.panda.open.api.gateway.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.*;

/**
 * @author: renwei
 * @Date:Created on 10:562018/11/12
 */
@RestController
public class UserController {

    /**
     * 自定义线程池
     */
    public static ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat("user-pool-%d").build();
    public static Executor poolExecutor = new ThreadPoolExecutor(1, 1, 2,
            TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(2), factory, new ThreadPoolExecutor.AbortPolicy());


    @Autowired
    private UserService userService;
    @Autowired
    private TaskService taskService;

    @RequestMapping(value = "/test", method = RequestMethod.GET)
    public void process() {
        TaskDO taskDO = null;
        try {
            User user = new User();
            //日志记录,任务提交到线程池
            taskDO = new TaskDO();
            String json = JSON.toJSONString(user);
            taskDO.setJson(json);
            //任务初始化状态
            taskDO.setStatus(1);
            int count = taskService.saveTask(taskDO);
            if (count == 0) {
                throw new RuntimeException("保存日志信息异常");
            }
            final Long taskId = taskDO.getId();
            poolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    processUserTask(taskId, taskService, userService);

                }
            });
        } catch (RejectedExecutionException e) {
            //线程池无法处理新的任务,删除数据库的任务日志信息
            System.out.println(e.getMessage());
        }
    }

    public static void processUserTask(Long taskId, TaskService taskService, UserService userService) {
        //记录日志信息吗,任务状态设置为2,处理中
        TaskDO task = new TaskDO();
        task.setId(taskId);
        task.setStatus(2);
        //设置时间为当前的时间的两分钟后,便于SQL优化
        task.setExpiredTime(System.currentTimeMillis() + 2 * 60 * 1000);
        taskService.updateTaskStatus(task);
        Long userId = 2L;
        //处理业务和
        userService.updateStatus(userId, taskId);
    }

}

UserService.class接口

package com.panda.open.api.gateway.service;

/**
 * @author: renwei
 * @Date:Created on 10:572018/11/12
 */
public interface UserService {
    /**
     * 更改用户的状态,并且删除task日志信息
     * @param userId
     * @param taskId
     */
    void updateStatus(Long userId, Long taskId);
}

UserServiceImpl.class接口

package com.panda.open.api.gateway.service.impl;

import com.panda.open.api.gateway.mapper.TaskMapper;
import com.panda.open.api.gateway.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.TimeUnit;


/**
 * @author: renwei
 * @Date:Created on 10:572018/11/12
 */
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private TaskMapper taskMapper;

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)
    @Override
    public void updateStatus(Long userId, Long taskId) {
        try {
            System.out.println("修改用户信息,幂等操作:userId=" + userId);
            TimeUnit.MILLISECONDS.sleep(500);
            taskMapper.deleteByPK(taskId);
        } catch (InterruptedException e) {
            throw new RuntimeException("位置异常,任务需要重新提交");
        }
    }
}

TaskService.class

package com.panda.open.api.gateway.service;

import com.panda.open.api.gateway.pojo.TaskDO;

import java.util.List;

/**
 * @author: renwei
 * @Date:Created on 16:132018/11/12
 */
public interface TaskService {
    int saveTask(TaskDO taskDO);

    int updateTaskStatus(TaskDO taskDO);

    void deleteByPK(long id);

    /**
     * 获取失败的任务,status=2&System.currentTimeMillis()>expiredTime
     *
     * @param currentTime
     * @return
     */
    List<TaskDO> getFailedTask(Long currentTime,Integer status);

    /**
     * 设置任务为1的初始化状态
     * @param list
     */
    void batchUpdateForInit(List<Long> list);
}

TaskServiceImpl.class

package com.panda.open.api.gateway.service.impl;

import com.panda.open.api.gateway.mapper.TaskMapper;
import com.panda.open.api.gateway.pojo.TaskDO;
import com.panda.open.api.gateway.service.TaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * @author: renwei
 * @Date:Created on 16:142018/11/12
 */
@Service
public class TaskServiceImpl implements TaskService {
    @Autowired
    private TaskMapper taskMapper;

    /**
     * 保存提交的任务日志信息
     *
     * @param taskDO
     * @return
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)
    public int saveTask(TaskDO taskDO) {
        return taskMapper.insert(taskDO);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)
    public int updateTaskStatus(TaskDO taskDO) {
        return taskMapper.updateTaskStatus(taskDO);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)
    public void deleteByPK(long taskId) {
        taskMapper.deleteByPK(taskId);
    }

    @Override
    public List<TaskDO> getFailedTask(Long currentTime,Integer status) {
        return taskMapper.listFailedTask(currentTime,status);
    }

    @Override
    public void batchUpdateForInit(List<Long> list) {
        taskMapper.batchUpdateForInit(list);
    }
}

pojo类

package com.panda.open.api.gateway.pojo;

import java.io.Serializable;

/**
 * @author: renwei
 * @Date:Created on 10:492018/11/12
 */
public class TaskDO implements Serializable {
    /**
     * 任务ID
     */
    private Long id;

    /**
     * json的字符串
     */
    private String json;

    /**
     * 任务状态,1:任务初始化提交线程池,2:线程池收到任务,正在处理中。
     * 业务处理成功的时候删除任务,(业务处理和删除任务必须同时成功,要么多失败)
     */
    private Integer status;

    /**
     * 设置任务状态2的时候设置超时的时间
     */
    private Long expiredTime;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getJson() {
        return json;
    }

    public void setJson(String json) {
        this.json = json;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public Long getExpiredTime() {
        return expiredTime;
    }

    public void setExpiredTime(Long expiredTime) {
        this.expiredTime = expiredTime;
    }
}
package com.panda.open.api.gateway.pojo;

import java.io.Serializable;

/**
 * @author: renwei
 * @Date:Created on 10:482018/11/12
 */
public class User implements Serializable {
    private Integer id;
    private String name;

    private Integer status;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }
}

SpringUtils工具类

package com.panda.open.api.gateway.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @author: renwei
 * @Date:Created on 22:232018/11/12
 */
@Component
public class SpringUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtils.applicationContext = applicationContext;
    }

    public static <T> T getBean(String beanName) {
        if (applicationContext.containsBean(beanName)) {
            return (T) applicationContext.getBean(beanName);
        } else {
            return null;
        }
    }

    public static <T> Map<String, T> getBeansOfType(Class<T> baseType) {
        return applicationContext.getBeansOfType(baseType);
    }

    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }
}

ScheduledService.class定时器任务类

package com.panda.open.api.gateway.config;

import com.fasterxml.jackson.databind.annotation.JsonAppend;
import com.panda.open.api.gateway.controller.UserController;
import com.panda.open.api.gateway.pojo.TaskDO;
import com.panda.open.api.gateway.service.TaskService;
import com.panda.open.api.gateway.service.UserService;
import com.panda.open.api.gateway.util.SpringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * @author: renwei
 * @Date:Created on 22:272018/11/12
 */
@Component
public class ScheduledService {
    @Autowired
    private UserService userService;

    @Scheduled(cron = "0 0/1 * * * *")
    public void scheduled() {
        //定时器任务,每隔一分钟执行异常,如果当前时间和任务提交的时间超过2分钟任务还是2的状态,则任务失败需要重新提交
        TaskService taskService = SpringUtils.getBean(TaskService.class);
        List<TaskDO> failedTasks = taskService.getFailedTask(System.currentTimeMillis(), 2);
        if (!CollectionUtils.isEmpty(failedTasks)) {
            //设置这些task的status=1
            List<Long> ids = new ArrayList<>();
            for (TaskDO taskDO : failedTasks) {
                ids.add(taskDO.getId());
            }
            taskService.batchUpdateForInit(ids);
        }
        List<TaskDO> initTasks = taskService.getFailedTask(System.currentTimeMillis(), 1);
        if (CollectionUtils.isEmpty(initTasks)) {
            return;
        }
        //提交任务
        for (TaskDO taskDO : initTasks) {
            final Long taskId = taskDO.getId();
            UserController.poolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    UserController.processUserTask(taskId, taskService, userService);
                }
            });
        }
    }
}

TaskMapper.class

package com.panda.open.api.gateway.mapper;

import com.panda.open.api.gateway.pojo.TaskDO;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * @author: renwei
 * @Date:Created on 10:532018/11/12
 */
public interface TaskMapper {
    /**
     * 查询task日志信息
     *
     * @param taskDO
     * @return
     */
    int insert(TaskDO taskDO);

    /**
     * 修改task日志信息
     *
     * @param taskDO
     * @return
     */
    int updateTaskStatus(TaskDO taskDO);

    /**
     * 根据主键删除task日志信息
     *
     * @param taskId
     */
    void deleteByPK(Long taskId);

    /**
     * @param currentTime
     * @return
     */
    List<TaskDO> listFailedTask(@Param("currentTime") Long currentTime, @Param("status") Integer status);

    /**
     * @param list
     */
    void batchUpdateForInit(List<Long> list);
}

TaskMapper .xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.panda.open.api.gateway.mapper.TaskMapper">
    <resultMap id="BaseResultMap" type="com.panda.open.api.gateway.pojo.TaskDO">
        <id column="id" property="id"/>
        <result column="data" property="json"/>
    </resultMap>
    <sql id="Base_Column_List">
        id, data
    </sql>
    <insert id="insert" parameterType="com.panda.open.api.gateway.pojo.TaskDO" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO rw_test(data,status)VALUES(#{json},#{status});
    </insert>
    <update id="updateTaskStatus" parameterType="com.panda.open.api.gateway.pojo.TaskDO">
        UPDATE rw_test SET status=#{status},time=#{expiredTime} WHERE id=#{id} AND status=1
    </update>
    <delete id="deleteByPK" parameterType="java.lang.Long">
        DELETE FROM rw_test WHERE id=#{id}
    </delete>
    <!--获取任务-->
    <select id="listFailedTask" resultMap="BaseResultMap" resultType="java.lang.Long">
        SELECT id,data FROM rw_test WHERE status=#{status} AND <![CDATA[ time < #{currentTime}
        ]]>
    </select>
    <!--设置任务为1的状态-->
    <update id="batchUpdateForInit" parameterType="java.util.List">
        UPDATE rw_test SET status=1 WHERE status=2 AND id IN
        <foreach collection="list" item="item" index="index" separator="," open="(" close=")">
            #{item}
        </foreach>
    </update>
</mapper>

解释:这里的位置设置的超时时间System.currentTimeMillis() + 2 * 60 * 1000,一开始写入的是System.currentTimeMillis()
我们分析两种不同的写法下如何判断如何是否超时
原始的写法查询超时任务的SQL:SELECT * FROM rw_test WHERE status=2 AND 20-time>2。
我们看一下这个SQL的查询效率EXPLAIN SELECT * FROM rw_test WHERE status=2 AND 20-time>2
在这里插入图片描述
Explain中type:这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为const、eq_reg、ref、range、index和ALL。type显示的是访问类型,是较为重要的一个指标,结果值从好到坏依次是:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL。一般来说,得保证查询至少达到range级别,最好能达到ref。
从explain的结果来看type=ALL是效率最差的。
优化后的SQL语句:SELECT * FROM rw_test WHERE status=2 AND 20>time,这里的time=设置任务状态=2的时间+两分钟。我们explain一下看看有没有效果。
在这里插入图片描述
优化后的type=range,还是有效果的。
根据自己的一些想法写了一个简单的demo程序,不合理之处,请各位大侠批评指正。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值