4.中間件
要求: 能熟練使用、部署、調(diào)優(yōu)、問題排查、懂原理
1.緩存中間件: Redis/Memecache
1.redis有什么缺點(diǎn)
- 緩存和數(shù)據(jù)庫雙寫一致性問題
- 緩存雪崩問題 (所有key一起失效)
- 緩存擊穿問題 (key失效一起訪問MySQL)
- 緩存穿透問題 (Redis和MySQL都沒有數(shù)據(jù))
- 緩存的并發(fā)競爭問題 (兩個(gè)鏈接同時(shí)對(duì)一個(gè)key的值操作)
2.如何應(yīng)對(duì)Redis 擊穿、穿透、雪崩 如何應(yīng)對(duì)并發(fā)競爭key
3.單線程的Redis為什么這么快?
多路復(fù)用
Resp協(xié)議
單線程
內(nèi)存操作
為什么快:
4.Redis數(shù)據(jù)類型和使用場景
- String:一般做一些復(fù)雜的計(jì)數(shù)功能的緩存;
- Hash:存儲(chǔ)二維數(shù)據(jù)或?qū)ο螅?/li>
- List:可實(shí)現(xiàn)隊(duì)列,棧及有序的數(shù)據(jù)存儲(chǔ);
- Set:常用于黑名單,微信抽獎(jiǎng)等功能,應(yīng)用場景多變;
- SortedSet:做排行榜應(yīng)用,取TOPN操作;延時(shí)任務(wù);做范圍查找
5.Redis過期策略和內(nèi)存淘汰機(jī)制
惰性刪除策略
6.Redis和數(shù)據(jù)庫雙寫一致性問題
最終一致性和強(qiáng)一致性
強(qiáng)一致性:在任何時(shí)刻所有的用戶或者進(jìn)程查詢到的都是最近一次成功更新的數(shù)據(jù)。強(qiáng)一致性是程度最高一致性要求,也是最難實(shí)現(xiàn)的。關(guān)系型數(shù)據(jù)庫更新操作就是這個(gè)案例。
最終一致性:和強(qiáng)一致性相對(duì),在某一時(shí)刻用戶或者進(jìn)程查詢到的數(shù)據(jù)可能都不同,但是最終成功更新的數(shù)據(jù)都會(huì)被所有用戶或者進(jìn)程查詢到。
強(qiáng)一致性 三種更新策略
理論上給緩存設(shè)置過期時(shí)間可以保證最終一致性的解決方案,但是在這種方案下,設(shè)置過期時(shí)間過長會(huì)導(dǎo)致錯(cuò)誤數(shù)據(jù)過多,設(shè)置過期時(shí)間過短會(huì)導(dǎo)致影響并發(fā)性能問題
- 1.先更新數(shù)據(jù)庫,在更新緩存
- 2.先更新數(shù)據(jù)庫,在刪除緩存
- 3.先刪除緩存,在更新數(shù)據(jù)庫
第1種,
- 請求A更新數(shù)據(jù)庫
- 請求B更新數(shù)據(jù)庫
- 請求B更新緩存
- 請求A更新緩存
或者:
- 請求A查詢緩存發(fā)現(xiàn)沒有,從數(shù)據(jù)庫拿到舊值
- 請求B更新數(shù)據(jù)庫, 更新緩存
- 請求A把拿到的舊值,更新緩存
因?yàn)榫W(wǎng)絡(luò)等原因,或者業(yè)務(wù)代碼執(zhí)行時(shí)間原因,會(huì)導(dǎo)致臟數(shù)據(jù).
MySQL集群,讀寫分離,查詢拿值比寫入更快,是最不建議的
第2種,
- 請求A更新數(shù)據(jù)庫,刪除緩存
- 請求B拿取數(shù)據(jù)庫舊值,更新緩存
一般因?yàn)樽x寫分離,如果兩部同時(shí)發(fā)生,第二步會(huì)比第一步更快操作緩存,但是如果有意外發(fā)生,可以設(shè)置延遲刪除,但是如果刪除失敗呢, 也比較不可取
第3種,
- 請求A刪除緩存,更新數(shù)據(jù)庫
- 請求B發(fā)現(xiàn)緩存不存在,從數(shù)據(jù)庫拿取舊值,更新緩存
即使用了行鎖,請求A和請求B同時(shí)執(zhí)行,請求B先于A從庫里讀取數(shù)據(jù),也會(huì)把臟數(shù)據(jù)存到緩存,同時(shí)先刪除緩存如果數(shù)據(jù)庫更新失敗,緩存無法直接恢復(fù)的問題,不可取
延時(shí)雙刪策略
- 先刪除緩存
- 更新數(shù)據(jù)庫, 更新緩存
- 再休眠一段時(shí)間,再次刪除緩存
這么做,可以將一段時(shí)間的緩存可能存在的臟數(shù)據(jù),再次刪除
那么這個(gè)一段時(shí)間,具體該設(shè)置多久呢,即使根據(jù)業(yè)務(wù)設(shè)計(jì)幾百ms,在高并發(fā)的場景下也會(huì)具有一定的風(fēng)險(xiǎn)性,并且頻繁的刪除從寫,也會(huì)影響性能
名為《Cache-Aside pattern》的緩存更新策略中指出
- 失效:應(yīng)用程序先從cache取數(shù)據(jù),沒有得到,則從數(shù)據(jù)庫中取數(shù)據(jù),成功后,放到緩存中。
- 命中:應(yīng)用程序從cache中取數(shù)據(jù),取到后返回。
- 更新:先把數(shù)據(jù)存到數(shù)據(jù)庫中,成功后,再讓緩存失效。
目前有一個(gè)比較完善的針對(duì)第2種解決方案的完善方案

