以下文章來(lái)源于sowhat1412 躬络,作者sowhat1412
高清思維導(dǎo)圖已同步Git:https://github.com/SoWhat1412/xmindfile
1谈竿、基本類型及底層實(shí)現(xiàn)
1.1纤壁、String
用途:
適用于簡(jiǎn)單key-value存儲(chǔ)众辨、setnx key value實(shí)現(xiàn)分布式鎖商佑、計(jì)數(shù)器(原子性)豪嚎、分布式全局唯一ID搔驼。
底層:C語(yǔ)言中String用char[]數(shù)組表示,源碼中用SDS
(simple dynamic string)封裝char[]侈询,這是是Redis存儲(chǔ)的最小單元
舌涨,一個(gè)SDS最大可以存儲(chǔ)512M信息。
struct sdshdr{
unsigned int len; // 標(biāo)記char[]的長(zhǎng)度
unsigned int free; //標(biāo)記char[]中未使用的元素個(gè)數(shù)
char buf[]; // 存放元素的坑
}
Redis對(duì)SDS再次封裝生成了RedisObject
扔字,核心有兩個(gè)作用:
- 說(shuō)明是5種類型哪一種囊嘉。
- 里面有指針用來(lái)指向 SDS。
當(dāng)你執(zhí)行set name sowhat
的時(shí)候革为,其實(shí)Redis會(huì)創(chuàng)建兩個(gè)RedisObject對(duì)象扭粱,鍵的RedisObject 和 值的RedisOjbect 其中它們type = REDIS_STRING,而SDS分別存儲(chǔ)的就是 name 跟 sowhat 字符串咯震檩。
并且Redis底層對(duì)SDS有如下優(yōu)化:
- SDS修改后大小 > 1M時(shí) 系統(tǒng)會(huì)多分配空間來(lái)進(jìn)行
空間預(yù)分配
琢蛤。- SDS是
惰性釋放空間
的,你free了空間,可是系統(tǒng)把數(shù)據(jù)記錄下來(lái)下次想用時(shí)候可直接使用虐块。不用新申請(qǐng)空間俩滥。
1.2、List
查看源碼底層 adlist.h
會(huì)發(fā)現(xiàn)底層就是個(gè) 雙端鏈表贺奠,該鏈表最大長(zhǎng)度為2^32-1霜旧。常用就這幾個(gè)組合。
lpush + lpop = stack 先進(jìn)后出的棧
lpush + rpop = queue 先進(jìn)先出的隊(duì)列
lpush + ltrim = capped collection 有限集合
lpush + brpop = message queue 消息隊(duì)列
一般可以用來(lái)做簡(jiǎn)單的消息隊(duì)列儡率,并且當(dāng)數(shù)據(jù)量小的時(shí)候可能用到獨(dú)有的壓縮列表來(lái)提升性能挂据。當(dāng)然專業(yè)點(diǎn)還是要 RabbitMQ、ActiveMQ等
1.3儿普、Hash
散列非常適用于將一些相關(guān)的數(shù)據(jù)存儲(chǔ)在一起崎逃,比如用戶的購(gòu)物車。該類型在日常用途還是挺多的眉孩。
這里需要明確一點(diǎn):Redis中只有一個(gè)K个绍,一個(gè)V。其中 K 絕對(duì)是字符串對(duì)象浪汪,而 V 可以是String巴柿、List、Hash死遭、Set广恢、ZSet任意一種。
hash的底層主要是采用字典dict的結(jié)構(gòu)呀潭,整體呈現(xiàn)層層封裝钉迷。從小到大如下:
1.3.1、dictEntry
真正的數(shù)據(jù)節(jié)點(diǎn)钠署,包括key糠聪、value 和 next 節(jié)點(diǎn)。
1.3.2谐鼎、dictht
1枷颊、數(shù)據(jù) dictEntry 類型的數(shù)組,每個(gè)數(shù)組的item可能都指向一個(gè)鏈表该面。
2夭苗、數(shù)組長(zhǎng)度 size。
3隔缀、sizemask 等于 size - 1题造。
4、當(dāng)前 dictEntry 數(shù)組中包含總共多少節(jié)點(diǎn)猾瘸。
1.3.3界赔、dict
1丢习、dictType 類型,包括一些自定義函數(shù)淮悼,這些函數(shù)使得key和value能夠存儲(chǔ)
2咐低、rehashidx 其實(shí)是一個(gè)標(biāo)志量,如果為
-1
說(shuō)明當(dāng)前沒(méi)有擴(kuò)容袜腥,如果不為 -1
則記錄擴(kuò)容位置见擦。3、dictht數(shù)組羹令,兩個(gè)Hash表鲤屡。
4、iterators 記錄了當(dāng)前字典正在進(jìn)行中的迭代器
組合后結(jié)構(gòu)就是如下:
1.3.4福侈、漸進(jìn)式擴(kuò)容
為什么 dictht ht[2]是兩個(gè)呢酒来?目的是在擴(kuò)容的同時(shí)不影響前端的CURD,慢慢的把數(shù)據(jù)從ht[0]轉(zhuǎn)移到ht[1]中肪凛,同時(shí)rehashindex
來(lái)記錄轉(zhuǎn)移的情況堰汉,當(dāng)全部轉(zhuǎn)移完成,將ht[1]改成ht[0]使用伟墙。
rehashidx = -1說(shuō)明當(dāng)前沒(méi)有擴(kuò)容翘鸭,rehashidx != -1則表示擴(kuò)容到數(shù)組中的第幾個(gè)了。
擴(kuò)容之后的數(shù)組大小為大于used2的2的n次方*的最小值远荠,跟 HashMap 類似矮固。然后挨個(gè)遍歷數(shù)組同時(shí)調(diào)整rehashidx的值失息,對(duì)每個(gè)dictEntry[i] 再挨個(gè)遍歷鏈表將數(shù)據(jù) Hash 后重新映射到 dictht[1]里面譬淳。并且 dictht[0].use 跟 dictht[1].use 是動(dòng)態(tài)變化的。
整個(gè)過(guò)程的重點(diǎn)在于rehashidx
盹兢,其為第一個(gè)數(shù)組正在移動(dòng)的下標(biāo)位置邻梆,如果當(dāng)前內(nèi)存不夠,或者操作系統(tǒng)繁忙绎秒,擴(kuò)容的過(guò)程可以隨時(shí)停止浦妄。
停止之后如果對(duì)該對(duì)象進(jìn)行操作,那是什么樣子的呢见芹?
1剂娄、如果是新增,則直接新增后第二個(gè)數(shù)組玄呛,因?yàn)槿绻略龅降谝粋€(gè)數(shù)組阅懦,以后還是要移過(guò)來(lái),沒(méi)必要浪費(fèi)時(shí)間
2徘铝、如果是刪除耳胎,更新惯吕,查詢,則先查找第一個(gè)數(shù)組怕午,如果沒(méi)找到废登,則再查詢第二個(gè)數(shù)組。
1.4郁惜、Set
如果你明白Java中HashSet是HashMap的簡(jiǎn)化版那么這個(gè)Set應(yīng)該也理解了堡距。都是一樣的套路而已。這里你可以認(rèn)為是沒(méi)有Value的Dict扳炬±粲保看源碼 t.set.c
就可以了解本質(zhì)了。
int setTypeAdd(robj *subject, robj *value) {
long long llval;
if (subject->encoding == REDIS_ENCODING_HT) {
// 看到底層調(diào)用的還是dictAdd恨樟,只不過(guò)第三個(gè)參數(shù)= NULL
if (dictAdd(subject->ptr,value,NULL) == DICT_OK) {
incrRefCount(value);
return 1;
}
....
1.5半醉、ZSet
范圍查找 的天敵就是 有序集合,看底層 redis.h
后就會(huì)發(fā)現(xiàn) Zset用的就是可以跟二叉樹(shù)媲美的跳躍表
來(lái)實(shí)現(xiàn)有序劝术。跳表就是多層鏈表的結(jié)合體缩多,跳表分為許多層(level),每一層都可以看作是數(shù)據(jù)的索引养晋,這些索引的意義就是加快跳表查找數(shù)據(jù)速度衬吆。
每一層的數(shù)據(jù)都是有序的,上一層數(shù)據(jù)是下一層數(shù)據(jù)的子集绳泉,并且第一層(level 1)包含了全部的數(shù)據(jù)逊抡;層次越高,跳躍性越大零酪,包含的數(shù)據(jù)越少冒嫡。并且隨便插入一個(gè)數(shù)據(jù)該數(shù)據(jù)是否會(huì)是跳表索引完全隨機(jī)的跟玩骰子一樣。
跳表包含一個(gè)表頭四苇,它查找數(shù)據(jù)時(shí)孝凌,是從上往下,從左往右
進(jìn)行查找≡乱福現(xiàn)在找出值為37的節(jié)點(diǎn)為例蟀架,來(lái)對(duì)比說(shuō)明跳表和普遍的鏈表。
-
沒(méi)有跳表查詢 比如我查詢數(shù)據(jù)37榆骚,如果沒(méi)有上面的索引時(shí)候路線如下圖:
-
有跳表查詢 有跳表查詢37的時(shí)候路線如下圖:
應(yīng)用場(chǎng)景:
積分排行榜片拍、時(shí)間排序新聞、延時(shí)隊(duì)列妓肢。
1.6捌省、Redis Geo
以前寫過(guò)Redis Geo核心原理解析,想看的直接跳轉(zhuǎn)即可职恳。他的核心思想就是將地球近似為球體來(lái)看待所禀,然后 GEO利用 GeoHash 將二維的經(jīng)緯度轉(zhuǎn)換成字符串方面,來(lái)實(shí)現(xiàn)位置的劃分跟指定距離的查詢。
1.7色徘、HyperLogLog
HyperLogLog :是一種概率
數(shù)據(jù)結(jié)構(gòu)恭金,它使用概率算法來(lái)統(tǒng)計(jì)集合的近似基數(shù)。而它算法的最本源則是伯努利過(guò)程 + 分桶 + 調(diào)和平均數(shù)
褂策。具體實(shí)現(xiàn)可看 HyperLogLog 講解横腿。
功能:誤差允許范圍內(nèi)做基數(shù)統(tǒng)計(jì) (基數(shù)就是指一個(gè)集合中不同值的個(gè)數(shù)) 的時(shí)候非常有用,每個(gè)HyperLogLog的鍵可以計(jì)算接近2^64不同元素的基數(shù)斤寂,而大小只需要12KB耿焊。錯(cuò)誤率大概在0.81%。所以如果用做 UV 統(tǒng)計(jì)很合適遍搞。
HyperLogLog底層 一共分了 2^14 個(gè)桶罗侯,也就是 16384 個(gè)桶。每個(gè)(registers)桶中是一個(gè) 6 bit 的數(shù)組溪猿,這里有個(gè)騷操作就是一般人可能直接用一個(gè)字節(jié)當(dāng)桶浪費(fèi)2個(gè)bit空間钩杰,但是Redis底層只用6個(gè)然后通過(guò)前后拼接實(shí)現(xiàn)對(duì)內(nèi)存用到了極致,最終就是 16384*6/8/1024 = 12KB诊县。
1.8讲弄、bitmap
BitMap 原本的含義是用一個(gè)比特位來(lái)映射某個(gè)元素的狀態(tài)。由于一個(gè)比特位只能表示 0 和 1 兩種狀態(tài)依痊,所以 BitMap 能映射的狀態(tài)有限避除,但是使用比特位的優(yōu)勢(shì)是能大量的節(jié)省內(nèi)存空間。
在 Redis 中BitMap 底層是基于字符串類型實(shí)現(xiàn)的胸嘁,可以把 Bitmaps 想象成一個(gè)以比特位為單位的數(shù)組瓶摆,數(shù)組的每個(gè)單元只能存儲(chǔ)0和1,數(shù)組的下標(biāo)在 Bitmaps 中叫做偏移量缴渊,BitMap 的 offset 值上限 2^32 - 1赏壹。
- 用戶簽到
key = 年份:用戶id offset = (今天是一年中的第幾天) % (今年的天數(shù))
- 統(tǒng)計(jì)活躍用戶
使用日期作為 key鱼炒,然后用戶 id 為 offset 設(shè)置不同offset為0 1 即可衔沼。
PS : Redis 它的通訊協(xié)議是基于TCP的應(yīng)用層協(xié)議 RESP(REdis Serialization Protocol)。
1.9昔瞧、Bloom Filter
使用布隆過(guò)濾器得到的判斷結(jié)果:不存在的一定不存在指蚁,存在的不一定存在
。
布隆過(guò)濾器 原理:
當(dāng)一個(gè)元素被加入集合時(shí)自晰,通過(guò)K個(gè)散列函數(shù)將這個(gè)元素映射成一個(gè)位數(shù)組中的K個(gè)點(diǎn)(有效降低沖突概率)凝化,把它們置為1。檢索時(shí)酬荞,我們只要看看這些點(diǎn)是不是都是1就知道集合中有沒(méi)有它了:如果這些點(diǎn)有任何一個(gè)為0搓劫,則被檢元素一定不在瞧哟;如果都是1,則被檢元素很可能在枪向。這就是布隆過(guò)濾器的基本思想勤揩。
想玩的話可以用Google的guava
包玩耍一番。
1.10 發(fā)布訂閱
redis提供了發(fā)布秘蛔、訂閱
模式的消息機(jī)制陨亡,其中消息訂閱者與發(fā)布者不直接通信,發(fā)布者向指定的頻道(channel)發(fā)布消息深员,訂閱該頻道的每個(gè)客戶端都可以接收到消息负蠕。不過(guò)比專業(yè)的MQ(RabbitMQ RocketMQ ActiveMQ Kafka)相比不值一提,這個(gè)功能就算球了倦畅。
2遮糖、持久化
因?yàn)镽edis數(shù)據(jù)在內(nèi)存,斷電既丟叠赐,因此持久化到磁盤是必須得有的止吁,Redis提供了RDB跟AOF兩種模式。
2.1燎悍、RDB
RDB 持久化機(jī)制敬惦,是對(duì) Redis 中的數(shù)據(jù)執(zhí)行周期性的持久化。更適合做冷備谈山。優(yōu)點(diǎn):
1俄删、壓縮后的二進(jìn)制文,適用于備份奏路、全量復(fù)制畴椰,用于災(zāi)難恢復(fù)加載RDB恢復(fù)數(shù)據(jù)遠(yuǎn)快于AOF方式,適合大規(guī)模的數(shù)據(jù)恢復(fù)鸽粉。
2斜脂、如果業(yè)務(wù)對(duì)數(shù)據(jù)完整性和一致性要求不高,RDB是很好的選擇触机。數(shù)據(jù)恢復(fù)比AOF快帚戳。
缺點(diǎn):
1、RDB是周期間隔性的快照文件儡首,數(shù)據(jù)的完整性和一致性不高片任,因?yàn)镽DB可能在最后一次備份時(shí)宕機(jī)了。
2蔬胯、備份時(shí)占用內(nèi)存对供,因?yàn)镽edis 在備份時(shí)會(huì)獨(dú)立fork一個(gè)子進(jìn)程,將數(shù)據(jù)寫入到一個(gè)臨時(shí)文件(此時(shí)內(nèi)存中的數(shù)據(jù)是原來(lái)的兩倍哦)氛濒,最后再將臨時(shí)文件替換之前的備份文件产场。所以要考慮到大概兩倍的數(shù)據(jù)膨脹性鹅髓。
注意手動(dòng)觸發(fā)及COW:
1、
SAVE
直接調(diào)用 rdbSave 京景,阻塞
Redis 主進(jìn)程迈勋,導(dǎo)致無(wú)法提供服務(wù)。2醋粟、BGSAVE
則 fork 出一個(gè)子進(jìn)程靡菇,子進(jìn)程負(fù)責(zé)調(diào)用 rdbSave ,在保存完成后向主進(jìn)程發(fā)送信號(hào)告知完成米愿。在BGSAVE 執(zhí)行期間仍可以繼續(xù)處理客戶端的請(qǐng)求厦凤。3、Copy On Write 機(jī)制育苟,備份的是開(kāi)始那個(gè)時(shí)刻內(nèi)存中的數(shù)據(jù)较鼓,只復(fù)制被修改內(nèi)存頁(yè)數(shù)據(jù),不是全部?jī)?nèi)存數(shù)據(jù)违柏。
4博烂、Copy On Write 時(shí)如果父子進(jìn)程大量寫操作會(huì)導(dǎo)致分頁(yè)錯(cuò)誤。
2.2漱竖、AOF
AOF 機(jī)制對(duì)每條寫入命令作為日志禽篱,以 append-only 的模式寫入一個(gè)日志文件中,因?yàn)檫@個(gè)模式是只追加的方式馍惹,所以沒(méi)有任何磁盤尋址的開(kāi)銷躺率,所以很快,有點(diǎn)像 Mysql 中的binlog万矾。AOF更適合做熱備悼吱。
優(yōu)點(diǎn):
AOF是一秒一次去通過(guò)一個(gè)后臺(tái)的線程fsync操作,數(shù)據(jù)丟失不用怕良狈。
缺點(diǎn):
1后添、對(duì)于相同數(shù)量的數(shù)據(jù)集而言,AOF文件通常要大于RDB文件薪丁。RDB 在恢復(fù)大數(shù)據(jù)集時(shí)的速度比 AOF 的恢復(fù)速度要快遇西。
2、根據(jù)同步策略的不同窥突,AOF在運(yùn)行效率上往往會(huì)慢于RDB努溃×蛩唬總之阻问,每秒同步策略的效率是比較高的。
AOF整個(gè)流程分兩步:第一步是命令的實(shí)時(shí)寫入沦疾,不同級(jí)別可能有1秒數(shù)據(jù)損失称近。命令先追加到aof_buf
然后再同步到AO磁盤第队,如果實(shí)時(shí)寫入磁盤會(huì)帶來(lái)非常高的磁盤IO,影響整體性能刨秆。
第二步是對(duì)aof文件的重寫凳谦,目的是為了減少AOF文件的大小,可以自動(dòng)觸發(fā)或者手動(dòng)觸發(fā)(BGREWRITEAOF)衡未,是Fork出子進(jìn)程操作尸执,期間Redis服務(wù)仍可用。
1缓醋、在重寫期間如失,由于主進(jìn)程依然在響應(yīng)命令,為了保證最終備份的完整性送粱;它
依然會(huì)寫入舊
的AOF中褪贵,如果重寫失敗,能夠保證數(shù)據(jù)不丟失抗俄。2脆丁、為了把重寫期間響應(yīng)的寫入信息也寫入到新的文件中,因此也會(huì)
為子進(jìn)程保留一個(gè)buf
动雹,防止新寫的file丟失數(shù)據(jù)槽卫。3、重寫是直接把
當(dāng)前內(nèi)存的數(shù)據(jù)生成對(duì)應(yīng)命令
胰蝠,并不需要讀取老的AOF文件進(jìn)行分析晒夹、命令合并。4姊氓、無(wú)論是 RDB 還是 AOF 都是先寫入一個(gè)臨時(shí)文件丐怯,然后通過(guò)
rename
完成文件的替換工作。
關(guān)于Fork的建議:
1翔横、降低fork的頻率读跷,比如可以手動(dòng)來(lái)觸發(fā)RDB生成快照、與AOF重寫禾唁;
2效览、控制Redis最大使用內(nèi)存,防止fork耗時(shí)過(guò)長(zhǎng)荡短;
3丐枉、配置牛逼點(diǎn),合理配置Linux的內(nèi)存分配策略掘托,避免因?yàn)槲锢韮?nèi)存不足導(dǎo)致fork失敗瘦锹。
4、Redis在執(zhí)行
BGSAVE
和BGREWRITEAOF
命令時(shí),哈希表的負(fù)載因子>=5弯院,而未執(zhí)行這兩個(gè)命令時(shí)>=1辱士。目的是盡量減少寫操作,避免不必要的內(nèi)存寫入操作听绳。5颂碘、哈希表的擴(kuò)展因子:哈希表已保存節(jié)點(diǎn)數(shù)量 / 哈希表大小。因子決定了是否擴(kuò)展哈希表椅挣。
2.3头岔、恢復(fù)
啟動(dòng)時(shí)會(huì)先檢查AOF(數(shù)據(jù)更完整)文件是否存在,如果不存在就嘗試加載RDB鼠证。
2.4切油、建議
既然單獨(dú)用RDB會(huì)丟失很多數(shù)據(jù)。單獨(dú)用AOF名惩,數(shù)據(jù)恢復(fù)沒(méi)RDB來(lái)的快澎胡,所以出現(xiàn)問(wèn)題了第一時(shí)間用RDB恢復(fù),然后AOF做數(shù)據(jù)補(bǔ)全才說(shuō)王道娩鹉。
3攻谁、Redis為什么那么快
3.1、 基于內(nèi)存實(shí)現(xiàn):
數(shù)據(jù)都存儲(chǔ)在內(nèi)存里弯予,相比磁盤IO操作快百倍戚宦,操作速率很快。
3.2锈嫩、高效的數(shù)據(jù)結(jié)構(gòu):
Redis底層多種數(shù)據(jù)結(jié)構(gòu)支持不同的數(shù)據(jù)類型受楼,比如HyperLogLog它連2個(gè)字節(jié)都不想浪費(fèi)。
3.3呼寸、豐富而合理的編碼:
Redis底層提供了 豐富而合理的編碼 艳汽,五種數(shù)據(jù)類型根據(jù)長(zhǎng)度及元素的個(gè)數(shù)適配不同的編碼格式。
1对雪、String:自動(dòng)存儲(chǔ)int類型河狐,非int類型用raw編碼。
2瑟捣、List:字符串長(zhǎng)度且元素個(gè)數(shù)小于一定范圍使用 ziplist 編碼馋艺,否則轉(zhuǎn)化為 linkedlist 編碼。
3迈套、Hash:hash 對(duì)象保存的鍵值對(duì)內(nèi)的鍵和值字符串長(zhǎng)度小于一定值及鍵值對(duì)捐祠。
4、Set:保存元素為整數(shù)及元素個(gè)數(shù)小于一定范圍使用 intset 編碼桑李,任意條件不滿足踱蛀,則使用 hashtable 編碼窿给。
5、Zset:保存的元素個(gè)數(shù)小于定值且成員長(zhǎng)度小于定值使用 ziplist 編碼,任意條件不滿足,則使用 skiplist 編碼锻梳。
3.4枢劝、合適的線程模型:
I/O 多路復(fù)用
模型同時(shí)監(jiān)聽(tīng)客戶端連接,多線程是需要上下文切換的圈浇,對(duì)于內(nèi)存數(shù)據(jù)庫(kù)來(lái)說(shuō)這點(diǎn)很致命寥掐。
3.5、 Redis6.0后引入多線程
提速:
要知道 讀寫網(wǎng)絡(luò)的read/write系統(tǒng)耗時(shí) >>
Redis運(yùn)行執(zhí)行耗時(shí)磷蜀,Redis的瓶頸主要在于網(wǎng)絡(luò)的 IO 消耗, 優(yōu)化主要有兩個(gè)方向:
1召耘、提高網(wǎng)絡(luò) IO 性能,典型的實(shí)現(xiàn)比如使用 DPDK 來(lái)替代內(nèi)核網(wǎng)絡(luò)棧的方式
2褐隆、使用多線程充分利用多核污它,典型的實(shí)現(xiàn)比如 Memcached。
協(xié)議棧優(yōu)化的這種方式跟 Redis 關(guān)系不大庶弃,支持多線程是一種最有效最便捷的操作方式衫贬。所以Redis支持多線程主要就是兩個(gè)原因:
1、可以充分利用服務(wù)器 CPU 資源歇攻,目前主線程只能利用一個(gè)核
2固惯、多線程任務(wù)可以分?jǐn)?Redis 同步 IO 讀寫負(fù)荷
關(guān)于多線程須知:
- Redis 6.0 版本 默認(rèn)多線程是關(guān)閉的 io-threads-do-reads no
- Redis 6.0 版本 開(kāi)啟多線程后 線程數(shù)也要 謹(jǐn)慎設(shè)置。
- 多線程可以使得性能翻倍缴守,但是多線程只是用來(lái)處理網(wǎng)絡(luò)數(shù)據(jù)的讀寫和協(xié)議解析葬毫,執(zhí)行命令仍然是單線程順序執(zhí)行。
4屡穗、常見(jiàn)問(wèn)題
4.1贴捡、緩存雪崩
雪崩定義:
Redis中大批量key在同一時(shí)間同時(shí)失效導(dǎo)致所有請(qǐng)求都打到了MySQL。而MySQL扛不住導(dǎo)致大面積崩塌村砂。
雪崩解決方案:
1栈暇、緩存數(shù)據(jù)的過(guò)期時(shí)間加上個(gè)隨機(jī)值,防止同一時(shí)間大量數(shù)據(jù)過(guò)期現(xiàn)象發(fā)生箍镜。
2源祈、如果緩存數(shù)據(jù)庫(kù)是分布式部署,將熱點(diǎn)數(shù)據(jù)均勻分布在不同搞得緩存數(shù)據(jù)庫(kù)中色迂。
3香缺、設(shè)置熱點(diǎn)數(shù)據(jù)永遠(yuǎn)不過(guò)期。
4.2歇僧、緩存穿透
穿透定義:
緩存穿透 是 指緩存和數(shù)據(jù)庫(kù)中
都沒(méi)有
的數(shù)據(jù)图张,比如ID默認(rèn)>0锋拖,黑客一直 請(qǐng)求ID= -12的數(shù)據(jù)那么就會(huì)導(dǎo)致數(shù)據(jù)庫(kù)壓力過(guò)大,嚴(yán)重會(huì)擊垮數(shù)據(jù)庫(kù)祸轮。
穿透解決方案:
1兽埃、后端接口層增加 用戶鑒權(quán)校驗(yàn),參數(shù)做校驗(yàn)等适袜。
2柄错、單個(gè)IP每秒訪問(wèn)次數(shù)超過(guò)閾值直接拉黑IP,關(guān)進(jìn)小黑屋1天苦酱,在獲取IP代理池的時(shí)候我就被拉黑過(guò)售貌。
3、從緩存取不到的數(shù)據(jù)疫萤,在數(shù)據(jù)庫(kù)中也沒(méi)有取到颂跨,這時(shí)也可以將key-value對(duì)寫為key-null 失效時(shí)間可以為15秒防止惡意攻擊。
4扯饶、用Redis提供的 Bloom Filter 特性也OK恒削。
4.3、緩存擊穿
擊穿定義:
現(xiàn)象:大并發(fā)集中對(duì)這一個(gè)熱點(diǎn)key進(jìn)行訪問(wèn)尾序,當(dāng)這個(gè)Key在失效的瞬間钓丰,持續(xù)的大并發(fā)就穿破緩存,直接請(qǐng)求數(shù)據(jù)庫(kù)蹲诀。
擊穿解決:
設(shè)置熱點(diǎn)數(shù)據(jù)永遠(yuǎn)不過(guò)期 加上互斥鎖也能搞定了
4.4斑粱、雙寫一致性
雙寫:緩存
跟數(shù)據(jù)庫(kù)
均更新數(shù)據(jù),如何保證數(shù)據(jù)一致性脯爪?
1则北、先更新數(shù)據(jù)庫(kù),再更新緩存
安全問(wèn)題:線程A更新數(shù)據(jù)庫(kù)->線程B更新數(shù)據(jù)庫(kù)->線程B更新緩存->線程A更新緩存痕慢。
導(dǎo)致臟讀
尚揣。業(yè)務(wù)場(chǎng)景:讀少寫多場(chǎng)景,頻繁更新數(shù)據(jù)庫(kù)而緩存根本沒(méi)用掖举。更何況如果緩存是疊加計(jì)算后結(jié)果更
浪費(fèi)性能
快骗。
2、先刪緩存塔次,再更新數(shù)據(jù)庫(kù)
A 請(qǐng)求寫來(lái)更新緩存方篮。
B 發(fā)現(xiàn)緩存不在去數(shù)據(jù)查詢舊值后寫入緩存。
A 將數(shù)據(jù)寫入數(shù)據(jù)庫(kù)励负,此時(shí)緩存跟數(shù)據(jù)庫(kù)不一致藕溅。
因此 FackBook 提出了 Cache Aside Pattern
失效:應(yīng)用程序先從cache取數(shù)據(jù),沒(méi)有得到继榆,則從數(shù)據(jù)庫(kù)中取數(shù)據(jù)巾表,成功后汁掠,放到緩存中。
命中:應(yīng)用程序從cache中取數(shù)據(jù)集币,取到后返回考阱。
更新:
先把數(shù)據(jù)存到數(shù)據(jù)庫(kù)中,成功后鞠苟,再讓緩存失效
乞榨。
4.5、腦裂
腦裂是指因?yàn)榫W(wǎng)絡(luò)原因偶妖,導(dǎo)致master節(jié)點(diǎn)姜凄、slave節(jié)點(diǎn) 和 sentinel集群處于不用的網(wǎng)絡(luò)分區(qū)政溃,此時(shí)因?yàn)閟entinel集群無(wú)法感知到master的存在趾访,所以將slave節(jié)點(diǎn)提升為master節(jié)點(diǎn) 此時(shí)存在兩個(gè)不同的master節(jié)點(diǎn)就像一個(gè)大腦分裂成了兩個(gè)。其實(shí)在Hadoop
董虱、Spark
集群中都會(huì)出現(xiàn)這樣的情況扼鞋,只是解決方法不同而已(用ZK配合強(qiáng)制殺死)。
集群腦裂問(wèn)題中愤诱,如果客戶端還在基于原來(lái)的master節(jié)點(diǎn)繼續(xù)寫入數(shù)據(jù)那么新的master節(jié)點(diǎn)將無(wú)法同步這些數(shù)據(jù)云头,當(dāng)網(wǎng)絡(luò)問(wèn)題解決后sentinel集群將原先的master節(jié)點(diǎn)降為slave節(jié)點(diǎn),此時(shí)再?gòu)男碌膍aster中同步數(shù)據(jù)將造成大量的數(shù)據(jù)丟失淫半。
Redis處理方案是redis的配置文件中存在兩個(gè)參數(shù)
min-replicas-to-write 3 表示連接到master的最少slave數(shù)量
min-replicas-max-lag 10 表示slave連接到master的最大延遲時(shí)間
如果連接到master的slave數(shù)量 < 第一個(gè)參數(shù) 且 ping的延遲時(shí)間 <= 第二個(gè)參數(shù)那么master就會(huì)拒絕寫請(qǐng)求溃槐,配置了這兩個(gè)參數(shù)后如果發(fā)生了集群腦裂則原先的master節(jié)點(diǎn)接收到客戶端的寫入請(qǐng)求會(huì)拒絕就可以減少數(shù)據(jù)同步之后的數(shù)據(jù)丟失。
4.6科吭、事務(wù)
MySQL 中的事務(wù)還是挺多道道的還要昏滴,而在Redis中的事務(wù)只要有如下三步:
關(guān)于事務(wù)具體結(jié)論:
1、redis事務(wù)就是一次性对人、順序性谣殊、排他性的執(zhí)行一個(gè)隊(duì)列中的一系列命令。
2牺弄、Redis事務(wù)沒(méi)有隔離級(jí)別的概念:批量操作在發(fā)送 EXEC 命令前被放入隊(duì)列緩存姻几,并不會(huì)被實(shí)際執(zhí)行,也就不存在事務(wù)內(nèi)的查詢要看到事務(wù)里的更新势告,事務(wù)外查詢不能看到蛇捌。
3、Redis不保證原子性:Redis中單條命令是原子性執(zhí)行的咱台,但事務(wù)不保證原子性络拌。
4、Redis編譯型錯(cuò)誤事務(wù)中所有代碼均不執(zhí)行吵护,指令使用錯(cuò)誤盒音。運(yùn)行時(shí)異常是錯(cuò)誤命令導(dǎo)致異常表鳍,其他命令可正常執(zhí)行。
5祥诽、watch指令類似于樂(lè)觀鎖譬圣,在事務(wù)提交時(shí),如果watch監(jiān)控的多個(gè)KEY中任何KEY的值已經(jīng)被其他客戶端更改雄坪,則使用EXEC執(zhí)行事務(wù)時(shí)厘熟,事務(wù)隊(duì)列將不會(huì)被執(zhí)行。
4.7维哈、正確開(kāi)發(fā)步驟
上線前
:Redis 高可用绳姨,主從+哨兵,Redis cluster阔挠,避免全盤崩潰飘庄。
上線時(shí)
:本地 ehcache 緩存 + Hystrix 限流 + 降級(jí),避免MySQL扛不住购撼。上線后
:Redis持久化采用 RDB + AOF 來(lái)保證斷點(diǎn)后自動(dòng)從磁盤上加載數(shù)據(jù)跪削,快速恢復(fù)緩存數(shù)據(jù)。
5迂求、分布式鎖
日常開(kāi)發(fā)中我們可以用 synchronized 碾盐、Lock 實(shí)現(xiàn)并發(fā)編程。但是Java中的鎖只能保證在同一個(gè)JVM進(jìn)程內(nèi)中執(zhí)行揩局。如果在分布式集群環(huán)境下用鎖呢毫玖?日常一般有兩種選擇方案。
5.1凌盯、 Zookeeper實(shí)現(xiàn)分布式鎖
你需要知道一點(diǎn)基本zookeeper
知識(shí):
1付枫、持久節(jié)點(diǎn):客戶端斷開(kāi)連接zk不刪除persistent類型節(jié)點(diǎn) 2、臨時(shí)節(jié)點(diǎn):客戶端斷開(kāi)連接zk刪除ephemeral類型節(jié)點(diǎn) 3十气、順序節(jié)點(diǎn):節(jié)點(diǎn)后面會(huì)自動(dòng)生成類似0000001的數(shù)字表示順序 4励背、節(jié)點(diǎn)變化的通知:客戶端注冊(cè)了監(jiān)聽(tīng)節(jié)點(diǎn)變化的時(shí)候,會(huì)調(diào)用回調(diào)方法
大致流程如下砸西,其中注意每個(gè)節(jié)點(diǎn)只
監(jiān)控它前面那個(gè)節(jié)點(diǎn)狀態(tài)叶眉,從而避免羊群效應(yīng)
。關(guān)于模板代碼百度即可芹枷。
缺點(diǎn):
頻繁的創(chuàng)建刪除節(jié)點(diǎn)衅疙,加上注冊(cè)watch事件,對(duì)于zookeeper集群的壓力比較大鸳慈,性能也比不上Redis實(shí)現(xiàn)的分布式鎖饱溢。
5.2、 Redis實(shí)現(xiàn)分布式鎖
本身原理也比較簡(jiǎn)單走芋,Redis 自身就是一個(gè)單線程處理器绩郎,具備互斥的特性潘鲫,通過(guò)setNX,exist等命令就可以完成簡(jiǎn)單的分布式鎖肋杖,處理好超時(shí)釋放鎖的邏輯即可溉仑。
SETNX
SETNX 是SET if Not eXists的簡(jiǎn)寫,日常指令是
SETNX key value
状植,如果 key 不存在則set成功返回 1浊竟,如果這個(gè)key已經(jīng)存在了返回0。
SETEX
SETEX key seconds value 表達(dá)的意思是 將值 value 關(guān)聯(lián)到 key 津畸,并將 key 的生存時(shí)間設(shè)為多少秒振定。如果 key 已經(jīng)存在,setex命令將覆寫舊值肉拓。并且 setex是一個(gè)
原子性
(atomic)操作后频。
加鎖:
一般就是用一個(gè)標(biāo)識(shí)唯一性的字符串比如UUID 配合 SETNX 實(shí)現(xiàn)加鎖。
解鎖:
這里用到了LUA腳本帝簇,LUA可以保證是原子性的徘郭,思路就是判斷一下Key和入?yún)⑹欠裣嗟瓤恳妫堑脑捑蛣h除丧肴,返回成功1,0就是失敗胧后。
缺點(diǎn):
這個(gè)鎖是無(wú)法重入的芋浮,且自己實(shí)心的話各種邊邊角角都要考慮到,所以了解個(gè)大致思路流程即可壳快,工程化還是用開(kāi)源工具包就行纸巷。
5.3、 Redisson實(shí)現(xiàn)分布式鎖
Redisson 是在Redis基礎(chǔ)上的一個(gè)服務(wù)眶痰,采用了基于NIO的Netty框架瘤旨,不僅能作為Redis底層驅(qū)動(dòng)客戶端,還能將原生的RedisHash竖伯,List存哲,Set,String七婴,Geo祟偷,HyperLogLog等數(shù)據(jù)結(jié)構(gòu)封裝為Java里大家最熟悉的映射(Map),列表(List)打厘,集(Set)修肠,通用對(duì)象桶(Object Bucket),地理空間對(duì)象桶(Geospatial Bucket)户盯,基數(shù)估計(jì)算法(HyperLogLog)等結(jié)構(gòu)嵌施。
這里我們只是用到了關(guān)于分布式鎖的幾個(gè)指令饲化,他的大致底層原理:
Redisson加鎖解鎖 大致流程圖如下:
6、Redis 過(guò)期策略和內(nèi)存淘汰策略
6.1吗伤、Redis的過(guò)期策略
Redis中 過(guò)期策略 通常有以下三種:
1滓侍、定時(shí)過(guò)期:
每個(gè)設(shè)置過(guò)期時(shí)間的key都需要?jiǎng)?chuàng)建一個(gè)定時(shí)器,到過(guò)期時(shí)間就會(huì)立即對(duì)key進(jìn)行清除牲芋。該策略可以立即清除過(guò)期的數(shù)據(jù)撩笆,對(duì)內(nèi)存很友好;但是會(huì)占用大量的CPU資源去處理過(guò)期的數(shù)據(jù)缸浦,從而影響緩存的響應(yīng)時(shí)間和吞吐量夕冲。
2、惰性過(guò)期:
只有當(dāng)訪問(wèn)一個(gè)key時(shí)裂逐,才會(huì)判斷該key是否已過(guò)期歹鱼,過(guò)期則清除。該策略可以最大化地節(jié)省CPU資源卜高,卻對(duì)內(nèi)存非常不友好弥姻。極端情況可能出現(xiàn)大量的過(guò)期key沒(méi)有再次被訪問(wèn),從而不會(huì)被清除掺涛,占用大量?jī)?nèi)存庭敦。
3、定期過(guò)期:
每隔一定的時(shí)間薪缆,會(huì)掃描一定數(shù)量的數(shù)據(jù)庫(kù)的expires字典中一定數(shù)量的key秧廉,并清除其中已過(guò)期的key。該策略是前兩者的一個(gè)折中方案拣帽。通過(guò)調(diào)整定時(shí)掃描的時(shí)間間隔和每次掃描的限定耗時(shí)疼电,可以在不同情況下使得CPU和內(nèi)存資源達(dá)到最優(yōu)的平衡效果。
expires字典會(huì)保存所有設(shè)置了過(guò)期時(shí)間的key的過(guò)期時(shí)間數(shù)據(jù)减拭,其中 key 是指向鍵空間中的某個(gè)鍵的指針蔽豺,value是該鍵的毫秒精度的UNIX時(shí)間戳表示的過(guò)期時(shí)間。鍵空間是指該Redis集群中保存的所有鍵拧粪。
Redis采用的過(guò)期策略:惰性刪除
+ 定期刪除
修陡。memcached采用的過(guò)期策略:惰性刪除
。
6.2既们、6種內(nèi)存淘汰策略
Redis的內(nèi)存淘汰策略是指在Redis的用于緩存的內(nèi)存不足時(shí)濒析,怎么處理需要新寫入且需要申請(qǐng)額外空間的數(shù)據(jù)。
1啥纸、volatile-lru:從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中挑選最近最少使用的數(shù)據(jù)淘汰
2号杏、volatile-ttl:從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中挑選將要過(guò)期的數(shù)據(jù)淘汰
3、volatile-random:從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中任意選擇數(shù)據(jù)淘汰
4、allkeys-lru:從數(shù)據(jù)集(server.db[i].dict)中挑選最近最少使用的數(shù)據(jù)淘汰
5盾致、allkeys-random:從數(shù)據(jù)集(server.db[i].dict)中任意選擇數(shù)據(jù)淘汰 6主经、no-enviction(驅(qū)逐):禁止驅(qū)逐數(shù)據(jù),不刪除的意思庭惜。
面試常問(wèn)痴肿ぃ考的也就是LRU了,大家熟悉的LinkedHashMap
中也實(shí)現(xiàn)了LRU
算法的护赊,實(shí)現(xiàn)如下:
class SelfLRUCache<K, V> extends LinkedHashMap<K, V> {
private final int CACHE_SIZE;
/**
* 傳遞進(jìn)來(lái)最多能緩存多少數(shù)據(jù)
* @param cacheSize 緩存大小
*/
public SelfLRUCache(int cacheSize) {
// true 表示讓 linkedHashMap 按照訪問(wèn)順序來(lái)進(jìn)行排序惠遏,最近訪問(wèn)的放在頭部,最老訪問(wèn)的放在尾部骏啰。
super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
CACHE_SIZE = cacheSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
// 當(dāng) map中的數(shù)據(jù)量大于指定的緩存?zhèn)€數(shù)的時(shí)候节吮,就自動(dòng)刪除最老的數(shù)據(jù)。
return size() > CACHE_SIZE;
}
}
6.2判耕、總結(jié)
Redis的內(nèi)存淘汰策略的選取并不會(huì)影響過(guò)期的key的處理透绩。內(nèi)存淘汰策略用于處理內(nèi)存不足時(shí)的需要申請(qǐng)額外空間的數(shù)據(jù),過(guò)期策略用于處理過(guò)期的緩存數(shù)據(jù)壁熄。
7帚豪、Redis 集群高可用
單機(jī)問(wèn)題有機(jī)器故障、容量瓶頸草丧、QPS瓶頸狸臣。在實(shí)際應(yīng)用中,Redis的多機(jī)部署時(shí)候會(huì)涉及到redis主從復(fù)制
方仿、Sentinel哨兵模式
固棚、Redis Cluster
。
模式 | 優(yōu)點(diǎn) | 缺點(diǎn) |
---|---|---|
單機(jī)版 | 架構(gòu)簡(jiǎn)單仙蚜,部署方便 | 機(jī)器故障、容量瓶頸厂汗、QPS瓶頸 |
主從復(fù)制 | 高可靠性委粉,讀寫分離 | 故障恢復(fù)復(fù)雜,主庫(kù)的寫跟存受單機(jī)限制 |
Sentinel 哨兵 | 集群部署簡(jiǎn)單娶桦,HA | 原理繁瑣贾节,slave存在資源浪費(fèi),不能解決讀寫分離問(wèn)題 |
Redis Cluster | 數(shù)據(jù)動(dòng)態(tài)存儲(chǔ)solt衷畦,可擴(kuò)展栗涂,高可用 | 客戶端動(dòng)態(tài)感知后端變更,批量操作支持查 |
7.1祈争、redis主從復(fù)制
該模式下 具有高可用性且讀寫分離斤程, 會(huì)采用 增量同步
跟 全量同步
兩種機(jī)制。
7.1.1菩混、全量同步
Redis全量復(fù)制一般發(fā)生在Slave初始化階段忿墅,這時(shí)Slave需要將Master上的所有數(shù)據(jù)都復(fù)制一份:
1扁藕、slave連接master,發(fā)送
psync
命令疚脐。2亿柑、master接收到
psync
命名后,開(kāi)始執(zhí)行bgsave命令生成RDB文件并使用緩沖區(qū)記錄此后執(zhí)行的所有寫命令棍弄。3望薄、master發(fā)送快照文件到slave,并在發(fā)送期間繼續(xù)記錄被執(zhí)行的寫命令呼畸。4式矫、slave收到快照文件后丟棄所有舊數(shù)據(jù),載入收到的快照役耕。
5采转、master快照發(fā)送完畢后開(kāi)始向slave發(fā)送緩沖區(qū)中的寫命令。
6瞬痘、slave完成對(duì)快照的載入故慈,開(kāi)始接收命令請(qǐng)求,并執(zhí)行來(lái)自master緩沖區(qū)的寫命令框全。
7.1.2察绷、增量同步
也叫指令同步,就是從庫(kù)重放在主庫(kù)中進(jìn)行的指令津辩。Redis會(huì)把指令存放在一個(gè)環(huán)形隊(duì)列當(dāng)中拆撼,因?yàn)閮?nèi)存容量有限,如果備機(jī)一直起不來(lái)喘沿,不可能把所有的內(nèi)存都去存指令闸度,也就是說(shuō),如果備機(jī)一直未同步蚜印,指令可能會(huì)被覆蓋掉莺禁。
Redis增量復(fù)制是指Slave初始化后開(kāi)始正常工作時(shí)master發(fā)生的寫操作同步到slave的過(guò)程。增量復(fù)制的過(guò)程主要是master每執(zhí)行一個(gè)寫命令就會(huì)向slave發(fā)送相同的寫命令窄赋。
7.1.3哟冬、Redis主從同步策略:
1、
主從剛剛連接的時(shí)候忆绰,進(jìn)行全量同步浩峡;全同步結(jié)束后,進(jìn)行增量同步
错敢。當(dāng)然翰灾,如果有需要,slave 在任何時(shí)候都可以發(fā)起全量同步。redis 策略是预侯,無(wú)論如何致开,首先會(huì)嘗試進(jìn)行增量同步,如不成功萎馅,要求從機(jī)進(jìn)行全量同步双戳。2、slave在同步master數(shù)據(jù)時(shí)候如果slave丟失連接不用怕糜芳,slave在重新連接之后丟失重補(bǔ)
飒货。3、一般通過(guò)主從來(lái)實(shí)現(xiàn)讀寫分離峭竣,但是如果master掛掉后如何保證Redis的 HA呢塘辅?引入
Sentinel
進(jìn)行master的選擇。
7.2皆撩、高可用之哨兵模式
Redis-sentinel 本身是一個(gè)獨(dú)立運(yùn)行的進(jìn)程扣墩,一般sentinel集群 節(jié)點(diǎn)數(shù)至少三個(gè)且奇數(shù)個(gè),它能監(jiān)控多個(gè)master-slave集群扛吞,sentinel節(jié)點(diǎn)發(fā)現(xiàn)master宕機(jī)后能進(jìn)行自動(dòng)切換呻惕。Sentinel可以監(jiān)視任意多個(gè)主服務(wù)器以及主服務(wù)器屬下的從服務(wù)器,并在被監(jiān)視的主服務(wù)器下線時(shí)滥比,自動(dòng)執(zhí)行故障轉(zhuǎn)移操作亚脆。這里需注意sentinel
也有single-point-of-failure
問(wèn)題。大致羅列下哨兵用途:
集群監(jiān)控:循環(huán)監(jiān)控master跟slave節(jié)點(diǎn)盲泛。
消息通知:當(dāng)它發(fā)現(xiàn)有redis實(shí)例有故障的話濒持,就會(huì)發(fā)送消息給管理員
故障轉(zhuǎn)移:這里分為主觀下線(單獨(dú)一個(gè)哨兵發(fā)現(xiàn)master故障了)∷鹿觯客觀下線(多個(gè)哨兵進(jìn)行抉擇發(fā)現(xiàn)達(dá)到quorum數(shù)時(shí)候開(kāi)始進(jìn)行切換)柑营。
配置中心:如果發(fā)生了故障轉(zhuǎn)移,它會(huì)通知將master的新地址寫在配置中心告訴客戶端玛迄。
7.3由境、Redis Cluster
RedisCluster是Redis的分布式解決方案,在3.0版本后推出的方案蓖议,有效地解決了Redis分布式的需求。
7.3.1讥蟆、分區(qū)規(guī)則
常見(jiàn)的分區(qū)規(guī)則
節(jié)點(diǎn)取余
:hash(key) % N一致性哈希
:一致性哈希環(huán)虛擬槽哈希
:CRC16[key] & 16383
RedisCluster采用了虛擬槽分區(qū)
方式勒虾,具題的實(shí)現(xiàn)細(xì)節(jié)如下:
1、采用去中心化的思想瘸彤,它使用虛擬槽solt分區(qū)覆蓋到所有節(jié)點(diǎn)上修然,取數(shù)據(jù)一樣的流程,節(jié)點(diǎn)之間使用輕量協(xié)議通信Gossip來(lái)減少帶寬占用所以性能很高,
2愕宋、自動(dòng)實(shí)現(xiàn)負(fù)載均衡與高可用玻靡,自動(dòng)實(shí)現(xiàn)failover并且支持動(dòng)態(tài)擴(kuò)展,官方已經(jīng)玩到可以1000個(gè)節(jié)點(diǎn) 實(shí)現(xiàn)的復(fù)雜度低中贝。
3囤捻、每個(gè)Master也需要配置主從,并且內(nèi)部也是采用哨兵模式邻寿,如果有半數(shù)節(jié)點(diǎn)發(fā)現(xiàn)某個(gè)異常節(jié)點(diǎn)會(huì)共同決定更改異常節(jié)點(diǎn)的狀態(tài)蝎土。
4、如果集群中的master沒(méi)有slave節(jié)點(diǎn)绣否,則master掛掉后整個(gè)集群就會(huì)進(jìn)入fail狀態(tài)誊涯,因?yàn)榧旱膕lot映射不完整。如果集群超過(guò)半數(shù)以上的master掛掉蒜撮,集群都會(huì)進(jìn)入fail狀態(tài)暴构。
5、官方推薦 集群部署至少要3臺(tái)以上的master節(jié)點(diǎn)段磨。
8取逾、Redis 限流
經(jīng)常乘坐北京西二旗地鐵或者在北京西站乘坐的時(shí)候經(jīng)常會(huì)遇到一種情況就是如果人很多,地鐵的工作人員拿個(gè)小牌前面一檔讓你等會(huì)兒再檢票薇溃,這就是實(shí)際生活應(yīng)對(duì)人流量巨大的措施菌赖。
在開(kāi)發(fā)高并發(fā)系統(tǒng)時(shí),有三把利器用來(lái)保護(hù)系統(tǒng):緩存
沐序、降級(jí)
和限流
琉用。那么何為限流呢?顧名思義策幼,限流就是限制流量邑时,就像你寬帶包了1個(gè)G的流量,用完了就沒(méi)了特姐。通過(guò)限流晶丘,我們可以很好地控制系統(tǒng)的qps,從而達(dá)到保護(hù)系統(tǒng)的目的唐含。
1浅浮、基于Redis的setnx、zset
1.2捷枯、setnx
比如我們需要在10秒內(nèi)限定20個(gè)請(qǐng)求滚秩,那么我們?cè)趕etnx的時(shí)候可以設(shè)置過(guò)期時(shí)間10,當(dāng)請(qǐng)求的setnx數(shù)量達(dá)到20時(shí)候即達(dá)到了限流效果淮捆。
缺點(diǎn):比如當(dāng)統(tǒng)計(jì)1-10秒的時(shí)候郁油,無(wú)法統(tǒng)計(jì)2-11秒之內(nèi)本股,如果需要統(tǒng)計(jì)N秒內(nèi)的M個(gè)請(qǐng)求,那么我們的Redis中需要保持N個(gè)key等等問(wèn)題桐腌。
1.3拄显、zset
其實(shí)限流涉及的最主要的就是滑動(dòng)窗口,上面也提到1-10怎么變成2-11案站。其實(shí)也就是起始值和末端值都各+1即可躬审。我們可以將請(qǐng)求打造成一個(gè)zset數(shù)組,當(dāng)每一次請(qǐng)求進(jìn)來(lái)的時(shí)候嚼吞,value保持唯一盒件,可以用UUID生成,而score可以用當(dāng)前時(shí)間戳表示舱禽,因?yàn)閟core我們可以用來(lái)計(jì)算當(dāng)前時(shí)間戳之內(nèi)有多少的請(qǐng)求數(shù)量炒刁。而zset數(shù)據(jù)結(jié)構(gòu)也提供了range方法讓我們可以很輕易的獲取到2個(gè)時(shí)間戳內(nèi)有多少請(qǐng)求,
缺點(diǎn):就是zset的數(shù)據(jù)結(jié)構(gòu)會(huì)越來(lái)越大誊稚。
2翔始、漏桶算法
漏桶算法思路:把水比作是請(qǐng)求,漏桶比作是系統(tǒng)處理能力極限里伯,水先進(jìn)入到漏桶里城瞎,漏桶里的水按一定速率流出,當(dāng)流出的速率小于流入的速率時(shí),由于漏桶容量有限,后續(xù)進(jìn)入的水直接溢出(拒絕請(qǐng)求)易核,以此實(shí)現(xiàn)限流。
3蜒灰、令牌桶算法
令牌桶算法的原理:可以理解成醫(yī)院的掛號(hào)看病,只有拿到號(hào)以后才可以進(jìn)行診病肩碟。
細(xì)節(jié)流程大致:
1强窖、所有的請(qǐng)求在處理之前都需要拿到一個(gè)可用的令牌才會(huì)被處理。
2削祈、根據(jù)限流大小翅溺,設(shè)置按照一定的速率往桶里添加令牌。
3髓抑、設(shè)置桶最大可容納值咙崎,當(dāng)桶滿時(shí)新添加的令牌就被丟棄或者拒絕。
4吨拍、請(qǐng)求達(dá)到后首先要獲取令牌桶中的令牌叙凡,拿著令牌才可以進(jìn)行其他的業(yè)務(wù)邏輯,處理完業(yè)務(wù)邏輯之后密末,將令牌直接刪除握爷。
5、令牌桶有最低限額严里,當(dāng)桶中的令牌達(dá)到最低限額的時(shí)候新啼,請(qǐng)求處理完之后將不會(huì)刪除令牌,以此保證足夠的限流刹碾。
工程化:
1燥撞、自定義注解、aop迷帜、Redis + Lua 實(shí)現(xiàn)限流物舒。
2、推薦 guava 的RateLimiter實(shí)現(xiàn)戏锹。
9冠胯、常見(jiàn)知識(shí)點(diǎn)
- 字符串模糊查詢時(shí)用
Keys
可能導(dǎo)致線程阻塞,盡量用scan
指令進(jìn)行無(wú)阻塞的取出數(shù)據(jù)然后去重下即可锦针。 - 多個(gè)操作的情況下記得用
pipeLine
把所有的命令一次發(fā)過(guò)去荠察,避免頻繁的發(fā)送、接收帶來(lái)的網(wǎng)絡(luò)開(kāi)銷奈搜,提升性能悉盆。 - bigkeys可以掃描redis中的大key,底層是使用scan命令去遍歷所有的鍵馋吗,對(duì)每個(gè)鍵根據(jù)其類型執(zhí)行STRLEN焕盟、LLEN、SCARD宏粤、HLEN脚翘、ZCARD這些命令獲取其長(zhǎng)度或者元素個(gè)數(shù)。缺陷是線上試用并且個(gè)數(shù)多不一定空間大商架,
- 線上應(yīng)用記得開(kāi)啟Redis慢查詢?nèi)罩九堆咴梗舅悸犯鶰ySQL類似。
- Redis中因?yàn)閮?nèi)存分配策略跟增刪數(shù)據(jù)是會(huì)導(dǎo)致
內(nèi)存碎片
蛇摸,你可以重啟服務(wù)也可以執(zhí)行activedefrag yes
進(jìn)行內(nèi)存重新整理來(lái)解決此問(wèn)題备图。
1、Ratio >1 表明有內(nèi)存碎片赶袄,越大表明越多嚴(yán)重揽涮。
2、Ratio < 1 表明正在使用虛擬內(nèi)存饿肺,虛擬內(nèi)存其實(shí)就是硬盤蒋困,性能比內(nèi)存低得多,這是應(yīng)該增強(qiáng)機(jī)器的內(nèi)存以提高性能敬辣。
3雪标、一般來(lái)說(shuō)零院,mem_fragmentation_ratio的數(shù)值在1 ~ 1.5之間是比較健康的。