今日份雞湯:成年人的世界哪有容易可言,但我們依舊要做打不死的小強猪叙!
1娇斩、Redis用了哪些數(shù)據(jù)結(jié)構(gòu)、場景穴翩?
(1)String:單值緩存犬第、對象緩存(對象設(shè)置成一個json串)、分布式鎖芒帕、計數(shù)器(文章閱讀數(shù)量)歉嗓、分布式系統(tǒng)全局序列號(分庫分表使用redis實現(xiàn)自增id:業(yè)務(wù)量大的時候,影響性能背蟆,因為他主要是用做緩存鉴分,可以批量獲取,一次拿100個带膀,incrby orderid 100)志珍。
(2)hash:對象緩存(一個表中有很多字段,但是我經(jīng)常就只用某一個字段垛叨,比如余額伦糯,這種場景適合使用hash,不適合使用string)、電商購物車(用戶id為key舔株,商品id為filed莺琳,商品數(shù)量為value)。
(3)list:常用數(shù)據(jù)結(jié)構(gòu)(棧 = LPUSH + LPOP载慈、隊列 = LPUSH + RPOP惭等、阻塞隊列 = LPUSH + BRPOP,屬于分布式數(shù)據(jù)結(jié)構(gòu),所以沒用java的數(shù)據(jù)結(jié)構(gòu))办铡、微博和微信公眾號消息流(用數(shù)據(jù)庫實現(xiàn)的話辞做,性能低,因為是時間倒排)寡具。
(4)set:微信抽獎小程序秤茅、微信微博點贊收藏標簽、集合操作實現(xiàn)微博微信關(guān)注模型(sinter set1 set2童叠,找出共同關(guān)注的人)框喳。
(5)zset:排行榜(熱搜新聞)。
數(shù)據(jù)結(jié)構(gòu)底層:跳表(skiplist:原始跳表是按照分值從小到大一次排列的一個鏈表厦坛。就是把我們的有序鏈表改造為支持“折半查找”算法的一個結(jié)構(gòu)五垮,每兩個元素往上建立一個索引層,這個索引層也會建立一個指針關(guān)系杜秸,以空間換時間放仗,耗費空間大約一倍,效率也提升一倍撬碟,元素越多用跳表越好诞挨,默認超過128個元素自動轉(zhuǎn)為skiplist)、壓縮列表(ziplist:原本是一個鏈表呢蛤,他把指針去掉了惶傻,類似于數(shù)組,區(qū)別在于記錄了相對的偏移位置顾稀,元素少的時候可以用壓縮列表)达罗。
2坝撑、雪崩静秆、穿透、擊穿
(1)雪崩
原因:(1)redis服務(wù)掛掉巡李。(2)redis中的key大面積失效抚笔,可能是key過期,也可能是服務(wù)宕機侨拦,導致請求直接打到數(shù)據(jù)庫殊橙。(redis緩存key同一時間大面積失效,導致請求打到數(shù)據(jù)庫,造成數(shù)據(jù)庫掛掉)膨蛮。
解決:
1》設(shè)置緩存失效時間叠纹,讓他不要在同一時間失效,設(shè)置緩存時敞葛,隨機初始化他的失效時間誉察,讓緩存不在同一時間全部失效。
2》redis集群部署惹谐,熱點key放到不同節(jié)點持偏,讓熱點緩存平均分布在redis節(jié)點上。
3》不設(shè)置緩存失效時間氨肌,讓緩存永遠不失效鸿秆。
4》跑定時任務(wù),定時刷緩存怎囚,比如說設(shè)置三小時失效卿叽,失效前,把緩存刷進去恳守。然后再設(shè)置三個小時附帽,不斷用定時任務(wù)去跑,這樣就不會失效井誉。
(2)穿透
原因:redis緩存和數(shù)據(jù)庫中都沒有這樣的數(shù)據(jù)蕉扮,一般出現(xiàn)這種情況都是惡意用戶。
解決:
1》請求穿過redis直接到數(shù)據(jù)庫颗圣,數(shù)據(jù)庫無論查出什么結(jié)果喳钟,是空還是有值,都會緩存到redis里面去在岂,這樣下次同一參數(shù)發(fā)請求就不會穿透redis奔则。但請求可以換不同的參數(shù)。
2》把這個ip拉黑蔽午,但他可能換不同的ip易茬。
3》對參數(shù)合法性進行校驗,參數(shù)不合法直接return掉及老。
4》使用布隆過濾器抽莱。bloom算法類似一個hash set,用來判斷某個元素(key)是否在某個集合中骄恶。就是他能保證食铐,存在的數(shù)據(jù)一定會在集合中,但是不存在的數(shù)據(jù)有可能存在僧鲁,存在誤判率虐呻,但是很低
(3)擊穿
原因:大量請求訪問某個熱點key象泵,當這個熱點的key突然失效,請求將打到數(shù)據(jù)庫斟叼。
解決:
1》key不設(shè)置過期時間偶惠。
2》使用分布式鎖。當熱點key失效的時候朗涩,請求打到數(shù)據(jù)庫洲鸠,將請求數(shù)據(jù)庫這一步給他加上鎖,這個時候就只有這一個線程能搶到這個鎖馋缅,所以也就有一個線程能操作這個數(shù)據(jù)庫扒腕,這個時候?qū)?shù)據(jù)庫的壓力就比較小,當他查詢到這個數(shù)據(jù)后萤悴,再把這個數(shù)據(jù)重新寫到redis里面去瘾腰,其他沒有搶到鎖的線程,讓他先睡幾毫秒覆履,然后再重新去redis里面去查詢這個數(shù)據(jù)蹋盆。
3、緩存一致性怎么保證硝全?
(1)延遲雙刪:沒有辦法預(yù)估等待時間栖雾,可能還會出現(xiàn)數(shù)據(jù)不一致問題。
延時雙刪的方案的思路是伟众,為了避免更新數(shù)據(jù)庫的時候析藕,其他線程從緩存中讀取不到數(shù)據(jù),就在更新完數(shù)據(jù)庫之后凳厢,再sleep一段時間账胧,然后再次刪除緩存。
sleep的時間要對業(yè)務(wù)讀寫緩存的時間做出評估先紫,sleep時間大于讀寫緩存的時間即可治泥。
流程如下:
線程1刪除緩存,然后去更新數(shù)據(jù)庫
線程2來讀緩存遮精,發(fā)現(xiàn)緩存已經(jīng)被刪除居夹,所以直接從數(shù)據(jù)庫中讀取,這時候由于線程1還沒有更新完成本冲,所以讀到的是舊值准脂,然后把舊值寫入緩存
線程1,根據(jù)估算的時間眼俊,sleep意狠,由于sleep的時間大于線程2讀數(shù)據(jù)+寫緩存的時間,所以緩存被再次刪除
如果還有其他線程來讀取緩存的話疮胖,就會再次從數(shù)據(jù)庫中讀取到最新值
(2)內(nèi)存隊列:性能低环戈,相當于串行執(zhí)行。
這是網(wǎng)上很多文章里都有寫過的方案澎灸。但是這個方案的缺陷會更明顯一點院塞。
先更新數(shù)據(jù)庫,成功后往消息隊列發(fā)消息性昭,消費到消息后再刪除緩存拦止,借助消息隊列的重試機制來實現(xiàn),達到最終一致性的效果糜颠。
這個解決方案其實問題更多汹族。
引入消息中間件之后,問題更復雜了其兴,怎么保證消息不丟失更麻煩
就算更新數(shù)據(jù)庫和刪除緩存都沒有發(fā)生問題顶瞒,消息的延遲也會帶來短暫的不一致性,不過這個延遲相對來說還是可以接受的
(3)進階版消息隊列:
為了解決緩存一致性的問題單獨引入一個消息隊列元旬,太復雜了榴徐。
其實,一般大公司本身都會有監(jiān)聽binlog消息的消息隊列存在匀归,主要是為了做一些核對的工作坑资。
這樣,我們可以借助監(jiān)聽binlog的消息隊列來做刪除緩存的操作穆端。這樣做的好處是袱贮,不用你自己引入,侵入到你的業(yè)務(wù)代碼中体啰,中間件幫你做了解耦字柠,同時,中間件的這個東西本身就保證了高可用狡赐。
當然窑业,這樣消息延遲的問題依然存在,但是相比單純引入消息隊列的做法更好一點枕屉。
而且常柄,如果并發(fā)不是特別高的話,這種做法的實時性和一致性都還算可以接受的搀擂。
(4)分布式鎖西潘,但是分布式鎖效率比較低。
(5)讀寫鎖哨颂,適合于讀多寫少的場景喷市。
(6)設(shè)置緩存過期時間。
每次放入緩存的時候威恼,設(shè)置一個過期時間品姓,比如5分鐘寝并,以后的操作只修改數(shù)據(jù)庫,不操作緩存腹备,等待緩存超時后從數(shù)據(jù)庫重新讀取衬潦。
如果對于一致性要求不是很高的情況,可以采用這種方案植酥。
這個方案還會有另外一個問題镀岛,就是如果數(shù)據(jù)更新的特別頻繁,不一致性的問題就很大了友驮。
在實際生產(chǎn)中漂羊,我們有一些活動的緩存數(shù)據(jù)是使用這種方式處理的。
因為活動并不頻繁發(fā)生改變卸留,而且對于活動來說走越,短暫的不一致性并不會有什么大的問題。
(7)TiDB艾猜。
4买喧、Redis分布式鎖的實現(xiàn)及要注意的問題
synchronized單機版是ok的,但是分布式項目中就不可以了匆赃,這時候就考慮到取消單機鎖淤毛,使用redis的分布式鎖setnx,使用分布式鎖的情況算柳,需要注意鎖是否被釋放低淡,比如出現(xiàn)異常情況,所以要在代碼層面finally中釋放鎖瞬项。除了這種情況蔗蹋,比如說宕機了,根本沒有走到finally塊里面囱淋,這樣也沒有辦法保證釋放鎖猪杭,所以在緩存這個key的時候,需要設(shè)置過期時間妥衣。這個時候也要注意皂吮,必須要setnx和過期時間設(shè)置的代碼寫在同一行,來保證原子操作税手。除此以外蜂筹,還要規(guī)定只能自己刪除自己的鎖,你不能把別人的鎖刪除了(比如說芦倒,我加鎖超時時間為10s艺挪,然后我線程1過來執(zhí)行了10s還沒有執(zhí)行完,然后鎖釋放了兵扬,這時候線程二過來麻裳,正好拿到了線程1釋放的鎖口蝠,然后線程1、線程二都執(zhí)行5s掂器,線程1現(xiàn)在執(zhí)行結(jié)束了亚皂,然后他開始釋放鎖俱箱,這時候国瓮,他釋放的就是線程2所加的鎖。)另外狞谱,redis集群環(huán)境下乃摹,我們即便是上面的所有操作都保證沒問題了,也不能保證加鎖沒問題跟衅,主節(jié)點加鎖后掛了孵睬,從節(jié)點沒有拿到鎖數(shù)據(jù),所以我們就使用redlock的redisson來實現(xiàn)伶跷,使用redisson在unlock的時候掰读,加上判斷,redisson.islocked() && redisson.isHeldByCurrentThread(),判斷當前是否是加鎖狀態(tài)叭莫,以及是否是當前線程加的鎖蹈集。
5、Redis單線程為什么性能還很高
(1)內(nèi)存級別操作:數(shù)據(jù)讀寫在內(nèi)存中雇初,去除了磁盤IO的時間拢肆,異步持久化到磁盤。
(2)基于非阻塞的io多路復用機制:加一個監(jiān)視的效果靖诗,請求準備完畢就直接讓redis對這個請求進行處理郭怪。epoll:監(jiān)視請求的時候,給每個請求都設(shè)置一個標識符刊橘,標識請求是否準備完畢鄙才。
(3)work線程是單線程,避免了線程上下文切換促绵。
(4)豐富的數(shù)據(jù)結(jié)構(gòu):底層數(shù)據(jù)結(jié)構(gòu)攒庵。hash、壓縮表绞愚、跳表叙甸。
6、Redis淘汰策略
(1)惰性過期:只有當訪問一個key的時候位衩,才會判斷這個key是否過期裆蒸,如果過期就刪除,比較耗內(nèi)存糖驴。
(2)定時過期:redis中沒有用到僚祷,但是也是一種常見的策略佛致,給每一個key設(shè)置一個定時器,到時間就把key刪除掉辙谜,對內(nèi)存友好俺榆,但是對cpu不友好。
(3)定期過期:每隔一段時間装哆,會掃描一定數(shù)量的key罐脊,然后判斷key是否過期,如果過期就刪除蜕琴。
redis中同時使用了惰性過期和定期過期萍桌。
7、Redis集群還是哨兵凌简?集群key分布上炎?
使用的是哨兵模式,因為主從復制模式雏搂,如果master掛掉就無法提供服務(wù)藕施,不是高可用,為了達到高可用使用了哨兵模式凸郑,主節(jié)點掛掉后裳食,會內(nèi)部投票機制從從節(jié)點中選擇出來一個新的主節(jié)點,類似心跳機制线椰,一般設(shè)置3-5秒去檢測一次胞谈。耗費性能和資源
redis集群模式是在3.0之后才有的:完成高可用高并發(fā),三主三從憨愉,無中心結(jié)構(gòu)烦绳,就是沒有master
Redis Cluster 集群引入了主從模式,一個主節(jié)點對應(yīng)一個或者多個從節(jié)點配紫,當主節(jié)點宕機的時候径密,就會啟用從節(jié)點。當其它主節(jié)點 PING一個主節(jié)點時躺孝,如果半數(shù)以上的主節(jié)點與該主節(jié)點通信超時享扔,那么認為主節(jié)點A下線,這時它的從節(jié)點會頂上去服務(wù)植袍。但是如果該主節(jié)點和它的從節(jié)點都宕機了惧眠,那么此集群就無法再提供服務(wù)了。 在對應(yīng)的所有鍵中通過 Redis 內(nèi)部實現(xiàn)算法 CRC16 對鍵進行哈希算法運算得到一個整數(shù)值后在 mod上 16384后于个,將得到的值映射對應(yīng)到編號為 0 ~ 16383的槽子中氛魁。 Cluster 還允許用戶強制將某個 key 掛在特定槽位上,通過在 key 字符串里面寫入 tag 標記,這時key 所掛在的槽位等于 tag 所在的槽位秀存。
8捶码、Redis集群之間怎么通訊?
在分布式存儲中需要提供維護節(jié)點元數(shù)據(jù)信息的機制或链,所謂元數(shù)據(jù)是指:節(jié)點負責哪些數(shù)據(jù)惫恼,是否出現(xiàn)故障等狀態(tài)信息。常見的元數(shù)據(jù)維護方式分為:集中式和P2P方式澳盐。Redis集群采用P2P的Gossip(流言)協(xié)議祈纯,Gossip協(xié)議工作原理就是節(jié)點彼此不斷通信交換信息,一段時間后所有的節(jié)點都會知道集群完整的信息洞就,這種方式類似流言傳播
集群中的每個節(jié)點都會單獨開辟一個TCP通道盆繁,用于節(jié)點之間彼此通信掀淘,通信端口號在基礎(chǔ)端口上加10000
每個節(jié)點在固定周期內(nèi)通過特定規(guī)則選擇幾個節(jié)點發(fā)送ping消息
接收到ping消息的節(jié)點用pong消息作為響應(yīng)
集群中每個節(jié)點通過一定規(guī)則挑選要通信的節(jié)點旬蟋,每個節(jié)點可能知道全部節(jié)點,也可能僅知道部分節(jié)點革娄,只要這些節(jié)點彼此可以正常通信倾贰,最終它們會達到一致的狀態(tài)。當節(jié)點出故障拦惋、新節(jié)點加入匆浙、主從角色變化、槽信息變更等事件發(fā)生時厕妖,通過不斷的ping/pong消息通信首尼,經(jīng)過一段時間后所有的節(jié)點都會知道整個集群全部節(jié)點的最新狀態(tài),從而達到集群狀態(tài)同步的目的言秸。
Gossip消息
Gossip協(xié)議的主要職責就是信息交換软能。信息交換的載體就是節(jié)點彼此發(fā)送的Gossip消息,常用的Gossip消息可分為:ping消息举畸、pong消息查排、meet消息、fail消息
meet消息:用于通知新節(jié)點加入抄沮。消息發(fā)送者通知接收者加入到當前集群跋核,meet消息通信正常完成后,接收節(jié)點會加入到集群中并進行周期性的ping叛买、pong消息交換
ping消息:集群內(nèi)交換最頻繁的消息砂代,集群內(nèi)每個節(jié)點每秒向多個其他節(jié)點發(fā)送ping消息,用于檢測節(jié)點是否在線和交換彼此狀態(tài)信息率挣。ping消息發(fā)送封裝了自身節(jié)點和部分其他節(jié)點的狀態(tài)數(shù)據(jù)
pong消息:當接收到ping刻伊、meet消息時,作為響應(yīng)消息回復給發(fā)送方確認消息正常通信。pong消息內(nèi)部封裝了自身狀態(tài)數(shù)據(jù)娃圆。節(jié)點也可以向集群內(nèi)廣播自身的pong消息來通知整個集群對自身狀態(tài)進行更新
fail消息:當節(jié)點判定集群內(nèi)另一個節(jié)點下線時玫锋,會向集群內(nèi)廣播一個fail消息,其他節(jié)點接收到fail消息之后把對應(yīng)節(jié)點更新為下線狀態(tài)
在所有的 redis 實例節(jié)點之間彼此使用 Gossip 協(xié)議進行通信讼呢,通過 PING-PONG 機制來達到互聯(lián)狀態(tài)撩鹿,在傳輸過程中使用特殊的二進制協(xié)議相互交互集群信息。