流程如下圖所示:
(1)更新數(shù)據(jù)庫數(shù)據(jù)
(2)數(shù)據(jù)庫會(huì)將操作信息寫入binlog日志當(dāng)中
(3)訂閱程序提取出所需要的數(shù)據(jù)以及key
(4)另起一段非業(yè)務(wù)代碼,獲得該信息
(5)嘗試刪除緩存操作,發(fā)現(xiàn)刪除失敗
(6)將這些信息發(fā)送至消息隊(duì)列
(7)重新從消息隊(duì)列中獲得該數(shù)據(jù),重試操作。
訂閱binlog程序在mysql中有現(xiàn)成的中間件叫canal,可以完成訂閱binlog日志的功能.
上述策略略微負(fù)責(zé),在時(shí)間充裕的時(shí)候可以實(shí)現(xiàn),那有么有特殊的場景和過度方法呢?
版本校驗(yàn)策略
如果更新的數(shù)據(jù)是數(shù)字,同時(shí)數(shù)據(jù)的一致性和在一定延時(shí)的情況下準(zhǔn)確性要求不高,可以考慮
比如點(diǎn)贊,收藏,關(guān)注,瀏覽數(shù)等等,把數(shù)量當(dāng)成迭代版本號(hào),數(shù)量越大時(shí)越是新的數(shù)據(jù),采用方案1,先更新數(shù)據(jù)庫在更新緩存,在更新緩存時(shí),先進(jìn)行比較.如果數(shù)量小,就不更新.多數(shù)情況下,避免臟數(shù)據(jù)的產(chǎn)生.
最終一致性
如果對(duì)數(shù)據(jù)一致性和準(zhǔn)確性,有嚴(yán)格的一致的要求,
比如商品秒殺、搶購場景.可以直接只使用緩存作為唯一真實(shí)來源,等活動(dòng)結(jié)束,背后批量去更新數(shù)據(jù)庫
異構(gòu)數(shù)據(jù)庫確實(shí)很難保持強(qiáng)一致性,利用補(bǔ)償機(jī)制和減少時(shí)間窗口的方法,以及設(shè)置過期時(shí)間兜底,達(dá)到最終的一致.
7.CAP原理與強(qiáng)一致性、弱一致性、最終一致性
- 一致性(Consistency)
- 可用性(Availability)
- 分區(qū)容忍性(Partition tolerance)
CAP原理指的是,這三個(gè)要素最多只能同時(shí)實(shí)現(xiàn)兩點(diǎn),不可能三者兼顧。因此在進(jìn)行分布式架構(gòu)設(shè)計(jì)時(shí),必須做出取舍。而對(duì)于分布式數(shù)據(jù)系統(tǒng),分區(qū)容忍性是基本要求,否則就失去了價(jià)值。因此設(shè)計(jì)分布式數(shù)據(jù)系統(tǒng),就是在一致性和可用性之間取一個(gè)平衡。對(duì)于大多數(shù)web應(yīng)用,其實(shí)并不需要強(qiáng)一致性,因此犧牲一致性而換取高可用性,是目前多數(shù)分布式數(shù)據(jù)庫產(chǎn)品的方向
當(dāng)然,犧牲一致性,并不是完全不管數(shù)據(jù)的一致性,否則數(shù)據(jù)是混亂的,那么系統(tǒng)可用性再高分布式再好也沒有了價(jià)值。犧牲一致性,只是不再要求關(guān)系型數(shù)據(jù)庫中的強(qiáng)一致性,而是只要系統(tǒng)能達(dá)到最終一致性即可,考慮到客戶體驗(yàn),這個(gè)最終一致的時(shí)間窗口,要盡可能的對(duì)用戶透明,也就是需要保障“用戶感知到的一致性”。通常是通過數(shù)據(jù)的多份異步復(fù)制來實(shí)現(xiàn)系統(tǒng)的高可用和數(shù)據(jù)的最終一致性的,“用戶感知到的一致性”的時(shí)間窗口則取決于數(shù)據(jù)復(fù)制到一致狀態(tài)的時(shí)間。
從客戶端角度,多進(jìn)程并發(fā)訪問時(shí),更新過的數(shù)據(jù)在不同進(jìn)程如何獲取的不同策略,決定了不同的一致性。對(duì)于關(guān)系型數(shù)據(jù)庫,要求更新過的數(shù)據(jù)能被后續(xù)的訪問都能看到,這是強(qiáng)一致性。如果能容忍后續(xù)的部分或者全部訪問不到,則是弱一致性。如果經(jīng)過一段時(shí)間后要求能訪問到更新后的數(shù)據(jù),則是最終一致性。
- 因果一致性。如果進(jìn)程A通知進(jìn)程B它已更新了一個(gè)數(shù)據(jù)項(xiàng),那么進(jìn)程B的后續(xù)訪問將返回更新后的值,且一次寫入將保證取代前一次寫入。與進(jìn)程A無因果關(guān)系的進(jìn)程C的訪問遵守一般的最終一致性規(guī)則。
- “讀己之所寫(read-your-writes)”一致性。當(dāng)進(jìn)程A自己更新一個(gè)數(shù)據(jù)項(xiàng)之后,它總是訪問到更新過的值,絕不會(huì)看到舊值。這是因果一致性模型的一個(gè)特例。
- 會(huì)話(Session)一致性。這是上一個(gè)模型的實(shí)用版本,它把訪問存儲(chǔ)系統(tǒng)的進(jìn)程放到會(huì)話的上下文中。只要會(huì)話還存在,系統(tǒng)就保證“讀己之所寫”一致性。如果由于某些失敗情形令會(huì)話終止,就要建立新的會(huì)話,而且系統(tǒng)的保證不會(huì)延續(xù)到新的會(huì)話。
- 單調(diào)(Monotonic)讀一致性。如果進(jìn)程已經(jīng)看到過數(shù)據(jù)對(duì)象的某個(gè)值,那么任何后續(xù)訪問都不會(huì)返回在那個(gè)值之前的值。
- 單調(diào)寫一致性。系統(tǒng)保證來自同一個(gè)進(jìn)程的寫操作順序執(zhí)行。要是系統(tǒng)不能保證這種程度的一致性,就非常難以編程了。
7.Redis布隆過濾器(Bloom Filter)

