1. 生產需求:
- 用戶下訂單後,15分鐘未支付自動取消;
- 用戶成功下單支付後確認收貨, 15天默認好評
2. 實現思路
利用redis的排序列表,ZSet進行需求實現, 下面是我的流程圖和思路導線
3. 思路說明
我們把Zset中的score當成時間戳, 這樣我們就可以獲得以時間戳排序的任務列表, 這我們通過score區間進行拉取任務,進行消費.
4.代碼封裝實現
- 首先是封裝延時隊列的工廠(完美契合Spring框架), 如果想要創建自己的特色延時隊列則需要繼承這個抽象工廠
package com.zjrcinfo.zjguahao.common.redis.delayqueue;
import com.zjrcinfo.zjguahao.common.redis.cluster.JedisClusterCache;
import com.zjrcinfo.zjguahao.common.utils.ThreadPoolUtil;
import com.zjrcinfo.zjguahao.common.web.log.LoggerName;
import org.apache.shiro.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import redis.clients.jedis.Tuple;
import javax.annotation.PostConstruct;
import java.util.Calendar;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Description: 延時隊列工廠
* User: zhouzhou
* Date: 2019-09-26
* Time: 14:32
*/
public abstract class AbstractDelayQueueMachineFactory {
protected Logger logger = LoggerFactory.getLogger(LoggerName.KAFKA);
@Autowired
protected JedisClusterCache jedisClusterCache;
/**
* 插入任務id
* @param jobId 任務id(隊列內唯一)
* @param time 延時時間(單位 :秒)
* @return 是否插入成功
*/
public boolean addJobId(String jobId, Integer time) {
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND, time);
long delaySeconds = instance.getTimeInMillis() / 1000;
Long zadd = jedisClusterCache.zadd(setDelayQueueName(), delaySeconds, jobId);
return zadd > 0;
}
private void startDelayQueueMachine() {
logger.info(String.format("延時隊列機器{%s}開始運作", setDelayQueueName()));
// 發生異常捕獲並且繼續不能讓戰鬥停下來
while (true) {
try {
// 獲取當前時間的時間戳
long now = System.currentTimeMillis() / 1000;
// 獲取當前時間前的任務列表
Set<Tuple> tuples = jedisClusterCache.zrangeByScoreWithScores(setDelayQueueName(), 0, now);
// 如果不爲空則遍歷判斷其是否滿足取消要求
if (!CollectionUtils.isEmpty(tuples)) {
for (Tuple tuple : tuples) {
String jobId = tuple.getElement();
Long num = jedisClusterCache.zrem(setDelayQueueName(), jobId);
// 如果移除成功, 則取消訂單
if (num > 0) {
ThreadPoolUtil.execute(() ->invoke(jobId));
}
}
}
} catch (Exception e) {
logger.warn(String.format("處理延時任務發生異常,異常原因爲{%s}", e.getMessage()), e);
} finally {
// 間隔一秒鐘搞一次
try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 最終執行的任務方法
* @param jobId 任務id
*/
public abstract void invoke(String jobId);
/**
* 要實現延時隊列的名字
*
*/
public abstract String setDelayQueueName();
@PostConstruct
public void init(){
new Thread(this::startDelayQueueMachine).start();
}
}
- 測試延時隊列的實現
/**
* Description: 測試訂單延時隊列
* User: zhouzhou
* Date: 2019-09-26
* Time: 15:14
*/
@Component
public class TestOrderDelayQueue extends AbstractDelayQueueMachineFactory {
@Autowired
private TestOrderDelayQueueService testOrderDelayQueueService;
@Override
public void invoke(String jobId) {
testOrderDelayQueueService.cancelOrder(jobId);
}
@Override
public String setDelayQueueName() {
return "TestOrder";
}
}
- 具體延時消費的Service
package com.zjrcinfo.zjguahao.product.service.impl;
import org.springframework.stereotype.Service;
/**
* Description:
* User: zhouzhou
* Date: 2019-09-26
* Time: 15:21
*/
@Service
public class TestOrderDelayQueueService {
public void cancelOrder(String orderNumber) {
System.out.println(System.currentTimeMillis() + "ms:redis消費了一個任務:消費的訂單OrderId爲" + orderNumber);
}
public void cancelReg(String orderNumber) {
System.out.println(System.currentTimeMillis() + "ms:redis消費了一個任務:消費的掛號訂單OrderId爲" + orderNumber);
}
}
- 測試用的Controller
package com.zjrcinfo.zjguahao.product.controller;
import com.zjrcinfo.zjguahao.common.web.log.LoggerName;
import com.zjrcinfo.zjguahao.product.service.delay.TestOrderDelayQueue;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.Random;
/**
* Description:
* User: zhouzhou
* Date: 2019-06-03
* Time: 17:47
*/
@RestController
@Api("緩存測試")
@RequestMapping("/redis")
public class RedisTestController {
private Logger logger = LoggerFactory.getLogger(LoggerName.REMOTE);
@Autowired
private TestOrderDelayQueue testOrderDelayQueue;
// ------------------------ 延時隊列 -------------------
@ApiOperation("添加定時orderId")
@RequestMapping(value = "/addDelayOrder/{orderId}/{time}", method = RequestMethod.POST)
public Object addZset(@PathVariable String orderId, @PathVariable Integer time) {
boolean flag = testOrderDelayQueue.addJobId(orderId, time);
return String.format("已經存入了訂單id{%s},延時{%s}秒", orderId, time);
}
}
5.啓動測試
- 項目啓動:日誌打印延時機器啓動成功
- 通過swagger向隊列插入任務
- 等待五秒,日誌打印,測試成功
最後奉上, github代碼示例: https://github.com/zjhzzhouzhou/redis-project 希望幫助到大家.