redis過期策略以及內存淘汰機制(理論+配置)


一、redis的過期策略:

redis的過期策略是:定期刪除+惰性刪除
redis在存儲數據時,可能會設置過期時間,而所謂的定期刪除,指的是redis默認是每隔100ms就隨機抽取一些設置了過期時間的key進行檢查,如果過期了就會刪除。
至於為啥是每隔100ms隨機抽取一些數據進行檢查而不是全部檢查,這就與cpu負載有關了,如redis中的數據十分龐大,並且全部都設置了過期時間,依次全部檢查並且進行刪除的話負載太高,影響性能。
但是,由於是隨機抽取的key進行檢查進行刪除,那么很多的key可能會到了過期時間了還沒進行刪除,那么怎么辦呢?這時候,惰性刪除就會發揮作用了,所謂的惰性刪除,就是在讀取某個key的時候,redis會先檢查一個該key是否過期,如果過期了,就會在此時刪除,然后不會給你返回任何東西。
但是此時就會產生另外一個問題,假如一些key設置了過期時間,而定期刪除的隨機抽取沒有選中這些key,而恰好也沒有人去獲取這些key,惰性刪除也發揮不了作用了,那么這些數據就會越積累越多,redis一般作為緩存的,是基於內存的,這些數據越來越多的時候回導致內存耗盡,影響性能,這時候應該怎么辦呢?這時候,另一個重量型的武器就要發揮作用了,那就是:內存淘汰機制


 二、內存淘汰機制:

redis 內存淘汰機制(內存淘汰策略)有以下幾個:
• noeviction: 當內存不足以容納新寫入數據時,新寫入操作會報錯,這個一般沒人用吧,實在是太惡心了。
• allkeys-lru:當內存不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的 key(這個是最常用的)。
• allkeys-random:當內存不足以容納新寫入數據時,在鍵空間中,隨機移除某個 key,這個一般沒人用吧,為啥要隨機,肯定是把最近最少使用的 key 給干掉啊。
• volatile-lru:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,移除最近最少使用的 key(這個一般不太合適)。
• volatile-random:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,隨機移除某個 key。
• volatile-ttl:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,有更早過期時間的 key 優先移除。

但是理論性的東西我們明白了,那么該如何配置內存淘汰策略呢?


三、內存淘汰策略的配置

我們通過配置redis.conf中的maxmemory這個值來開啟內存淘汰功能。

# maxmemory

值得注意的是,maxmemory為0的時候表示我們對Redis的內存使用沒有限制。

根據應用場景,選擇淘汰策略

# maxmemory-policy noeviction

我們來了解下,內存淘汰的過程

首先,客戶端發起了需要申請更多內存的命令(如set)。

然后,Redis檢查內存使用情況,如果已使用的內存大於maxmemory則開始根據用戶配置的不同淘汰策略來淘汰內存(key),從而換取一定的內存。

最后,如果上面都沒問題,則這個命令執行成功。

動態改配置命令

此外,redis支持動態改配置,無需重啟。

設置最大內存

config set maxmemory 100000

設置淘汰策略

config set maxmemory-policy noeviction

如何選擇淘汰策略

下面看看幾種策略的適用場景

allkeys-lru:如果我們的應用對緩存的訪問符合冪律分布,也就是存在相對熱點數據,或者我們不太清楚我們應用的緩存訪問分布狀況,我們可以選擇allkeys-lru策略。

allkeys-random:如果我們的應用對於緩存key的訪問概率相等,則可以使用這個策略。

volatile-ttl:這種策略使得我們可以向Redis提示哪些key更適合被eviction。

另外,volatile-lru策略和volatile-random策略適合我們將一個Redis實例既應用於緩存和又應用於持久化存儲的時候,然而我們也可以通過使用兩個Redis實例來達到相同的效果,值得一提的是將key設置過期時間實際上會消耗更多的內存,因此我們建議使用allkeys-lru策略從而更有效率的使用內存。


四、設置過期時間

redis有四種命令可以用於設置鍵的生存時間和過期時間:

EXPIRE <KEY> <TTL> : 將鍵的生存時間設為 ttl 秒
PEXPIRE <KEY> <TTL> :將鍵的生存時間設為 ttl 毫秒
EXPIREAT <KEY> <timestamp> :將鍵的過期時間設為 timestamp 所指定的秒數時間戳
PEXPIREAT <KEY> <timestamp>: 將鍵的過期時間設為 timestamp 所指定的毫秒數時間戳.