先查詢緩存,緩存不命中再查詢數(shù)據(jù)庫。 然后將查詢結(jié)果放在緩存中即使數(shù)據(jù)不存在,也需要?jiǎng)?chuàng)建一個(gè)緩存,用來防止穿庫。這里需要區(qū)分一下數(shù)據(jù)是否存在。 如果數(shù)據(jù)不存在,緩存時(shí)間可以設(shè)置相對(duì)較短,防止因?yàn)橹鲝耐降葐栴},導(dǎo)致問題被放大。
這個(gè)流程中存在薄弱的問題是,當(dāng)用戶量太大時(shí),我們會(huì)緩存大量數(shù)據(jù)空數(shù)據(jù),并且一旦來一波冷用戶,會(huì)造成雪崩效應(yīng)。 對(duì)于這種情況,我們產(chǎn)生第二個(gè)版本流程:redis過濾冷用戶緩存流程

引用鏈接:https://www.cnblogs.com/yscl/p/12003359.html
什么是布隆過濾器?
本質(zhì)上布隆過濾器( BloomFilter )是一種數(shù)據(jù)結(jié)構(gòu),比較巧妙的概率型數(shù)據(jù)結(jié)構(gòu)(probabilistic data structure),特點(diǎn)是高效地插入和查詢,可以用來告訴你 “某樣?xùn)|西一定不存在或者可能存在”。
相比于傳統(tǒng)的 Set、Map 等數(shù)據(jù)結(jié)構(gòu),它更高效、占用空間更少,但是缺點(diǎn)是其返回的結(jié)果是概率性的,而不是確切的。
布隆過濾器原理
布隆過濾器內(nèi)部維護(hù)一個(gè)bitArray(位數(shù)組), 開始所有數(shù)據(jù)全部置 0 。當(dāng)一個(gè)元素過來時(shí),能過多個(gè)哈希函數(shù)(hash1,hash2,hash3....)計(jì)算不同的在哈希值,并通過哈希值找到對(duì)應(yīng)的bitArray下標(biāo)處,將里面的值 0 置為 1 。 需要說明的是,布隆過濾器有一個(gè)誤判率的概念,誤判率越低,則數(shù)組越長,所占空間越大。誤判率越高則數(shù)組越小,所占的空間越小。
下面以網(wǎng)址為例來進(jìn)行說明, 例如布隆過濾器的初始情況如下圖所示:

