一、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
