1)高性能java
假設這麼個場景,你有個操做,一個請求過來,吭哧吭哧你各類亂七八糟操做mysql,半天查出來一個結果,耗時600ms。可是這個結果可能接下來幾個小時都不會變了,或者變了也能夠不用當即反饋給用戶。那麼此時咋辦?node
緩存啊,折騰600ms查出來的結果,扔緩存裏,一個key對應一個value,下次再有人查,別走mysql折騰600ms了。直接從緩存裏,經過一個key查出來一個value,2ms搞定。性能提高300倍。mysql
2)高併發面試
mysql這麼重的數據庫,壓根兒設計不是讓你玩兒高併發的,雖然也能夠玩兒,可是自然支持很差。mysql單機支撐到2000qps也開始容易報警了。redis
因此要是你有個系統,高峯期一秒鐘過來的請求有1萬,那一個mysql單機絕對會死掉。你這個時候就只能上緩存,把不少數據放緩存,別放mysql。緩存功能簡單,說白了就是key-value式操做,單機支撐的併發量輕鬆一秒幾萬十幾萬,支撐高併發so easy。單機承載併發量是mysql單機的幾十倍。算法
這個事兒吧,你能夠比較出N多個區別來,可是我仍是採起redis做者給出的幾個比較吧sql
1)Redis支持服務器端的數據操做:Redis相比Memcached來講,擁有更多的數據結構和並支持更豐富的數據操做,一般在Memcached裏,你須要將數據拿到客戶端來進行相似的修改再set回去。這大大增長了網絡IO的次數和數據體積。在Redis中,這些複雜的操做一般和通常的GET/SET同樣高效。因此,若是須要緩存可以支持更復雜的結構和操做,那麼Redis會是不錯的選擇。數據庫
2)集羣模式:memcached沒有原生的集羣模式,須要依靠客戶端來實現往集羣中分片寫入數據;可是redis目前是原生支持cluster模式的,redis官方就是支持redis cluster集羣模式的,比memcached來講要更好api
1)純內存操做 2)核心是基於非阻塞的IO多路複用機制 3)單線程反而避免了多線程的頻繁上下文切換問題緩存
(1)string
這是最基本的類型了,沒啥可說的,就是普通的set和get,作簡單的kv緩存
(2)hash
這個是相似map的一種結構,這個通常就是能夠將結構化的數據,好比一個對象(前提是這個對象沒嵌套其餘的對象)給緩存在redis裏,而後每次讀寫緩存的時候,能夠就操做hash裏的某個字段。
key=150
value={ 「id」: 150, 「name」: 「zhangsan」, 「age」: 20 }
hash類的數據結構,主要是用來存放一些對象,把一些簡單的對象給緩存起來,後續操做的時候,你能夠直接僅僅修改這個對象中的某個字段的值
value={ 「id」: 150, 「name」: 「zhangsan」, 「age」: 21 }
(3)list
有序列表,這個是能夠玩兒出不少花樣的
微博,某個大v的粉絲,就能夠以list的格式放在redis裏去緩存
key=某大v
value=[zhangsan, lisi, wangwu]
好比能夠經過list存儲一些列表型的數據結構,相似粉絲列表了、文章的評論列表了之類的東西
好比能夠經過lrange命令,就是從某個元素開始讀取多少個元素,能夠基於list實現分頁查詢,這個很棒的一個功能,基於redis實現簡單的高性能分頁,能夠作相似微博那種下拉不斷分頁的東西,性能高,就一頁一頁走
好比能夠搞個簡單的消息隊列,從list頭懟進去,從list尾巴那裏弄出來
(4)set
無序集合,自動去重
直接基於set將系統裏須要去重的數據扔進去,自動就給去重了,若是你須要對一些數據進行快速的全局去重,你固然也能夠基於jvm內存裏的HashSet進行去重,可是若是你的某個系統部署在多臺機器上呢?
得基於redis進行全局的set去重
能夠基於set玩兒交集、並集、差集的操做,好比交集吧,能夠把兩我的的粉絲列表整一個交集,看看倆人的共同好友是誰?對吧
把兩個大v的粉絲都放在兩個set中,對兩個set作交集
(5)sorted set
排序的set,去重可是能夠排序,寫進去的時候給一個分數,自動根據分數排序,這個能夠玩兒不少的花樣,最大的特色是有個分數能夠自定義排序規則
單機的redis幾乎不太可能說QPS超過10萬+,除非一些特殊狀況,好比你的機器性能特別好,配置特別高,物理機,維護作的特別好,並且你的總體的操做不是太複雜
單機在幾萬
讀寫分離,通常來講,對緩存,通常都是用來支撐讀高併發的,寫的請求是比較少的,可能寫請求也就一秒鐘幾千,一兩千
大量的請求都是讀,一秒鐘二十萬次讀
讀寫分離
主從架構 -> 讀寫分離 -> 支撐10萬+讀QPS的架構
redis主從架構 -> 讀寫分離架構 -> 可支持水平擴展的讀高併發架構
(1)redis採用異步方式複製數據到slave節點,不過redis 2.8開始,slave node會週期性地確認本身每次複製的數據量
(2)一個master node是能夠配置多個slave node的
(3)slave node也能夠鏈接其餘的slave node
(4)slave node作複製的時候,是不會block master node的正常工做的
(5)slave node在作複製的時候,也不會block對本身的查詢操做,它會用舊的數據集來提供服務; 可是複製完成的時候,須要刪除舊數據集,加載新數據集,這個時候就會暫停對外服務了
(6)slave node主要用來進行橫向擴容,作讀寫分離,擴容的slave node能夠提升讀的吞吐量
若是採用了主從架構,那麼建議必須開啓master node的持久化!
不建議用slave node做爲master node的數據熱備,由於那樣的話,若是你關掉master的持久化,可能在master宕機重啓的時候數據是空的,而後可能一通過複製,salve node數據也丟了
master -> RDB和AOF都關閉了 -> 所有在內存中
master宕機,重啓,是沒有本地數據能夠恢復的,而後就會直接認爲本身IDE數據是空的
master就會將空的數據集同步到slave上去,全部slave的數據所有清空
100%的數據丟失
master節點,必需要使用持久化機制
第二個,master的各類備份方案,要不要作,萬一說本地的全部文件丟失了; 從備份中挑選一份rdb去恢復master; 這樣才能確保master啓動的時候,是有數據的
即便採用了後續講解的高可用機制,slave node能夠自動接管master node,可是也可能sentinal尚未檢測到master failure,master node就自動重啓了,仍是可能致使上面的全部slave node數據清空故障
sentinal,中文名是哨兵
哨兵是redis集羣架構中很是重要的一個組件,主要功能以下
(1)集羣監控,負責監控redis master和slave進程是否正常工做 (2)消息通知,若是某個redis實例有故障,那麼哨兵負責發送消息做爲報警通知給管理員 (3)故障轉移,若是master node掛掉了,會自動轉移到slave node上 (4)配置中心,若是故障轉移發生了,通知client客戶端新的master地址
哨兵自己也是分佈式的,做爲一個哨兵集羣去運行,互相協同工做
(1)故障轉移時,判斷一個master node是宕機了,須要大部分的哨兵都贊成才行,涉及到了分佈式選舉的問題 (2)即便部分哨兵節點掛掉了,哨兵集羣仍是能正常工做的,由於若是一個做爲高可用機制重要組成部分的故障轉移系統自己是單點的,那就很坑爹了
目前採用的是sentinal 2版本,sentinal 2相對於sentinal 1來講,重寫了不少代碼,主要是讓故障轉移的機制和算法變得更加健壯和簡單
(1)哨兵至少須要3個實例,來保證本身的健壯性 (2)哨兵 + redis主從的部署架構,是不會保證數據零丟失的,只能保證redis集羣的高可用性 (3)對於哨兵 + redis主從這種複雜的部署架構,儘可能在測試環境和生產環境,都進行充足的測試和演練
哨兵集羣必須部署2個以上節點
若是哨兵集羣僅僅部署了個2個哨兵實例,quorum=1
+----+ +----+ | M1 |---------| R1 | | S1 | | S2 | +----+ +----+
Configuration: quorum = 1
master宕機,s1和s2中只要有1個哨兵認爲master宕機就能夠還行切換,同時s1和s2中會選舉出一個哨兵來執行故障轉移
同時這個時候,須要majority,也就是大多數哨兵都是運行的,2個哨兵的majority就是2(2的majority=2,3的majority=2,5的majority=3,4的majority=2),2個哨兵都運行着,就能夠容許執行故障轉移
可是若是整個M1和S1運行的機器宕機了,那麼哨兵只有1個了,此時就沒有majority來容許執行故障轉移,雖然另一臺機器還有一個R1,可是故障轉移不會執行
主備切換的過程,可能會致使數據丟失
(1)異步複製致使的數據丟失
由於master -> slave的複製是異步的,因此可能有部分數據還沒複製到slave,master就宕機了,此時這些部分數據就丟失了
腦裂,也就是說,某個master所在機器忽然脫離了正常的網絡,跟其餘slave機器不能鏈接,可是實際上master還運行着
此時哨兵可能就會認爲master宕機了,而後開啓選舉,將其餘slave切換成了master
這個時候,集羣裏就會有兩個master,也就是所謂的腦裂
此時雖然某個slave被切換成了master,可是可能client還沒來得及切換到新的master,還繼續寫向舊master的數據可能也丟失了
所以舊master再次恢復的時候,會被做爲一個slave掛到新的master上去,本身的數據會清空,從新重新的master複製數據
min-slaves-to-write 1 min-slaves-max-lag 10
要求至少有1個slave,數據複製和同步的延遲不能超過10秒
若是說一旦全部的slave,數據複製和同步的延遲都超過了10秒鐘,那麼這個時候,master就不會再接收任何請求了
上面兩個配置能夠減小異步複製和腦裂致使的數據丟失
有了min-slaves-max-lag這個配置,就能夠確保說,一旦slave複製數據和ack延時太長,就認爲可能master宕機後損失的數據太多了,那麼就拒絕寫請求,這樣能夠把master宕機時因爲部分數據未同步到slave致使的數據丟失下降的可控範圍內
若是一個master出現了腦裂,跟其餘slave丟了鏈接,那麼上面兩個配置能夠確保說,若是不能繼續給指定數量的slave發送數據,並且slave超過10秒沒有給本身ack消息,那麼就直接拒絕客戶端的寫請求
這樣腦裂後的舊master就不會接受client的新數據,也就避免了數據丟失
上面的配置就確保了,若是跟任何一個slave丟了鏈接,在10秒後發現沒有slave給本身ack,那麼就拒絕新的寫請求
所以在腦裂場景下,最多就丟失10秒的數據
sdown和odown兩種失敗狀態
sdown是主觀宕機,就一個哨兵若是本身以爲一個master宕機了,那麼就是主觀宕機
odown是客觀宕機,若是quorum數量的哨兵都以爲一個master宕機了,那麼就是客觀宕機
sdown達成的條件很簡單,若是一個哨兵ping一個master,超過了is-master-down-after-milliseconds指定的毫秒數以後,就主觀認爲master宕機
sdown到odown轉換的條件很簡單,若是一個哨兵在指定時間內,收到了quorum指定數量的其餘哨兵也認爲那個master是sdown了,那麼就認爲是odown了,客觀認爲master宕機
哨兵互相之間的發現,是經過redis的pub/sub系統實現的,每一個哨兵都會往__sentinel__:hello這個channel裏發送一個消息,這時候全部其餘哨兵均可以消費到這個消息,並感知到其餘的哨兵的存在
每隔兩秒鐘,每一個哨兵都會往本身監控的某個master+slaves對應的__sentinel__:hello channel裏發送一個消息,內容是本身的host、ip和runid還有對這個master的監控配置
每一個哨兵也會去監聽本身監控的每一個master+slaves對應的__sentinel__:hello channel,而後去感知到一樣在監聽這個master+slaves的其餘哨兵的存在
每一個哨兵還會跟其餘哨兵交換對master的監控配置,互相進行監控配置的同步
哨兵會負責自動糾正slave的一些配置,好比slave若是要成爲潛在的master候選人,哨兵會確保slave在複製現有master的數據; 若是slave鏈接到了一個錯誤的master上,好比故障轉移以後,那麼哨兵會確保它們鏈接到正確的master上
若是一個master被認爲odown了,並且majority哨兵都容許了主備切換,那麼某個哨兵就會執行主備切換操做,此時首先要選舉一個slave來
會考慮slave的一些信息
(1)跟master斷開鏈接的時長 (2)slave優先級 (3)複製offset (4)run id
若是一個slave跟master斷開鏈接已經超過了down-after-milliseconds的10倍,外加master宕機的時長,那麼slave就被認爲不適合選舉爲master
(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state
接下來會對slave進行排序
(1)按照slave優先級進行排序,slave priority越低,優先級就越高 (2)若是slave priority相同,那麼看replica offset,哪一個slave複製了越多的數據,offset越靠後,優先級就越高 (3)若是上面兩個條件都相同,那麼選擇一個run id比較小的那個slave
每次一個哨兵要作主備切換,首先須要quorum數量的哨兵認爲odown,而後選舉出一個哨兵來作切換,這個哨兵還得獲得majority哨兵的受權,才能正式執行切換
若是quorum < majority,好比5個哨兵,majority就是3,quorum設置爲2,那麼就3個哨兵受權就能夠執行切換
可是若是quorum >= majority,那麼必須quorum數量的哨兵都受權,好比5個哨兵,quorum是5,那麼必須5個哨兵都贊成受權,才能執行切換
六、configuration epoch
哨兵會對一套redis master+slave進行監控,有相應的監控的配置
執行切換的那個哨兵,會從要切換到的新master(salve->master)那裏獲得一個configuration epoch,這就是一個version號,每次切換的version號都必須是惟一的
若是第一個選舉出的哨兵切換失敗了,那麼其餘哨兵,會等待failover-timeout時間,而後接替繼續執行切換,此時會從新獲取一個新的configuration epoch,做爲新的version號
哨兵完成切換以後,會在本身本地更新生成最新的master配置,而後同步給其餘的哨兵,就是經過以前說的pub/sub消息機制
這裏以前的version號就很重要了,由於各類消息都是經過一個channel去發佈和監聽的,因此一個哨兵完成一次新的切換以後,新的master配置是跟着新的version號的
其餘的哨兵都是根據版本號的大小來更新本身的master配置的
就是若是你用redis緩存技術的話,確定要考慮如何用redis來加多臺機器,保證redis是高併發的,還有就是如何讓Redis保證本身不是掛掉之後就直接死掉了,redis高可用
我這裏會選用我以前講解過這一塊內容,redis高併發、高可用、緩存一致性
redis高併發:主從架構,一主多從,通常來講,不少項目其實就足夠了,單主用來寫入數據,單機幾萬QPS,多從用來查詢數據,多個從實例能夠提供每秒10萬的QPS。
redis高併發的同時,還須要容納大量的數據:一主多從,每一個實例都容納了完整的數據,好比redis主就10G的內存量,其實你就最對只能容納10g的數據量。若是你的緩存要容納的數據量很大,達到了幾十g,甚至幾百g,或者是幾t,那你就須要redis集羣,並且用redis集羣以後,能夠提供可能每秒幾十萬的讀寫併發。
redis高可用:若是你作主從架構部署,其實就是加上哨兵就能夠了,就能夠實現,任何一個實例宕機,自動會進行主備切換。
講解分佈式數據存儲的核心算法,數據分佈的算法
hash算法 -> 一致性hash算法(memcached) -> redis cluster,hash slot算法
用不一樣的算法,就決定了在多個master節點的時候,數據如何分佈到這些節點上去,解決這個問題
複製代碼
redis cluster
(1)自動將數據進行分片,每一個master上放一部分數據 (2)提供內置的高可用支持,部分master不可用時,仍是能夠繼續工做的
在redis cluster架構下,每一個redis要放開兩個端口號,好比一個是6379,另一個就是加10000的端口號,好比16379
16379端口號是用來進行節點間通訊的,也就是cluster bus的東西,集羣總線。cluster bus的通訊,用來進行故障檢測,配置更新,故障轉移受權
cluster bus用了另一種二進制的協議,主要用於節點間進行高效的數據交換,佔用更少的網絡帶寬和處理時間
redis cluster有固定的16384個hash slot,對每一個key計算CRC16值,而後對16384取模,能夠獲取key對應的hash slot
redis cluster中每一個master都會持有部分slot,好比有3個master,那麼可能每一個master持有5000多個hash slot
hash slot讓node的增長和移除很簡單,增長一個master,就將其餘master的hash slot移動部分過去,減小一個master,就將它的hash slot移動到其餘master上去
移動hash slot的成本是很是低的
客戶端的api,能夠對指定的數據,讓他們走同一個hash slot,經過hash tag來實現
跟集中式不一樣,不是將集羣元數據(節點信息,故障,等等)集中存儲在某個節點上,而是互相之間不斷通訊,保持整個集羣全部節點的數據是完整的
維護集羣的元數據用得,集中式,一種叫作gossip
集中式:好處在於,元數據的更新和讀取,時效性很是好,一旦元數據出現了變動,當即就更新到集中式的存儲中,其餘節點讀取的時候當即就能夠感知到; 很差在於,全部的元數據的跟新壓力所有集中在一個地方,可能會致使元數據的存儲有壓力
gossip:好處在於,元數據的更新比較分散,不是集中在一個地方,更新請求會陸陸續續,打到全部節點上去更新,有必定的延時,下降了壓力; 缺點,元數據更新有延時,可能致使集羣的一些操做會有一些滯後
咱們剛纔作reshard,去作另一個操做,會發現說,configuration error,達成一致
每一個節點都有一個專門用於節點間通訊的端口,就是本身提供服務的端口號+10000,好比7001,那麼用於節點間通訊的就是17001端口
每隔節點每隔一段時間都會往另外幾個節點發送ping消息,同時其餘幾點接收到ping以後返回pong
故障信息,節點的增長和移除,hash slot信息,等等
gossip協議包含多種消息,包括ping,pong,meet,fail,等等
meet: 某個節點發送meet給新加入的節點,讓新節點加入集羣中,而後新節點就會開始與其餘節點進行通訊
redis-trib.rb add-node
其實內部就是發送了一個gossip meet消息,給新加入的節點,通知那個節點去加入咱們的集羣
ping: 每一個節點都會頻繁給其餘節點發送ping,其中包含本身的狀態還有本身維護的集羣元數據,互相經過ping交換元數據
每一個節點每秒都會頻繁發送ping給其餘的集羣,ping,頻繁的互相之間交換數據,互相進行元數據的更新
pong: 返回ping和meet,包含本身的狀態和其餘信息,也能夠用於信息廣播和更新
fail: 某個節點判斷另外一個節點fail以後,就發送fail給其餘節點,通知其餘節點,指定的節點宕機了
ping很頻繁,並且要攜帶一些元數據,因此可能會加劇網絡負擔
每一個節點每秒會執行10次ping,每次會選擇5個最久沒有通訊的其餘節點
固然若是發現某個節點通訊延時達到了cluster_node_timeout / 2,那麼當即發送ping,避免數據交換延時過長,落後的時間太長了
好比說,兩個節點之間都10分鐘沒有交換數據了,那麼整個集羣處於嚴重的元數據不一致的狀況,就會有問題
因此cluster_node_timeout能夠調節,若是調節比較大,那麼會下降發送的頻率
每次ping,一個是帶上本身節點的信息,還有就是帶上1/10其餘節點的信息,發送出去,進行數據交換
至少包含3個其餘節點的信息,最多包含總節點-2個其餘節點的信息
開發,jedis,redis的java client客戶端,redis cluster,jedis cluster api
jedis cluster api與redis cluster集羣交互的一些基本原理
redis-cli -c,自動重定向
客戶端可能會挑選任意一個redis實例去發送命令,每一個redis實例接收到命令,都會計算key對應的hash slot
若是在本地就在本地處理,不然返回moved給客戶端,讓客戶端進行重定向
cluster keyslot mykey,能夠查看一個key對應的hash slot是什麼
用redis-cli的時候,能夠加入-c參數,支持自動的請求重定向,redis-cli接收到moved以後,會自動重定向到對應的節點執行命令
計算hash slot的算法,就是根據key計算CRC16值,而後對16384取模,拿到對應的hash slot
用hash tag能夠手動指定key對應的slot,同一個hash tag下的key,都會在一個hash slot中,好比set mykey1:{100}和set mykey2:{100}
節點間經過gossip協議進行數據交換,就知道每一個hash slot在哪一個節點上
基於重定向的客戶端,很消耗網絡IO,由於大部分狀況下,可能都會出現一次請求重定向,才能找到正確的節點
因此大部分的客戶端,好比java redis客戶端,就是jedis,都是smart的
本地維護一份hashslot -> node的映射表,緩存,大部分狀況下,直接走本地緩存就能夠找到hashslot -> node,不須要經過節點進行moved重定向
在JedisCluster初始化的時候,就會隨機選擇一個node,初始化hashslot -> node映射表,同時爲每一個節點建立一個JedisPool鏈接池
每次基於JedisCluster執行操做,首先JedisCluster都會在本地計算key的hashslot,而後在本地映射表找到對應的節點
若是那個node正好仍是持有那個hashslot,那麼就ok; 若是說進行了reshard這樣的操做,可能hashslot已經不在那個node上了,就會返回moved
若是JedisCluter API發現對應的節點返回moved,那麼利用該節點的元數據,更新本地的hashslot -> node映射表緩存
重複上面幾個步驟,直到找到對應的節點,若是重試超過5次,那麼就報錯,JedisClusterMaxRedirectionException
jedis老版本,可能會出如今集羣某個節點故障還沒完成自動切換恢復時,頻繁更新hash slot,頻繁ping節點檢查活躍,致使大量網絡IO開銷
jedis最新版本,對於這些過分的hash slot更新和ping,都進行了優化,避免了相似問題
若是hash slot正在遷移,那麼會返回ask重定向給jedis
jedis接收到ask重定向以後,會從新定位到目標節點去執行,可是由於ask發生在hash slot遷移過程當中,因此JedisCluster API收到ask是不會更新hashslot本地緩存
已經能夠肯定說,hashslot已經遷移完了,moved是會更新本地hashslot->node映射表緩存的
redis cluster的高可用的原理,幾乎跟哨兵是相似的
若是一個節點認爲另一個節點宕機,那麼就是pfail,主觀宕機
若是多個節點都認爲另一個節點宕機了,那麼就是fail,客觀宕機,跟哨兵的原理幾乎同樣,sdown,odown
在cluster-node-timeout內,某個節點一直沒有返回pong,那麼就被認爲pfail
若是一個節點認爲某個節點pfail了,那麼會在gossip ping消息中,ping給其餘節點,若是超過半數的節點都認爲pfail了,那麼就會變成fail
對宕機的master node,從其全部的slave node中,選擇一個切換成master node
檢查每一個slave node與master node斷開鏈接的時間,若是超過了cluster-node-timeout * cluster-slave-validity-factor,那麼就沒有資格切換成master
這個也是跟哨兵是同樣的,從節點超時過濾的步驟
哨兵:對全部從節點進行排序,slave priority,offset,run id
每一個從節點,都根據本身對master複製數據的offset,來設置一個選舉時間,offset越大(複製數據越多)的從節點,選舉時間越靠前,優先進行選舉
全部的master node開始slave選舉投票,給要進行選舉的slave進行投票,若是大部分master node(N/2 + 1)都投票給了某個從節點,那麼選舉經過,那個從節點能夠切換成master
從節點執行主備切換,從節點切換爲主節點
整個流程跟哨兵相比,很是相似,因此說,redis cluster功能強大,直接集成了replication和sentinal的功能
沒有辦法去給你們深刻講解redis底層的設計的細節,核心原理和設計的細節,那個除非單獨開一門課,redis底層原理深度剖析,redis源碼
對於我們這個架構課來講,主要關注的是架構,不是底層的細節,對於架構來講,核心的原理的基本思路,是要梳理清晰的
事前:redis高可用,主從+哨兵,redis cluster,避免全盤崩潰
事中:本地ehcache緩存 + hystrix限流&降級,避免MySQL被打死
過後:redis持久化,快速恢復緩存數據