現(xiàn)在我們需要往布隆過濾里中插入baidu這個(gè)url,經(jīng)過3個(gè)哈希函數(shù)的計(jì)算,hash值分別為1,4,7,那么我們就需要對(duì)布隆過濾器的對(duì)應(yīng)的bit位置1, 就如圖下所示:
接下來,需要繼續(xù)往布隆過濾器中添加tencent這個(gè)url,然后它計(jì)算出來的hash值分別3,4,8,繼續(xù)往對(duì)應(yīng)的bit位置1。這里就需要注意一個(gè)點(diǎn), 上面兩個(gè)url最后計(jì)算出來的hash值都有4,這個(gè)現(xiàn)象也是布隆不能確認(rèn)某個(gè)元素一定存在的原因,最后如下圖所示:

布隆過濾器的查詢也很簡單,例如我們需要查找python,只需要計(jì)算出它的hash值, 如果該值為2,4,7,那么因?yàn)閷?duì)應(yīng)bit位上的數(shù)據(jù)有一個(gè)不為1, 那么一定可以斷言python不存在,但是如果它計(jì)算的hash值是1,3,7,那么就只能判斷出python可能存在,這個(gè)例子就可以看出來, 我們沒有存入python,但是由于其他key存儲(chǔ)的時(shí)候返回的hash值正好將python計(jì)算出來的hash值對(duì)應(yīng)的bit位占用了,這樣就不能準(zhǔn)確地判斷出python是否存在。
因此, 隨著添加的值越來越多, 被占的bit位越來越多, 這時(shí)候誤判的可能性就開始變高,如果布隆過濾器所有bit位都被置為1的話,那么所有key都有可能存在, 這時(shí)候布隆過濾器也就失去了過濾的功能。至此,選擇一個(gè)合適的過濾器長度就顯得非常重要。
從上面布隆過濾器的實(shí)現(xiàn)原理可以看出,它不支持刪除, 一旦將某個(gè)key對(duì)應(yīng)的bit位置0,可能會(huì)導(dǎo)致同樣bit位的其他key的存在性判斷錯(cuò)誤。
布隆過濾器的準(zhǔn)確性
布隆過濾器的核心思想有兩點(diǎn):
- 多個(gè)hash,增大隨機(jī)性,減少hash碰撞的概率
- 擴(kuò)大數(shù)組范圍,使hash值均勻分布,進(jìn)一步減少hash碰撞的概率。
雖然布隆過濾器已經(jīng)盡可能的減小hash碰撞的概率了,但是,并不能徹底消除,因此正如上面的小例子所舉的小例子的結(jié)果來看, 布隆過濾器只能告訴我們某樣?xùn)|西一定不存在以及它可能存在。
關(guān)于布隆過濾器的數(shù)組大小以及相應(yīng)的hash函數(shù)個(gè)數(shù)的選擇, 可以參考網(wǎng)上的其他博客或者是這個(gè)維基百科上對(duì)應(yīng)詞條上的結(jié)果:Probability of false positives .

