Redis:從應(yīng)用到底層,一文幫你搞定

以下文章來(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è)作用:

  1. 說(shuō)明是5種類型哪一種囊嘉。
  2. 里面有指針用來(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)化:

  1. SDS修改后大小 > 1M時(shí) 系統(tǒng)會(huì)多分配空間來(lái)進(jìn)行空間預(yù)分配琢蛤。
  2. 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].usedictht[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ō)明跳表和普遍的鏈表。

  1. 沒(méi)有跳表查詢 比如我查詢數(shù)據(jù)37榆骚,如果沒(méi)有上面的索引時(shí)候路線如下圖:
    圖片
  2. 有跳表查詢 有跳表查詢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赏壹。

圖片

  1. 用戶簽到

key = 年份:用戶id offset = (今天是一年中的第幾天) % (今年的天數(shù))

  1. 統(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í)行BGSAVEBGREWRITEAOF命令時(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)于多線程須知:

  1. Redis 6.0 版本 默認(rèn)多線程是關(guān)閉的 io-threads-do-reads no
  2. Redis 6.0 版本 開(kāi)啟多線程后 線程數(shù)也要 謹(jǐn)慎設(shè)置。
  3. 多線程可以使得性能翻倍缴守,但是多線程只是用來(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ī)則

  1. 節(jié)點(diǎn)取余:hash(key) % N
  2. 一致性哈希:一致性哈希環(huán)
  3. 虛擬槽哈希: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、推薦 guavaRateLimiter實(shí)現(xiàn)戏锹。

9冠胯、常見(jiàn)知識(shí)點(diǎn)

  1. 字符串模糊查詢時(shí)用Keys可能導(dǎo)致線程阻塞,盡量用scan指令進(jìn)行無(wú)阻塞的取出數(shù)據(jù)然后去重下即可锦针。
  2. 多個(gè)操作的情況下記得用pipeLine把所有的命令一次發(fā)過(guò)去荠察,避免頻繁的發(fā)送、接收帶來(lái)的網(wǎng)絡(luò)開(kāi)銷奈搜,提升性能悉盆。
  3. bigkeys可以掃描redis中的大key,底層是使用scan命令去遍歷所有的鍵馋吗,對(duì)每個(gè)鍵根據(jù)其類型執(zhí)行STRLEN焕盟、LLEN、SCARD宏粤、HLEN脚翘、ZCARD這些命令獲取其長(zhǎng)度或者元素個(gè)數(shù)。缺陷是線上試用并且個(gè)數(shù)多不一定空間大商架,
  4. 線上應(yīng)用記得開(kāi)啟Redis慢查詢?nèi)罩九堆咴梗舅悸犯鶰ySQL類似。
  5. 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之間是比較健康的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末村刨,一起剝皮案震驚了整個(gè)濱河市告抄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嵌牺,老刑警劉巖打洼,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異逆粹,居然都是意外死亡募疮,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門僻弹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)阿浓,“玉大人,你說(shuō)我怎么就攤上這事奢方∩Ρ猓” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵蟋字,是天一觀的道長(zhǎng)稿蹲。 經(jīng)常有香客問(wèn)我,道長(zhǎng)鹊奖,這世上最難降的妖魔是什么苛聘? 我笑而不...
    開(kāi)封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮忠聚,結(jié)果婚禮上设哗,老公的妹妹穿的比我還像新娘。我一直安慰自己两蟀,他們只是感情好网梢,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著赂毯,像睡著了一般战虏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上党涕,一...
    開(kāi)封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天烦感,我揣著相機(jī)與錄音,去河邊找鬼膛堤。 笑死手趣,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的肥荔。 我是一名探鬼主播绿渣,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼朝群,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了怯晕?” 一聲冷哼從身側(cè)響起潜圃,我...
    開(kāi)封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎舟茶,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體堵第,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吧凉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了踏志。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阀捅。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖针余,靈堂內(nèi)的尸體忽然破棺而出饲鄙,到底是詐尸還是另有隱情,我是刑警寧澤圆雁,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布忍级,位于F島的核電站,受9級(jí)特大地震影響伪朽,放射性物質(zhì)發(fā)生泄漏轴咱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一烈涮、第九天 我趴在偏房一處隱蔽的房頂上張望朴肺。 院中可真熱鬧,春花似錦坚洽、人聲如沸戈稿。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鞍盗。三九已至,卻和暖如春绘雁,著一層夾襖步出監(jiān)牢的瞬間橡疼,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工庐舟, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留欣除,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓挪略,卻偏偏與公主長(zhǎng)得像历帚,于是被迫代替她去往敵國(guó)和親滔岳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容