Redis 中的過期數據清理機制的簡單實現


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM