需求:
业务系统中有需要用到推送功能,由于网路或对方系统未知原因导致推送失败,需进行间隔重发,如失败按2、4、8分钟间隔依次再发,最多3次。
后续某个时间对推送失败的指定记录可再次发起重发。
设计:
表设计:PUB_REDO_JNL
REDO_NO | 重发主键 |
REG_TIME | 注册时间-创建时间不变 |
HOST_NAME | 主机名 |
TRAN_CODE | 重发服务名 |
TRAN_DATA | 重发参数 |
REDO_TIME | 下次重发时间 |
TLV | 重发间隔 |
TLV_MUL | 重发间隔倍数 |
MAX_TMS | 重发最大次数 |
FAIL_TMS | 重发失败次数 |
STS | 重发状态U-等待 S-成功 F-失败 |
对应的Mapper操作
public interface PubRedoMapper{
/**
* 新增重发记录
* @param paramMap
* @return
*/
int insertEntity(Map<String, Object> paramMap);
/**
* 查询需要重发的记录
* @param paramMap
* @return
*/
List<Map<String, Object>> selectRedoJnlList(Map<String, Object> paramMap);
/**
* 更新重发记录
* @param paramMap
* @return
*/
int updateRedoJnl(Map<String, Object> paramMap);
}
<?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.boot.db.mapper.PubRedoMapper">
<insert id="insertEntity" parameterType="java.util.Map">
INSERT INTO PUB_REDO_JNL(
REDO_NO ,
REG_TIME ,
HOST_NAME,
TRAN_CODE,
TRAN_DATA,
REDO_TIME,
TLV ,
TLV_MUL ,
MAX_TMS ,
FAIL_TMS ,
STS
)VALUES(
#{REDO_NO },
#{REG_TIME },
#{HOST_NAME},
#{TRAN_CODE},
#{TRAN_DATA},
#{REDO_TIME},
#{TLV },
#{TLV_MUL },
#{MAX_TMS },
#{FAIL_TMS },
#{STS }
)
</insert>
<select id="selectRedoJnlList" parameterType="java.util.Map" resultType="java.util.Map">
SELECT REDO_NO,TRAN_CODE,TRAN_DATA,TLV,TLV_MUL,MAX_TMS,FAIL_TMS
FROM PUB_REDO_JNL
WHERE STS = 'U'
<if test="HOST_NAME != null and HOST_NAME != ''">
and HOST_NAME = #{HOST_NAME,jdbcType=VARCHAR}
</if>
<if test="HOST_NAME == null or HOST_NAME == ''">
and HOST_NAME is null
</if>
ORDER BY REDO_TIME ASC
</select>
<update id="updateRedoJnl" parameterType="java.util.Map">
UPDATE PUB_REDO_JNL
SET REDO_TIME = TO_CHAR(TO_DATE(REDO_TIME,'yyyyMMddHH24miss')+#{DELAY_SEC}/(24*60*60),'yyyyMMddHH24miss'),
FAIL_TMS = #{FAIL_TMS },
STS = #{STS }
WHERE REDO_NO = #{REDO_NO}
</update>
</mapper>
系统启动加载重发线程
/**
* 公共初始化类
*/
@Component
public class CommonPostConstructConfig {
final private static Logger logger = LoggerFactory.getLogger(CommonPostConstructConfig.class);
private static final ExecutorService CACHED_THREAD_POOL = new ThreadPoolExecutor(AdminConstants.AVAILABLE_PROCESSORS, AdminConstants.AVAILABLE_PROCESSORS,
0L,TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),new ThreadFactoryBuilder().setNameFormat("init-pool-%d").build(),new ThreadPoolExecutor.AbortPolicy());
@PostConstruct
public void postConstruct() {
//加载重发服务
CACHED_THREAD_POOL.execute(new Runnable() {
@Override
public void run() {
try {
//生产验证,可能系统启动PageHelper未加载完成,导致分页失败,特此延后服务加载60秒
Thread.sleep(60*1000);
} catch (Exception e) {
logger.info("延后服务加载异常,请检查!");
}
String loadFlg = FilePropertiesConfig.getPropertyByKey("system.start.load.redo");
logger.info("loadFlg===>" + loadFlg);
if("N".equals(loadFlg)){
logger.info("加载重发服务!");
IPubRedoService pubRedoService = AppContext.getBean(IPubRedoService.class);
long lStart = 0;
long lEnd = 0;
while(true){
lStart = System.currentTimeMillis();
try {
pubRedoService.excuteRedo();
lEnd = System.currentTimeMillis();
if(lEnd-lStart<=1000){
Thread.sleep(60*1000);
}
} catch (Exception e) {
e.printStackTrace();
logger.info("重发任务失败:{}!",e.getMessage());
try {
Thread.sleep(60*1000);
} catch (Exception e2) {
logger.info("线程休眠失败:{}!",e2.getMessage());
}
}
}
}else{
logger.info("系统设置,加载标识:{},不加载重发服务!",loadFlg);
}
}
});
}
@PreDestroy
public void destroy(){
/**关闭定时对象池**/
CACHED_THREAD_POOL.shutdown();
}
}
定义重发调度接口类
public interface IPubRedoService {
/**
* 登记重发交易,失败后的延后时间计算=tlv*(Math.power(tlvMul,failTms))
* @param redoNo 重发流水号,要求唯一
* @param iCode 重发调用交易的ICode
* @param tranMap 重发参数
* @param tlv 重发时间间隔秒
* @param tlvMul 重发间隔的倍数
* @param maxTms 重发次数
* @throws Exception
*/
public void excuteRedo() throws Exception ;
/**
* 登记重发交易
* @param redoNo 重发流水号,要求唯一
* @param iCode 重发调用交易的ICode
* @param tranMap 重发参数
* @param tlv 重发时间间隔
* @param tlvMul 重发间隔的倍数
* @param maxTms 重发次数
* @throws Exception
*/
public void registerRedo(String redoNo, String iCode, Map<String, Object> tranMap, int tlv, int tlvMul, int maxTms) throws Exception ;
}
重发调度接口实现
@Service
public class PubRedoServiceImpl implements IPubRedoService{
private static final Logger log = LoggerFactory.getLogger(PubRedoServiceImpl.class);
@Autowired
PubRedoMapper pubRedoMapper;
/**
* 登记重发交易,失败后的延后时间计算=tlv*(Math.power(tlvMul,failTms))
* @param redoNo 重发流水号,要求唯一
* @param iCode 重发调用交易的ICode
* @param tranMap 重发参数
* @param tlv 重发时间间隔秒
* @param tlvMul 重发间隔的倍数
* @param maxTms 重发次数
* @throws Exception
*/
@SuppressWarnings("unchecked")
@Override
public void excuteRedo() throws Exception {
Map<String, Object> paramMap = new HashMap<String,Object>();
List<Map<String, Object>> mapList = null;
Map<String, Object> tranMap = null;
Map<String, Object> resMap = null;
String redoNo = null;
String tranCode = null;
String tranData = null;
String tlv = null;
String tlvMul = null;
String maxTms = null;
String failTms = null;
String tranSts = null;
int delaySecond = 0;
IRedoService redoService = null;
int iMaxTotal = 1000;
//设置查询HostName
paramMap.put("HOST_NAME", getConfigHostName());
for(int i=1;i<=iMaxTotal;i++){
PageHelper.startPage(i, 100,false);//默认查询100条
mapList = pubRedoMapper.selectRedoJnlList(paramMap);
for(Map<String, Object> tempMap:mapList){
redoNo = MapUtils.getString(tempMap, "REDO_NO");
tranCode = MapUtils.getString(tempMap, "TRAN_CODE");
tranData = MapUtils.getString(tempMap, "TRAN_DATA");
tlv = MapUtils.getString(tempMap, "TLV" ,"60");
tlvMul = MapUtils.getString(tempMap, "TLV_MUL" ,"2");
maxTms = MapUtils.getString(tempMap, "MAX_TMS" ,"3");
failTms = MapUtils.getString(tempMap, "FAIL_TMS","0");
tranMap = (Map<String, Object>)JSONObject.parse(tranData);
//重发发起
try {
//重发成功处理
redoService = AppContext.getBean(tranCode, IRedoService.class);
resMap = redoService.execRedo(tranMap);
log.info("重发编号:{},重发结果:{}!",redoNo,resMap);
if("true".equalsIgnoreCase(MapUtils.getString(resMap, "RSP_COD"))){
tranSts = "S";
failTms = failTms;//只是为了代码格式一致
delaySecond = 0;
log.info("重发成功,重发编号:{},重发次数:{}!",redoNo,failTms);
}else{
if((Integer.parseInt(failTms)+1) >= Integer.parseInt(maxTms)){
//已处理最大失败次数 直接置为失败
tranSts = "F";
failTms = maxTms;
delaySecond = 0;
log.info("重发确认失败,重发编号:{},重发次数:{}!",redoNo,failTms);
}else{
//未达到最大失败次数 失败次数加一
tranSts = "U";
failTms = (Integer.parseInt(failTms)+1)+"";
delaySecond = getDiscreteNum(tlv,tlvMul,failTms);
log.info("重发尝试失败,重发编号:{},重发次数:{}!",redoNo,failTms);
}
}
} catch (Exception e) {
if((Integer.parseInt(failTms)+1) >= Integer.parseInt(maxTms)){
//已处理最大失败次数 直接置为失败
tranSts = "F";
failTms = maxTms;
delaySecond = 0;
log.info("重发确认失败,重发编号:{},重发次数:{}!",redoNo,failTms);
}else{
//未达到最大失败次数 失败次数加一
tranSts = "U";
failTms = (Integer.parseInt(failTms)+1)+"";
delaySecond = getDiscreteNum(tlv,tlvMul,failTms);
log.info("重发尝试失败,重发编号:{},重发次数:{}!",redoNo,failTms);
}
}finally{
Map<String, Object> updateRedoMap = new HashMap<String, Object>();
updateRedoMap.put("DELAY_SEC", delaySecond+"");
updateRedoMap.put("FAIL_TMS" , failTms);
updateRedoMap.put("STS" , tranSts);
updateRedoMap.put("REDO_NO" , redoNo);
pubRedoMapper.updateRedoJnl(updateRedoMap);
log.info("重发尝试失败,重发编号:{},重发次数:{}!",redoNo,failTms);
}
}
//数据不足直接退出
if(null==mapList||mapList.size()<100){
log.info("本次重发已处理完毕,结束!");
break;
}
}
}
/**
* 登记重发交易
* @param redoNo 重发流水号,要求唯一
* @param iCode 重发调用交易的ICode
* @param tranMap 重发参数
* @param tlv 重发时间间隔
* @param tlvMul 重发间隔的倍数
* @param maxTms 重发次数
* @throws Exception
*/
@Override
public void registerRedo(String redoNo, String iCode, Map<String, Object> tranMap, int tlv, int tlvMul, int maxTms) throws Exception {
Map<String, Object> paramMap = new HashMap<String,Object>();
paramMap.put("REDO_NO" , redoNo);
paramMap.put("REG_TIME" , DateTimeUtils.getTime("yyyyMMddHHmmss"));
paramMap.put("HOST_NAME", getConfigHostName());
paramMap.put("TRAN_CODE", iCode);
paramMap.put("TRAN_DATA", JSONObject.toJSONString(tranMap));
paramMap.put("REDO_TIME", DateTimeUtils.getTime("yyyyMMddHHmmss"));
paramMap.put("TLV" , tlv);
paramMap.put("TLV_MUL" , tlvMul);
paramMap.put("MAX_TMS" , maxTms);
paramMap.put("FAIL_TMS" , "0");
paramMap.put("STS" , "U");
pubRedoMapper.insertEntity(paramMap);
log.info("重发新增:{}!",paramMap);
}
private int getDiscreteNum(String tlv,String tlvMul,String failNum){
int iRes = 0;// tlv*(tlvNum的failNum次方)
Double dTlv = Double.parseDouble(tlv);
Double dTlvMul = Double.parseDouble(tlvMul);
Double dFileNum = Double.parseDouble(failNum);
iRes = (int)(dTlv*Math.pow(dTlvMul,dFileNum));
return iRes;
}
/**
* 根据执行模式,确定是否将本机IP存储至记录HOST_NAME中
* SINGLE:存取都使用本机IP为关联
* OTHER :不以F本机IP为查询条件
* @return
*/
private String getConfigHostName(){
// String execModel = FilePropertiesConfig.getPropertyByKey("redo.execute.model");
String execModel = "SINGLE";
String proName = FilePropertiesConfig.getPropertyByKey("spring.application.name");
if("SINGLE".equals(execModel)){
return MachineUtil.getIpAddress() + "-" + proName;
}
return "";
}
定义需要重发的接口类,具体的重发需要实现接口
public interface IRedoService {
public Map<String, Object> execRedo(Map<String, Object> paramMap);
}