延遲隊列:顧名思義就是支持將消息按照一定的要求延遲投遞的消息隊列。生活中需要使用延遲隊列最普遍的場景就是訂單支付,訂單隻有在規定的時間內完成支付,交易纔算真正的完成,沒有在規定時間內完成支付的訂單將會被取消。
Caffeine:一款高性能、接近最優的緩存庫。
Caffeine和延遲隊列又有什麼聯繫呢?延遲隊列的核心特徵就是將消息延遲投遞,Caffeine的老化機制剛好可以滿足延遲隊列的基本要求。Caffeine可以按照時間對存儲的值進行老化,不同值的老化時間可以不同,並且Caffeine支持將值的老化信息發送到監聽器,利用這一特性就可以實現簡單的延遲隊列。
當前已經有很多優秀的延遲隊列實現,如果需要延遲隊列功能請使用經過考驗的軟件哦。
Delay Queue架構
基於Java自帶的Blocking Queue和Caffeine實現Delay Queue。Delay Queue只提供兩個簡單的接口用於讀寫數據,
write: 客戶端向Delay Queue添加數據時,添加的數據會直接寫入Caffeine並根據參數設置老化時間。
read: 客戶端從Delay Queue獲取數據,如果有可被獲取的數據Delay Queue直接返回位於隊列頭部的數據,否則將會阻塞客戶端直到有可用數據爲止。
event: Caffeine將值的過期事件處理後寫入 Blocking Queue。
接口說明
write:
public void write(E element, long delay, TimeUnit unit);
寫入element到隊列,並設置經過delay的延遲後element才能被read。
read:
public E read(long timeout, TimeUnit unit) throws InterruptedException;
嘗試從queue讀取數據,最多等待timeout時間
3 代碼實現
3.1 數據封裝
引入寫入到queue中的每個數據需要被延遲處理的時間不同,因此再將值寫入到Caffeine之前需要簡單的封裝,封裝後的數據包括原始數據和延遲時間等信息。
private static final class DataWrapper<E> {
private final E data;
private final long delay;
private final TimeUnit unit;
public DataWrapper(E data, long delay, TimeUnit unit) {
this.data = data;
this.delay = delay;
this.unit = unit;
}
}
3.2 event處理
我們需要監聽Caffeine對過期數據的處理,並把數據寫入到Blocking Queue中。
private static final class Listener<E> implements RemovalListener<String, DataWrapper<E>> {
private final BlockingQueue<E> blockingQueue;
public Listener(BlockingQueue<E> blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void onRemoval(@Nullable String s, @Nullable DataWrapper<E> dataWrapper, @NonNull RemovalCause removalCause) {
try {
if (Objects.nonNull(dataWrapper)) {
blockingQueue.put(dataWrapper.data);
}
} catch (InterruptedException e) {
e.printStackTrace(); // 僅用於測試
}
}
}
3.3 過期策略設置
對寫入到Caffeine中的每一個數據設置一個過期時間,這可以通過Caffeine中的Expiry接口實現
private Expiry<String, DataWrapper<E>> expiry = new Expiry<>() {
@Override
public long expireAfterCreate(@NonNull String key, @NonNull DataWrapper<E> dataWrapper, long currentTime) {
return dataWrapper.unit.toNanos(dataWrapper.delay);
}
@Override
public long expireAfterUpdate(@NonNull String key, @NonNull DataWrapper<E> dataWrapper, long currentTime, @NonNegative long currentDuration) {
return dataWrapper.unit.toNanos(dataWrapper.delay);
}
@Override
public long expireAfterRead(@NonNull String key, @NonNull DataWrapper<E> dataWrapper, long currentTime, @NonNegative long currentDuration) {
return dataWrapper.unit.toNanos(dataWrapper.delay);
}
};
3.4 CaffeineDelayQueue
在完成3.1-3.3章節的代碼編寫後,實現一個基於Caffeine的延遲隊列就變得十分簡單,關鍵代碼如下:
public class CaffeineDelayQueue<E> {
private final BlockingQueue<E> blockingQueue;
private final Cache<String, DataWrapper<E>> scheduler;
public CaffeineDelayQueue(int size) {
this.blockingQueue = new ArrayBlockingQueue<>(size);
this.scheduler = Caffeine.newBuilder()
.expireAfter(expiry)
.evictionListener(new Listener<>(blockingQueue))
.scheduler(Scheduler.systemScheduler())
.build();
}
}
4 使用樣例
CaffeineDelayQueue<Integer> queue = new CaffeineDelayQueue<>(1024);
queue.write(1, 1, TimeUnit.SECONDS);
queue.write(2, 3, TimeUnit.SECONDS);
queue.write(3, 2, TimeUnit.SECONDS);
for (; ; ) {
Integer data = queue.read(200, TimeUnit.MILLISECONDS);
if (Objects.nonNull(data)) {
System.out.println(data);
}
}
上面樣例程序的輸出如下:
1
3
2