Redis我們一般是用作緩存,扛并發(fā);或者用于某些特定的業(yè)務場景,比如前面說到redis各種數(shù)據(jù)類型的使用場景以及redis的哨兵和集群模式。
這里主要整理了下redis用作緩存,存在的一些問題,以及改善方案。
?
簡單的流程就像這個樣子,一般請先到緩存區(qū)獲取,如果緩存沒有再到后端的數(shù)據(jù)庫去查詢。
1.緩存穿透
緩存穿透是指,是指查詢一個根本不存在數(shù)據(jù),這樣緩存層里面沒有,就會去訪問后面的存儲層了。如果有大量的這種惡意請求過來,都打向后面的存儲層。顯然我們的存儲層是扛不住這樣的壓力。這樣緩存就失去了保護后面存儲的意義了。
解決方案:
1.緩存空對象
對于緩存穿透,可以采用緩存空對象,第一次進來緩存和
DB都沒有,就存?zhèn)€空對象到緩存里面。但是如果大批量的惡意請求過來,這樣做就會導致緩存的key暴增,顯然不是一個很好的方案。
2.布隆過濾器
對于不存在的數(shù)據(jù)布隆過濾器一般都能夠過濾掉,不讓請求再往后端發(fā)送。當布隆過濾器說某個值存在時,這個值可能不存在;但是
它說不存在時,那就肯定不存在。布隆過濾器是一個大型的位數(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存在所致。
?
?guvua包布隆過濾器的使用,導包
?
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId>
</dependency>
?
偽代碼:
public void bloomFilterTest() { BloomFilter<CharSequence> bloomFilter =
BloomFilter.create( Funnels.stringFunnel(Charset.forName("UTF-8")), 1000, //
期望存入的數(shù)據(jù)個數(shù) 0.001);//誤差率 //添加到布隆過濾器 String[] keys = new String[1000]; for (String
key: keys) { bloomFilter.put(key); } String key= "key"; boolean exist =
bloomFilter.mightContain(key);if (!exist) { return; } //todo 存在才去緩存獲取 }
可以看到這個類里面有很多的hash算法:com.google.common.hash.Hashing
redisson也有布隆過濾器的實現(xiàn)。
?
?
2.緩存失效
由于大批量的
key同時失效,導致,大量的請求同時打向數(shù)據(jù)庫,造成數(shù)據(jù)庫壓力過大,甚至直接掛掉。我們在批量寫入緩存的時候,設置超時時間,可以是一個固定時間+隨機時間方式來生成,這樣就可以錯開失效時間。
3.緩存雪崩
緩存雪崩是指緩存層掛掉之后,所有請求都打向數(shù)據(jù)庫,數(shù)據(jù)庫扛不住,也可能掛掉,就導致對應的服務也掛掉,也會影響上游的調用服務。這樣的級聯(lián)問題。就像雪崩最開始一小片,然后越來越大,導致整個服務崩潰。
解決方案:
1.保證緩存層的高可用性,比如redis哨兵或者redis集群。
2.各依賴服務之間做限流,熔斷,降級等,比如Hystri,阿里的sentinel
4.緩存一致性
引入緩存之后,隨之而來的問題就是當DB數(shù)據(jù)更新時,緩存中的數(shù)據(jù)就會與db數(shù)據(jù)不一致。所以數(shù)據(jù)修改時是先更新緩存還是先更新DB?
如果先更新緩存,然后更新DB失敗,那么下一個請求過來讀取的緩存數(shù)據(jù)不是最新的。而我們實際上最終數(shù)據(jù)肯定都是以DB為準的。
先更新db 在更新緩存,這是在更新DB的時候來的請求讀取的數(shù)據(jù)也是不是最新的
淘汰緩存——更新DB——重新刷進緩存,在更新db是來的請求在緩存沒有數(shù)據(jù),就會去請求DB,如果并發(fā) 可能操作多各請求去寫DB,那么就需要加鎖了
加鎖——淘汰緩存——更新DB——重新刷進緩存,這樣相對而言就比較保險了
5.bigkey問題
Bigkey是什么?在redis中,一個字符串最大512MB;hash,list,set,zset可以存儲2^31 - 1 個元素。
一般來說字符串超過10kb,其他的幾種元素個數(shù)不要超過5000個。
可以使用src/redis-cli --bigkeys
來查看bigkey,我這里設置了一個30多K的字符串,看下掃描結果,掃除了一個字符串類型的bigkey,4084字節(jié)。
?
?
Bigkey有哪些危害。一是刪除時阻塞其他請求,比如一個bigkey,平時都沒什么,但是設置了過期時間,到期了刪除時,可能就會阻塞其他請求,4.0之后可以開啟lazyfree-lazy-
expire
yes來異步刪除;二是造成網(wǎng)絡擁堵,比如一個key數(shù)據(jù)量達到1MB,假設并發(fā)量1000,這個時候獲取它就會產(chǎn)生1000MB的流量,千兆網(wǎng)卡,峰值的速率也才128MB/S,并不是扛不住并發(fā),而是會占用大量網(wǎng)絡帶寬。
對于很大
list,set這些,我們可以將數(shù)據(jù)拆分,生成一個系列的的key去存放數(shù)據(jù)。如果是redis集群這些key自然就可以分到不同的小主從上面去,如果是單機,那么可以自己實現(xiàn)一個路由算法,來如何獲取這一系列key中的某一個。
6. 客戶端使用
1.避免多個服務使用一個redis實例,如果實在有,可以看下將業(yè)務拆分,把這些公共數(shù)據(jù)服務化。
2.使用連接池,控制有效連接,同時也提高效率。連接池重要參數(shù)設置:
1 maxActive?資源池中最大連接數(shù) 默認值8?
2 maxIdle 資源池允許最大空閑 的連接數(shù) 默認值8?
3 minIdle 資源池確保最少空閑 的連接數(shù) 默認值0?
4 blockWhenExhausted 當資源池用盡后,調用者是否要等待。只有當為true時,下面的maxWaitMillis才會生效,默認值
true 建議使用默認值
5 maxWaitMillis 當資源池連接用盡后,調用者的最大等待時間(單位為毫秒)?-1:表示永不超時 不建議使用默認值
6 testOnBorrow 向資源池借用連接時是否做連接有效性檢測(ping),無效連接會被移除 默認值false 業(yè)務量很大時候建議
設置為false(多一次 ping的開銷)。
7 testOnReturn 向資源池歸還連接時是否做連接有效性檢測(ping),無效連接會被移除 默認值false 業(yè)務量很大時候建議
設置為false(多一次 ping的開銷)。
8 jmxEnabled 是否開啟jmx監(jiān)控,可用于監(jiān)控 默認值true 建議開啟,但應用本身也要開啟
前面三個參數(shù)相對而言更重要,單獨拎出來再說下:
最大連接數(shù)maxActive:
可以從業(yè)務希望的并發(fā)量,客戶端執(zhí)行時間,redis資源設置(應用個數(shù)(集群部署多少個實例) * maxActive?<=
maxclients(redis最大連接數(shù),redis配置中設置的)),等因素考慮。
比如一次客戶端執(zhí)行時間
2ms,那么一個連接的QPS就是500,業(yè)務期望的QPS是3000,那么理論上連接池大小3000/500=60個,實際上考慮其他影響,一般設置比理論值稍微大點。但這個值不是越大越好,一方面連接太多占用客戶端和服務端資源,另一方面對 于Redis這種高
QPS的服務器,一個大命令的阻塞即使設置再大資源池仍然會無濟于事。
最大空閑連接數(shù)maxIdle:
maxIdle實際上才是業(yè)務需要的最大連接數(shù),空閑的連接造好放在那兒,進來一個請求就可以直接拿來用了。maxActive是為了給出總量,所以
maxIdle不要設置過小,否則會有當空閑連接不夠,就會創(chuàng)建新的連接,又會有新的開銷,最佳就是maxActive?=
maxIdle。這樣就避免連接池伸縮帶來的性能干擾。但是如果并發(fā)量不大或者maxActive設置過高,會導致不必要的連接資源浪費。一般推薦
maxIdle可以設置為按上面的業(yè)務期望QPS計算出來的理論連接數(shù),maxActive可以再放大一些。
最小空閑連接數(shù)minIdle:
至少保持多少空閑連接,在使用連接的過程中,如果連接數(shù)超過了minIdle,那么繼續(xù)建立連接,如果超過了
maxIdle,當超過的連接執(zhí)行完業(yè)務后會慢慢被移出連接池釋放掉。
3.緩存預熱
比如說上線一個搶購活動,肯定到點開始就會有很多人來請求了,這個時候就可以提前做數(shù)據(jù)的預熱,既可以把連接池初始化好,也可以把數(shù)據(jù)放好。
熱門工具 換一換