A:需求說明:
- 如果系統中需要用到定時執行計劃的,又不想用到中間件,如果輪詢數據庫的話,會導致大量資源消耗,這樣我們就可以使用Redis來實現類似功(需要使用rabbitMQ的請看這裏:https://blog.csdn.net/u010096717/article/details/82148681)
- 業務類型,如訂單一些評論,如果48h用戶未對商家評論,系統會自動產生一條默認評論,還有排隊到時提醒等
B:實現思路:
- 將整個Redis當做消息池,以kv形式存儲消息,key爲id,value爲具體的消息body
- 使用ZSET做優先隊列,按照score維持優先級(用當前時間+需要延時的時間作爲score)
- 輪詢ZSET,拿出score比當前時間戳大的數據(已過期的)
- 根據id拿到消息池的具體消息進行消費
- 消費成功,刪除改隊列和消息
- 消費失敗,讓該消息重新回到隊列
C:代碼實現
-
Message消息封裝類
@Data public class Message { /** * 消息id */ private String id; /** * 消息延遲/毫秒 */ private long delay; /** * 消息存活時間 */ private int ttl; /** * 消息體,對應業務內容 */ private String body; /** * 創建時間,如果只有優先級沒有延遲,可以設置創建時間爲0 * 用來消除時間的影響 */ private long createTime; }
2.基於redis的消息隊列
@Component
public class RedisMQ {
/**
* 消息池前綴,以此前綴加上傳遞的消息id作爲key,以消息{@link Message}
* 的消息體body作爲值存儲
*/
public static final String MSG_POOL = "Message:Pool:";
/**
* zset隊列 名稱 queue
*/
public static final String QUEUE_NAME = "Message:Queue:";
private static final int SEMIH = 30*60;
@Autowired
private RedisService redisService;
/**
* 存入消息池
* @param message
* @return
*/
public boolean addMsgPool(Message message) {
if (null != message) {
return redisService.setExp(MSG_POOL + message.getId(), message.getBody(), Long.valueOf(message.getTtl() + SEMIH));
}
return false;
}
/**
* 從消息池中刪除消息
* @param id
* @return
*/
public void deMsgPool(String id) {
redisService.remove(MSG_POOL + id);
}
/**
* 向隊列中添加消息
* @param key
* @param score 優先級
* @param val
* @return 返回消息id
*/
public void enMessage(String key, long score, String val) {
redisService.zsset(key,val,score);
}
/**
* 從隊列刪除消息
* @param id
* @return
*/
public boolean deMessage(String key, String id) {
return redisService.zdel(key, id);
}
}
3Redis操作工具類,這個工具類比較多方法,就不貼在這裏了(https://blog.csdn.net/u010096717/article/details/83783865)
4.編寫消息發送(生產者)
@Component
public class MessageProvider {
static Logger logger = LoggerFactory.getLogger(MessageProvider.class);
private static int delay = 30;//30秒,可自己動態傳入
@Resource
private RedisMQ redisMQ;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//改造成redis
public void sendMessage(String messageContent) {
try {
if (messageContent != null){
String seqId = UUID.randomUUID().toString();
// 將有效信息放入消息隊列和消息池中
Message message = new Message();
// 可以添加延遲配置
message.setDelay(delay*1000);
message.setCreateTime(System.currentTimeMillis());
message.setBody(messageContent);
message.setId(seqId);
// 設置消息池ttl,防止長期佔用
message.setTtl(delay + 360);
redisMQ.addMsgPool(message);
//當前時間加上延時的時間,作爲score
Long delayTime = message.getCreateTime() + message.getDelay();
String d = sdf.format(message.getCreateTime());
System.out.println("當前時間:" + d+",消費的時間:" + sdf.format(delayTime));
redisMQ.enMessage(RedisMQ.QUEUE_NAME,delayTime, message.getId());
}else {
logger.warn("消息內容爲空!!!!!");
}
}catch (Exception e){
e.printStackTrace();
}
}
}
5.消息消費者
@Component
public class RedisMQConsumer {
@Resource
private RedisMQ redisMQ;
@Autowired
private RedisService redisService;
@Autowired
private MessageProvider provider;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 消息隊列監聽器<br>
*
*/
@Scheduled(cron = "*/1 * * * * *")
public void monitor() {
Set<String> set = redisService.rangeByScore(RedisMQ.QUEUE_NAME, 0, System.currentTimeMillis());
if (null != set) {
long current = System.currentTimeMillis();
for (String id : set) {
long score = redisService.getScore(RedisMQ.QUEUE_NAME, id).longValue();
if (current >= score) {
// 已超時的消息拿出來消費
String str = "";
try {
str = redisService.get(RedisMQ.MSG_POOL + id);
System.out.println("消費了:" + str+ ",消費的時間:" + sdf.format(System.currentTimeMillis()));
} catch (Exception e) {
e.printStackTrace();
//如果出了異常,則重新放回隊列
System.out.println("消費異常,重新回到隊列");
provider.sendMessage(str);
} finally {
redisMQ.deMessage(RedisMQ.QUEUE_NAME, id);
redisMQ.deMsgPool(id);
}
}
}
}
}
}
6.配置信息
<!--1依賴引入-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2yml配置
spring:
redis:
database: 1
host: 127.0.0.1
port: 6379
以上代碼已經實現了延遲消費功能,現在來測試一下,調用MessageProvider的sendMessage方法,我設定了30秒
可以看到結果
因爲我們是用定時器去輪詢的,會出現誤差