Redis 中的過期資料清理機制
阿新 • • 發佈:2021-10-26
目前常見的過期清理機制有: 惰性清理、定時清理、定期清理
在 Redis 中採用: 定期清理 + 惰性清理機制來刪除過期資料
惰性清理機制
package com.xtyuns.redisclean; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; /** * 惰性清理機制 * 優點: 無需額外操作, 僅在取值前判斷是否過期即可 * 缺點: 如果過期的資料不再進行取值操作就會一直存在, 浪費記憶體空間, 因此在 Redis 中需要配合其他策略來使用 */ public class ALazyClean { private static final Map<String, String> redisMap = new HashMap<>(); private static final Map<String, Long> expireMap = new HashMap<>(); /** * 向容器中新增資料 * @param key 鍵 * @param value 值 * @param expire 設定有效時間, null 表示不自動過期 */ public static void set(String key, String value, Long expire) { redisMap.put(key, value); if (null != expire) { expireMap.put(key, System.currentTimeMillis() + expire); } } /** * 惰性清理, 當取值時才進行資料清理 * @param key 所取資料的鍵 * @return 返回指定資料, 資料失效時返回 null */ public static String get(String key) { Long end = expireMap.get(key); if (null != end && System.currentTimeMillis() > end) { redisMap.remove(key); expireMap.remove(key); } return redisMap.get(key); } public static void main(String[] args) throws InterruptedException { set("k1", "v1", null); set("k2", "v2", 2000L); TimeUnit.SECONDS.sleep(3); System.out.println(get("k1")); System.out.println(get("k2")); } }
定時清理機制
package com.xtyuns.redisclean; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * 定時清理機制 * 優點: 實時刪除, 記憶體中不存在已過期的資料 * 缺點: 當存在大量定時資料時, 需要鉅額的執行緒開銷, 因此在 Redis 中沒有采用這種策略 */ public class BOnTimeClean { private static final Map<String, String> redisMap = new HashMap<>(); private static final ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor( 10, 20, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); /** * 向容器中新增資料, 當該資料設定有效時間時為該資料啟動一條清理執行緒, 在資料有效期結束時刪除該資料 * @param key 鍵 * @param value 值 * @param expire 設定有效時間, null 表示不自動過期 */ public static void set(String key, String value, Long expire) { redisMap.put(key, value); if (null != expire) { poolExecutor.submit(() -> { try { TimeUnit.MILLISECONDS.sleep(expire); } catch (InterruptedException e) { e.printStackTrace(); } redisMap.remove(key); }); } } /** * 獲取指定資料 * @param key 所取資料的鍵 * @return 返回指定資料, 資料失效時返回 null */ public static String get(String key) { return redisMap.get(key); } public static void main(String[] args) throws InterruptedException { set("k1", "v1", null); set("k2", "v2", 2000L); TimeUnit.SECONDS.sleep(3); System.out.println(get("k1")); System.out.println(get("k2")); } }
週期清理機制
package com.xtyuns.redisclean; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.TimeUnit; /** * 週期清理機制 * 優點: 避免了使用大量執行緒的開銷, 同時週期性的清除掉了過期資料 * 缺點: 由於清理執行緒的週期性會造成資料髒讀情況, 因此在 Redis 中需要配合其他策略來使用 */ public class CScheduleClean { private static final Map<String, String> redisMap = new HashMap<>(); private static final Map<String, Long> expireMap = new HashMap<>(); // 在程式開始時啟用一條清理執行緒執行清理任務, 每次任務的時間間隔是 10 秒 static { Thread doClean = new Thread(() -> { while (true) { try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } // 這裡不能通過 forEach 來刪除, 因為會觸發 ConcurrentModificationException Iterator<Map.Entry<String, Long>> iterator = expireMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, Long> next = iterator.next(); if (System.currentTimeMillis() > next.getValue()) { iterator.remove(); redisMap.remove(next.getKey()); } } } }); doClean.setDaemon(true); doClean.start(); } /** * 向容器中新增資料 * @param key 鍵 * @param value 值 * @param expire 設定有效時間, null 表示不自動過期 */ public static void set(String key, String value, Long expire) { redisMap.put(key, value); if (null != expire) { expireMap.put(key, System.currentTimeMillis() + expire); } } /** * 獲取指定資料 * @param key 所取資料的鍵 * @return 返回指定資料, 資料失效時返回 null */ public static String get(String key) { return redisMap.get(key); } public static void main(String[] args) throws InterruptedException { set("k1", "v1", null); set("k2", "v2", 2000L); // 3 秒內清理執行緒還未執行, 因此出現髒讀現象 TimeUnit.SECONDS.sleep(3); System.out.println(get("k1")); System.out.println(get("k2")); } }
週期 + 惰性清理機制
package com.xtyuns.redisclean;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 週期 + 惰性清理機制
* 優點: 即保證了記憶體中不會永久儲存過期資料, 也解決了髒讀問題
* 缺點: 雙重機制執行, 相較於惰性清理機制增加了清理執行緒的開銷, 但是總體影響不大, Redis 中採用這種策略來清除過期資料
*
* tip: 在 Redis 的週期清理執行緒中並不是每次都遍歷所有資料, 而是每次隨機取出一定的資料進行遍歷
*/
public class DScheduleLazyClean {
private static final Map<String, String> redisMap = new HashMap<>();
private static final Map<String, Long> expireMap = new HashMap<>();
// 在程式開始時啟用一條清理執行緒執行清理任務, 每次任務的時間間隔是 10 秒
static {
Thread doClean = new Thread(() -> {
while (true) {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 這裡不能通過 forEach 來刪除, 因為會觸發 ConcurrentModificationException
Iterator<Map.Entry<String, Long>> iterator = expireMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Long> next = iterator.next();
if (System.currentTimeMillis() > next.getValue()) {
iterator.remove();
redisMap.remove(next.getKey());
}
}
}
});
doClean.setDaemon(true);
doClean.start();
}
/**
* 向容器中新增資料
* @param key 鍵
* @param value 值
* @param expire 設定有效時間, null 表示不自動過期
*/
public static void set(String key, String value, Long expire) {
redisMap.put(key, value);
if (null != expire) {
expireMap.put(key, System.currentTimeMillis() + expire);
}
}
/**
* 週期 + 惰性清理, 當取值時觸發二次資料清理機制
* @param key 所取資料的鍵
* @return 返回指定資料, 資料失效時返回 null
*/
public static String get(String key) {
Long end = expireMap.get(key);
if (null != end && System.currentTimeMillis() > end) {
redisMap.remove(key);
expireMap.remove(key);
}
return redisMap.get(key);
}
public static void main(String[] args) throws InterruptedException {
set("k1", "v1", null);
set("k2", "v2", 2000L);
TimeUnit.SECONDS.sleep(3);
System.out.println(get("k1"));
System.out.println(get("k2"));
}
}