上圖的縱坐標(biāo)p是誤判率,橫坐標(biāo)n表示插入的元素個(gè)數(shù),m表示布隆過濾器的bit長度,當(dāng)然上圖結(jié)果成立都假設(shè)hash函數(shù)的個(gè)數(shù)k滿足條件k = (m/n)ln2(忽略k是整數(shù))。
從上面的結(jié)果來看, 選擇合適后誤判率還是比較低的。
布隆過濾器的應(yīng)用
- 網(wǎng)頁爬蟲對(duì)URL的去重,避免爬取相同的URL地址
- 反垃圾郵件,從數(shù)十億個(gè)垃圾郵件列表中判斷某郵箱是否垃圾郵箱(同理,垃圾短信)
- 緩存穿透,將所有可能存在的數(shù)據(jù)緩存放到布隆過濾器中,當(dāng)黑客訪問不存在的緩存時(shí)迅速返回避免緩存及DB掛掉。
- 黑名單過濾,
布隆過濾器的使用
- Python版,不依賴Redis
安裝: pip3 install pybloom_live
from pybloom_live import ScalableBloomFilter
# mode=ScalableBloomFilter.SMALL_SET_GROWTH
sbf = ScalableBloomFilter(initial_capacity=100, error_rate=0.001, mode=ScalableBloomFilter.LARGE_SET_GROWTH)
url = "皮卡丘1"
url2 = "皮卡丘2"
sbf.add(url)
print(url in sbf) # True
print(url2 in sbf) # False
如果需要去重的數(shù)據(jù)量不是特別大,推薦用這種方式, BloomFilter(定容)和ScalableBloomFilter(可伸縮的),ScalableBloomFilter超過參數(shù)默認(rèn)值,也可以使用,推薦mode用默認(rèn)
- Redis中使用布隆過濾器
詳細(xì)的文檔可以參考官方文檔。
這個(gè)模塊不僅僅實(shí)現(xiàn)了布隆過濾器,還實(shí)現(xiàn)了 CuckooFilter(布谷鳥過濾器),以及 TopK功能。CuckooFilter是在 BloomFilter的基礎(chǔ)上主要解決了BloomFilter不能刪除的缺點(diǎn)。 下面只說明了布隆過濾器
安裝
傳統(tǒng)的redis服務(wù)器安裝 RedisBloom 插件,詳情可以參考centos中安裝redis插件bloom-filter
127.0.0.1:6379> bf.reserve black_male 0.001 1000
OK
bf.reserve {key} {error_rate} {size} # 創(chuàng)建一個(gè)空的名為
key的布隆過濾器,并設(shè)置一個(gè)期望的錯(cuò)誤率和初始大小。{error_rate}過濾器的錯(cuò)誤率在0-1之間bf.add {key} {item} # 往過濾器中添加元素。如果key不存在,過濾器會(huì)自動(dòng)創(chuàng)建
bf.madd {key} {item} [item…] # 批量添加
bf.exists {key} {item} # 判斷過濾器中是否存在該元素,不存在返回0,存在返回1
bf.mexists {key} {item} [item…] # 批量判斷存在