當然,平時我們也可以使用java代碼的方式進行過期時間的設置,一些工具類可以直接滿足要求,在這里,我就提供一個工具類:

 import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Component;
    import org.springframework.util.CollectionUtils;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.TimeUnit;

    /**
     * redis的工具類
     * @author zangchuanlei
     * @date 2019.09.18
     */
    @Component
    public final class RedisUtil {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

        /**
         * 指定緩存失效的時間
         * @param key 鍵
         * @param time 時間(秒)
         * @return
         */
    public boolean expire(String key,long time){
        try {
            if(time > 0){
                redisTemplate.expire(key,time,TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
         * 根據key獲取過期時間
         * @param key 鍵 不能為null
         * @return 時間(秒)返回0代表為永久有效
     */
    public long getExpire(String key){
        return redisTemplate.getExpire(key,TimeUnit.SECONDS);
    }

        /**
         * 判斷key是否存在
         * @param key 鍵
         * @return true 存在 false 不存在
         */
    public boolean hasKey(String key){
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

        /**
         * 刪除緩存
         * @param key 可以傳一個值或者多個值
         */
    public void del(String... key){
        if(key != null && key.length > 0){
            if(key.length == 1){
                redisTemplate.delete(key[0]);
            }else{
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    //=========================String======================
        /**
         *普通緩存獲取
         * @param key 鍵
         * @return*/
    public Object get(String key){
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

        /**
         * 普通緩存的放入
         * @param key
         * @param value
         * @return
         */
    public boolean set(String key, Object value){
        try {
            redisTemplate.opsForValue().set(key,value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

        /**
         * 普通緩存放入並設置時間
         * @param key 鍵
         * @param value 值
         * @param time 時間(秒) time要大於0,如果time要是小於0,將設置無限期
         * @return true成功 false 失敗
         */
    public boolean set(String key,Object value,long time){
        try {
            if(time > 0){
                redisTemplate.opsForValue().set(key,value,time,TimeUnit.SECONDS);
            }else{
                set(key,value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

        /**
         * 遞增
         * @param key 鍵
         * @param delta 要增加幾(大於0)
         * @return
         */
    public long incr(String key,long delta){
        if(delta < 0){
            throw  new RuntimeException("增強因子必須大於0");
        }
        return redisTemplate.opsForValue().increment(key,delta);
    }

        /**
         * 遞減
         * @param key 鍵
         * @param delta 要減少幾(大於0)
         * @return
         */
    public long decr(String key,long delta){
        if(delta < 0){
            throw  new RuntimeException("遞減因子必須大於0");
        }
        return redisTemplate.opsForValue().increment(key,-delta);
    }

        /**
         * HashGet
         * @param key 鍵 不能為null
         * @param item 項 不能為null
         * @return*/
    public Object hget(String key,String item){
        return redisTemplate.opsForHash().get(key,item);
    }

        /**
         * 獲取hashKey對應的所有的鍵值
         * @param key 鍵
         * @return 對應的多個鍵值
         */
    public Map<Object,Object> hmget(String key){
        return redisTemplate.opsForHash().entries(key);
    }

        /**
         * hashSet
         * @param key 鍵
         * @param map 對應多個鍵值
         * @return true成功 false 失敗
         */
    public boolean hmset(String key,Map<String,Object> map){
        try {
            redisTemplate.opsForHash().putAll(key,map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    
        /**
         * HashSet並設置時間
         * @param key 鍵
         * @param map 對應多個鍵值
         * @param time 時間秒
         * @return true 成功 false 失敗
         */
    public boolean hmset(String key,Map<String,Object> map,long time){
        try {
            redisTemplate.opsForHash().putAll(key,map);
            if(time > 0){
                expire(key,time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

        /**
         * 向一張hash表中放入數據,如果不存在將創建
         * @param key 鍵
         * @param item 項
         * @param value 值
         * @return true成功 false失敗
         */
    public boolean hset(String key,String item,Object value){
        try {
            redisTemplate.opsForHash().put(key,item,value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

        /**
         * 向一張hash表中放入數據,如果不存在將創建
         * @param key 鍵
         * @param item 項
         * @param value 值
         * @param time 時間(秒) 注意:如果已經存在的hash表有時間,這里將會替換原有的時間
         * @return true成功 false 失敗
         */
    public boolean hset(String key,String item,Object value,long time){
        try {
            redisTemplate.opsForHash().put(key,item,value);
            if(time > 0){
                expire(key,time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    
        /**
         * 刪除hash表中的值
         * @param key 鍵 不能為null
         * @param item 項 可以使用多個 但不能為null
         */
    public void hdel(String key,Object... item){
        redisTemplate.opsForHash().delete(key,item);
    }

        /**
         * 判斷hash表中是否有該項的值
         * @param key 鍵 不能為null
         * @param item 項 不能為null
         * @return true 存在 false 不存在
         */
    public boolean hHasKey(String key,String item){
        return redisTemplate.opsForHash().hasKey(key,item);
    }

        /**
         * hash遞增 如果不存在,就會創建一個 把新增后的值返回
         * @param key 鍵
         * @param item 項
         * @param by 要增加幾(大於0)
         * @return
         */
    public double hincr(String key,String item,double by){
        return redisTemplate.opsForHash().increment(key,item,by);
    }

        /**
         * hash遞減 如果不存在,就會創建一個 把新增后的值返回
         * @param key 鍵
         * @param item 項
         * @param by 要減少幾(大於0)
         * @return
         */
    public double hdecr(String key,String item,double by){
        return redisTemplate.opsForHash().increment(key,item,-by);
    }

    //==========================set====================

        /**
         * 根據key獲取set中的所有值
         * @param key 鍵
         * @return
         */
    public Set<Object> sGet(String key){
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
        /**
         * 根據value從一個set中查詢,是否存在
         * @param key 鍵
         * @param value 值
         * @return true存在 false不存在
         */
    public boolean sHashKey(String key,Object value){
        try {
            return redisTemplate.opsForSet().isMember(key,value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

        /**
         * 將數據放入set緩存
         * @param key 鍵
         * @param values 值 可以是多個
         * @return 成功個數
         */
    public long sSet(String key,Object... values){
        try {
            return redisTemplate.opsForSet().add(key,values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

        /**
         * 將set數據放入緩存並設置失效時間
         * @param key 鍵
         * @param time 時間(秒)
         * @param values 值 可以是多個
         * @return 成功個數
         */
    public long sSetAndTime(String key,long time,Object... values){
        try {
            Long count = redisTemplate.opsForSet().add(key,values);
            if(time > 0){
                expire(key,time);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

        /**
         * 獲取set緩存的長度
         * @param key 鍵
         * @return
         */
    public long sGetSetSize(String key){
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

        /**
         * 移除值為value的
         * @param key 鍵
         * @param values 值 可以是多個
         * @return 移除的個數
         */
    public long setRemove(String key,Object... values){
        try {
            Long count = redisTemplate.opsForSet().remove(key,values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    //===================list====================

        /**
         * 獲取list緩存的內容
         * @param key 鍵
         * @param start 開始
         * @param end 結束 0到-1代表所有值
         * @return
         */
    public List<Object> lGet(String key,long start,long end){
        try {
            return redisTemplate.opsForList().range(key,start,end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

        /**
         * 獲取list緩存的長度
         * @param key 鍵
         * @return
         */
    public long lGetListSize(String key){
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

        /**
         * 通過索引 獲取list中的值
         * @param key 鍵
         * @param index 索引 index>=0, 0表頭,1 第二個元素 ;
         *              索引index<0時,-1,表尾,-2倒數第二個元素 以此類推
         * @return
         */
    public Object iGetIndex(String key,long index){
        try {
            return redisTemplate.opsForList().index(key,index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

        /**
         * 將list放入緩存
         * @param key 鍵
         * @param value 值
         * @return
         */
    public boolean lSet(String key,Object value){
        try {
            redisTemplate.opsForList().rightPush(key,value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 將list放入緩存
     * @param key 鍵
     * @param value 值
     * @param time 時間(秒)
     * @return
     */
    public boolean lSet(String key,Object value,long time){
        try {
            redisTemplate.opsForList().rightPush(key,value);
            if(time > 0){
                expire(key,time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 將list放入緩存
     * @param key 鍵
     * @param value 值
     * @return
     */
    public boolean lSet(String key,List<Object> value){
        try {
            redisTemplate.opsForList().rightPushAll(key,value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 將list放入緩存
     * @param key 鍵
     * @param value 值
     * @param time 時間(秒)
     * @return
     */
    public boolean lSet(String key,List<Object> value,long time){
        try {
            redisTemplate.opsForList().rightPushAll(key,value);
            if(time > 0){
                expire(key,time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根據索引修改list中的某條數據
     * @param key 鍵
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key,long index,Object value){
        try {
            redisTemplate.opsForList().set(key,index,value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N個值為value
     * @param key 鍵
     * @param count 移除多少個
     * @param value 值
     * @return 移除的個數
     */
    public long lRemove(String key,long count,Object value){
        try {
            Long remove = redisTemplate.opsForList().remove(key,count,value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    } }

 



參考原文鏈接:https://blog.csdn.net/baidu_26954625/article/details/90648597


免責聲明!

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



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