Redis本質(zhì)上是一個(gè)Key-Value類型的內(nèi)存數(shù)據(jù)庫(kù),很像memcached,整個(gè)數(shù)據(jù)庫(kù)統(tǒng)統(tǒng)加載 在內(nèi)存當(dāng)中進(jìn)行操作,定期通過異步操作把數(shù)據(jù)庫(kù)數(shù)據(jù)flush到硬盤上進(jìn)行保存。
因?yàn)槭羌儍?nèi)存操作,Redis的性能非常出色,每秒可以處理超過 10萬(wàn)次讀寫操作,是已知性能 最快的Key-Value DB。
Redis的出色之處不僅僅是性能,Redis最大的魅力是支持保存多種數(shù)據(jù)結(jié)構(gòu),此外單個(gè)value 的最大限制是1GB,不像 memcached只能保存1MB的數(shù)據(jù),因此Redis可以用來實(shí)現(xiàn)很多有用的功能。比方說用他的List來做FIFO雙向鏈表,實(shí)現(xiàn)一個(gè)輕量級(jí)的高性 能消息隊(duì)列服務(wù),用他的Set可 以做高性能的tag系統(tǒng)等等。
另外Redis也可以對(duì)存入的Key-Value設(shè)置expire時(shí)間,因此也可以被當(dāng)作一 個(gè)功能加強(qiáng)版的 memcached來用。 Redis的主要缺點(diǎn)是數(shù)據(jù)庫(kù)容量受到物理內(nèi)存的限制,不能用作海量數(shù)據(jù) 的高性能讀寫,因此Redis適合的場(chǎng)景主要局限在較小數(shù)據(jù)量的高性能操作和運(yùn)算上。
字符串(String):二進(jìn)制安全字符串。
列表(List):根據(jù)插入順序排序的字符串元素列表,基于鏈表實(shí)現(xiàn)。
集合(Set):唯一的亂序的字符串元素的集合。
有序集合(Sorted Set):與集合類似,但是每個(gè)字符串元素都與一個(gè)稱為score的數(shù)字相關(guān)聯(lián)。元素總是按其score排序,并且可以檢索一定score范圍的元素。
哈希(Hash):由字段與值相關(guān)聯(lián)組成的映射,字段和值都是字符串。
位圖(Bitmap):像操作位數(shù)組一樣操作字符串值,可以設(shè)置和清除某個(gè)位,對(duì)所有為1的位進(jìn)行計(jì)數(shù),找到第一個(gè)設(shè)置1的位,找到第一個(gè)設(shè)置0的位等等。
HyperLogLogs:一種概率數(shù)據(jù)結(jié)構(gòu),使用較小的內(nèi)存空間來統(tǒng)計(jì)唯一元素的數(shù)量,誤差小于1%。
1.完全基于內(nèi)存,絕大部分請(qǐng)求是純粹的內(nèi)存操作,非常快速。數(shù)據(jù)存在內(nèi)存中,類似于 HashMap,HashMap 的優(yōu)勢(shì)就是查找和操作的時(shí)間復(fù)雜度都是O(1);
2.數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)單,對(duì)數(shù)據(jù)操作也簡(jiǎn)單,Redis 中的數(shù)據(jù)結(jié)構(gòu)是專門進(jìn)行設(shè)計(jì)的;
3.采用單線程,避免了不必要的上下文切換和競(jìng)爭(zhēng)條件,也不存在多進(jìn)程或者多線程導(dǎo)致的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因?yàn)榭赡艹霈F(xiàn)死鎖而導(dǎo)致的性能消耗;
4.使用多路 I/O 復(fù)用模型,非阻塞 IO;
5.使用底層模型不同,它們之間底層實(shí)現(xiàn)方式以及與客戶端之間通信的應(yīng)用協(xié)議不一樣,Redis 直接自己構(gòu)建了 VM 機(jī)制 ,因?yàn)橐话愕南到y(tǒng)調(diào)用系統(tǒng)函數(shù)的話,會(huì)浪費(fèi)一定的時(shí)間去移動(dòng)和請(qǐng)求;
1.緩存:減輕數(shù)據(jù)庫(kù)的壓力,提高系統(tǒng)性能。
2.排行榜:利用 Redis 的 SortSet(有序集合)實(shí)現(xiàn);
3.計(jì)數(shù)器/限速器:利用 Redis 中原子性的自增操作,我們可以統(tǒng)計(jì)類似用戶點(diǎn)贊數(shù)、用戶訪問數(shù)等。這類操作如果用 MySQL,頻繁的讀寫會(huì)帶來相當(dāng)大的壓力;限速器比較典型的使用場(chǎng)景是限制某個(gè)用戶訪問某個(gè) API 的頻率,常用的有搶購(gòu)時(shí),防止用戶瘋狂點(diǎn)擊帶來不必要的壓力;
4.好友關(guān)系:利用集合的一些命令,比如求交集、并集、差集等。可以方便解決一些共同好友、共同愛好之類的功能;
5.消息隊(duì)列:除了 Redis 自身的發(fā)布/訂閱模式,我們也可以利用 List 來實(shí)現(xiàn)一個(gè)隊(duì)列機(jī)制,比如:到貨通知、郵件發(fā)送之類的需求,不需要高可靠,但是會(huì)帶來非常大的 DB 壓力,完全可以用 List 來完成異步解耦;
6.Session 共享:Session 是保存在服務(wù)器的文件中,如果是集群服務(wù),同一個(gè)用戶過來可能落在不同機(jī)器上,這就會(huì)導(dǎo)致用戶頻繁登陸;采用 Redis 保存 Session 后,無論用戶落在那臺(tái)機(jī)器上都能夠獲取到對(duì)應(yīng)的 Session 信息。
緩存穿透指查詢一個(gè)一定不存在的數(shù)據(jù),由于緩存是不命中時(shí)需要從數(shù)據(jù)庫(kù)查詢,查不到數(shù)據(jù)則不寫入緩存,這將導(dǎo)致這個(gè)不存在的數(shù)據(jù)每次請(qǐng)求都要到數(shù)據(jù)庫(kù)去查詢,進(jìn)而給數(shù)據(jù)庫(kù)帶來壓力。
解決方案:
1)將空數(shù)據(jù)也緩存:占有一定的空間,可能帶來短期的數(shù)據(jù)不一致。
如果一個(gè)查詢返回的數(shù)據(jù)為空(不管是數(shù)據(jù)不存在,還是系統(tǒng)故障),我們?nèi)匀话堰@個(gè)空結(jié)果進(jìn)行緩存,但它的過期時(shí)間會(huì)很短,最長(zhǎng)不超過五分鐘,
2)使用布隆過濾器bloom filter:是一種預(yù)防的方案,占用空間少、誤差可控。
將所有可能存在的數(shù)據(jù)哈希到一個(gè)足夠大的bitmap中,一個(gè)一定不存在的數(shù)據(jù)會(huì)被這個(gè)bitmap攔截掉,從而避免了對(duì)底層存儲(chǔ)系統(tǒng)的查詢壓力。
緩存雪崩是指在某一個(gè)時(shí)間段,緩存集中過期失效。當(dāng)某一個(gè)時(shí)刻出現(xiàn)大規(guī)模的緩存失效的情況,那么就會(huì)導(dǎo)致大量的請(qǐng)求直接打在數(shù)據(jù)庫(kù)上面,導(dǎo)致數(shù)據(jù)庫(kù)壓力巨大,如果在高并發(fā)的情況下,可能瞬間就會(huì)導(dǎo)致數(shù)據(jù)庫(kù)宕機(jī)。這時(shí)候如果運(yùn)維馬上又重啟數(shù)據(jù)庫(kù),馬上又會(huì)有新的流量把數(shù)據(jù)庫(kù)打死。這就是緩存雪崩。
解決方案
1)過期時(shí)間設(shè)置隨機(jī)值:在原有的失效時(shí)間上加上一個(gè)隨機(jī)值,比如,1-5分鐘隨機(jī)。這樣就避免了同一時(shí)間大量數(shù)據(jù)過期現(xiàn)象的發(fā)生而導(dǎo)致緩存雪崩。
2)分布式部署且均勻分布熱點(diǎn)數(shù)據(jù):如果緩存數(shù)據(jù)庫(kù)是分布式部署,將熱點(diǎn)數(shù)據(jù)均勻分布在不同搞得緩存數(shù)據(jù)庫(kù)中。同時(shí),分布式集群可以防止Redis宕機(jī)導(dǎo)致緩存雪崩的問題。
3)熱點(diǎn)數(shù)據(jù)永不過期:設(shè)置熱點(diǎn)數(shù)據(jù)永遠(yuǎn)不過期。
4)使用熔斷機(jī)制。當(dāng)流量到達(dá)一定的閾值時(shí),就直接返回“系統(tǒng)擁擠”之類的提示,防止過多的請(qǐng)求打在數(shù)據(jù)庫(kù)上。至少能保證一部分用戶是可以正常使用,其他用戶多刷新幾次也能得到結(jié)果。
5)提高數(shù)據(jù)庫(kù)的容災(zāi)能力,可以使用分庫(kù)分表,讀寫分離的策略。
造成緩存雪崩的關(guān)鍵在于在同一時(shí)間大規(guī)模的key失效。出現(xiàn)這個(gè)問題有下面幾種可能:
第一種可能是Redis宕機(jī),
第二種可能是采用了相同的過期時(shí)間。
某一個(gè)熱點(diǎn) key,在緩存過期的一瞬間,同時(shí)有大量的請(qǐng)求打進(jìn)來,由于此時(shí)緩存過期了,所以請(qǐng)求最終都會(huì)走到數(shù)據(jù)庫(kù),造成瞬時(shí)數(shù)據(jù)庫(kù)請(qǐng)求量大、壓力驟增,甚至可能打垮數(shù)據(jù)庫(kù)。
解決方案:
1.加互斥鎖。在并發(fā)的多個(gè)請(qǐng)求中,只有第一個(gè)請(qǐng)求線程能拿到鎖并執(zhí)行數(shù)據(jù)庫(kù)查詢操作,其他的線程拿不到鎖就阻塞等著,等到第一個(gè)線程將數(shù)據(jù)寫入緩存后,直接走緩存。
2.JVM 鎖保證了在單臺(tái)服務(wù)器上只有一個(gè)請(qǐng)求走到數(shù)據(jù)庫(kù),通常來說已經(jīng)足夠保證數(shù)據(jù)庫(kù)的壓力大大降低,同時(shí)在性能上比分布式鎖更好。
需要注意的是,無論是使用“分布式鎖”,還是“JVM 鎖”,加鎖時(shí)要按 key 維度去加鎖。
Redis通過MULTI、EXEC、WATCH等一組命令集合,來實(shí)現(xiàn)事務(wù)機(jī)制。事務(wù)支持一次執(zhí)行多個(gè)命令,一個(gè)事務(wù)中所有命令都會(huì)被序列化。在事務(wù)執(zhí)行過程,會(huì)按照順序串行化執(zhí)行隊(duì)列中的命令,其他客戶端提交的命令請(qǐng)求不會(huì)插入到事務(wù)執(zhí)行命令序列中。簡(jiǎn)言之,Redis事務(wù)就是順序性、一次性、排他性的執(zhí)行一個(gè)隊(duì)列中的一系列命令。
Redis執(zhí)行事務(wù)的流程如下:開始事務(wù)(MULTI)、命令入隊(duì)、執(zhí)行事務(wù)(EXEC)、撤銷事務(wù)(DISCARD )。
可以的,Redis提供兩個(gè)指令生成RDB,分別是save和bgsave。
如果是save指令,會(huì)阻塞,因?yàn)槭侵骶€程執(zhí)行的。
如果是bgsave指令,是fork一個(gè)子進(jìn)程來寫入RDB文件的,快照持久化完全交給子進(jìn)程來處理,父進(jìn)程則可以繼續(xù)處理客戶端的請(qǐng)求。
一般來說, 如果想達(dá)到足以媲美PostgreSQL的數(shù)據(jù)安全性,你應(yīng)該同時(shí)使用兩種持久化功能。在這種情況下,當(dāng) Redis 重啟的時(shí)候會(huì)優(yōu)先載入AOF文件來恢復(fù)原始的數(shù)據(jù),因?yàn)樵谕ǔG闆r下AOF文件保存的數(shù)據(jù)集要比RDB文件保存的數(shù)據(jù)集要完整。
如果你非常關(guān)心你的數(shù)據(jù), 但仍然可以承受數(shù)分鐘以內(nèi)的數(shù)據(jù)丟失,那么你可以只使用RDB持久化。有很多用戶都只使用AOF持久化,但并不推薦這種方式,因?yàn)槎〞r(shí)生成RDB快照(snapshot)非常便于進(jìn)行數(shù)據(jù)庫(kù)備份, 并且 RDB 恢復(fù)數(shù)據(jù)集的速度也要比AOF恢復(fù)的速度要快,除此之外,使用RDB還可以避免AOF程序的bug。
如果你只希望你的數(shù)據(jù)在服務(wù)器運(yùn)行的時(shí)候存在,你也可以不使用任何持久化方式。
緩存預(yù)熱就是系統(tǒng)上線后,將相關(guān)的緩存數(shù)據(jù)直接加載到緩存系統(tǒng)。這樣就可以避免在用戶請(qǐng)求的時(shí)候,先查詢數(shù)據(jù)庫(kù),然后再將數(shù)據(jù)緩存的問題!用戶直接查詢事先被預(yù)熱的緩存數(shù)據(jù)!
解決方案
1.直接寫個(gè)緩存刷新頁(yè)面,上線時(shí)手工操作一下;
2.數(shù)據(jù)量不大,可以在項(xiàng)目啟動(dòng)的時(shí)候自動(dòng)進(jìn)行加載;
3.定時(shí)刷新緩存;
Redis6.0采用多線程IO,不過命令的執(zhí)行還是單線程的。
Redis6.0之前,IO線程和執(zhí)行線程都是單線程的。
分表是EXPIRE和PERSIST命令進(jìn)行設(shè)置。
加鎖重鍵(互斥鎖):
熱鍵不過期:在緩存中創(chuàng)建一個(gè)時(shí)間戳,先判斷時(shí)間戳是否過期,如果沒有過期返回原數(shù)據(jù),過期了則訪問數(shù)據(jù)源。
布隆過濾器是一個(gè)叫“布隆”的人提出的,它本身是一個(gè)很長(zhǎng)的二進(jìn)制向量,既然是二進(jìn)制的向量,那么顯而易見的,存放的不是0,就是1。布隆過濾器是一種由位數(shù)組和多個(gè)哈希函數(shù)組成概率數(shù)據(jù)結(jié)構(gòu),返回兩種結(jié)果可能存在和一定不存在。布隆過濾器里的一個(gè)元素由多個(gè)狀態(tài)值共同確定。位數(shù)組存儲(chǔ)狀態(tài)值,哈希函數(shù)計(jì)算狀態(tài)值的位置。
優(yōu)點(diǎn):由于存放的不是完整的數(shù)據(jù),所以占用的內(nèi)存很少,而且新增,查詢速度夠快;
缺點(diǎn): 隨著數(shù)據(jù)的增加,誤判率隨之增加;無法做到刪除數(shù)據(jù);只能判斷數(shù)據(jù)是否一定不存在,而無法判斷數(shù)據(jù)是否一定存在。
常用命令: set,get,decr,incr,mget 等。
含義:String數(shù)據(jù)結(jié)構(gòu)是簡(jiǎn)單的Key-Value類型,value不僅可以是String,也可以是數(shù)字。
數(shù)據(jù)結(jié)構(gòu):內(nèi)部結(jié)構(gòu)實(shí)現(xiàn)上類似于 Java 的 ArrayList,采用預(yù)分配冗余空間的方式來減少內(nèi)存的頻繁分配,如圖所示:
len 是當(dāng)前字符串實(shí)際長(zhǎng)度,capacity 是為字符串分配的可用空間,當(dāng)字符串長(zhǎng)度小于 1M 時(shí),擴(kuò)容都是加倍現(xiàn)有的空間,如果超過 1M,擴(kuò)容時(shí)一次只會(huì)多擴(kuò) 1M 的空間。字符串最大長(zhǎng)度為 512M。
應(yīng)用場(chǎng)景: 常規(guī)計(jì)數(shù),微博數(shù),粉絲數(shù)等。
常用命令: hget,hset,hgetall 等。
含義:Redis中的哈希結(jié)構(gòu)就如同Java中的map一樣,Hash是一個(gè)string類型的field和value的映射表,hash特別適合用于存儲(chǔ)對(duì)象。
數(shù)據(jù)結(jié)構(gòu):Redis Hash通過分桶的方式解決 hash 沖突。它是無序字典。內(nèi)部實(shí)現(xiàn)結(jié)構(gòu)是同樣的數(shù)組 + 鏈表二維結(jié)構(gòu)。第一維 hash 的數(shù)組位置碰撞時(shí),就會(huì)將碰撞的元素使用鏈表串接起來。第一維是數(shù)組,第二維是鏈表。數(shù)組中存儲(chǔ)的是第二維鏈表的第一個(gè)元素的指針。
應(yīng)用場(chǎng)景:存儲(chǔ)用戶信息,商品信息等等。例如修真院的首頁(yè)的職業(yè)信息,只是簡(jiǎn)單的信息集合,我們可以直接將它儲(chǔ)存到Redis中,在讀取的過程中就不用序列化對(duì)象,直接操作。
常用命令: lpush,rpush,lpop,rpop,lrange等
含義:list就是鏈表,Redis list的實(shí)現(xiàn)為一個(gè)雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的內(nèi)存開銷。
數(shù)據(jù)結(jié)構(gòu):Redis 的列表相當(dāng)于 Java 語(yǔ)言中的 LinkedList,注意它是鏈表而不是數(shù)組。這意味著 list 的插入和刪除操作非常快,時(shí)間復(fù)雜度為 O(1),但是索引定位很慢,時(shí)間復(fù)雜度為 O(n)。
list的特點(diǎn)是:
1)有序
2)可以重復(fù)
3)右邊進(jìn)左邊出或者左邊進(jìn)右邊出,則列表可以充當(dāng)隊(duì)列
4)左邊進(jìn)左邊出或者右邊進(jìn)右邊出,則列表可以充當(dāng)棧
應(yīng)用場(chǎng)景:微博的關(guān)注列表,粉絲列表,最新消息排行等功能
常用命令:sadd,spop,smembers,sunion 等
含義:set對(duì)外提供的功能與list類似,是一個(gè)列表的功能,特殊之處在于set是可以自動(dòng)排重的。 并且set提供了判斷某個(gè)成員是否在一個(gè)set集合內(nèi)的重要接口,這個(gè)也是list所不能提供的。
數(shù)據(jù)結(jié)構(gòu):set和字典非常類似,其內(nèi)部實(shí)現(xiàn)就是上述的hashTable的特殊實(shí)現(xiàn),與字典不同的地方有兩點(diǎn):
1)只關(guān)注key值,所有的value都是NULL。
2)在新增數(shù)據(jù)時(shí)會(huì)進(jìn)行去重。
場(chǎng)景應(yīng)用:
1.共同好友、二度好友
2.利用唯一性,可以統(tǒng)計(jì)訪問網(wǎng)站的所有獨(dú)立 IP
3.好友推薦的時(shí)候,根據(jù) tag 求交集,大于某個(gè) threshold 就可以推薦
常用命令: zadd,zrange,zrem,zcard等
含義:和set相比,sorted set增加了一個(gè)權(quán)重參數(shù)score,使得集合中的元素能夠按score進(jìn)行有序排列。
數(shù)據(jù)結(jié)構(gòu):zset是Redis非常有特色的數(shù)據(jù)結(jié)構(gòu),它是基于Set并提供排序的有序集合。其中最為重要的特點(diǎn)就是支持通過score的權(quán)重來指定權(quán)重。一些排行榜、延遲任務(wù)比如指定1小時(shí)后執(zhí)行, 就是使用這個(gè)數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的。
應(yīng)用場(chǎng)景:在直播系統(tǒng)中,實(shí)時(shí)排行信息包含直播間在線用戶列表,各種禮物排行榜,彈幕消息(可以理解為按消息維度的消息排行榜)等信息,適合使用Redis中的SortedSet結(jié)構(gòu)進(jìn)行存儲(chǔ)。
Redis 作為一個(gè)K-V的內(nèi)存數(shù)據(jù)庫(kù),它使用用一張全局的哈希來保存所有的鍵值對(duì)。這張哈希表,有多個(gè)哈希桶組成,哈希桶中的entry元素保存了key和value指針,其中*key指向了實(shí)際的鍵,*value指向了實(shí)際的值。
所謂的哈希沖突通是指過不同的key,計(jì)算出一樣的哈希值,導(dǎo)致落在同一個(gè)哈希桶中。
Redis為了解決哈希沖突,采用了鏈?zhǔn)焦!f準(zhǔn)焦J侵竿粋€(gè)哈希桶中,多個(gè)元素用一個(gè)鏈表來保存,它們之間依次用指針連接。
因?yàn)楣_突鏈上的元素只能通過指針逐一查找再操作,所以當(dāng)往哈希表插入數(shù)據(jù)很多,沖突也會(huì)越多,沖突鏈表就會(huì)越長(zhǎng),那查詢效率就會(huì)降低了。為了保持高效,Redis 會(huì)對(duì)哈希表做rehash操作,也就是增加哈希桶,減少?zèng)_突。為了rehash更高效,Redis還默認(rèn)使用了兩個(gè)全局哈希表,一個(gè)用于當(dāng)前使用,稱為主哈希表,一個(gè)用于擴(kuò)容,稱為備用哈希表。
Redis集群沒有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384個(gè)哈希槽,每個(gè)key通過CRC16校驗(yàn)后對(duì)16384取模來決定放置哪個(gè)槽,集群的每個(gè)節(jié)點(diǎn)負(fù)責(zé)一部分hash槽。
我們?cè)陧?xiàng)目中使用Redis,肯定不會(huì)是單點(diǎn)部署Redis服務(wù)的。因?yàn)椋瑔吸c(diǎn)部署一旦宕機(jī),就不可用了。為了實(shí)現(xiàn)高可用,通常的做法是,將數(shù)據(jù)庫(kù)復(fù)制多個(gè)副本以部署在不同的服務(wù)器上,其中一臺(tái)掛了也可以繼續(xù)提供服務(wù)。Redis 實(shí)現(xiàn)高可用有三種部署模式:主從模式,哨兵模式,集群模式。
主從模式中Redis部署了多臺(tái)機(jī)器,有負(fù)責(zé)讀寫操作主節(jié)點(diǎn)和只負(fù)責(zé)讀操作從節(jié)點(diǎn),從節(jié)點(diǎn)的數(shù)據(jù)來自主節(jié)點(diǎn),實(shí)現(xiàn)原理就是主從復(fù)制機(jī)制。主從復(fù)制包括全量復(fù)制,增量復(fù)制兩種。一般當(dāng)slave第一次啟動(dòng)連接master,或者認(rèn)為是第一次連接,就采用全量復(fù)制,全量復(fù)制流程如下:
1.slave發(fā)送sync命令到master。
2.master接收到SYNC命令后,執(zhí)行bgsave命令,生成RDB全量文件。
3.master使用緩沖區(qū),記錄RDB快照生成期間的所有寫命令。
4.master執(zhí)行完bgsave后,向所有slave發(fā)送RDB快照文件。
5.slave收到RDB快照文件后,載入、解析收到的快照。
6.master使用緩沖區(qū),記錄RDB同步期間生成的所有寫的命令。
7.master快照發(fā)送完畢后,開始向slave發(fā)送緩沖區(qū)中的寫命令;
8.salve接受命令請(qǐng)求,并執(zhí)行來自master緩沖區(qū)的寫命令
redis2.8版本之后,已經(jīng)使用psync來替代sync,因?yàn)閟ync命令非常消耗系統(tǒng)資源,psync的效率更高。
slave與master全量同步之后,master上的數(shù)據(jù),如果再次發(fā)生更新,就會(huì)觸發(fā)增量復(fù)制。
當(dāng)master節(jié)點(diǎn)發(fā)生數(shù)據(jù)增減時(shí),就會(huì)觸發(fā)replicationFeedSalves()函數(shù),接下來在 Master節(jié)點(diǎn)上調(diào)用的每一個(gè)命令會(huì)使用replicationFeedSlaves()來同步到Slave節(jié)點(diǎn)。執(zhí)行此函數(shù)之前呢,master節(jié)點(diǎn)會(huì)判斷用戶執(zhí)行的命令是否有數(shù)據(jù)更新,如果有數(shù)據(jù)更新的話,并且slave節(jié)點(diǎn)不為空,就會(huì)執(zhí)行此函數(shù)。這個(gè)函數(shù)作用就是:把用戶執(zhí)行的命令發(fā)送到所有的slave節(jié)點(diǎn),讓slave節(jié)點(diǎn)執(zhí)行。流程如下:
Redis的哨兵(sentinel) 系統(tǒng)用于管理多個(gè) Redis 服務(wù)器,該系統(tǒng)執(zhí)行以下三個(gè)任務(wù):
1)監(jiān)控(Monitoring): 哨兵(sentinel) 會(huì)不斷地檢查你的Master和Slave是否運(yùn)作正常。
2)提醒(Notification):當(dāng)被監(jiān)控的某個(gè) Redis出現(xiàn)問題時(shí), 哨兵(sentinel) 可以通過 API 向管理員或者其他應(yīng)用程序發(fā)送通知。
3)自動(dòng)故障遷移(Automatic failover):當(dāng)一個(gè)Master不能正常工作時(shí),哨兵(sentinel) 會(huì)開始一次自動(dòng)故障遷移操作,它會(huì)將失效Master的其中一個(gè)Slave升級(jí)為新的Master, 并讓失效Master的其他Slave改為復(fù)制新的Master; 當(dāng)客戶端試圖連接失效的Master時(shí),集群也會(huì)向客戶端返回新Master的地址,使得集群可以使用Master代替失效Master。
監(jiān)控主數(shù)據(jù)庫(kù)和從數(shù)據(jù)庫(kù)是否正常運(yùn)行。
主數(shù)據(jù)庫(kù)出現(xiàn)故障時(shí),可以自動(dòng)將從數(shù)據(jù)庫(kù)轉(zhuǎn)換為主數(shù)據(jù)庫(kù),實(shí)現(xiàn)自動(dòng)切換。
當(dāng)主節(jié)點(diǎn)出現(xiàn)故障時(shí),由Redis Sentinel自動(dòng)完成故障發(fā)現(xiàn)和轉(zhuǎn)移,并通知應(yīng)用方,實(shí)現(xiàn)高可用性。
其實(shí)整個(gè)過程只需要一個(gè)哨兵節(jié)點(diǎn)來完成,首先使用Raft算法(選舉算法)實(shí)現(xiàn)選舉機(jī)制,選出一個(gè)哨兵節(jié)點(diǎn)來完成轉(zhuǎn)移和通知
1)哨兵集群至少要 3 個(gè)節(jié)點(diǎn),來確保自己的健壯性
2)redis主從 + sentinel的架構(gòu),是不會(huì)保證數(shù)據(jù)的零丟失的,它是為了保證redis集群的高可用.
會(huì)有,主要考慮下面兩種情況。
1)主從異步復(fù)制導(dǎo)致的數(shù)據(jù)丟失:redis master 和slave 數(shù)據(jù)復(fù)制是異步的,這樣就有可能會(huì)出現(xiàn)部分?jǐn)?shù)據(jù)還沒有復(fù)制到slave中,master就掛掉了,那么這部分的數(shù)據(jù)就會(huì)丟失了
2)腦裂導(dǎo)致的數(shù)據(jù)丟失:腦裂其實(shí)就是網(wǎng)絡(luò)分區(qū)導(dǎo)致的現(xiàn)象,比如,我們的master機(jī)器網(wǎng)絡(luò)突然不正常了發(fā)生了網(wǎng)絡(luò)分區(qū),和其他的slave機(jī)器不能正常通信了,其實(shí)master并沒有掛還活著好好的呢,但是哨兵可不是吃閑飯的啊,它會(huì)認(rèn)為master掛掉了啊,那么問題來了,client可能還在繼續(xù)寫master的呀,還沒來得及更新到新的master呢,那這部分?jǐn)?shù)據(jù)就會(huì)丟失。
如果一個(gè)master被認(rèn)為宕機(jī)了,而且majority多數(shù)哨兵都允許了主備切換,那么某個(gè)哨兵就會(huì)執(zhí)行主備切換操作,此時(shí)首先要選舉一個(gè)slave來,主要通過下面幾個(gè)步驟
1)slave跟master斷開連接的時(shí)長(zhǎng)(斷開時(shí)間越短優(yōu)先級(jí)越高)
2)slave優(yōu)先級(jí)(在配置文件中的配置,slave priority越低,優(yōu)先級(jí)就越高。)
3)復(fù)制offset(哪個(gè)slave復(fù)制了越多的數(shù)據(jù),offset越靠后,優(yōu)先級(jí)就越高。)
4)run id(如果上面兩個(gè)條件都相同,那么選擇一個(gè)run id比較小的那個(gè)slave)
redis從3.0開始支持集群功能。redis集群采用無中心節(jié)點(diǎn)方式實(shí)現(xiàn),無需proxy代理,客戶端直接與redis集群的每個(gè)節(jié)點(diǎn)連接,根據(jù)同樣的hash算法計(jì)算出key對(duì)應(yīng)的slot,然后直接在slot對(duì)應(yīng)的redis節(jié)點(diǎn)上執(zhí)行命令。在redis看來,響應(yīng)時(shí)間是最苛刻的條件,增加一層帶來的開銷是redis不能接受的。因此,redis實(shí)現(xiàn)了客戶端對(duì)節(jié)點(diǎn)的直接訪問,為了去中心化,節(jié)點(diǎn)之間通過gossip協(xié)議交換互相的狀態(tài),以及探測(cè)新加入的節(jié)點(diǎn)信息。redis集群支持動(dòng)態(tài)加入節(jié)點(diǎn),動(dòng)態(tài)遷移slot,以及自動(dòng)故障轉(zhuǎn)移。
redis內(nèi)存數(shù)據(jù)集大小上升到一定大小的時(shí)候,就會(huì)施行數(shù)據(jù)淘汰策略。
可以好好利用Hash,list,sorted set,set等集合類型數(shù)據(jù),因?yàn)橥ǔG闆r下很多小的Key-Value可以用更緊湊的方式存放到一起。盡可能使用散列表(hashes),散列表(是說散列表里面存儲(chǔ)的數(shù)少)使用的內(nèi)存非常小,所以你應(yīng)該盡可能的將你的數(shù)據(jù)模型抽象到一個(gè)散列表里面。比如你的web系統(tǒng)中有一個(gè)用戶對(duì)象,不要為這個(gè)用戶的名稱,姓氏,郵箱,密碼設(shè)置單獨(dú)的key,而是應(yīng)該把這個(gè)用戶的所有信息存儲(chǔ)到一張散列表里面。
使用keys指令可以掃出指定模式的key列表。
對(duì)方接著追問:如果這個(gè)redis正在給線上的業(yè)務(wù)提供服務(wù),那使用keys指令會(huì)有什么問題?
這個(gè)時(shí)候你要回答redis關(guān)鍵的一個(gè)特性:redis的單線程的。keys指令會(huì)導(dǎo)致線程阻塞一段時(shí)間,線上服務(wù)會(huì)停頓,直到指令執(zhí)行完畢,服務(wù)才能恢復(fù)。這個(gè)時(shí)候可以使用scan指令,scan指令可以無阻塞的提取出指定模式的key列表,但是會(huì)有一定的重復(fù)概率,在客戶端做一次去重就可以了,但是整體所花費(fèi)的時(shí)間會(huì)比直接用keys指令長(zhǎng)。
Redis2.6開始redis-cli支持一種新的被稱之為pipe mode的新模式用于執(zhí)行大量數(shù)據(jù)插入工作。
1.Master最好不要做任何持久化工作,包括內(nèi)存快照和AOF日志文件,特別是不要啟用內(nèi)存快照做持久化。
2.如果數(shù)據(jù)比較關(guān)鍵,某個(gè)Slave開啟AOF備份數(shù)據(jù),策略為每秒同步一次。
3.為了主從復(fù)制的速度和連接的穩(wěn)定性,Slave和Master最好在同一個(gè)局域網(wǎng)內(nèi)。
4.盡量避免在壓力較大的主庫(kù)上增加從庫(kù)
5.Master調(diào)用BGREWRITEAOF重寫AOF文件,AOF在重寫的時(shí)候會(huì)占大量的CPU和內(nèi)存資源,導(dǎo)致服務(wù)load過高,出現(xiàn)短暫服務(wù)暫停現(xiàn)象。
6.為了Master的穩(wěn)定性,主從復(fù)制不要用圖狀結(jié)構(gòu),用單向鏈表結(jié)構(gòu)更穩(wěn)定,即主從關(guān)系為:Master<–Slave1<–Slave2<–Slave3…,這樣的結(jié)構(gòu)也方便解決單點(diǎn)故障問題,實(shí)現(xiàn)Slave對(duì)Master的替換,也即,如果Master掛了,可以立馬啟用Slave1做Master,其他不變。
所謂 Redis 的并發(fā)競(jìng)爭(zhēng) Key 的問題也就是多個(gè)系統(tǒng)同時(shí)對(duì)一個(gè) key 進(jìn)行操作,但是最后執(zhí)行的順序和我們期望的順序不同,這樣也就導(dǎo)致了結(jié)果的不同!
推薦一種方案:分布式鎖(zookeeper 和 redis 都可以實(shí)現(xiàn)分布式鎖,如果不存在 Redis 的并發(fā)競(jìng)爭(zhēng) Key 問題,不要使用分布式鎖,這樣會(huì)影響性能)。基于zookeeper臨時(shí)有序節(jié)點(diǎn)可以實(shí)現(xiàn)的分布式鎖。大致思想為:每個(gè)客戶端對(duì)某個(gè)方法加鎖時(shí),在zookeeper上的與該方法對(duì)應(yīng)的指定節(jié)點(diǎn)的目錄下,生成一個(gè)唯一的瞬時(shí)有序節(jié)點(diǎn)。判斷是否獲取鎖的方式很簡(jiǎn)單,只需要判斷有序節(jié)點(diǎn)中序號(hào)最小的一個(gè)。當(dāng)釋放鎖的時(shí)候,只需將這個(gè)瞬時(shí)節(jié)點(diǎn)刪除即可。同時(shí),其可以避免服務(wù)宕機(jī)導(dǎo)致的鎖無法釋放,而產(chǎn)生的死鎖問題。完成業(yè)務(wù)流程后,刪除對(duì)應(yīng)的子節(jié)點(diǎn)釋放鎖。
在實(shí)踐中,當(dāng)然是從以可靠性為主。所以首推Zookeeper。
Redis為單進(jìn)程單線程模式,采用隊(duì)列模式將并發(fā)訪問變成串行訪問,且多客戶端對(duì)Redis的連接并不存在競(jìng)爭(zhēng)關(guān)系Redis中可以使用SETNX命令實(shí)現(xiàn)分布式鎖。
當(dāng)且僅當(dāng) key 不存在,將 key 的值設(shè)為 value。若給定的 key 已經(jīng)存在,則 SETNX 不做任何動(dòng)作。其中SETNX 是『SET if Not eXists』(如果不存在,則 SET)的簡(jiǎn)寫。
返回值:設(shè)置成功,返回 1 。設(shè)置失敗,返回 0 。
從理論上說,只要我們?cè)O(shè)置了合理的鍵的過期時(shí)間,我們就能保證緩存和數(shù)據(jù)庫(kù)的數(shù)據(jù)最終是一致的。因?yàn)橹灰彺鏀?shù)據(jù)過期了,就會(huì)被刪除。隨后讀的時(shí)候,因?yàn)榫彺胬餂]有,就可以查數(shù)據(jù)庫(kù)的數(shù)據(jù),然后將數(shù)據(jù)庫(kù)查出來的數(shù)據(jù)寫入到緩存中。除了設(shè)置過期時(shí)間,我們還可以通過新增、更改、刪除數(shù)據(jù)庫(kù)操作時(shí)同步更新 Redis,可以使用事物機(jī)制來保證數(shù)據(jù)的一致性。一般有如下四種方案,具體如下:
1.先更新數(shù)據(jù)庫(kù),后更新緩存
2.先更新緩存,后更新數(shù)據(jù)庫(kù)
3.先刪除緩存,后更新數(shù)據(jù)庫(kù)
4先更新數(shù)據(jù)庫(kù),后刪除緩存
第一種方案存在問題是:并發(fā)更新數(shù)據(jù)庫(kù)場(chǎng)景下,會(huì)將臟數(shù)據(jù)刷到緩存。
第二種方案存在的問題是:如果先更新緩存成功,但是數(shù)據(jù)庫(kù)更新失敗,則肯定會(huì)造成數(shù)據(jù)不一致。
目前主要用第三和第四種方案。
Redis的內(nèi)存淘汰策略是指在Redis的用于緩存的內(nèi)存不足時(shí),怎么處理需要新寫入且需要申請(qǐng)額外空間的數(shù)據(jù)。
全局的鍵空間選擇性移除:
noeviction:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),新寫入操作會(huì)報(bào)錯(cuò)。
allkeys-lru:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),在鍵空間中,移除最近最少使用的key。(這個(gè)是最常用的)
allkeys-random:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),在鍵空間中,隨機(jī)移除某個(gè)key。
設(shè)置過期時(shí)間的鍵空間選擇性移除
volatile-lru:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),在設(shè)置了過期時(shí)間的鍵空間中,移除 近 少使用的key。
volatile-random:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),在設(shè)置了過期時(shí)間的鍵空間中,隨機(jī)移除某個(gè)key。
volatile-ttl:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),在設(shè)置了過期時(shí)間的鍵空間中,有更早過期時(shí)間的key優(yōu)先移除。
Redis的過期鍵的刪除策略是指當(dāng)Redis中的緩存的key過期了,Redis要如何處理。
Redis中提供了三種刪除策略:
1.定時(shí)刪除
當(dāng)放入數(shù)據(jù)后,設(shè)置一個(gè)定時(shí)器,當(dāng)定時(shí)器讀秒完畢后,將對(duì)應(yīng)的數(shù)據(jù)從dict中刪除。
優(yōu)點(diǎn): 內(nèi)存友好,數(shù)據(jù)一旦過期就會(huì)被刪除
缺點(diǎn): CPU不友好,定時(shí)器耗費(fèi)CPU資源,并且頻繁的執(zhí)行清理操作也會(huì)耗費(fèi)CPU資源。用時(shí)間換空間
2.惰性刪除
當(dāng)數(shù)據(jù)過期的時(shí)候,不做任何操作。當(dāng)訪問數(shù)據(jù)的時(shí)候,查看數(shù)據(jù)是否過期,如果過期返回null,并且將數(shù)據(jù)從內(nèi)存中清除。如果沒過期,就直接返回?cái)?shù)據(jù)。
優(yōu)點(diǎn): CPU友好,數(shù)據(jù)等到過期并且被訪問的時(shí)候,才會(huì)刪除。
缺點(diǎn): 內(nèi)存不友好,會(huì)占用大量?jī)?nèi)存。用空間換時(shí)間
3.定期刪除
定期刪除是定時(shí)刪除和惰性刪除的折中方案。每隔一段時(shí)間對(duì)redisServer中的所有redisDb的expires依次進(jìn)行隨機(jī)抽取檢查。
Redis中有一個(gè)server.hz定義了每秒鐘執(zhí)行定期刪除的次數(shù),每次執(zhí)行的時(shí)間為250ms/server.hz。Redis中會(huì)維護(hù)一個(gè)current_db變量來標(biāo)志當(dāng)前檢查的數(shù)據(jù)庫(kù)。current_db++,當(dāng)超過數(shù)據(jù)庫(kù)的數(shù)量的時(shí)候,會(huì)重新從0開始。
定期檢查就是執(zhí)行一個(gè)循環(huán),循環(huán)中的每輪操作會(huì)從current_db對(duì)應(yīng)的數(shù)據(jù)庫(kù)中隨機(jī)依次取出w個(gè)key,查看其是否過期。如果過期就將其刪除, 并且記錄刪除的key的個(gè)數(shù)。如果過期的key個(gè)數(shù)大于w25%,就會(huì)繼續(xù)檢查當(dāng)前數(shù)據(jù)庫(kù),當(dāng)過期的key小于w25%,會(huì)繼續(xù)檢查下一個(gè)數(shù)據(jù)庫(kù)。當(dāng)執(zhí)行時(shí)間超過規(guī)定的最大執(zhí)行時(shí)間的時(shí)候,會(huì)退出檢查。一次檢查中可以檢查多個(gè)數(shù)據(jù)庫(kù),但是最多檢查數(shù)量是redisServer中的數(shù)據(jù)庫(kù)個(gè)數(shù),也就是最多只能從當(dāng)前位置檢查一圈。
可以在同一個(gè)服務(wù)器部署多個(gè) Redis 的實(shí)例,并把他們當(dāng)作不同的服 務(wù)器來使用,在某些時(shí)候,無論如何一個(gè)服務(wù)器是不夠的, 所以, 如果你想使用多個(gè) CPU,你可以考慮一下分片(shard)。
分區(qū)可以讓Redis管理更大的內(nèi)存,Redis將可以使用所有機(jī)器的內(nèi)存。如果沒有分區(qū),你最多只能使用一臺(tái)機(jī)器的內(nèi)存。分區(qū)使Redis的計(jì)算能力通過簡(jiǎn)單地增加計(jì)算機(jī)得到成倍提升,Redis的網(wǎng)絡(luò)帶寬也會(huì)隨著計(jì)算機(jī)和網(wǎng)卡的增加而成倍增長(zhǎng)。
1.客戶端分區(qū)就是在客戶端就已經(jīng)決定數(shù)據(jù)會(huì)被存儲(chǔ)到哪個(gè)redis節(jié)點(diǎn)或者從哪個(gè)redis節(jié)點(diǎn)讀取。大多數(shù)客戶端已經(jīng)實(shí)現(xiàn)了客戶端分區(qū)。
2.代理分區(qū) 意味著客戶端將請(qǐng)求發(fā)送給代理,然后代理決定去哪個(gè)節(jié)點(diǎn)寫數(shù)據(jù)或者讀數(shù)據(jù)。代理根據(jù)分區(qū)規(guī)則決定請(qǐng)求哪些Redis實(shí)例,然后根據(jù)Redis的響應(yīng)結(jié)果返回給客戶端。redis和memcached的一種代理實(shí)現(xiàn)就是Twemproxy。
3.查詢路由(Query routing) 的意思是客戶端隨機(jī)地請(qǐng)求任意一個(gè)redis實(shí)例,然后由Redis將請(qǐng)求轉(zhuǎn)發(fā)給正確的Redis節(jié)點(diǎn)。Redis Cluster實(shí)現(xiàn)了一種混合形式的查詢路由,但并不是直接將請(qǐng)求從一個(gè)redis節(jié)點(diǎn)轉(zhuǎn)發(fā)到另一個(gè)redis節(jié)點(diǎn),而是在客戶端的幫助下直接redirected到正確的redis節(jié)點(diǎn)。
1.涉及多個(gè)key的操作通常不會(huì)被支持。例如你不能對(duì)兩個(gè)集合求交集,因?yàn)樗麄兛赡鼙淮鎯?chǔ)到不同的Redis實(shí)例(實(shí)際上這種情況也有辦法,但是不能直接使用交集指令)。
2.同時(shí)操作多個(gè)key,則不能使用Redis事務(wù)。
3.分區(qū)使用的粒度是key,不能使用一個(gè)非常長(zhǎng)的排序key存儲(chǔ)一個(gè)數(shù)據(jù)集(The partitioning granularity is the key, so it is not possible to shard a dataset with a single huge key like a very big sorted set)
4.當(dāng)使用分區(qū)的時(shí)候,數(shù)據(jù)處理會(huì)非常復(fù)雜,例如為了備份你必須從不同的Redis實(shí)例和主機(jī)同時(shí)收集RDB / AOF文件。
5.分區(qū)時(shí)動(dòng)態(tài)擴(kuò)容或縮容可能非常復(fù)雜。Redis集群在運(yùn)行時(shí)增加或者刪除Redis節(jié)點(diǎn),能做到最大程度對(duì)用戶透明地?cái)?shù)據(jù)再平衡,但其他一些客戶端分區(qū)或者代理分區(qū)方法則不支持這種特性。然而,有一種預(yù)分片的技術(shù)也可以較好的解決這個(gè)問題。