1. 程式人生 > 實用技巧 >快取擊穿 解決方案

快取擊穿 解決方案

本文程式碼邏輯思想來自阿里的JetCache框架,這裡只是自己的學習與理解,記錄下;具體實現可以去檢視JetCache原始碼:git地址:https://github.com/alibaba/jetcache
實際應用中可以接JetCache框架,使用@CachePenetrationProtect註解即可實現

當快取訪問未命中的情況下,對併發進行的載入行為進行保護。 當前版本實現的是單JVM內的保護,即同一個JVM中同一個key只有一個執行緒去載入,其它執行緒等待結果。

 1     // 本地快取map
 2     static Map<String, LoaderLock> loaderMap = new
ConcurrentHashMap<>(); 3 4 static Object cachePenetrationProtectTest() { 5 6 String redisKey = "redisKey"; 7 8 // TODO 查快取,查到則直接退出,沒查到則繼續往下執行,查詢資料庫 9 10 Object loadedValue; 11 // 用boolean陣列,而不是boolean變數,沒什麼特別原因,主要是computeIfAbsent中的變數需要final修飾 12 boolean
create[] = new boolean[1]; 13 LoaderLock ll = loaderMap.computeIfAbsent(redisKey, key -> { 14 // 只有第一個請求進來時create[0]才會為true 15 create[0] = true; 16 LoaderLock loaderLock = new LoaderLock(); 17 loaderLock.signal = new CountDownLatch(1); 18 return
loaderLock; 19 }); 20 // 是第一個進來的請求 21 if (create[0]) { 22 try { 23 // TODO 執行具體業務程式碼,這裡loadedValue暫時返回null(實際應該是業務程式碼執行後的返回值) 24 loadedValue = null; 25 // 這裡存放執行業務程式碼後需要返回的值 26 ll.value = loadedValue; 27 28 // 業務程式碼執行成功沒拋異常,則success修改為true 29 ll.success = true; 30 return loadedValue; 31 } finally { 32 // 刪除掉map中的lockKey值,使下個請求進來的時候create[0]可以為true 33 loaderMap.remove(redisKey); 34 // 讓其他執行緒結束等待 35 ll.signal.countDown(); 36 } 37 } else { 38 try { 39 // 其他請求在這等待,設定個超時時間,可以做成可配 40 ll.signal.await(5, TimeUnit.SECONDS); 41 if (ll.success) { 42 return ll.value; 43 } else { 44 // TODO 資料庫查詢異常,這裡可以丟擲異常,直接返回請求,配合熔斷措施處理 45 throw new RuntimeException("queryDB exception"); 46 } 47 } catch (InterruptedException e) { 48 throw new CacheException("loader wait interrupted", e); 49 } 50 } 51 } 52 53 static class LoaderLock { 54 CountDownLatch signal; 55 volatile boolean success; 56 volatile Object value; 57 }

JetCache部分原始碼(2.6.0版本),synchronizedLoad方法:

static <K, V> V synchronizedLoad(CacheConfig config, AbstractCache<K,V> abstractCache,
                                     K key, Function<K, V> newLoader, Consumer<V> cacheUpdater) {
        ConcurrentHashMap<Object, LoaderLock> loaderMap = abstractCache.initOrGetLoaderMap();
        Object lockKey = buildLoaderLockKey(abstractCache, key);
        while (true) {
            boolean create[] = new boolean[1];
            LoaderLock ll = loaderMap.computeIfAbsent(lockKey, (unusedKey) -> {
                create[0] = true;
                LoaderLock loaderLock = new LoaderLock();
                loaderLock.signal = new CountDownLatch(1);
                loaderLock.loaderThread = Thread.currentThread();
                return loaderLock;
            });
            if (create[0] || ll.loaderThread == Thread.currentThread()) {
                try {
                    V loadedValue = newLoader.apply(key);
                    ll.success = true;
                    ll.value = loadedValue;
                    cacheUpdater.accept(loadedValue);
                    return loadedValue;
                } finally {
                    if (create[0]) {
                        ll.signal.countDown();
                        loaderMap.remove(lockKey);
                    }
                }
            } else {
                try {
                    Duration timeout = config.getPenetrationProtectTimeout();
                    if (timeout == null) {
                        ll.signal.await();
                    } else {
                        boolean ok = ll.signal.await(timeout.toMillis(), TimeUnit.MILLISECONDS);
                        if(!ok) {
                            logger.info("loader wait timeout:" + timeout);
                            return newLoader.apply(key);
                        }
                    }
                } catch (InterruptedException e) {
                    logger.warn("loader wait interrupted");
                    return newLoader.apply(key);
                }
                if (ll.success) {
                    return (V) ll.value;
                } else {
                    continue;
                }

            }
        }
    }

static class LoaderLock {
        CountDownLatch signal;
        Thread loaderThread;
        volatile boolean success;
        volatile Object value;
    }