Redis
1. redis 簡(jiǎn)介
redis 的數(shù)據(jù)是存在內(nèi)存中的,所以讀寫(xiě)速度非陈蠛洌快弓候,因此 redis 被廣泛應(yīng)用于緩存方向休玩。另外,redis 也經(jīng)常用來(lái)做分布式鎖片择。redis 提供了多種數(shù)據(jù)類(lèi)型來(lái)支持不同的業(yè)務(wù)場(chǎng)景潜的。除此之外,redis 支持事務(wù) 字管、持久化啰挪、LUA腳本信不、LRU驅(qū)動(dòng)事件、多種集群方案抽活。
2. 為什么要用 redis、為什么要用緩存
主要從“高性能”和“高并發(fā)”這兩點(diǎn)來(lái)看待這個(gè)問(wèn)題下硕。
-
高性能
假如用戶第一次訪問(wèn)數(shù)據(jù)庫(kù)中的某些數(shù)據(jù)汁胆。這個(gè)過(guò)程會(huì)比較慢,因?yàn)槭菑挠脖P(pán)上讀取的嫩码。將該用戶訪問(wèn)的數(shù)據(jù)存在緩存中,這樣下一次再訪問(wèn)這些數(shù)據(jù)的時(shí)候就可以直接從緩存中獲取了谢谦。操作緩存就是直接操作內(nèi)存萝衩,所以速度相當(dāng)快。如果數(shù)據(jù)庫(kù)中的對(duì)應(yīng)數(shù)據(jù)改變之后猩谊,同步改變緩存中相應(yīng)的數(shù)據(jù)即可。
-
高并發(fā)
直接操作緩存能夠承受的請(qǐng)求是遠(yuǎn)遠(yuǎn)大于直接訪問(wèn)數(shù)據(jù)庫(kù)的墙牌,所以我們可以考慮把數(shù)據(jù)庫(kù)中的部分?jǐn)?shù)據(jù)轉(zhuǎn)移到緩存中去,這樣用戶的一部分請(qǐng)求會(huì)直接到緩存這里而不用經(jīng)過(guò)數(shù)據(jù)庫(kù)喜滨。
3. 為什么要用 redis 而不用 map/guava 做緩存
緩存分為本地緩存和分布式緩存撤防。
以 Java 為例虽风,使用自帶的 map 或者 guava 實(shí)現(xiàn)的是本地緩存,最主要的特點(diǎn)是輕量以及快速寄月,生命周期隨著 jvm 的銷(xiāo)毀而結(jié)束辜膝,并且在多實(shí)例的情況下,每個(gè)實(shí)例都需要各自保存一份緩存漾肮,緩存不具有一致性厂抖。
使用 redis 或 memcached 之類(lèi)的稱(chēng)為分布式緩存,在多實(shí)例的情況下克懊,各實(shí)例共用一份緩存數(shù)據(jù)忱辅,緩存具有一致性七蜘。缺點(diǎn)是需要保持 redis 或 memcached服務(wù)的高可用,整個(gè)程序架構(gòu)上較為復(fù)雜墙懂。
4. redis 的單線程模型
redis 內(nèi)部使用文件事件處理器 file event handler崔梗,這個(gè)文件事件處理器是單線程的,所以 redis 才叫做單線程的模型垒在。它采用 IO 多路復(fù)用機(jī)制同時(shí)監(jiān)聽(tīng)多個(gè) socket蒜魄,根據(jù) socket 上的事件來(lái)選擇對(duì)應(yīng)的事件處理器進(jìn)行處理。
文件事件處理器的結(jié)構(gòu)包含 4 個(gè)部分:
- 多個(gè) socket
- IO 多路復(fù)用程序
- 文件事件分派器
- 事件處理器(連接應(yīng)答處理器场躯、命令請(qǐng)求處理器谈为、命令回復(fù)處理器)
多個(gè) socket 可能會(huì)并發(fā)產(chǎn)生不同的操作,每個(gè)操作對(duì)應(yīng)不同的文件事件踢关,但是 IO 多路復(fù)用程序會(huì)監(jiān)聽(tīng)多個(gè) socket伞鲫,會(huì)將 socket 產(chǎn)生的事件放入隊(duì)列中排隊(duì),事件分派器每次從隊(duì)列中取出一個(gè)事件签舞,把該事件交給對(duì)應(yīng)的事件處理器進(jìn)行處理秕脓。
Redis客戶端對(duì)服務(wù)端的每次調(diào)用都經(jīng)歷了發(fā)送命令,執(zhí)行命令儒搭,返回結(jié)果三個(gè)過(guò)程吠架。其中執(zhí)行命令階段,所有到達(dá)服務(wù)端的命令不會(huì)立刻執(zhí)行傍药,而是會(huì)進(jìn)入一個(gè)隊(duì)列拐辽,然后逐個(gè)被執(zhí)行俱诸,多個(gè)客戶端發(fā)送的命令的執(zhí)行順序是不確定的睁搭。但是不會(huì)有兩條命令被同時(shí)執(zhí)行介袜,不會(huì)產(chǎn)生并發(fā)問(wèn)題遇伞。
為啥redis 單線程模型也能效率這么高鸠珠?
- 純內(nèi)存操作。Redis將所有數(shù)據(jù)放在內(nèi)存中炬太,內(nèi)存的響應(yīng)時(shí)長(zhǎng)大約為100納秒亲族,這是Redis達(dá)到每秒萬(wàn)級(jí)別訪問(wèn)的重要基礎(chǔ)霎迫。
- 基于非阻塞的 IO 多路復(fù)用機(jī)制知给。Redis使用epoll作為I/O多路復(fù)用技術(shù)的實(shí)現(xiàn)涩赢,再加上Redis自身的事件處理模型將epoll中的連接筒扒、讀寫(xiě)霎肯、關(guān)閉都轉(zhuǎn)換為事件,不在網(wǎng)絡(luò)I/O上浪費(fèi)過(guò)多的時(shí)間驮俗。
- 單線程可以簡(jiǎn)化數(shù)據(jù)結(jié)構(gòu)王凑、算法的實(shí)現(xiàn)索烹,單線程避免了線程切換和競(jìng)態(tài)產(chǎn)生(避免了加鎖百姓、解鎖)的消耗垒拢。
5. redis 和 memcached 的區(qū)別
- Redis支持更豐富的數(shù)據(jù)類(lèi)型(支持更復(fù)雜的應(yīng)用場(chǎng)景):Redis不僅僅支持簡(jiǎn)單的k/v類(lèi)型的數(shù)據(jù),同時(shí)還提供list奔垦,set椿猎,zset鸵贬,hash等數(shù)據(jù)結(jié)構(gòu)的存儲(chǔ)阔逼。memcache支持簡(jiǎn)單的數(shù)據(jù)類(lèi)型嗜浮,String危融。
- Redis支持?jǐn)?shù)據(jù)的持久化吉殃,可以將內(nèi)存中的數(shù)據(jù)保持在磁盤(pán)中蛋勺,重啟時(shí)可以再次加載進(jìn)行使用抱完,而Memecache把數(shù)據(jù)全部存在內(nèi)存之中。
- 集群模式:memcached沒(méi)有原生的集群模式烘贴,需要依靠客戶端來(lái)實(shí)現(xiàn)往集群中分片寫(xiě)入數(shù)據(jù);但是 redis 目前是原生支持 cluster 模式的.
- Memcached是多線程,非阻塞IO復(fù)用的網(wǎng)絡(luò)模型;Redis使用單線程的多路 IO 復(fù)用模型捺疼。
6. redis 常見(jiàn)數(shù)據(jù)結(jié)構(gòu)以及使用場(chǎng)景分析
-
String
常用命令:set, get, decr, incr, mget 等。
String數(shù)據(jù)結(jié)構(gòu)是簡(jiǎn)單的key-value類(lèi)型官扣,value其實(shí)不僅可以是String惕蹄,也可以是數(shù)字卖陵。
常規(guī)key-value緩存應(yīng)用:常規(guī)計(jì)數(shù)泪蔫,如微博數(shù)撩荣,粉絲數(shù)等餐曹。
-
Hash
常用命令:hget, hset, hgetall 等凸主。
hash 是一個(gè) string 類(lèi)型的 field 和 value 的映射表,hash 特別適合用于存儲(chǔ)對(duì)象锋华,后續(xù)操作的時(shí)候毯焕,可以直接僅僅修改這個(gè)對(duì)象中的某個(gè)字段的值婆咸。如我們可以用 hash 數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)用戶信息尚骄,商品信息等等倔丈。
key=JavaUser293847 value={ “id”: 1, “name”: “SnailClimb”, “age”: 22, “l(fā)ocation”: “Wuhan, Hubei” }
-
List
常用命令:lpush, rpush, lpop, rpop,lrange等需五。
list 就是鏈表宏邮,Redis list 的應(yīng)用場(chǎng)景非常多蜜氨,也是Redis最重要的數(shù)據(jù)結(jié)構(gòu)之一记劝,如微博的關(guān)注列表厌丑,粉絲列表怒竿,消息列表等功能都可以用Redis的 list 結(jié)構(gòu)來(lái)實(shí)現(xiàn)耕驰。
Redis list 的實(shí)現(xiàn)為一個(gè)雙向鏈表朦肘,即可以支持反向查找和遍歷媒抠,更方便操作趴生,不過(guò)帶來(lái)了部分額外的內(nèi)存開(kāi)銷(xiāo)苍匆。
另外可以通過(guò) lrange 命令浸踩,就是從某個(gè)元素開(kāi)始讀取多少個(gè)元素民轴,可以基于 list 實(shí)現(xiàn)分頁(yè)查詢后裸,這個(gè)很棒的一個(gè)功能微驶∫蚱唬基于 redis 實(shí)現(xiàn)簡(jiǎn)單的高性能分頁(yè)凶杖,可以做類(lèi)似微博那種下拉不斷分頁(yè)的東西(一頁(yè)一頁(yè)的往下走),性能高。
-
Set
常用命令:sadd, spop, smembers, sunion 等。
set 對(duì)外提供的功能與list類(lèi)似是一個(gè)列表的功能,特殊之處在于 set 無(wú)重復(fù)數(shù)據(jù)。并且set提供了判斷某個(gè)成員是否在一個(gè)set集合內(nèi)的重要接口,這個(gè)也是list所不能提供的⌒ǎ可以基于 set 輕易實(shí)現(xiàn)交集、并集、差集的操作。
如:在微博應(yīng)用中现拒,可以將一個(gè)用戶所有的關(guān)注人存在一個(gè)集合中脱衙,將其所有粉絲存在一個(gè)集合。Redis可以非常方便的實(shí)現(xiàn)如共同關(guān)注、共同粉絲、共同喜好等功能秸苗。這個(gè)過(guò)程也就是求交集的過(guò)程玖瘸,具體命令如下:
sinterstore key1 key2 key3 將交集存在key1內(nèi)
-
Sorted Set
常用命令:zadd, zrange, zrem, zcard等弧可。
和set相比,sorted set增加了一個(gè)權(quán)重參數(shù)score,使得集合中的元素能夠按score進(jìn)行有序排列。
如:在直播系統(tǒng)中座硕,實(shí)時(shí)排行信息包含直播間在線用戶列表机隙,各種禮物排行榜,彈幕消息(可以理解為按消息維度的消息排行榜)等信息,適合使用 Redis 中的 Sorted Set 結(jié)構(gòu)進(jìn)行存儲(chǔ)。
7. redis 刪除不必要的緩存數(shù)據(jù)
過(guò)期策略 + 內(nèi)存淘汰機(jī)制
7.1 過(guò)期策略
Redis中有個(gè)設(shè)置時(shí)間過(guò)期的功能模庐,即對(duì)存儲(chǔ)在 redis 數(shù)據(jù)庫(kù)中的值可以設(shè)置一個(gè)過(guò)期時(shí)間。作為一個(gè)緩存數(shù)據(jù)庫(kù)顶吮,這是非常實(shí)用的湃交。如我們一般項(xiàng)目中的 token 或者一些登錄信息,尤其是短信驗(yàn)證碼都是有時(shí)間限制的,按照傳統(tǒng)的數(shù)據(jù)庫(kù)處理方式温圆,一般都是自己判斷過(guò)期膝蜈,這樣無(wú)疑會(huì)嚴(yán)重影響項(xiàng)目性能。
我們 set key 時(shí)备绽,可以給一個(gè) expire time深浮,就是過(guò)期時(shí)間蜗顽,通過(guò)過(guò)期時(shí)間我們可以指定這個(gè) key 可以存活的時(shí)間栖忠。
redis 刪除過(guò)期key的方式:定期刪除 + 惰性刪除。
- 定期刪除:redis默認(rèn)是每隔 100ms 就隨機(jī)抽取一些設(shè)置了過(guò)期時(shí)間的key,檢查其是否過(guò)期古沥,如果過(guò)期就刪除。注意這里是隨機(jī)抽取的栋齿。為什么要隨機(jī)呢歌亲?想一想假如 redis 存了幾十萬(wàn)個(gè) key 陷揪,每隔100ms就遍歷所有的設(shè)置過(guò)期時(shí)間的 key 的話,就會(huì)給 CPU 帶來(lái)很大的負(fù)載。
- 惰性刪除:定期刪除可能會(huì)導(dǎo)致很多過(guò)期 key 到了時(shí)間并沒(méi)有被刪除掉溅漾,所以就有了惰性刪除。惰性刪除不再是Redis去主動(dòng)刪除,而是在客戶端要獲取某個(gè)key時(shí),Redis會(huì)先去檢測(cè)一下這個(gè)key是否已經(jīng)過(guò)期,如果沒(méi)有過(guò)期則返回給客戶端,如果已經(jīng)過(guò)期了,那么Redis會(huì)刪除這個(gè)key启具,不會(huì)返回給客戶端。
過(guò)期鍵有3種刪除策略:定期刪除、惰性刪除、定時(shí)刪除帝嗡。Redis不使用定時(shí)刪除策略。
為什么不使用定時(shí)刪除隘竭?所謂定時(shí)刪除爪幻,指的是用一個(gè)定時(shí)器來(lái)負(fù)責(zé)監(jiān)視key,當(dāng)這個(gè)key過(guò)期就自動(dòng)刪除篷店,雖然內(nèi)存及時(shí)釋放,但是十分消耗CPU資源。
所以惰性刪除可以解決一些過(guò)期了,但沒(méi)被定期刪除隨機(jī)抽取到的key。但有些過(guò)期的key既沒(méi)有被隨機(jī)抽取逮京,也沒(méi)有被客戶端訪問(wèn),就會(huì)一直保留在數(shù)據(jù)庫(kù),占用內(nèi)存,長(zhǎng)期下去可能會(huì)導(dǎo)致內(nèi)存耗盡。所以Redis提供了內(nèi)存淘汰機(jī)制來(lái)解決這個(gè)問(wèn)題。
7.2 內(nèi)存淘汰機(jī)制
redis 提供 8 種數(shù)據(jù)淘汰策略:(7硕并,8是4.0版本后新增加的)
- volatile-lru:從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中挑選最近最少使用的數(shù)據(jù)淘汰
- volatile-ttl:從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中挑選將要過(guò)期的數(shù)據(jù)淘汰
- volatile-random:從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中任意選擇數(shù)據(jù)淘汰
- allkeys-lru:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí)乙濒,在鍵空間中,移除最近最少使用的key(這個(gè)是最常用的)
- allkeys-random:從數(shù)據(jù)集(server.db[i].dict)中任意選擇數(shù)據(jù)淘汰
- no-eviction:禁止驅(qū)逐數(shù)據(jù)桑滩,也就是說(shuō)當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí),新寫(xiě)入操作會(huì)報(bào)錯(cuò)。這個(gè)應(yīng)該沒(méi)人使用吧!
- volatile-lfu:從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中挑選最不經(jīng)常使用的數(shù)據(jù)淘汰
- allkeys-lfu:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí),在鍵空間中机蔗,移除最不經(jīng)常使用的key
8. redis 持久化機(jī)制
redis 持久化機(jī)制保證 redis 掛掉之后再重啟數(shù)據(jù)庫(kù)可以進(jìn)行恢復(fù)。
很多時(shí)候,我們需要持久化數(shù)據(jù)也就是將內(nèi)存中的數(shù)據(jù)寫(xiě)入到硬盤(pán)里面,大部分原因是為了之后重用數(shù)據(jù)(比如重啟機(jī)器拔恰、機(jī)器故障之后恢復(fù)數(shù)據(jù))风皿,或者是為了防止系統(tǒng)故障而將數(shù)據(jù)備份到一個(gè)遠(yuǎn)程位置。
Redis不同于Memcached的很重一點(diǎn)就是媳维,Redis支持持久化朋凉,而且支持兩種不同的持久化操作盖灸。
- Redis的一種持久化方式叫快照(snapshotting赁炎,RDB)
- 另一種方式是只追加文件(append-only file讥裤,AOF)
8.1 RDB
快照(snapshotting)持久化
Redis可以通過(guò)創(chuàng)建快照來(lái)獲得存儲(chǔ)在內(nèi)存里面的數(shù)據(jù)在某個(gè)時(shí)間點(diǎn)上的副本。Redis創(chuàng)建快照之后厢破,可以對(duì)快照進(jìn)行備份摩泪,可以將快照復(fù)制到其他服務(wù)器從而創(chuàng)建具有相同數(shù)據(jù)的服務(wù)器副本(Redis主從結(jié)構(gòu)见坑,主要用來(lái)提高Redis性能)荞驴,還可以將快照留在原地以便重啟服務(wù)器的時(shí)候使用熊楼。
快照持久化是Redis默認(rèn)采用的持久化方式孙蒙,在redis.conf配置文件中默認(rèn)有此下配置:
save 900 1 # 在900秒(15分鐘)之后,如果至少有1個(gè)key發(fā)生變化合瓢,Redis就會(huì)自動(dòng)觸發(fā)BGSAVE命令創(chuàng)建快照晴楔。
save 300 10 # 在300秒(5分鐘)之后税弃,如果至少有10個(gè)key發(fā)生變化,Redis就會(huì)自動(dòng)觸發(fā)BGSAVE命令創(chuàng)建快照做修。
save 60 10000 # 在60秒(1分鐘)之后,如果至少有10000個(gè)key發(fā)生變化铣除,Redis就會(huì)自動(dòng)觸發(fā)BGSAVE命令創(chuàng)建快照。
8.2 AOF(append-only file)持久化
與快照持久化相比辑鲤,AOF持久化的實(shí)時(shí)性更好决左,因此已成為主流的持久化方案擦秽。
默認(rèn)情況下Redis沒(méi)有開(kāi)啟AOF(append only file)方式的持久化置谦,可以通過(guò)appendonly參數(shù)開(kāi)啟:
appendonly yes
開(kāi)啟AOF持久化后每執(zhí)行一條會(huì)更改Redis中數(shù)據(jù)的命令签餐,Redis就會(huì)將該命令寫(xiě)入硬盤(pán)中的AOF文件。
AOF文件的保存位置和RDB文件的位置相同,都是通過(guò)dir參數(shù)設(shè)置的搪缨,默認(rèn)的文件名是appendonly.aof。
在Redis的配置文件中存在三種不同的 AOF 持久化方式迫淹,它們分別是:
appendfsync always # 每次有數(shù)據(jù)修改發(fā)生時(shí)都會(huì)寫(xiě)入AOF文件瑞妇,這樣會(huì)嚴(yán)重降低Redis的速度
appendfsync everysec # 每秒鐘同步一次,顯示地將多個(gè)寫(xiě)命令同步到硬盤(pán)
appendfsync no # 讓操作系統(tǒng)決定何時(shí)進(jìn)行同步
為了兼顧數(shù)據(jù)和寫(xiě)入性能形导,用戶可以考慮 appendfsync everysec選項(xiàng) 伪阶,讓Redis每秒同步一次AOF文件,Redis性能幾乎沒(méi)受到任何影響。而且這樣即使出現(xiàn)系統(tǒng)崩潰,用戶最多只會(huì)丟失一秒之內(nèi)產(chǎn)生的數(shù)據(jù)。當(dāng)硬盤(pán)忙于執(zhí)行寫(xiě)入操作的時(shí)候,Redis還會(huì)優(yōu)雅的放慢自己的速度以便適應(yīng)硬盤(pán)的最大寫(xiě)入速度。
8.3 AOF 重寫(xiě)
AOF重寫(xiě)可以產(chǎn)生一個(gè)新的AOF文件泄私,這個(gè)新的AOF文件和原有的AOF文件所保存的數(shù)據(jù)庫(kù)狀態(tài)一樣瞪讼,但體積更小端姚。
AOF重寫(xiě)是一個(gè)有歧義的名字盆顾,該功能是通過(guò)讀取數(shù)據(jù)庫(kù)中的鍵值對(duì)來(lái)實(shí)現(xiàn)的,程序無(wú)須對(duì)現(xiàn)有AOF文件進(jìn)行任何讀入、分析或者寫(xiě)入操作捏卓。
在執(zhí)行 BGREWRITEAOF 命令時(shí),Redis 服務(wù)器會(huì)維護(hù)一個(gè) AOF 重寫(xiě)緩沖區(qū)锣笨,該緩沖區(qū)會(huì)在子進(jìn)程創(chuàng)建新AOF文件期間挺身,記錄服務(wù)器執(zhí)行的所有寫(xiě)命令祷蝌。當(dāng)子進(jìn)程完成創(chuàng)建新AOF文件的工作之后剑令,服務(wù)器會(huì)將重寫(xiě)緩沖區(qū)中的所有內(nèi)容追加到新AOF文件的末尾碍脏,使得新舊兩個(gè)AOF文件所保存的數(shù)據(jù)庫(kù)狀態(tài)一致糊探。最后瞪慧,服務(wù)器用新的AOF文件替換舊的AOF文件矢腻,以此來(lái)完成AOF文件重寫(xiě)操作。
8.4 Redis 4.0 對(duì)于持久化機(jī)制的優(yōu)化
Redis 4.0 開(kāi)始支持 RDB 和 AOF 的混合持久化(默認(rèn)關(guān)閉,可以通過(guò)配置項(xiàng) aof-use-rdb-preamble 開(kāi)啟)沮趣。
如果把混合持久化打開(kāi)温眉,AOF 重寫(xiě)時(shí)就直接把 RDB 的內(nèi)容寫(xiě)到 AOF 文件開(kāi)頭闯冷。這樣做的好處是可以結(jié)合 RDB 和 AOF 的優(yōu)點(diǎn)纺涤, 快速加載同時(shí)避免丟失過(guò)多的數(shù)據(jù)象迎。當(dāng)然缺點(diǎn)也是有的汪厨, AOF 里面的 RDB 部分是壓縮格式不再是 AOF 格式,可讀性較差殖妇。
9. redis 事務(wù)
Redis 通過(guò) MULTI前鹅、EXEC除盏、WATCH 等命令來(lái)實(shí)現(xiàn)事務(wù)(transaction)功能大磺。
事務(wù)提供了一種將多個(gè)命令請(qǐng)求打包锐锣,然后一次性斤彼、按順序地執(zhí)行多個(gè)命令的機(jī)制趁冈,并且在事務(wù)執(zhí)行期間扮超,服務(wù)器不會(huì)中斷事務(wù)而改去執(zhí)行其他客戶端的命令請(qǐng)求崩侠,它會(huì)將事務(wù)中的所有命令都執(zhí)行完畢八拱,然后才去處理其他客戶端的命令請(qǐng)求榛搔。
在傳統(tǒng)的關(guān)系式數(shù)據(jù)庫(kù)中芥吟,常常用 ACID 性質(zhì)來(lái)檢驗(yàn)事務(wù)功能的可靠性和安全性种樱。在 Redis 中佩抹,事務(wù)總是具有原子性(Atomicity)栏豺、一致性(Consistency)和隔離性(Isolation),并且當(dāng) Redis 運(yùn)行在某種特定的持久化模式下時(shí)攀细,事務(wù)也具有持久性(Durability)鱼的。
10. 緩存雪崩和緩存穿透問(wèn)題解決方案
10.1 緩存雪崩
緩存雪崩:緩存同一時(shí)間大面積的失效,所以地梨,后面的請(qǐng)求都會(huì)落到數(shù)據(jù)庫(kù)上雅镊,造成數(shù)據(jù)庫(kù)短時(shí)間內(nèi)承受大量請(qǐng)求而崩掉捌显。
解決辦法:
- 事前:盡量保證整個(gè) redis 集群的高可用性,發(fā)現(xiàn)機(jī)器宕機(jī)盡快補(bǔ)上桥状。選擇合適的內(nèi)存淘汰策略。
- 事中:本地ehcache緩存 + hystrix限流&降級(jí)他嫡,避免MySQL崩掉(因?yàn)檫@時(shí)是從數(shù)據(jù)庫(kù)請(qǐng)求數(shù)據(jù))。
- 事后:利用 redis 持久化機(jī)制保存的數(shù)據(jù)盡快恢復(fù)緩存蓉媳。
10.2 緩存穿透
緩存穿透說(shuō)簡(jiǎn)單點(diǎn)就是大量請(qǐng)求的 key 根本不存在于緩存中,導(dǎo)致請(qǐng)求直接到了數(shù)據(jù)庫(kù)上,根本沒(méi)有經(jīng)過(guò)緩存這一層桨武。
舉個(gè)例子:某個(gè)黑客故意制造我們緩存中不存在的 key 發(fā)起大量請(qǐng)求,導(dǎo)致大量請(qǐng)求落到數(shù)據(jù)庫(kù)。
一般 MySQL 默認(rèn)的最大連接數(shù)在 150 左右曾棕,這個(gè)可以通過(guò) show variables like '%max_connections%';
命令來(lái)查看翘地。
最大連接數(shù)一個(gè)還只是一個(gè)指標(biāo)时鸵,cpu某筐,內(nèi)存,磁盤(pán),網(wǎng)絡(luò)等物理?xiàng)l件都是其運(yùn)行指標(biāo)谦屑,這些指標(biāo)都會(huì)限制其并發(fā)能力驳糯。所以,一般 3000 個(gè)并發(fā)請(qǐng)求就能打死大部分?jǐn)?shù)據(jù)庫(kù)了氢橙。
解決辦法:
-
最基本的就是首先做好參數(shù)校驗(yàn)酝枢。
一些不合法的參數(shù)請(qǐng)求直接拋出異常信息返回給客戶端。比如查詢的數(shù)據(jù)庫(kù) id 不能小于 0悍手,傳入的郵箱格式不對(duì)的時(shí)候直接返回錯(cuò)誤消息給客戶端等等隧枫。
-
緩存無(wú)效 key。
如果緩存和數(shù)據(jù)庫(kù)都查不到某個(gè) key 的數(shù)據(jù)就寫(xiě)一個(gè)到 redis 中去并設(shè)置過(guò)期時(shí)間谓苟,具體命令如下:
SET key value EX 10086
官脓。這種方式可以解決請(qǐng)求的 key 變化不頻繁的情況,如果黑客惡意攻擊涝焙,每次構(gòu)建的不同的請(qǐng)求key卑笨,會(huì)導(dǎo)致 redis 中緩存大量無(wú)效的 key 。很明顯仑撞,這種方案并不能從根本上解決此問(wèn)題赤兴。如果非要用這種方式來(lái)解決穿透問(wèn)題的話,盡量將無(wú)效的 key 的過(guò)期時(shí)間設(shè)置短一點(diǎn)隧哮,比如 1 分鐘桶良。用代碼表示如下:
public Object getObjectInclNullById(Integer id) { // 從緩存中獲取數(shù)據(jù) Object cacheValue = cache.get(id); // 緩存為空 if (cacheValue == null) { // 從數(shù)據(jù)庫(kù)中獲取 Object storageValue = storage.get(key); // 緩存空對(duì)象 cache.set(key, storageValue); // 如果存儲(chǔ)數(shù)據(jù)為空,需要設(shè)置一個(gè)過(guò)期時(shí)間(300秒) if (storageValue == null) { // 必須設(shè)置過(guò)期時(shí)間沮翔,否則有被攻擊的風(fēng)險(xiǎn) cache.expire(key, 60 * 5); } return storageValue; } return cacheValue; }
-
布隆過(guò)濾器陨帆。
布隆過(guò)濾器是一個(gè)非常神奇的數(shù)據(jù)結(jié)構(gòu),通過(guò)它我們可以非常方便地判斷一個(gè)給定數(shù)據(jù)是否存在于海量數(shù)據(jù)中。
把所有可能存在的請(qǐng)求的值都存放在布隆過(guò)濾器中疲牵,當(dāng)用戶請(qǐng)求過(guò)來(lái)承二,先判斷用戶發(fā)來(lái)的請(qǐng)求的值是否存在于布隆過(guò)濾器中。不存在的話纲爸,直接返回請(qǐng)求參數(shù)錯(cuò)誤信息給客戶端亥鸠,存在的話才會(huì)走下面的流程。如下圖所示识啦。
11. 如何解決 Redis 的并發(fā)競(jìng)爭(zhēng) Key 問(wèn)題
Redis 的并發(fā)競(jìng)爭(zhēng) Key 的問(wèn)題负蚊,就是多個(gè)系統(tǒng)同時(shí)對(duì)一個(gè) key 進(jìn)行操作,但是最后執(zhí)行的順序和我們期望的順序不同颓哮,這樣也就導(dǎo)致了結(jié)果的不同盖桥。
推薦一種方案:分布式鎖(zookeeper 和 redis 都可以實(shí)現(xiàn)分布式鎖)。