一阱飘、概述
一個高并發(fā)的框架中,往往會設(shè)置多層緩存速妖,比如Nginx應(yīng)用層的緩存高蜂、web層JVM中的緩存、redis集群緩存罕容,最后才會到數(shù)據(jù)庫备恤。但是生產(chǎn)環(huán)境下的使用并沒有那么簡單,還需要考慮多種情況锦秒。
二烘跺、高并發(fā)下的緩存問題
緩存穿透
緩存穿透是指查詢一個根本不存在的數(shù)據(jù), 緩存層和存儲層都不會命中脂崔, 通常出于容錯的考慮滤淳, 如果從存儲 層查不到數(shù)據(jù)則不寫入緩存層。
緩存穿透將導(dǎo)致不存在的數(shù)據(jù)每次請求都要到存儲層去查詢砌左, 失去了緩存保護后端存儲的意義脖咐。 造成緩存穿透的基本原因有兩個:
- 自身業(yè)務(wù)代碼或者數(shù)據(jù)出現(xiàn)問題。
- 一些惡意攻擊汇歹、 爬蟲等造成大量空命中屁擅。
緩存穿透問題解決方案:
- 緩存空對象
String get(String key) {
// 從緩存中獲取數(shù)據(jù)
String cacheValue = cache.get(key);
// 緩存為空
if (StringUtils.isBlank(cacheValue)) {
// 從存儲中獲取
String storageValue = storage.get(key);
cache.set(key, storageValue);
/** 如果存儲數(shù)據(jù)為空,需要設(shè)置一個過期時間(300秒)产弹,原因有二派歌,
一是值為空的緩存短時間防止惡意高并發(fā)請求,但是過后沒意義要刪除痰哨;
二是防止后面真的存在該key胶果,需要重新緩存。
**/
if (storageValue == null) {
cache.expire(key, 60 * 5);
}
return storageValue;
// 緩存非空
} else {
return cacheValue;
}
}
- 布隆過濾器
對于惡意攻擊斤斧,向服務(wù)器請求大量不存在的數(shù)據(jù)造成的緩存穿透早抠,還可以用布隆過濾器先做一次過濾,對于不 存在的數(shù)據(jù)布隆過濾器一般都能夠過濾掉撬讽,不讓請求再往后端發(fā)送蕊连。當(dāng)布隆過濾器說某個值存在時悬垃,這個值可 能不存在;當(dāng)它說不存在時甘苍,那就肯定不存在尝蠕。
布隆過濾器就是一個大型的位數(shù)組和幾個不一樣的無偏 hash 函數(shù)。所謂無偏就是能夠把元素的 hash 值算得 比較均勻载庭。
向布隆過濾器中添加 key 時趟佃,會使用多個 hash 函數(shù)對 key 進行 hash 算得一個整數(shù)索引值然后對位數(shù)組長度 進行取模運算得到一個位置,每個 hash 函數(shù)都會算得一個不同的位置昧捷。再把位數(shù)組的這幾個位置都置為 1 就 完成了 add 操作。
向布隆過濾器詢問 key 是否存在時罐寨,跟 add 一樣靡挥,也會把 hash 的幾個位置都算出來,看看位數(shù)組中這幾個位 置是否都為 1鸯绿,只要有一個位為 0跋破,那么說明布隆過濾器中這個key 不存在。如果都是 1瓶蝴,這并不能說明這個 key 就一定存在毒返,只是極有可能存在,因為這些位被置為 1 可能是因為其它的 key 存在所致舷手。如果這個位數(shù)組 比較稀疏拧簸,這個概率就會很大,如果這個位數(shù)組比較擁擠男窟,這個概率就會降低盆赤。
這種方法適用于數(shù)據(jù)命中不高、 數(shù)據(jù)相對固定歉眷、 實時性低(通常是數(shù)據(jù)集較大) 的應(yīng)用場景牺六, 代碼維護較為 復(fù)雜, 但是緩存空間占用很少汗捡。
可以用guvua包自帶的布隆過濾器淑际,引入依賴:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>22.0</version>
</dependency>
示例偽代碼:
import com.google.common.hash.BloomFilter;
//初始化布隆過濾器
//1000:期望存入的數(shù)據(jù)個數(shù),0.001:期望的誤差率
BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.forName("utf‐8")), 1000, 0.001);
//把所有數(shù)據(jù)存入布隆過濾器
void init(){
for String key: keys) {
bloomFilter.put(key);
}
}
String get(String key) {
// 從布隆過濾器這一級緩存判斷下key是否存在
Boolean exist = bloomFilter.mightContain(key);
if(!exist){
return "";
}
// 從緩存中獲取數(shù)據(jù)
String cacheValue = cache.get(key);
// 緩存為空
if (StringUtils.isBlank(cacheValue)) {
// 從存儲中獲取
String storageValue = storage.get(key);
cache.set(key, storageValue);
// 如果存儲數(shù)據(jù)為空扇住, 需要設(shè)置一個過期時間(300秒)
if (storageValue == null) {
cache.expire(key, 60 * 5);
}
return storageValue;
} else {
// 緩存非空
return cacheValue;
}
}
緩存失效
由于大批量緩存在同一時間失效可能導(dǎo)致大量請求同時穿透緩存直達數(shù)據(jù)庫春缕,可能會造成數(shù)據(jù)庫瞬間壓力過大甚至掛掉,對于這種情況我們在批量增加緩存時好將這一批數(shù)據(jù)的緩存過期時間設(shè)置為一個時間段內(nèi)的不同時間艘蹋。
緩存失效問題解決方案:
String get(String key) {
// 從緩存中獲取數(shù)據(jù)
String cacheValue = cache.get(key);
// 緩存為空
if (StringUtils.isBlank(cacheValue)) {
// 從存儲中獲取
String storageValue = storage.get(key);
cache.set(key, storageValue);
//設(shè)置一個過期時間(300到600之間的一個隨機數(shù)) 淡溯,防止多個key在同一個時間內(nèi)失效。
int expireTime = new Random().nextInt(300) + 300;
if (storageValue == null) {
cache.expire(key, expireTime);
}
return storageValue;
// 緩存非空
} else {
return cacheValue;
}
}
注意:在有些應(yīng)用場景下簿训,若知道某個時間段中會發(fā)生高并發(fā)咱娶,那么最好提前做好緩存預(yù)熱米间,把數(shù)據(jù)庫中的數(shù)據(jù)提前放在緩存中,比如電商搶購的場景膘侮。
緩存雪崩
緩存雪崩指的是緩存層支撐不住或宕掉后屈糊, 流量會像奔逃的野牛一樣, 打向后端存儲層琼了。
由于緩存層承載著大量請求逻锐, 有效地保護了存儲層, 但是如果緩存層由于某些原因不能提供服務(wù)(比如超大并 發(fā)過來雕薪,緩存層支撐不住昧诱,或者由于緩存設(shè)計不好,類似大量請求訪問bigkey所袁,導(dǎo)致緩存能支撐的并發(fā)急劇下 降)盏档, 于是大量請求都會達到存儲層, 存儲層的調(diào)用量會暴增燥爷, 造成存儲層也會級聯(lián)宕機的情況蜈亩。
預(yù)防和解決緩存雪崩問題, 可以從以下三個方面進行著手前翎。
- 保證緩存層服務(wù)高可用性稚配,比如使用Redis Sentinel或Redis Cluster。
- 依賴隔離組件為后端限流并降級港华。比如使用Hystrix限流降級組件道川。
- 提前演練。 在項目上線前立宜, 演練緩存層宕掉后愤惰, 應(yīng)用以及后端的負載情況以及可能出現(xiàn)的問題, 在此基 礎(chǔ)上做一些預(yù)案設(shè)定赘理。
熱點緩存key重建優(yōu)化
開發(fā)人員使用“緩存+過期時間”的策略既可以加速數(shù)據(jù)讀寫宦言, 又保證數(shù)據(jù)的定期更新, 這種模式基本能夠滿 足絕大部分需求商模。 但是有兩個問題如果同時出現(xiàn)奠旺, 可能就會對應(yīng)用造成致命的危害:
- 當(dāng)前key是一個熱點key(例如一個熱門的娛樂新聞),并發(fā)量非常大施流。
- 重建緩存不能在短時間完成响疚, 可能是一個復(fù)雜計算, 例如復(fù)雜的SQL瞪醋、 多次IO忿晕、 多個依賴等。
在緩存失效的瞬間银受, 有大量線程來重建緩存践盼, 造成后端負載加大鸦采, 甚至可能會讓應(yīng)用崩潰。 要解決這個問題主要就是要避免大量線程同時重建緩存咕幻。 我們可以利用互斥鎖來解決渔伯,此方法只允許一個線程重建緩存, 其他線程等待重建緩存的線程執(zhí)行完肄程, 重新從 緩存獲取數(shù)據(jù)即可锣吼。
高并發(fā)緩存key重建優(yōu)化問題解決方案:
String get(String key) {
// 從Redis中獲取數(shù)據(jù)
String value = redis.get(key);
// 如果value為空, 則開始重構(gòu)緩存
if (value == null) {
// 只允許一個線程重建緩存蓝厌, 使用nx玄叠, 并設(shè)置過期時間
String mutexKey = "mutext:key:" + key;
if (redis.set(mutexKey, "1", "ex 180", "nx")) {
// 從數(shù)據(jù)源獲取數(shù)據(jù)
value = db.get(key);
// 回寫Redis, 并設(shè)置過期時間
redis.setex(key, timeout, value);
// 刪除key_mutex
redis.delete(mutexKey);
}
// 其他線程休息50毫秒后重試
else {
Thread.sleep(50);
get(key);
}
}
return value;
}
三拓提、開發(fā)規(guī)范與性能優(yōu)化
key名設(shè)計
- 【建議】: 可讀性和可管理性
以業(yè)務(wù)名(或數(shù)據(jù)庫名)為前綴(防止key沖突)读恃,用冒號分隔,比如業(yè)務(wù)名:表名:id
trade:order:1
- 【建議】:簡潔性
保證語義的前提下崎苗,控制key的長度,當(dāng)key較多時舀寓,內(nèi)存占用也不容忽視胆数,例如:
user:{uid}:friends:messages:{mid} 簡化為 u:{uid}:fr:m:{mid}
- 【強制】:不要包含特殊字符
反例:包含空格、換行互墓、單雙引號以及其他轉(zhuǎn)義字符
value設(shè)計
- 【強制】:拒絕bigkey(防止網(wǎng)卡流量必尼、慢查詢)
在Redis中,一個字符串大512MB篡撵,一個二級數(shù)據(jù)結(jié)構(gòu)(例如hash判莉、list、set育谬、zset)可以存 儲大約40億個(2^32-1)個元素券盅,但實際中如果下面兩種情況,我就會認為它是bigkey膛檀。
- 字符串類型:它的big體現(xiàn)在單個value值很大锰镀,一般認為超過10KB就是bigkey。
- 非字符串類型:哈希咖刃、列表泳炉、集合、有序集合嚎杨,它們的big體現(xiàn)在元素個數(shù)太多花鹅。
一般來說,string類型控制在10KB以內(nèi)枫浙,hash刨肃、list古拴、set、zset元素個數(shù)不要超過5000之景。 反例:一個包含200萬個元素的list斤富。
非字符串的bigkey,不要使用del刪除锻狗,使用hscan满力、sscan、zscan方式漸進式刪除轻纪,同時要注 意防止bigkey過期時間自動刪除問題(例如一個200萬的zset設(shè)置1小時過期油额,會觸發(fā)del操作,造 成阻塞)
- bigkey的危害
- 導(dǎo)致redis阻塞
- 網(wǎng)絡(luò)擁塞
bigkey也就意味著每次獲取要產(chǎn)生的網(wǎng)絡(luò)流量較大刻帚,假設(shè)一個bigkey為1MB潦嘶,客戶端每秒訪問 量為1000,那么每秒產(chǎn)生1000MB的流量崇众,對于普通的千兆網(wǎng)卡(按照字節(jié)算是128MB/s)的服務(wù) 器來說簡直是滅頂之災(zāi)掂僵,而且一般服務(wù)器會采用單機多實例的方式來部署,也就是說一個bigkey 可能會對其他實例也造成影響顷歌,其后果不堪設(shè)想锰蓬。 - 過期刪除
有個bigkey,它安分守己(只執(zhí)行簡單的命令眯漩,例如hget芹扭、lpop、zscore等)赦抖,但它設(shè)置了過 期時間舱卡,當(dāng)它過期后,會被刪除队萤,如果沒有使用Redis 4.0的過期異步刪除(lazyfree-lazyexpire yes)轮锥,就會存在阻塞Redis的可能性。
- bigkey的產(chǎn)生
一般來說要尔,bigkey的產(chǎn)生都是由于程序設(shè)計不當(dāng)交胚,或者對于數(shù)據(jù)規(guī)模預(yù)料不清楚造成的,來看幾 個例子:
- 社交類:粉絲列表盈电,如果某些明星或者大v不精心設(shè)計下肉瓦,必是bigkey等缀。
- 統(tǒng)計類:例如按天存儲某項功能或者網(wǎng)站的用戶集合霹抛,除非沒幾個人用栅表,否則必是bigkey。
- 緩存類:將數(shù)據(jù)從數(shù)據(jù)庫load出來序列化放到Redis里,這個方式非常常用互拾,但有兩個地方需 要注意歪今,第一,是不是有必要把所有字段都緩存颜矿;第二寄猩,有沒有相關(guān)關(guān)聯(lián)的數(shù)據(jù),有的同學(xué)為了 圖方便把相關(guān)數(shù)據(jù)都存一個key下骑疆,產(chǎn)生bigkey田篇。
- 如何優(yōu)化bigkey
- 拆
big list: list1、list2箍铭、...listN
big hash:可以講數(shù)據(jù)分段存儲泊柬,比如一個大的key,假設(shè)存了1百萬的用戶數(shù)據(jù)诈火,可以拆分成 200個key兽赁,每個key下面存放5000個用戶數(shù)據(jù) - 如果bigkey不可避免,也要思考一下要不要每次把所有元素都取出來(例如有時候僅僅需要 hmget冷守,而不是hgetall)刀崖,刪除也是一樣,盡量使用優(yōu)雅的方式來處理拍摇。
命令使用
- 【推薦】 O(N)命令關(guān)注N的數(shù)量
例如hgetall亮钦、lrange、smembers授翻、zrange或悲、sinter等并非不能使用孙咪,但是需要明確N的值堪唐。有 遍歷的需求可以使用hscan、sscan翎蹈、zscan代替淮菠。 - 【推薦】:禁用命令 禁止線上使用keys、flushall荤堪、flushdb等合陵,通過redis的rename機制禁掉命令,或者使用scan的 方式漸進式處理澄阳。
- 【推薦】合理使用select redis的多數(shù)據(jù)庫較弱拥知,使用數(shù)字進行區(qū)分,很多客戶端支持較差碎赢,同時多業(yè)務(wù)用多數(shù)據(jù)庫實際還 是單線程處理低剔,會有干擾
- 【推薦】使用批量操作提高效率
原生命令:例如mget、mset。
非原生命令:可以使用pipeline提高效率
管道(Pipeline) 客戶端可以一次性發(fā)送多個請求而不用等待服務(wù)器的響應(yīng)襟齿,待所有命令都發(fā)送完后再一次性讀取服務(wù)的響 應(yīng)姻锁,這樣可以極大的降低多條命令執(zhí)行的網(wǎng)絡(luò)傳輸開銷,管道執(zhí)行多條命令的網(wǎng)絡(luò)開銷實際上只相當(dāng)于一 次命令執(zhí)行的網(wǎng)絡(luò)開銷猜欺。需要注意到是用pipeline方式打包命令發(fā)送位隶,redis必須在處理完所有命令前先緩 存起所有命令的處理結(jié)果。打包的命令越多开皿,緩存消耗內(nèi)存也越多涧黄。所以并不是打包的命令越多越好。 pipeline中發(fā)送的每個command都會被server立即執(zhí)行副瀑,如果執(zhí)行失敗弓熏,將會在此后的響應(yīng)中得到信 息;也就是pipeline并不是表達“所有command都一起成功”的語義糠睡,管道中前面命令失敗挽鞠,后面命令 不會有影響,繼續(xù)執(zhí)行狈孔。
詳細代碼示例見上面jedis連接示例:
Pipeline pl = jedis.pipelined();
for (int i = 0; i < 10; i++) {
pl.incr("pipelineKey");
pl.set("zhuge" + i, "zhuge");
}
List<Object> results = pl.syncAndReturnAll();
System.out.println(results);
但要注意控制一次批量操作的元素個數(shù)(例如500以內(nèi)信认,實際也和元素字節(jié)數(shù)有關(guān))。 注意兩者不同:
- 原生是原子操作均抽,pipeline是非原子操作嫁赏。
- pipeline可以打包不同的命令,原生做不到
- pipeline需要客戶端和服務(wù)端同時支持油挥。
- 【建議】Redis事務(wù)功能較弱潦蝇,不建議過多使用,可以用lua替代
客戶端使用
- 【推薦】 避免多個應(yīng)用使用一個Redis實例
正例:不相干的業(yè)務(wù)拆分深寥,公共數(shù)據(jù)做服務(wù)化攘乒。
2.【推薦】
使用帶有連接池的數(shù)據(jù)庫,可以有效控制連接惋鹅,同時提高效率则酝,標準使用方式:
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(5);
jedisPoolConfig.setMaxIdle(2);
jedisPoolConfig.setTestOnBorrow(true);
JedisPool jedisPool = new JedisPool(jedisPoolConfig, "192.168.0.60", 6379, 3000, null);
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
//具體的命令
jedis.executeCommand()
} catch (Exception e) {
logger.error("op key {} error: " + e.getMessage(), key, e);
} finally {
//注意這里不是關(guān)閉連接,在JedisPool模式下闰集,Jedis會被歸還給資源池沽讹。
if (jedis != null)
jedis.close();
}
連接池參數(shù)含義:
序號 | 參數(shù)名 | 含義 | 默認值 | 使用建議 |
---|---|---|---|---|
1 | maxTotal | 資源池中大連接數(shù) | 8 | 設(shè)置建議見下面 |
2 | maxIdle | 資源池允許大空閑的連接數(shù) | 8 | 設(shè)置建議見下面 |
3 | minIdle | 資源池確保少空閑 的連接數(shù) | 0 | 設(shè)置建議見下面 |
4 | blockWhenExhausted | 當(dāng)資源池用盡后,調(diào)用者是否要等待武鲁。只有當(dāng)為true時爽雄,下面的maxWaitMillis才會 生效 | true | 建議使用默認值 |
5 | maxWaitMillis | 當(dāng)資源池連接用盡 后,調(diào)用者的大等 待時間(單位為毫秒) | -1:表示永不超時 | 不建議使用默認值 |
6 | testOnBorrow | 向資源池借用連接時 是否做連接有效性檢 測(ping)沐鼠,無效連接 會被移除 | false | 業(yè)務(wù)量很大時候建議 設(shè)置為false(多一次 ping的開銷)挚瘟。 |
7 | testOnReturn | 向資源池歸還連接時 是否做連接有效性檢 測(ping),無效連接 會被移除 | false | 業(yè)務(wù)量很大時候建議 設(shè)置為false(多一次 ping的開銷)。 |
8 | jmxEnabled | 是否開啟jmx監(jiān)控刽沾,可 用于監(jiān)控 | true | 建議開啟本慕,但應(yīng)用本 |
優(yōu)化建議:
maxTotal:大連接數(shù),早期的版本叫maxActive
實際上這個是一個很難回答的問題侧漓,考慮的因素比較多:
- 業(yè)務(wù)希望Redis并發(fā)量
- 客戶端執(zhí)行命令時間
- Redis資源:例如 nodes(例如應(yīng)用個數(shù)) * maxTotal 是不能超過redis的大連接數(shù) maxclients锅尘。
- 資源開銷:例如雖然希望控制空閑連接(連接池此刻可馬上使用的連接),但是不希望因 為連接池的頻繁釋放創(chuàng)建連接造成不必靠開銷布蔗。
以一個例子說明藤违,假設(shè):
- 一次命令時間(borrow|return resource + Jedis執(zhí)行命令(含網(wǎng)絡(luò)) )的平均耗時約為 1ms,一個連接的QPS大約是1000
- 業(yè)務(wù)期望的QPS是50000
那么理論上需要的資源池大小是50000 / 1000 = 50個纵揍。但事實上這是個理論值顿乒,還要考慮到要 比理論值預(yù)留一些資源,通常來講maxTotal可以比理論值大一些泽谨。 但這個值不是越大越好璧榄,一方面連接太多占用客戶端和服務(wù)端資源,另一方面對于Redis這種高 QPS的服務(wù)器吧雹,一個大命令的阻塞即使設(shè)置再大資源池仍然會無濟于事骨杂。
maxIdle和minIdle
maxIdle實際上才是業(yè)務(wù)需要的大連接數(shù),maxTotal是為了給出余量雄卷,所以maxIdle不要設(shè)置 過小搓蚪,否則會有new Jedis(新連接)開銷。
連接池的最佳性能是maxTotal = maxIdle丁鹉,這樣就避免連接池伸縮帶來的性能干擾妒潭。但是如果 并發(fā)量不大或者maxTotal設(shè)置過高,會導(dǎo)致不必要的連接資源浪費揣钦。一般推薦maxIdle可以設(shè)置 為按上面的業(yè)務(wù)期望QPS計算出來的理論連接數(shù)雳灾,maxTotal可以再放大一倍。
minIdle(小空閑連接數(shù))拂盯,與其說是小空閑連接數(shù)佑女,不如說是"至少需要保持的空閑連接 數(shù)"记靡,在使用連接的過程中谈竿,如果連接數(shù)超過了minIdle,那么繼續(xù)建立連接摸吠,如果超過了 maxIdle空凸,當(dāng)超過的連接執(zhí)行完業(yè)務(wù)后會慢慢被移出連接池釋放掉。
如果系統(tǒng)啟動完馬上就會有很多的請求過來寸痢,那么可以給redis連接池做預(yù)熱呀洲,比如快速的創(chuàng)建一 些redis連接,執(zhí)行簡單命令,類似ping()道逗,快速的將連接池里的空閑連接提升到minIdle的數(shù)量兵罢。
連接池預(yù)熱示例代碼:
List<Jedis> minIdleJedisList = new ArrayList<Jedis>(jedisPoolConfig.getMinIdle());
for (int i = 0; i < jedisPoolConfig.getMinIdle(); i++) {
Jedis jedis = null;
try {
jedis = pool.getResource();
minIdleJedisList.add(jedis);
jedis.ping();
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
//注意,這里不能馬上close將連接還回連接池滓窍,否則最后連接池里只會建立1個連接卖词。。
//jedis.close();
}
}
//統(tǒng)一將預(yù)熱的連接還回連接池
for (int i = 0; i < jedisPoolConfig.getMinIdle(); i++) {
Jedis jedis = null;
try {
jedis = minIdleJedisList.get(i);
//將連接歸還回連接池
jedis.close();
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
}
}
總之吏夯,要根據(jù)實際系統(tǒng)的QPS和調(diào)用redis客戶端的規(guī)模整體評估每個節(jié)點所使用的連接池大小此蜈。
3.【建議】
高并發(fā)下建議客戶端添加熔斷功能(例如netflix hystrix)
4.【推薦】
設(shè)置合理的密碼,如有必要可以使用SSL加密訪問
5.【建議】
Redis對于過期鍵有三種清除策略:
- 被動刪除:當(dāng)讀/寫一個已經(jīng)過期的key時噪生,會觸發(fā)惰性刪除策略裆赵,直接刪除掉這個過期key。
- 主動刪除:由于惰性刪除策略無法保證冷數(shù)據(jù)被及時刪掉跺嗽,所以Redis會定期主動淘汰一批已過期的key战授。
- 當(dāng)前已用內(nèi)存超過maxmemory限定時,觸發(fā)主動清理策略桨嫁。
當(dāng)REDIS運行在主從模式時陈醒,只有主結(jié)點才會執(zhí)行被動和主動這兩種過期刪除策略,然后把刪除 操作”del key”同步到從結(jié)點瞧甩。
第三種策略的情況如下:
當(dāng)前已用內(nèi)存超過maxmemory限定時钉跷,會觸發(fā)主動清理策略。
根據(jù)自身業(yè)務(wù)類型肚逸,選好maxmemory-policy(大內(nèi)存淘汰策略)爷辙,設(shè)置好過期時間。如果不設(shè) 置大內(nèi)存朦促,當(dāng) Redis 內(nèi)存超出物理內(nèi)存限制時膝晾,內(nèi)存的數(shù)據(jù)會開始和磁盤產(chǎn)生頻繁的交換 (swap), 會讓 Redis 的性能急劇下降务冕。
默認策略是volatile-lru血当,即超過大內(nèi)存后,在過期鍵中使用lru算法進行key的剔除禀忆,保證不過 期數(shù)據(jù)不被刪除臊旭,但是可能會出現(xiàn)OOM問題。 其他策略如下:
- allkeys-lru:根據(jù)LRU算法刪除鍵箩退,不管數(shù)據(jù)有沒有設(shè)置超時屬性离熏,直到騰出足夠空間 為止。
- allkeys-random:隨機刪除所有鍵戴涝,直到騰出足夠空間為止滋戳。
- volatile-random: 隨機刪除過期鍵钻蔑,直到騰出足夠空間為止。
- volatile-ttl:根據(jù)鍵值對象的ttl屬性奸鸯,刪除近將要過期數(shù)據(jù)咪笑。如果沒有,回退到 noeviction策略娄涩。
- noeviction:不會剔除任何數(shù)據(jù)蒲肋,拒絕所有寫入操作并返回客戶端錯誤信息"(error) OOM command not allowed when used memory",此時Redis只響應(yīng)讀操作钝满。