开发-通用设计-间隔重发

本文介绍了一个业务系统中实现推送功能的场景,涉及到失败后按2、4、8分钟间隔重试,最多3次。设计了表PUB_REDO_JNL来存储重发记录,并提供了相关的Mapper操作,包括插入、查询和更新。同时,展示了如何通过定时线程执行重发任务并管理失败状态。
摘要由CSDN通过智能技术生成

需求:

业务系统中有需要用到推送功能,由于网路或对方系统未知原因导致推送失败,需进行间隔重发,如失败按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);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值