使用單機緩存之王Caffeine實現一個延遲隊列

延遲隊列:顧名思義就是支持將消息按照一定的要求延遲投遞的消息隊列。生活中需要使用延遲隊列最普遍的場景就是訂單支付,訂單隻有在規定的時間內完成支付,交易纔算真正的完成,沒有在規定時間內完成支付的訂單將會被取消。

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章