一種簡單的Java延遲處理器

最近做一個新項目時,有這么一個場景:使用TDDL數據庫分庫分表,且按SQL讀寫分離。在為系統做緩存層時,考慮到并發讀寫,可能會出現這么個問題:

緩存未設置或已過期時,寫數據,主庫已完成但未拉起緩存,從庫也未完成同步,此時有查詢請求,將會訪問到從庫,把舊值查詢出來,如果此時寫數據線程已經用最新數據拉起緩存,那讀數據線程將會用舊值將緩存覆蓋,導致緩存與數據庫不一致,在緩存失效或下次修改之前,緩存數據將一直是過期的。

這種情況雖然比較少見,但還是有一定概率出現的。為了屏蔽主從同步可能帶來的問題,一種比較簡單的解決方法是延遲更新緩存,給數據庫一定時間完成同步再進行緩存設置,這樣就能保證最后緩存的是新數據。當然這種方案只能解決數據庫同步延遲不大的普通情況。

如果是一些讀多寫少的數據,也可以在修改完成改一遍緩存,再放到延遲任務改一遍。

為此實現了一個簡單的延遲任務處理器,主要思路是將任務放入DelayQueue,由一個專用線程從DelayQueue中取出到期的任務,放入執行線程池中執行。整體比較簡單。

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.List;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

/**
 * 延遲任務處理器
 * @author jerryli
 * @date 2018/3/27
 */
public class DelayedTaskExecutor implements InitializingBean, DisposableBean{

    private static final Logger logger = LoggerFactory.getLogger(DelayedTaskExecutor.class);

    private String name;

    private ThreadPoolExecutor executor;

    private volatile boolean running;

    private ThreadPoolExecutor putExecutor ;

    private DelayQueue<Task> delayQueue = new DelayQueue<>();

    public DelayedTaskExecutor(String name){
        this.name = name;
    }

    public void submit(Runnable runnable, long delayedTime){
        Task task = new Task(runnable,delayedTime);
        delayQueue.offer(task);
        logger.info("{}添加延遲任務 {} 延遲{}ms",this.name,task,delayedTime);
    }

    @Override
    public void destroy() throws Exception {
        running = false;

        if(putExecutor != null){
            putExecutor.shutdown();
            boolean shutdown = putExecutor.awaitTermination(8000, TimeUnit.MILLISECONDS);
            if(!shutdown){
                List<Runnable> notRunList = putExecutor.shutdownNow();
                if(CollectionUtils.isNotEmpty(notRunList)){
                    logger.warn("強制關閉延遲任務轉移線程...未執行任務數量{}",notRunList.size());
                }
            }
        }
        if(executor != null){
            executor.shutdown();
            boolean shutdown = executor.awaitTermination(8000, TimeUnit.MILLISECONDS);
            if(!shutdown){
                List<Runnable> notRunList = executor.shutdownNow();
                if(CollectionUtils.isNotEmpty(notRunList)){
                    logger.warn("強制關閉延遲任務執行線程池...未執行任務數量{}",notRunList.size());
                }
            }
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        running = true;

        executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors() + 1,
            Runtime.getRuntime().availableProcessors() * 2,0,TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
            new ThreadFactoryBuilder()
                .setNameFormat(this.name + "-pool-%d")
                .setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
                    @Override
                    public void uncaughtException(Thread t, Throwable e) {
                        logger.error("延遲任務執行線程異常,thread={}",t,e);
                    }
                })
                .build());

        putExecutor = new ThreadPoolExecutor(1,1,0,TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
            new ThreadFactoryBuilder()
                .setNameFormat(this.name + "-submiter")
                .setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
                    @Override
                    public void uncaughtException(Thread t, Throwable e) {
                        logger.error("延遲任務轉移線程異常,thread={}",t,e);
                    }
                })
                .build());

        putExecutor.execute(new Runnable() {
            @Override
            public void run() {
                while(running || delayQueue.size() > 0){
                    try {
                        Task task = delayQueue.poll(1000, TimeUnit.MILLISECONDS);
                        if(task == null){
                            continue;
                        }
                        logger.info("開始執行延遲任務 {}",task);
                        executor.execute(task.getRunnable());
                    } catch (InterruptedException e) {
                        logger.error("延遲任務轉移線程異常interrupted",e);
                    } catch (Throwable e){
                        logger.error("延遲任務轉移線程異常",e);
                    }
                }
                logger.info("延遲任務轉移線程退出");
            }
        });
    }

    class Task implements Delayed{

        // 提交時間
        private long submitTime;

        private long runningTime;

        private Runnable runnable;

        Task(Runnable runnable,long delayTime){
            this.runnable = runnable;
            this.submitTime = System.currentTimeMillis();
            this.runningTime = submitTime + delayTime;
        }

        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(runningTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }

        @Override
        public int compareTo(Delayed o) {
            if(o == null || !(o instanceof Task)){
                return 1;
            }
            if(o == this){
                return 0;
            }
            Task otherTask = (Task) o;
            if(this.runningTime > otherTask.runningTime){
                return 1;
            }else if(this.runningTime == otherTask.runningTime){
                return 0;
            }else{
                return -1;
            }
        }

        public Runnable getRunnable() {
            return runnable;
        }
    }

}

測試代碼:

public static void main(String[] args) throws Exception {
        // 使用Spring可不用調用afterPropertiesSet和destroy方法
        DelayedTaskExecutor delayedTaskExecutor = new DelayedTaskExecutor("cache-delay");
        delayedTaskExecutor.afterPropertiesSet();
        delayedTaskExecutor.submit(() -> System.out.println("hello"), 2000);
        delayedTaskExecutor.destroy();
    }
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • layout: posttitle: 《Java并發編程的藝術》筆記categories: Javaexcerpt...
    xiaogmail閱讀 5,875評論 1 19
  • 在服務器端程序開發領域,性能問題一直是備受關注的重點。業界有大量的框架、組件、類庫都是以性能為賣點而廣為人知。然而...
    零一間閱讀 895評論 0 12
  • 在服務器端程序開發領域,性能問題一直是備受關注的重點。業界有大量的框架、組件、類庫都是以性能為賣點而廣為人知。然而...
    dreamer_lk閱讀 1,033評論 0 17
  • feisky云計算、虛擬化與Linux技術筆記posts - 1014, comments - 298, trac...
    不排版閱讀 3,923評論 0 5
  • 我已經很久很久沒有更新文章了,久到好像過了個漫長的世紀。因為我在找尋另外一個我,那個不那么胖的我。 我不知道有多少...
    青露辰閱讀 267評論 0 0