1.可以使用线程池来进行轮询重发机制,比如在消息推送,发送邮件,充值提现状的时候,可以使用线程池去轮询发送信息。比如:充值的时候浦发的他行卡充值,充值状态不是立即返回,需要人工的去查询回来,而充值的状态查询不是时时的将结果返回的。可能一分钟,五分钟,十五分钟其状态才是成功的。如果需求需要近可能快的将状态查询回来可以使用线程池去轮询将结果查询回来。在比如,发送极光推送/邮件,当消息推送失败的时候,如果该消息比较重要,需要重新发送告知用户,也可以使用线程池去轮询的发送,当发送五次依旧是失败时,可人工进行处理。
代码如下:
工具类:
package com.adai.polling.util;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 工具类
* @author v-chenk25
* @since 2018-01-21
*/
public class Utils {
/**获取当前时间戳**/
public static String getCurrentTimes() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss sss").format(new Date()) ;
}
/**打印任务信息**/
public static void print(String taskId , String content ) {
System.out.println(taskId +" "+Utils.getCurrentTimes()+content);
}
}
package com.adai.polling.entry;
import java.io.Serializable;
import java.util.Date;
/**
* 实体基类
* @author v-chenk25
* @since 2018-01-21 17:49
*/
public class BaseEntry implements Serializable{
/**
*
*/
private static final long serialVersionUID = 5792454702410922836L;
/**任务记录id**/
protected String taskId ;
/**创建时间**/
private Date createTime ;
/**版本号**/
private Integer version = 0 ;
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
}
package com.adai.polling.entry;
import java.util.Date;
/**
* 通知记录实体类
* @author v-chenk25
* @since 2018-01-20 20:22
*/
public class NotifyRecord extends BaseEntry {
/**
*
*/
private static final long serialVersionUID = -769220440054615027L;
/**已发通知次数**/
private Integer notifyTimes = 0 ;
/**上次发送的通知时间**/
private Date lastNotifyTime ;
/**最大重发机制通知次数**/
private Integer maxLimitTimes = 5 ;
public Date getLastNotifyTime() {
return lastNotifyTime;
}
public void setLastNotifyTime(Date lastNotifyTime) {
this.lastNotifyTime = lastNotifyTime;
}
public Integer getNotifyTimes() {
return notifyTimes;
}
public void setNotifyTimes(Integer notifyTimes) {
this.notifyTimes = notifyTimes;
}
public Integer getMaxLimitTimes() {
return maxLimitTimes;
}
public void setMaxLimitTimes(Integer maxLimitTimes) {
this.maxLimitTimes = maxLimitTimes;
}
public NotifyRecord() {
super();
}
public NotifyRecord(Integer notifyTimes, Date lastNotifyTime, Integer maxLimitTimes) {
super();
this.notifyTimes = notifyTimes;
this.lastNotifyTime = lastNotifyTime;
this.maxLimitTimes = maxLimitTimes;
}
}
package com.adai.polling.entry;
import java.util.Map;
/**
* 自定义封装轮询机制类
* 该类notifyParam对象定义重发的次数和重发的时间机制
* @author v-chenk25
*
*/
public class NotifyParame {
/**轮询机制**/
private Map notifyParam;
/**获取最大重发次数**/
public Integer getMaxLimitTimes() {
return notifyParam.size();
}
public Map getNotifyParam() {
return notifyParam;
}
public void setNotifyParam(Map notifyParam) {
this.notifyParam = notifyParam;
}
}
spring配置文件
package com.adai.polling.core;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import com.adai.polling.app.App;
import com.adai.polling.entry.NotifyParame;
import com.adai.polling.entry.NotifyRecord;
import com.adai.polling.util.Utils;
/***
* 业务逻辑处理类
* 这里进行相关业务的处理
* @author v-chenk25
*
*/
public class NotifyTask implements Delayed , Runnable{
private long executeTime ;
/**轮询机制类**/
@Autowired
private NotifyParame notifyParame ;
/**通知消息记录类**/
private NotifyRecord notifyRecord ;
@Autowired
private NotifyQueue notifyQueue ;
public int compareTo(Delayed delayed) {
if(delayed instanceof NotifyTask ) {
NotifyTask o = (NotifyTask) delayed;
return this.executeTime > o.executeTime ? 1 : (this.executeTime < o.executeTime ? -1 : 0 ) ;
}
return 0;
}
public void run() {
int notifyTimes = notifyRecord.getNotifyTimes() ;
//开始执行任务,发送通知
try {
int temp = App.count.incrementAndGet() ;
//模拟业务
if( temp % 2 != 0) {
if(temp % 4 == 0 ) {
int i = 1/0 ;
}
Utils.print(notifyRecord.getTaskId(), "开始执行任务,发送通知成功");
}else {
//重发次数加+1
notifyRecord.setNotifyTimes(notifyTimes+1);
Utils.print(notifyRecord.getTaskId(), "开始执行任务,发送通知失败");
//添加任务到队列中去
notifyQueue.addTask(notifyRecord);
}
}catch (Exception e) {
Utils.print(notifyRecord.getTaskId(), "执行任务出现异常"+e.getMessage());
notifyRecord.setNotifyTimes(notifyTimes+1);
//添加任务到队列中去
notifyQueue.addTask(notifyRecord);
}
}
public long getDelay(TimeUnit unit) {
return unit.convert(this.executeTime - System.currentTimeMillis(), unit.SECONDS );
}
public NotifyTask() {
}
public NotifyTask(NotifyParame notifyParame, NotifyRecord notifyRecord, NotifyQueue notifyQueue) {
this.notifyParame = notifyParame;
this.notifyRecord = notifyRecord;
this.notifyQueue = notifyQueue;
this.executeTime = getExecuteTime( notifyRecord );
}
private long getExecuteTime(NotifyRecord notifyRecord ) {
long lastTime = notifyRecord.getLastNotifyTime().getTime();
Integer nextNotifyTime = notifyParame.getNotifyParam().get(notifyRecord.getNotifyTimes());
return (nextNotifyTime == null ? 0 : nextNotifyTime * 1000) + lastTime;
}
}
package com.adai.polling.core;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.adai.polling.app.App;
import com.adai.polling.entry.NotifyParame;
import com.adai.polling.entry.NotifyRecord;
import com.adai.polling.util.Utils;
/**
* 自定义封装任务队列
* 这里实现轮询机制和重发的时间间隔逻辑类
* @author v-chenk25
*
*/
@Component
public class NotifyQueue {
//轮询机制
@Autowired
private NotifyParame notifyParame ;
/**添加一个任务到队列中去**/
public void addTask(NotifyRecord notifyRecord ) {
//获取已经通知的次数
Integer notifyTimes = notifyRecord.getNotifyTimes() ;
//获取版本号
int version = notifyRecord.getVersion().intValue();
if(version == 0 ) { //如果值为初始化默认的值
//设置上次通知时间为当前时间
notifyRecord.setLastNotifyTime(new Date());
}
//获取上次发送通知的时间
long time = notifyRecord.getLastNotifyTime().getTime() ;
//最大重发次数
Integer maxLimitTime = notifyParame.getMaxLimitTimes() ;
if( notifyTimes < maxLimitTime ) {
//获取发送任务的时间机制
Integer nextTime = notifyParame.getNotifyParam().get(notifyTimes+1) ;
time += 1000 * 60 * nextTime +1 ; // 下一次发送任务的时间(距离上一次发送间隔多少[nextTime]分钟在这里进行逻辑设置)
notifyRecord.setLastNotifyTime(new Date(time));
//添加到任务队列中去 tasks 延迟队列DelayQueue一个对象
App.tasks.put(new NotifyTask(notifyParame , notifyRecord , this ));
}else { // 轮询机制已经完成,无法在发送信息,施行入库操作。
Utils.print(notifyRecord.getTaskId(), ":插入数据库");
}
}
}
package com.adai.polling.app;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import com.adai.polling.core.NotifyQueue;
import com.adai.polling.core.NotifyTask;
import com.adai.polling.entry.NotifyRecord;
/**
* 启动时加载之前没有发送成功的数据进行轮询发送。
* @author v-chenk25
* @since 2018-01-21
*/
public class App {
/**定义轮询延迟任务队列**/
public static DelayQueue tasks = new DelayQueue() ;
/**线程池**/
private static ThreadPoolTaskExecutor executorPool ;
/**自定义包装任务队列**/
private static NotifyQueue notifyQueue ;
public static AtomicInteger count = new AtomicInteger(0) ;
private static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"classpath:spring-content.xml"});
context.start();
executorPool = (ThreadPoolTaskExecutor) context.getBean("threadPool") ;
notifyQueue = (NotifyQueue) context.getBean("notifyQueue") ;
//重数据库加载需发送的数据
startInitFromDB();
startThread();
System.out.println("== context start");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**开始执行任务**/
private static void startThread() {
try {
while(true) {
//如果当前活动线程等于最大线程,不执行任务逻辑
if(executorPool.getActiveCount() < executorPool.getMaxPoolSize() ) { // 此处判断很重要,避免浪费资源,当有线程空闲时才执行任务
//获取任务队列中第一个任务
final NotifyTask task = tasks.poll();
if(task != null) {
executorPool.execute(new Runnable() {
public void run() {
System.out.println("线程活动数:"+executorPool.getActiveCount());
//将任务从队列中移除
tasks.remove(task);
//执行任务
task.run();
}
});
}
}
}
}catch (Exception e) {
System.out.println(e);
}
}
/** 启动时加载发送失败的任务信息 **/
private static void startInitFromDB() {
// 查询状态和通知次数符合以下条件的数据进行通知
// String[] status = new String[] { "101", "102", "200", "201" };
// Integer[] notifyTime = new Integer[] { 0, 1, 2, 3, 4 };
// // 组装查询条件
// Map paramMap = new HashMap();
// paramMap.put("statusList", status);
// paramMap.put("notifyTimeList", notifyTime);
//
// PageBean pageBean = notifyFacade.queryNotifyRecordListPage(pageParam, paramMap);
System.out.println("get data from database ");
Integer pageNum = 1 ;
//获取数据库失败任务的总记录页数
Integer endNum = 20 ;
while(pageNum <= endNum ) {
//分页查询数据库,加载发送失败的任务信息
System.out.println("query database befor fail data task");
//将任务信息放入到任务队列中去
//new NotifyRecord(lastNotifyTime, notifyTimes, limitNotifyTimes, url, merchantNo, merchantOrderNo, status, notifyType)
NotifyRecord notifyRecord = new NotifyRecord(0 , new Date() , 5 ) ; // 模拟数据
notifyRecord.setTaskId(pageNum+"");
notifyRecord.setLastNotifyTime(new Date());
notifyQueue.addTask(notifyRecord);
//开始读取下一页
pageNum =pageNum+1;
}
}
}
总结:使用线程池和延迟队列实现一个轮询机制,每隔多少分钟进行信息的重发。如果使用了消息中间键。此处代码可以放在消费端,当消费者从mq拿到数据时,先将数据插入大数据库中,状态为为发送,并将该信息放入到任务队列中,等带发送。如果发送失败,则进行轮询重发机制进行处理。如果发送成功,修改数据库状态。如果一直失败,这入库如果有必要可以在加一个标识,需要人工进行处理。
运行结果: