分布式緩存
緩存好處:高性能 + 高并發(fā)
高性能(常用)
數(shù)據(jù)庫查詢耗費了800ms,其他用戶對同一個數(shù)據(jù)再次查詢 嚷堡,假設該數(shù)據(jù)在10分鐘以內(nèi)沒有變化過厘灼,并且 10 分鐘之內(nèi)有 1000 個用戶 都查詢了同一數(shù)據(jù),10 分鐘之內(nèi)栖忠,那 1000 個用戶崔挖,每個人查詢這個數(shù)據(jù)都感覺很慢 800ms
比如 :某個商品信息,在 一天之內(nèi)都不會改變庵寞,但是這個商品每次查詢一次都要耗費2s狸相,一天之內(nèi)被瀏覽 100W次
mysql 單機也就 2000qps,緩存單機輕松幾萬幾十萬qps,單機 承載并發(fā)量是 mysql 單機的幾十倍。
高并發(fā)
在中午高峰期捐川,有 100W 個用戶訪問系統(tǒng) A脓鹃,每秒有 4000 個請求去查詢數(shù)據(jù)庫,數(shù)據(jù)庫承載每秒 4000 個請求會宕機古沥,加上緩存后瘸右,可以 3000 個請求走緩存 ,1000 個請求走數(shù)據(jù)庫岩齿。
緩存是走內(nèi)存的太颤,內(nèi)存天然可以支撐4w/s的請求,數(shù)據(jù)庫(基于磁盤)一般建議并發(fā)請求不要超過 2000/s
緩存不良后果
- 緩存與數(shù)據(jù)庫雙寫不一致
- 緩存雪崩
- 緩存穿透
- 緩存并發(fā)競爭
Redis 線程模型
redis 單線程 盹沈,memcached 多線程
redis 是單線程 nio 異步線程模型
Redis 和 Memcached 區(qū)別
- Redis 支持服務器端的數(shù)據(jù)操作:Redis比Memcached來說龄章,擁有更多的數(shù)據(jù)結(jié)構(gòu)和并支持更豐富的數(shù)據(jù)操作,通常在Memcached里乞封,你需要將數(shù)據(jù)拿到客戶端來進行類似的修改再set回去做裙。這大大增加了網(wǎng)絡 IO 的次數(shù)和數(shù)據(jù)體積。在Redis中肃晚,這些復雜的操作通常和一般的GET/SET一樣高效菇用。所以,如果需要緩存能支持更復雜的結(jié)構(gòu)和操作陷揪,那么Redis會是不錯的選擇
- 集群模式:memcached 沒有原生的集群模式惋鸥,需要依靠客戶端來實現(xiàn)往集群中分片寫入數(shù)據(jù)杂穷,但是 Redis 目前是原生支持 cluster模式的
Redis 單線程模型
一個線程+一個隊列
redis 基于 reactor 模式開發(fā)了網(wǎng)絡事件處理器操软,這個處理器叫做文件事件處理器奢啥,file event handler,這個文件事件處理器是單線程的缩擂,所以redis 是單線程的模型滤港,采用 io多路復用機制同時監(jiān)聽多個 socket,根據(jù)socket上的事件來選擇對應的事件處理器來處理這個事件廊蜒。
文件事件處理器包含:多個 socket,io多路復用程序,文件事件分派器溅漾,事件處理器(命令請求處理器山叮、命令回復處理器、連接應答處理器)
文件事件處理器是單線程的添履,通過 io 多路復用機制監(jiān)聽多個 socket屁倔,實現(xiàn)高性能和線程模型簡單性
被監(jiān)聽的 socket 準備好執(zhí)行 accept,read,write,close等操作的時候,會產(chǎn)生對應的文件事件暮胧,調(diào)用之前關(guān)聯(lián)好的時間處理器處理
多個 socket并發(fā)操作锐借,產(chǎn)生不同的文件事件,i/o多路復用會監(jiān)聽多個socket往衷,將這些 socket放入一個隊列中排隊钞翔。事件分派器從隊列中取出socket給對應事件處理器。
一個socket時間處理完后席舍,事件分派器才能從隊列中拿到下一個socket布轿,給對應事件處理器來處理。
文件事件:
AE_READABLE 對應 socket變得可讀(客戶端對redis執(zhí)行 write操作)
AE_WRITABLE 對應 socket 變得可寫(客戶端對 redis執(zhí)行 read操作)
I/O 多路復用可以同時監(jiān)聽AE_REABLE和 AE_WRITABLE 来颤,如果同時達到則優(yōu)先處理 AE_REABLE 時間
文件事件處理器:
連接應答處理器 對應 客戶端要連接 redis
命令請求處理器 對應 客戶端寫數(shù)據(jù)到 redis
命令回復處理器 對應 客戶端從 redis 讀數(shù)據(jù)
流程:
- redis 初始化時驮捍,會將連接應答處理器跟 AE_READABLE事件關(guān)聯(lián)
- 客戶端對 redis 發(fā)起連接,產(chǎn)生一個 AE_READABLE 事件
- 連接應答處理器處理客戶端 AE_READABLE 事件脚曾,創(chuàng)建客戶端對應的 socket东且,同時將這個 socket的 AE_READABLE 事件和命令請求處理器關(guān)聯(lián)
- 客戶端對 redis 發(fā)起讀請求,會在 socket上產(chǎn)生一個 AE_READABLE 事件
- 綁定 AE_READABLE 事件的命令請求處理器會從 socket 中讀取請求相關(guān)數(shù)據(jù)本讥,執(zhí)行對應操作珊泳,當執(zhí)行完畢后,將 socket的 AE_WRITABLE 事件跟命令回復處理器關(guān)聯(lián)
- 當客戶端這邊準備好讀取響應時拷沸,會在 socket上產(chǎn)生一個AE_WRITABLE事件
- 綁定 AE_WRITABLE 事件的命令回復處理器將準備好的響應數(shù)據(jù)寫入 socket色查,供客戶端來讀取
- 命令回復處理器寫完后,刪掉 socket的 AE_WRITABLE 事件和命令回復處理器的綁定關(guān)系
Redis 單線程模型效率高
一秒鐘可以處理幾萬個請求
- 非阻塞 I/O 多路復用機制(不處理事件撞芍,只輪詢請求壓入隊列)
- 純內(nèi)存操作(操作只有幾微秒)
- 單線程反而 避免了多線程頻繁上下文切換的問題
Redis 數(shù)據(jù)類型
- string
普通的 set,get kv緩存 - hash
類型 map結(jié)構(gòu)秧了,比如一個對象(沒有嵌套對象)緩存到 redis里面,然后讀寫緩存的時候序无,可以直接操作hash的字段(比如把 age 改成 21验毡,其他的不變)
key=150
value = {
"id":150,
"name":"zhangsan",
"age":20
} - list
有序列表 衡创,元素可以重復
可以通過 list 存儲一些列表型數(shù)據(jù)結(jié)構(gòu),類似粉絲列表晶通,文章評論列表璃氢。
例如:微信大 V的粉絲,可以以 list 的格式放在 redis 里去緩存
key=某大 V value=[zhangsan,lisi,wangwu]
比如 lrange 可以從某個元素開始讀取多少個元素狮辽,可以基于 list 實現(xiàn)分頁查詢功能一也,基于 redis實現(xiàn)高性能分頁,類似微博下來不斷分頁東西喉脖。
可以搞個簡單的消息隊列椰苟,從 list頭懟進去(lpush),list尾巴出來 (brpop) - set
無序集合树叽,自動去重
需要對一些數(shù)據(jù)快速全局去重舆蝴,(當然也可以基于 HashSet,但是單機)
基于 set 玩差集菱皆、并集、交集的操作挨稿。比如:2 個人的粉絲列表整一個交集仇轻,看看 2 個人的共同好友是誰?
把 2 個大 V 的粉絲都放在 2 個 set中奶甘,對 2 個 set做交集(sinter) - sorted set
排序的 set篷店,去重但是可以排序,寫進去的時候給一個分數(shù)臭家,自動根據(jù)分數(shù)排序
排行榜:
- 將每個用戶以及其對應的分數(shù)寫入進去
zadd board score username - zrevrange board 0 99 可以獲取排名前 100 的用戶
- zrank board username 可以看到用戶在排行榜里的排名
例如:
zadd board 85 zhangsan
zadd board 72 wangwu
zadd board 96 lis
zadd board 62 zhaoliu
自動排序為:
96 lisi
85 zhangsan
72 wangwu
62 zhaoliu
獲取排名前 3 的用戶 : zrevrange board 0 3
96 lisi
85 zhangsan
72 wangwu
查看zhaoliu的排行 :zrank board zhaoliu 返回 4
Redis 過期策略
內(nèi)存是寶貴的疲陕,磁盤是廉價的
給key設置過期時間后,redis對這批key是定期刪除+惰性刪除
定期刪除:
redis 默認每隔 100ms隨機抽取一些設置了過期時間的 key钉赁,檢查其是否過期了蹄殃,如果過期就刪除。
注意:redis是每隔100ms隨機抽取一些 key來檢查和刪除你踩,而不是遍歷所有的設置過期時間的key(否則CPU 負載會很高诅岩,消耗在檢查過期 key 上)
惰性刪除:
獲取某個key的時候, redis 會檢查一下带膜,這個key如果設置了過期時間那么是否過期吩谦,如果過期了則刪除。
如果定期刪除漏掉了許多過期key膝藕,然后你也沒及時去查式廷,也沒走惰性刪除,如果大量過期的key堆積在內(nèi)存里芭挽,導致 redis 內(nèi)存塊耗盡滑废,則走內(nèi)存淘汰機制蝗肪。
內(nèi)存淘汰策略:
- noeviction:當內(nèi)存不足以容納新寫入數(shù)據(jù)時,新寫入操作直接報錯(沒人用)
- allkeys-lru: 當內(nèi)存不足以容納新寫入數(shù)據(jù)時策严,在鍵空間中穗慕,移除最近最少使用的key(最常用)
- allkeys-random: 當內(nèi)存不足以容納新寫入數(shù)據(jù)時,在鍵空間中妻导,隨機移除某個 key逛绵,(沒人用)
- volatile-lru:當內(nèi)存不足以容納新寫入數(shù)據(jù)時,在設置了過期時間的鍵空間中倔韭,移除最近最少使用的key(不合適)
- volatile-ttl:當內(nèi)存不足以容納新寫入數(shù)據(jù)時术浪,在設置了過期時間的鍵空間中,有更早過期時間的 key 優(yōu)先移除(不合適)
LRU 算法:
package com.mousycoder.mycode;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @version 1.0
* @author: mousycoder
* @date: 2019/10/31 17:55
*/
public class LRUCache<K,V> extends LinkedHashMap<K,V> {
private final int CACHE_SIZE;
public LRUCache( int cacheSize) {
super((int)Math.ceil(cacheSize / 0.75) + 1 ,0.75f,true);
this.CACHE_SIZE = cacheSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > CACHE_SIZE;
}
public static void main(String[] args) {
LRUCache<Integer,Integer> lruCache = new LRUCache<>(10);
for (int i = 0; i < 15; i++) {
lruCache.put(i,i);
}
Integer integer1 = lruCache.get(0);
for (Integer integer : lruCache.keySet()) {
System.out.println(integer);
}
}
}
Redis 高并發(fā)和高可用
緩存架構(gòu)(多級緩存架構(gòu)寿酌、熱點緩存)
redis 高并發(fā)瓶頸在單機胰苏,讀寫分離,一般是支撐讀高并發(fā)醇疼,寫請求少硕并,也就 一秒一兩千,大量請求讀秧荆,一秒鐘二十萬次倔毙。
主從架構(gòu)
一主多從,主負責寫乙濒,將數(shù)據(jù)同步復制到其他 slave節(jié)點陕赃,從節(jié)點負責讀,所有讀的請求全部走從節(jié)點颁股。主要是解決讀高并發(fā)么库。、
主從架構(gòu)->讀寫分離->支撐10W+讀QPS架構(gòu)
Redis Replication
master->slave 復制甘有,是異步的
核心機制:
- redis 采用異步方式復制數(shù)據(jù)到 slave 節(jié)點
- 一個 master node是可以配置多個 slave node的
- slave node也可以連接其他的 slave node
- slave node 做復制的時候诉儒,是不會 block master node的正常工作
- slave node 在做復制的時候,也不會 block對自己的查詢操作亏掀,它會用舊的數(shù)據(jù)集來提供服務允睹。但是復制完成時,需要刪除舊數(shù)據(jù)集幌氮,加載新的數(shù)據(jù)集缭受,這個時候就會暫停對外服務了。
- slave node 主要用來進行橫向擴容该互,做讀寫分離米者,擴容 slave node 可以提高讀的吞吐量
master持久化對主從架構(gòu)的意義:
如果開啟了主從架構(gòu),一定要開啟 master node的持久化,不然 master宕機重啟數(shù)據(jù)是空的蔓搞,一經(jīng)復制胰丁,slave的數(shù)據(jù)也丟了
主從復制原理:
第一次啟動或者斷開重連情況:
- 當啟動一個 slave node的時候,它會發(fā)送一個 PSYNC 命令給 master node
- master 會觸發(fā)一次 full resynchronization (如果不是第一次連接喂分,master 只會復制給 slave 部分缺少的數(shù)據(jù)锦庸,從backlog里找)
- master會啟動一個后臺線程,開始生成一份 RDB 快照( bgsave,也可以直接在內(nèi)存中創(chuàng)建)蒲祈,同時將從客戶端收到的所有寫命令緩存在內(nèi)存中甘萧。RDB 文件生成完畢之后,master會將這個RDB發(fā)送給slave梆掸,slave會先寫入本地磁盤扬卷,然后再從本地磁盤加載到內(nèi)存中。然后 master會將內(nèi)存中緩存的寫命令發(fā)送給 slave,slave也會同步這些數(shù)據(jù)(slave如果跟 master網(wǎng)絡故障酸钦,斷開連接怪得,會自動重連,master如果發(fā)現(xiàn)有多個 slave 來重新連接卑硫,僅僅只會啟動一個 RDB save 操作徒恋,用一份數(shù)據(jù)服務所有 slave node)
正常情況下:
master 來一條數(shù)據(jù),就異步給 slave
Redis高可用性
全年 99.99%的時間欢伏,都是出于可用的狀態(tài)入挣,那么就可以稱為高可用性
redis 高可用架構(gòu)叫故障轉(zhuǎn)移,failover颜懊,也可以叫做主備切換财岔,切換的時間不可用风皿,但是整體高可用河爹。
sentinal node(哨兵)
Sentinal
作用:
- 集群監(jiān)控,負責監(jiān)控 redis master 和 slave進程是否正常
- 消息通知桐款,如果某個 redis 實例有故障咸这,那么哨兵負責發(fā)送消息作為報警通知給管理員
- 故障轉(zhuǎn)移,如果 master 掛掉魔眨,會自動轉(zhuǎn)移到 slave
- 配置中心媳维,如果故障轉(zhuǎn)移了,通知 client 客戶端新的 master地址
兩節(jié)點哨兵集群
quorum = 1 (代表哨兵最低個數(shù)可以嘗試故障轉(zhuǎn)移遏暴,選舉執(zhí)行的哨兵)
master 宕機侄刽,只有 S2 存活,因為 quorum =1 可以嘗試故障轉(zhuǎn)移朋凉,但是沒達到 majority =2 (最低允許執(zhí)行故障轉(zhuǎn)移的哨兵存活數(shù))的標準州丹,無法執(zhí)行故障轉(zhuǎn)移
三節(jié)點哨兵集群(經(jīng)典)
如果 M1 宕機了,S2,S3 認為 master宕機,選舉一個執(zhí)行故障轉(zhuǎn)移墓毒,因為 3 個哨兵的 majority = 2吓揪,所以可以執(zhí)行故障轉(zhuǎn)移
Redis 主從 + 哨兵
丟數(shù)據(jù):
-
master內(nèi)存中數(shù)據(jù)異步同步到 slave master 就掛掉了,丟掉了 master 內(nèi)存中的數(shù)據(jù)
- 腦裂,某個 master 所在機器突然脫離了正常的網(wǎng)絡所计,其他 slave機器不能連接柠辞,但是實際上 master還在運行,哨兵認為 master 宕機主胧,選舉 slave為master叭首,此時集群里有 2 個 master, client還沒來得及切換到新的master,還繼續(xù)寫在舊的 master上讥裤,數(shù)據(jù)丟了放棒,此時舊的 master再次恢復,被被作為一個 slave 掛到新的 master 上己英,自己的數(shù)據(jù)被清空 (腦裂间螟,大腦一分為 2,同時指揮同一個人)
解決方案:
- min-slaves-max-lag 10 (至少一個 slave同步的延遲不能超過 10s) 減少異步復制的數(shù)據(jù)丟失损肛,發(fā)現(xiàn)slave復制數(shù)據(jù)和 ack延時過長厢破,拒絕寫入,減少同步數(shù)據(jù)損失治拿。讓client做降級寫到本地磁盤里和限流摩泪,或者先暫存到消息隊列,然后重新發(fā)回 master
- min-slaves-to-write 1 減少腦裂帶來的數(shù)據(jù)丟失劫谅,最多損失 10 s數(shù)據(jù)见坑,假設master 不能繼續(xù)給 slave發(fā)送數(shù)據(jù),并且 slave 10s沒給自己的 ack消息捏检,直接拒絕客戶端寫請求荞驴,同時 client做降寫到本地磁盤、限流贯城,或者先暫存到消息隊列熊楼,然后重新發(fā)回 master
哨兵
sdown 主觀宕機,哨兵覺得一個 master 宕機(ping 超過了 is-master-down-after-milliseconds毫秒數(shù))
odown 客觀宕機能犯,quorum數(shù)量的哨兵都覺得 master宕機
哨兵互相感知通過 redis的 pub/sub系統(tǒng)鲫骗,每隔 2 秒往同一個 channel里發(fā)消息(自己的 host,ip,runid),其他哨兵可以消費這個消息
以及同步交換master的監(jiān)控信息踩晶。
哨兵確保其他slave修改master信息為新選舉的master
當一個 master被認為 odown && marjority哨兵都同意执泰,那么某個哨兵會執(zhí)行主備切換,選舉一個slave成為master(考慮 1. 跟master斷開連接的時長 2. slave 優(yōu)先級 3.復制 offset 4. runid)
選舉算法:
- 如果slave跟master斷開連接已經(jīng)超過 down-after-milliseconds * 10 + master宕機時間渡蜻,則放棄
- 按 slave 優(yōu)先級排序 术吝,slave-priority 越小越靠前
- replica offset ,哪個slave復制越多的數(shù)據(jù),越靠前
- runid 越小顿苇,越靠前
quorum 數(shù)量哨兵認為odown->選舉一個哨兵切換->獲得 majority哨兵的授權(quán)(quorum < majority 需要 majority個哨兵授權(quán)峭咒,quorum >= majority 需要 quorum 哨兵授權(quán))
第一個選舉出來的哨兵切換失敗了,其他哨兵等待 failover-time之后纪岁,重新拿confiuration epoch做為新的version 切換凑队,保證拿到最新配置,用于 configuration傳播(通過 pu/sub消息機制幔翰,其他哨兵對比 version 新舊更新 master配置)
Redis 優(yōu)化方案
高并發(fā):主從架構(gòu)
高容量:Redis集群漩氨,支持每秒幾十萬的讀寫并發(fā)
高可用:主從+哨兵
Redis 持久化
持久化的意義在于故障恢復數(shù)據(jù)備份(到其他服務器)+故障恢復(遇到災難,機房斷電遗增,電纜被切)
- RDB 對 Redis 中的數(shù)據(jù)執(zhí)行周期性的持久化叫惊。
- AOF 機制,每條寫命令作為日志做修,以 append-only模式寫入一個日志文件總霍狰,在 redis重啟的時候,可以通過回放AOF日志中的寫入指令來重新構(gòu)建整個數(shù)據(jù)集
AOF 只有一個饰及,Redis 中的數(shù)據(jù)是有一定限量的蔗坯,內(nèi)存大小是一定的,AOF 是存放寫命令的,當大到一定的時候燎含,AOF 做 rewrite 操作宾濒,就會基于當時 redis 內(nèi)存中的數(shù)據(jù),來重新構(gòu)造一個更小的 AOF 文件屏箍,然后將舊的膨脹很大的文件給刪掉绘梦,AOF 文件一直會被限制在和Redis內(nèi)存中一樣的數(shù)據(jù)。AOF同步間隔比 RDB 小赴魁,數(shù)據(jù)更完整
RDB
優(yōu)點:
- RDB 會生成多個數(shù)據(jù)文件卸奉,每個數(shù)據(jù)文件都代表了某一個時刻中 redis 的數(shù)據(jù),這種多個數(shù)據(jù)文件的方式尚粘,非常適合做冷備择卦,可以將這種完整的數(shù)據(jù)文件發(fā)送到一些遠程的安全存儲上去敲长,RDB 做冷備郎嫁,生成多個文件,每個文件都代表某一個時刻的完整的數(shù)據(jù)快照祈噪,AOF 也可以做冷備泽铛,只有一個文件,每隔一定時間去 copy一份這個文件出來辑鲤。 RDB 做冷備盔腔,由Redis控制固定時長去生成快照文件,比較方便。AOF弛随,需要自己寫腳本定時控制瓢喉。
- RDB 對 redis對外提供的讀寫服務,影響非常小舀透,可以讓 redis 保持高性能栓票,因為 redis 主進程只需要 fork一個子進程,讓子進程執(zhí)行磁盤 IO 操作來進行 RDB 持久化
- 相對于 AOF 持久化機制來說愕够,直接基于 RDB 數(shù)據(jù)文件來重啟和恢復 redis 進程走贪,更加快速
缺點: - 如果想要在 redis故障時,盡可能少丟數(shù)據(jù)惑芭,那么 RDB 沒有 AOF 好坠狡,一般 RDB 數(shù)據(jù)快照,都是間隔 5 分鐘遂跟,或者更長的時候生成一次逃沿,這個時候就得接受一旦 redis 進程宕機,那么會丟失最近 5 分鐘數(shù)據(jù)
- RDB 每次在 fork子進程來執(zhí)行 RDB 快早數(shù)據(jù)文件生成的時候幻锁,如果數(shù)據(jù)文件特別大感挥,可能會導致對客戶端提供的服務暫停數(shù)毫秒,甚至數(shù)秒(RDB 生成間隔不要太長)
AOF 存放的指令日志越败,數(shù)據(jù)恢復的時候触幼,需要回放執(zhí)行所有指令日志,RDB 就是一份數(shù)據(jù)文件究飞,直接加載到內(nèi)存中置谦。
AOF
優(yōu)點:
- 更好保護數(shù)據(jù)不丟失,后臺線程 fsync 操作亿傅,最多丟失一秒鐘數(shù)據(jù)媒峡,保證 os cache中的數(shù)據(jù)寫入磁盤中
- AOF 用 append-only 模式,沒有磁盤尋址開銷葵擎,寫入性能非常高谅阿,文件不容易損壞。
- AOF 日志過大的時候酬滤,后臺 rewrite log時候签餐,老的日志文件照常寫入,新的merge后的日志文件 ready的時候盯串,再交換新老日志文件
- 適合災難性恢復氯檐,某人不小心 flushall清空所有數(shù)據(jù),只要后臺 rewrite還沒發(fā)生体捏,那么可以立刻拷貝 AOF 文件冠摄,將最后一條 flushall命令給刪了糯崎,然后再將該 AOF 文件放回去,可以通過恢復機制河泳,自動恢復所有數(shù)據(jù)
缺點:
- AOF 日志文件比 RDB 數(shù)據(jù)快照文件大
- 降低 Redis的寫 QPS
- AOF 復雜沃呢,Bug多
- 數(shù)據(jù)恢復比較慢
最佳方案
AOF 來保證數(shù)據(jù)不丟失,RDB 做不同時間的冷備
Redis Cluster
支持 N 個 Redis master node,每個 master node掛載多個 slave node
多master + 讀寫分離 + 高可用
數(shù)據(jù)量很少拆挥,高并發(fā) -> replication + sentinal 集群
海量數(shù)據(jù) + 高并發(fā) + 高可用 -> redis cluster
分布式算法
hash算法->一致性 hash 算法-> redis cluster->hash slot算法
redis cluster :自動對數(shù)據(jù)進行分片樟插,每個 master 上放一部分數(shù)據(jù),提供內(nèi)置的高可用支持竿刁,部分master不可用時黄锤,還是可以繼續(xù)工作
cluster bus 通過 16379進行通信,故障檢測食拜,配置更新鸵熟,故障轉(zhuǎn)移授權(quán),另外一種二進制協(xié)議负甸,主要用于節(jié)點間進行高效數(shù)據(jù)交換流强,占用更少的網(wǎng)絡帶寬和處理時間
hash算法
key進行hash,然后對節(jié)點數(shù)量取模呻待,最大問題只有任意一個 master 宕機打月,大量數(shù)據(jù)就要根據(jù)新的節(jié)點數(shù)取模,會導致大量緩存失效蚕捉。
一致性 hash 算法
key進行hash奏篙,對應圓環(huán)上一個點,順時針尋找距離最近的一個點迫淹。保證任何一個 master 宕機秘通,只受 master 宕機那臺影響,其他節(jié)點不受影響敛熬,此時會瞬間去查數(shù)據(jù)庫肺稀。
緩存熱點問題:
可能集中在某個 hash區(qū)間內(nèi)的值特別多,那么會導致大量的數(shù)據(jù)都涌入同一個 master 內(nèi)应民,造成 master的熱點問題话原,性能出現(xiàn)瓶頸。
解決方法:
給每個 master 都做了均勻分布的虛擬節(jié)點诲锹,這樣每個區(qū)間內(nèi)大量數(shù)據(jù)都會均勻的分布到不同節(jié)點內(nèi)繁仁,而不是順時針全部涌入到同一個節(jié)點中。
Hash Slot算法
redis cluster 有固定 16384 個 hash slot,對每個key計算 CRC16 值辕狰,然后對16384取模改备,可以獲取 key對應的 hash slot
redis cluster 中每個 master 都會持有部分 slot ,當一臺 master 宕機時候控漠,會最快速度遷移 hash slot到可用的機器上(只會短暫的訪問不到)
走同一個 hash slot 通過 hash tag實現(xiàn)
Redis Cluster 核心
-
基礎通信
通過 gossip 協(xié)議通信(小道留言蔓倍,所有節(jié)點都持有一份元數(shù)據(jù)悬钳,不同的節(jié)點如果出現(xiàn)了元數(shù)據(jù)的變更,就不斷將元數(shù)據(jù)發(fā)送給其他節(jié)點偶翅,讓其他節(jié)點也進行元數(shù)據(jù)的變更)
集群元數(shù)據(jù):包括 hashslot->node之間的映射表關(guān)系默勾,master->slave之間的關(guān)系,故障的信息
集群元數(shù)據(jù)集中式存儲(storm)聚谁,底層基于zookeeper(分布式協(xié)調(diào)中間件)集群所有元數(shù)據(jù)的維護母剥。好處:元數(shù)據(jù)的更新和讀取,時效性好形导,一旦變更环疼,其他節(jié)點立刻可以感知。缺點:所有元數(shù)據(jù)的更新壓力全部集中在一個地方朵耕,可能會導致元數(shù)據(jù)的存儲有壓力炫隶。
goosip: 好處:元數(shù)據(jù)的更新比較分散,有一定的延時阎曹,降低了壓力伪阶。缺點:更新有延時,集群的一些操作會滯后处嫌。(reshared操作時configuration error) 10000 端口
自己提供服務的端口號+ 10000 栅贴,每隔一段時間就會往另外幾個節(jié)點發(fā)送ping消息,同時其他幾點接收到ping之后返回pong交換的信息
故障信息熏迹,節(jié)點的增加和移除檐薯, hash slot 信息gossip協(xié)議
meet:某個節(jié)點發(fā)送 meet給新加入的節(jié)點,讓新節(jié)點加入集群中注暗,然后新節(jié)點就會開始于其他節(jié)點進行通信
ping:每個節(jié)點都會頻繁給其他節(jié)點發(fā)送ping厨剪,其中包含自己的狀態(tài)還有自己維護的集群元數(shù)據(jù),互相通過ping交換元數(shù)據(jù)
ping:返回ping和meet友存,包含自己的狀態(tài)和其他信息
fail:某個節(jié)點判斷另一個節(jié)點fail之后祷膳,就發(fā)送 fail 給其他節(jié)點,通知其他節(jié)點屡立,指定的節(jié)點宕機了ping消息
ping 很頻繁直晨,且攜帶元數(shù)據(jù),會加重網(wǎng)絡負擔
每個節(jié)點每秒會執(zhí)行 10 次 ping膨俐,每次選擇 5 個最久沒有通信的其他節(jié)點
當如果發(fā)現(xiàn)某個節(jié)點通信延遲達到了 cluster_node_timeout /2 勇皇,那么立即發(fā)送 ping, 避免數(shù)據(jù)交換延遲過長焚刺,落后時間太長(2 個節(jié)點之間 10 分鐘沒有交換數(shù)據(jù)敛摘,整個集群處于嚴重的元數(shù)據(jù)不一致的情況)。
每次ping乳愉,一個是帶上自己的節(jié)點信息兄淫,還有就是帶上1/10其他節(jié)點的信息屯远,發(fā)送出去,進行數(shù)據(jù)交換
至少包含 3 個其他節(jié)點信息捕虽,最多包含總節(jié)點-2 個其他節(jié)點的信息JRedis原理
請求重定向
客戶端發(fā)送到任意一個redis實例發(fā)送命令慨丐,每個redis實例接受到命令后,都會計算key對應的hash slot泄私,如果在本地就本地處理房揭,否則返回moved給客戶端,讓客戶端進行重定向 (redis-cli -c)hash slot
通過tag指定key對應的slot,同一個 tag 下的 key晌端,都會在一個 hash slot中捅暴,比如 set key1:{100} 和 set key2:{100}smart jedis
本地維護一份hashslot->node的映射表。
JedisCluster 初始化的時候咧纠,隨機選擇一個 node伶唯,初始化 hashslot->node 映射表,同時為每個節(jié)點創(chuàng)建一個JedisPool連接池惧盹,每次基于JedisCluster執(zhí)行操作乳幸,首先JedisCluster都會在本地計算key的hashslot,然后再本地映射表中找到對應的節(jié)點钧椰,如果發(fā)現(xiàn)對應的節(jié)點返回moved粹断,那么利用該節(jié)點的元數(shù)據(jù),更新 hashslot->node映射表(重試超過 5 次報錯)hashslot遷移和ask重定向
hash slot正在遷移嫡霞,那么會返回ask 重定向給jedis,jedis 接受到ask重定向之后瓶埋,,會重定向到目標節(jié)點去執(zhí)行高可用性和主備切換原理
判斷節(jié)點宕機:
如果一個節(jié)點認為另外一個節(jié)點宕機了诊沪, 就是pfail,主觀宕機
如果多個節(jié)點都認為另外一個節(jié)點宕機了养筒,那么就是fail,客觀宕機(跟哨兵原理一樣)
在cluster-node-timeout內(nèi)端姚,某個節(jié)點一直沒有返回 pong,那么就被認為是 pfail
如果一個節(jié)點認為某個節(jié)點pfail了晕粪,那么會在gossip消息中,ping給其他節(jié)點渐裸,如果超過半數(shù)的節(jié)點認為pfail了巫湘,那么就會變成fail。
從節(jié)點過濾:
對宕機的 mster node 昏鹃,從其所有的 slave node中尚氛,選擇一個切換成 master node
檢查每個 slave node與master node斷開連接的時間,如果超過了cluster-node-timeout * cluster-slave-validity-factor洞渤,那么就沒資格切換成 master(和哨兵一致)
從節(jié)點選舉:
每個從節(jié)點阅嘶,根據(jù)自己對 master 復制數(shù)據(jù)的 offset,設置一個選舉時間载迄,offset越大(復制數(shù)據(jù)越多)的從節(jié)點讯柔,選舉時間越靠前抡蛙,所有的 master node 開始投票,給要進行選舉的 slave進行投票咖熟,如果大部分 master node(N/2 +1) 都投票給某個從節(jié)點逊拍,那么選舉通過,從節(jié)點執(zhí)行主備切換,從節(jié)點切換成主節(jié)點
總結(jié):和哨兵很像蛋欣,直接集成了 replication 和 sentinal
緩存雪崩
方案:
事前:保證 redis 集群高可用性 (主從+哨兵或 redis cluster),避免全盤崩潰
事中:本地 ehcache 緩存 + hystrix 限流(保護數(shù)據(jù)庫) & 降級熄诡,避免 MySQL被打死
事后: redis持久化临燃,快速恢復緩存數(shù)據(jù),繼續(xù)分流高并發(fā)請求
限制組件每秒就 2000 個請求通過限流組件進入數(shù)據(jù)庫蒜田,剩余的 3000 個請求走降級稿械,返回一些默認 的值,或者友情提示
好處 :
- 數(shù)據(jù)庫絕對不會死冲粤,確保了每秒只會過去 2000 個請求
- 只要數(shù)據(jù)庫不死美莫,對于用戶來說 2/5的請求可以被處理
- 系統(tǒng)沒死,用戶多點幾次可能就刷出來了
緩存穿透
4000 個請求黑客攻擊請求數(shù)據(jù)庫里沒有的數(shù)據(jù)
解決方案:把黑客查數(shù)據(jù)庫中不存在的數(shù)據(jù)的值梯捕,寫到緩存中厢呵,比如: set -999 UNKNOWN
緩存與數(shù)據(jù)庫雙寫一致性
-
cache aside pattern
讀的時候,先讀緩存傀顾,緩存沒有襟铭,就讀數(shù)據(jù)庫,然后取出數(shù)據(jù)后放入緩存短曾,同時返回響應
更新的時候寒砖,刪除緩存,更新數(shù)據(jù)庫
為什么不更新緩存:
更新緩存代價太高(更新 20 次嫉拐,只讀 1 次)哩都,lazy思想,需要的時候再計算婉徘,不需要的時候不計算 -
修改數(shù)據(jù)庫成功茅逮,刪除緩存失敗,導致數(shù)據(jù)庫是新的數(shù)據(jù)判哥,緩存中是舊的數(shù)據(jù)
方案:先刪除緩存献雅,再修改數(shù)據(jù)庫
-
修改數(shù)據(jù)庫還沒修改完,同時又有查詢請求塌计,把舊的數(shù)據(jù)放到緩存中(高并發(fā)挺身,每秒并發(fā)讀幾萬,每秒只要有數(shù)據(jù)更新請求锌仅,就可能出現(xiàn)數(shù)據(jù)庫+緩存不一致情況)
方案:寫章钾,讀路由到相同的一個內(nèi)存隊列(唯一標識墙贱,hash,取模)里贱傀,更新和讀操作進行串行化(后臺線程異步執(zhí)行隊列串行化操作)惨撇,(隊列里只放一個更新查詢操作即可,多余的過濾掉府寒,內(nèi)存隊列里沒有該數(shù)據(jù)更新操作魁衙,直接返回 )有該數(shù)據(jù)更新操作則輪詢?nèi)【彺嬷担瑫r取不到緩存值株搔,直接取一次數(shù)據(jù)庫的舊值
TP 99 意思是99%的請求可以在200ms內(nèi)返回
注意點:多個商品的更新操作都積壓在一個隊列里面(太多操作積壓只能增加機器)剖淀,導致讀請求發(fā)生大量的超時,導致大量的讀請求走數(shù)據(jù)庫
一秒 500 寫操作纤房,每200ms纵隔,100 個寫操作,20 個內(nèi)存隊列炮姨,每個隊列積壓 5 個寫操作捌刮,一般在20ms完成
Redis 并發(fā)競爭問題
方案:分布式鎖 + 時間戳比較
Redis 集群部署架構(gòu)
10臺機器,5 主 5 從舒岸,每個節(jié)點QPS 5W 绅作,一共 25W QPS(Redis cluster 32G + 8 核 ,Redis 進程不超過 10G)總內(nèi)存 50g吁津,每條數(shù)據(jù)10kb棚蓄,10W 條數(shù)據(jù)1g,200W 條數(shù)據(jù) 20G碍脏,占用總內(nèi)存不到50%梭依,目前高峰期 3500 QPS
本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布!