目前常見的過期清理機制有: 惰性清理、定時清理、定期清理
在 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"));
}
}