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<Integer,Integer> notifyParam;
/**獲取最大重發次數**/
public Integer getMaxLimitTimes() {
return notifyParam.size();
}
public Map<Integer, Integer> getNotifyParam() {
return notifyParam;
}
public void setNotifyParam(Map<Integer, Integer> notifyParam) {
this.notifyParam = notifyParam;
}
}
spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.0.xsd">
<!-- 配置線程池 -->
<bean id="threadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<!-- 線程池維護線程的最小數量 -->
<property name="corePoolSize" value="1" />
<!-- 線程池維護線程所允許的空閒時間 -->
<property name="keepAliveSeconds" value="3000" />
<!-- 線程池所使用的最大數量 -->
<property name="maxPoolSize" value="3" />
<!-- 線程池所使用的緩衝隊列 -->
<property name="queueCapacity" value="1000" />
</bean>
<!-- 輪詢機制類,重發五次,每次間隔多少分鐘 -->
<bean id="notifyParam" class="com.adai.polling.entry.NotifyParame">
<property name="notifyParam">
<map>
<entry key="1" value="0" />
<entry key="2" value="1" />
<entry key="3" value="2" />
<entry key="4" value="5" />
<entry key="5" value="15" />
</map>
</property>
</bean>
<!-- 採用註釋的方式配置bean -->
<context:annotation-config />
<!-- 配置要掃描的包 -->
<context:component-scan base-package="com.adai.polling" />
</beans>
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<NotifyTask> tasks = new DelayQueue<NotifyTask>() ;
/**線程池**/
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<String, Object> paramMap = new HashMap<String, Object>();
// 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拿到數據時,先將數據插入大數據庫中,狀態爲爲發送,並將該信息放入到任務隊列中,等帶發送。如果發送失敗,則進行輪詢重發機制進行處理。如果發送成功,修改數據庫狀態。如果一直失敗,這入庫如果有必要可以在加一個標識,需要人工進行處理。運行結果: