99热99这里只有精品6国产,亚洲中文字幕在线天天更新,在线观看亚洲精品国产福利片 ,久久久久综合网

歡迎加入QQ討論群258996829
麥子學(xué)院 頭像
蘋果6袋
6
麥子學(xué)院

基于Redis 如何構(gòu)建數(shù)據(jù)服務(wù)?

發(fā)布時間:2016-12-13 22:48  回復(fù):0  查看:3176   最后回復(fù):2016-12-13 22:48  

今天我們來聊聊如何基于redis數(shù)據(jù)庫擴展數(shù)據(jù)服務(wù),如何實現(xiàn)分片(sharding)以及高可用(high availability)。

分布式系統(tǒng)不存在完美的設(shè)計,處處都體現(xiàn)了trade off。

因此我們在開始正文前,需要確定后續(xù)的討論原則,仍然以分布式系統(tǒng)設(shè)計中的CAP原則為例。由于主角是redis,那性能表現(xiàn)肯定是最高設(shè)計目標(biāo),之后討論過程中的所有抉擇,都會優(yōu)先考慮CAP中的AP性質(zhì)。

基于Redis 如何構(gòu)建數(shù)據(jù)服務(wù)?

兩個點按順序來,先看分片。

何謂分片?簡單來說,就是對單機redis做水平擴展。

當(dāng)然,做游戲的同學(xué)可能要問了,一服一個redis,為什么需要水平擴展?這個話題我們在之前幾篇文章中都有討論,可以看這里,或這里,小說君不再贅述。

如果要實現(xiàn)服務(wù)級別的復(fù)用,那么數(shù)據(jù)服務(wù)的定位往往是全局服務(wù)。如此僅用單實例的redis就難以應(yīng)對多變的負(fù)載情況——畢竟redis是單線程的。

mysql一路用過來的同學(xué)這時都會習(xí)慣性地水平拆分,redis中也是類似的原理,將整體的數(shù)據(jù)進行切分,每一部分是一個分片(shard),不同的分片維護不同的key集合。

那么,分片問題的實質(zhì)就是如何基于多個redis實例設(shè)計全局統(tǒng)一的數(shù)據(jù)服務(wù)。同時,有一個約束條件,那就是我們無法保證強一致性。

也就是說,數(shù)據(jù)服務(wù)進行分片擴展的前提是,不提供跨分片事務(wù)的保障。redis cluster也沒有提供類似支持,因為分布式事務(wù)本來就跟redis的定位是有沖突的。

因此,我們的分片方案有兩個限制:

·不同分片中的數(shù)據(jù)一定是嚴(yán)格隔離的,比如是不同組服的數(shù)據(jù),或者是完全不相干的數(shù)據(jù)。要想實現(xiàn)跨分片的數(shù)據(jù)交互,必須依賴更上層的協(xié)調(diào)機制保證,數(shù)據(jù)服務(wù)層面不做任何承諾。 而且這樣一來,如果想給應(yīng)用層提供協(xié)調(diào)機制,只要在每個分片上部署上篇文章介紹的 單實例簡易鎖機制 即可,簡單明了。

·我們的分片方案無法在分片間做類似分布式存儲系統(tǒng)的數(shù)據(jù)冗余機制,換言之,一份數(shù)據(jù)交叉存在多個分片中。

如何實現(xiàn)分片?

首先,我們要確定分片方案需要解決什么問題。

分片的redis集群,實際上共同組成了一個有狀態(tài)服務(wù)(stateful service)。設(shè)計有狀態(tài)服務(wù),我們通常會從兩點考慮:

cluster membership,系統(tǒng)間各個節(jié)點,或者說各個分片的關(guān)系是怎樣的。

work distribution,外部請求應(yīng)該如何、交由哪個節(jié)點處理,或者說用戶(以下都簡稱dbClient)的一次讀或?qū)憫?yīng)該去找哪個分片。

針對第一個問題,解決方案通常有三:

·presharding,也就是sharding靜態(tài)配置。

·

·gossip protocol,其實就是redis cluster采用的方案。簡單地說就是集群中每個節(jié)點會由于網(wǎng)絡(luò)分化、節(jié)點抖動等原因而具有不同的集群全局視圖。節(jié)點之間通過gossip protocol進行節(jié)點信息共享。這是業(yè)界比較流行的去中心化的方案。

·consensus system,這種方案跟上一種正相反,是依賴外部分布式一致性設(shè)施,由其仲裁來決定集群中各節(jié)點的身份。

·需求決定解決方案,小說君認(rèn)為,對于游戲服務(wù)端以及大多數(shù)應(yīng)用型后端情景,后兩者的成本太高,會增加很多不確定的復(fù)雜性,因此兩種方案都不是合適的選擇。 而且,大部分服務(wù)通常是可以在設(shè)計階段確定每個分片的容量上限的,也不需要太復(fù)雜的機制支持。

但是presharding的缺點也很明顯,做不到動態(tài)增容減容,而且無法高可用。不過其實只要稍加改造,就足以滿足需求了。

不過,在談具體的改造措施之前,我們先看之前提出的分片方案要解決的第二個問題—— work distribution 。

這個問題實際上是從另一種維度看分片,解決方案很多,但是如果從對架構(gòu)的影響上來看,大概分為兩種:

·一種是proxy-based,基于額外的轉(zhuǎn)發(fā)代理。例子有twemproxy/Codis。

·一種是client sharding,也就是dbClient(每個對數(shù)據(jù)服務(wù)有需求的服務(wù))維護sharding規(guī)則,自助式選擇要去哪個redis實例。redis cluster本質(zhì)上就屬于這種,dblient側(cè)緩存了部分sharding信息。

第一種方案的缺點顯而易見——在整個架構(gòu)中增加了額外的間接層,流程中增加了一趟round-trip。如果是像twemproxy或者Codis這種支持高可用的還好,但是github上隨便一翻還能找到特別多的沒法做到高可用的proxy-based方案,無緣無故多個單點,這樣就完全搞不明白sharding的意義何在了。

第二種方案的缺點,小說君能想到的就是集群狀態(tài)發(fā)生變化的時候沒法即時通知到dbClient

第一種方案,我們其實可以直接pass掉了。因為這種方案更適合私有云的情景,開發(fā)數(shù)據(jù)服務(wù)的部門有可能和業(yè)務(wù)部門相去甚遠(yuǎn),因此需要統(tǒng)一的轉(zhuǎn)發(fā)代理服務(wù)。但是對于一些簡單的應(yīng)用開發(fā)情景,數(shù)據(jù)服務(wù)邏輯服務(wù)都是一幫人寫的,沒什么增加額外中間層的必要。

那么,看起來只能選擇第二種方案了。

preshardingclient sharding結(jié)合起來后,現(xiàn)在我們的成果是:數(shù)據(jù)服務(wù)是全局的,redis可以開多個實例,不相干的數(shù)據(jù)需要到不同的分片上存取,dbClient掌握這個映射關(guān)系。

基于Redis 如何構(gòu)建數(shù)據(jù)服務(wù)?


不過目前的方案只能算是滿足了應(yīng)用對數(shù)據(jù)服務(wù)的基本需求。

游戲行業(yè)中,大部分采用redis的團隊,一般最終會選定這個方案作為自己的數(shù)據(jù)服務(wù)。后續(xù)的擴展其實對他們來說不是不可以做,但是可能有維護上的復(fù)雜性與不確定性。

但是作為一名有操守的程序員,小說君選擇繼續(xù)擴展。

現(xiàn)在的這個方案存在兩個問題:

·首先,雖然我們沒有支持在線數(shù)據(jù)遷移的必要,但是離線數(shù)據(jù)遷移是必須得有的,畢竟presharding做不到萬無一失。而在這個方案中,如果用單純的哈希算法,增加一個shard會導(dǎo)致原先的keyshard的對應(yīng)關(guān)系變得非常亂,抬高數(shù)據(jù)遷移成本。

·其次,分片方案固然可以將整個數(shù)據(jù)服務(wù)的崩潰風(fēng)險分散在不同shard中,比如相比于不分片的數(shù)據(jù)服務(wù),一臺機器掛掉了,只影響到一部分client。但是,我們理應(yīng)可以對數(shù)據(jù)服務(wù)做更深入的擴展,讓其可用程度更強。

針對第一個問題,處理方式跟proxy-based采用的處理方式?jīng)]太大區(qū)別,由于目前的數(shù)據(jù)服務(wù)方案比較簡單,采用一致性哈希即可?;蛘卟捎靡环N比較簡單的兩段映射,第一段是靜態(tài)的固定哈希,第二段是動態(tài)的可配置map。前者通過算法,后者通過map配置維護的方式,都能最小化影響到的key集合。

而對于第二個問題,解決方案就是實現(xiàn)高可用。

如何讓數(shù)據(jù)服務(wù)高可用?在 討論這個問題之前,我們首先看redis如何實現(xiàn)「 可用性」。

對于redis來說,可用性的本質(zhì)是什么?其實就是redis實例掛掉之后可以有后備節(jié)點頂上。

redis通過兩種機制支持這一點。

第一種機制是replication。 通常的replication方案主要分為兩種。

·一種是active-passive,也就是active節(jié)點先修改自身狀態(tài),然后寫統(tǒng)一持久化log,然后passive節(jié)點讀log跟進狀態(tài)。

·另一種是active-active,寫請求統(tǒng)一寫到持久化log,然后每個active節(jié)點自動同步log進度。

redisreplication方案采用的是一種一致性較弱的active-passive方案。也就是master自身維護log,將log向其他slave同步,master掛掉有可能導(dǎo)致部分log丟失,client寫完master即可收到成功返回,是一種異步replication

這個機制只能解決節(jié)點數(shù)據(jù)冗余的問題,redis要具有可用性就還得解決redis實例掛掉讓備胎自動頂上的問題,畢竟由人肉去監(jiān)控master狀態(tài)再人肉切換是不現(xiàn)實的。 因此還需要第二種機制。

第二種機制是redis自帶的能夠自動化fail-overredis sentinelreds sentinel實際上是一種特殊的redis實例,其本身就是一種高可用服務(wù)——可以多開,可以自動服務(wù)發(fā)現(xiàn)(基于redis內(nèi)置的pub-sub支持,sentinel并沒有禁用掉pub-subcommand map),可以自主leader election(基于 raft算法 實現(xiàn),作為 sentinel的一個模塊 ),然后在發(fā)現(xiàn)master掛掉時由leader發(fā)起fail-over,并將掉線后再上線的master降為新masterslave。

redis基于這兩種機制,已經(jīng)能夠?qū)崿F(xiàn)一定程度的可用性。

基于Redis 如何構(gòu)建數(shù)據(jù)服務(wù)?


接下來,我們來看數(shù)據(jù)服務(wù)如何高可用。

數(shù)據(jù)服務(wù)具有可用性的本質(zhì)是什么?除了能實現(xiàn)redis可用性的需求——redis實例數(shù)據(jù)冗余、故障自動切換之外,還需要將切換的消息通知到每個dbClient。

也就是說把最開始的圖,改成下面這個樣子:

基于Redis 如何構(gòu)建數(shù)據(jù)服務(wù)?


每個分片都要改成主從模式。

如果redis sentinel負(fù)責(zé)主從切換,拿最自然的想法就是讓dbClientsentinel請求當(dāng)前節(jié)點主從連接信息。但是redis sentinel本身也是redis實例,數(shù)量也是動態(tài)的,redis sentinel的連接信息不僅在配置上成了一個難題,動態(tài)更新時也會有各種問題。

而且,redis sentinel本質(zhì)上是整個服務(wù)端的static parts(要向dbClient提供服務(wù)),但是卻依賴于redis的啟動,并不是特別優(yōu)雅。另一方面,dbClient要想問redis sentinel要到當(dāng)前連接信息,只能依賴其內(nèi)置的pub-sub機制。redispub-sub只是一個簡單的消息分發(fā),沒有消息持久化,因此需要輪詢式的請求連接信息模型。

那么,我們是否可以以較低的成本定制一種服務(wù),既能取代redis sentinel,又能解決上述問題?

回憶下前文我們解決resharding問題的思路:

1.一致性哈希。

2.采用一種比較簡單的兩段映射,第一段是靜態(tài)的固定哈希,第二段是動態(tài)的可配置map。前者通過算法,后者通過map配置維護的方式,都能最小化影響到的key集合。

兩種方案都可以實現(xiàn)動態(tài)resharding,dbClient可以動態(tài)更新:

·如果采用兩段映射,那么我們可以動態(tài)下發(fā)第二段的配置數(shù)據(jù)。

·如果采用一致性哈希,那么我們可以動態(tài)下發(fā)分片的連接信息。

再梳理一下,我們要實現(xiàn)的服務(wù)(下文簡稱為watcher),至少要實現(xiàn)這些需求 :

·要能夠監(jiān)控redis的生存狀態(tài)。這一點實現(xiàn)起來很簡單,定期的PING redis實例即可。需要的信息以及做出客觀下線和主觀下線的判斷依據(jù)都可以直接照搬sentinel實現(xiàn)。

·要做到自主服務(wù)發(fā)現(xiàn),包括其他watcher的發(fā)現(xiàn)與所監(jiān)控的master-slave組中的新節(jié)點的發(fā)現(xiàn)。在實現(xiàn)上,前者可以基于消息隊列的pub-sub功能,后者只要向redis實例定期INFO獲取信息即可。

·要在發(fā)現(xiàn)master客觀下線的時候選出leader進行后續(xù)的故障轉(zhuǎn)移流程。這部分實現(xiàn)起來算是最復(fù)雜的部分,接下來會集中討論。

·選出leader之后將一個最合適的slave提升為master,然后等老的master再上線了就把它降級為新masterslave。

解決這些問題,watcher就兼具了擴展性、定制性,同時還提供分片數(shù)據(jù)服務(wù)的部分在線遷移機制。這樣,我們的數(shù)據(jù)服務(wù)也就更加健壯,可用程度更高。

這樣一來,雖然保證了redis每個分片的master-slave組具有可用性,但是因為我們引入了新的服務(wù),那就引入了新的不確定性——如果引入這個服務(wù)的同時還要保證數(shù)據(jù)服務(wù)具有可用性,那我們就還得保證這個服務(wù)本身是可用的。

說起來可能有點繞,換個說法,也就是服務(wù)A借助服務(wù)B實現(xiàn)了高可用,那么服務(wù)B本身也需要高可用。

先簡單介紹一下redis sentinel是如何做到高可用的。同時監(jiān)控同一組主從的sentinel可以有多個,master掛掉的時候,這些sentinel會根據(jù)redis自己實現(xiàn)的一種raft算法選舉出leader,算法流程也不是特別復(fù)雜,至少比paxos簡單多了。所有sentinel都是follower,判斷出master客觀下線的sentinel會升級成candidate同時向其他follower拉票,所有follower同一epoch內(nèi)只能投給第一個向自己拉票的candidate。在具體表現(xiàn)中,通常一兩個epoch就能保證形成多數(shù)派,選出leader。有了leader,后面再對redisSLAVEOF的時候就容易多了。

基于Redis 如何構(gòu)建數(shù)據(jù)服務(wù)?


如果想用watcher取代sentinel,最復(fù)雜的實現(xiàn)細(xì)節(jié)可能就是這部分邏輯了。

這部分邏輯說白了就是要在分布式系統(tǒng)中維護一個一致狀態(tài),舉個例子,可以將「誰是leader 」這個概念當(dāng)作一個狀態(tài)量,由分布式系統(tǒng)中的身份相等的幾個節(jié)點共同維護,既然誰都有可能修改這個變量,那究竟誰的修改才奏效呢?

幸好,針對這種常見的問題情景,我們有現(xiàn)成的基礎(chǔ)設(shè)施抽象可以解決。

這種基礎(chǔ)設(shè)施就是分布式系統(tǒng)的協(xié)調(diào)器組件(coordinator),老牌的有zookeeper(基于對paxos改進過的zab協(xié)議,下面都簡稱zk了),新一點的有etcd(這個大家都清楚,基于raft協(xié)議)。這種組件通常沒有重復(fù)開發(fā)的必要,像paxos這種算法理解起來都得老半天,實現(xiàn)起來的細(xì)節(jié)數(shù)量級更是難以想象。因此很多開源項目都是依賴這兩者實現(xiàn)高可用的,比如codis一開始就是用的zk

zk解決了什么問題?

以通用的應(yīng)用服務(wù)需求來說,zk可以用來選leader,還可以用來維護dbClient的配置數(shù)據(jù)——dbClient直接去找zk要數(shù)據(jù)就行了。

zk的具體原理小說君就不再介紹了,有時間有精力可以研究下paxos,看看lamportpaper,沒時間沒精力的話搜一下看看zk實現(xiàn)原理的博客就行了。

簡單介紹下如何基于zk實現(xiàn)leader election。zk提供了一個類似于os文件系統(tǒng)的目錄結(jié)構(gòu),目錄結(jié)構(gòu)上的每個節(jié)點都有類型的概念同時可以存儲一些數(shù)據(jù)。zk還提供了一次性觸發(fā)的watch機制。

應(yīng)用層要做leader election就可以基于這幾點概念實現(xiàn)。

假設(shè)有某個目錄節(jié)點「 /election 」,watcher1啟動的時候在這個節(jié)點下面創(chuàng)建一個子節(jié)點,節(jié)點類型是臨時順序節(jié)點,也就是說這個節(jié)點會隨創(chuàng)建者掛掉而掛掉,順序的意思就是會在節(jié)點的名字后面加個數(shù)字后綴,唯一標(biāo)識這個節(jié)點在 「 /election 」 的子節(jié)點中的id。

·一個簡單的方案是讓每個watcherwatch 「 /election 」 的所有子節(jié)點,然后看自己的id是否是最小的,如果是就說明自己是leader,然后告訴應(yīng)用層自己是leader,讓應(yīng)用層進行后續(xù)操作就行了。但是這樣會產(chǎn)生驚群效應(yīng),因為一個子節(jié)點刪除,每個watcher都會收到通知,但是至多一個watcher會從follower變?yōu)?/span>leader。

·優(yōu)化一些的方案是每個節(jié)點都關(guān)注比自己小一個排位的節(jié)點。這樣如果id最小的節(jié)點掛掉之后,id次小的節(jié)點會收到通知然后了解到自己成為了leader,避免了驚群效應(yīng)。

小說君在實踐中發(fā)現(xiàn),還有一點需要注意,臨時順序節(jié)點的臨時性體現(xiàn)在一次session而不是一次連接的終止。

例如watcher1每次申請節(jié)點都叫watcher1,第一次它申請成功的節(jié)點全名假設(shè)是watcher10002(后面的是zk自動加的序列號),然后下線,watcher10002節(jié)點還會存在一段時間,如果這段時間內(nèi)watcher1再上線,再嘗試創(chuàng)建watcher1就會失敗,然后之前的節(jié)點過一會兒就因為session超時而銷毀,這樣就相當(dāng)于這個watcher1消失了。

解決方案有兩個,可以創(chuàng)建節(jié)點前先顯式delete一次,也可以通過其他機制保證每次創(chuàng)建節(jié)點的名字不同,比如guid

至于配置下發(fā),就更簡單了。配置變更時直接更新節(jié)點數(shù)據(jù),就能借助zk通知到關(guān)注的dbClient,這種事件通知機制相比于輪詢請求sentinel要配置數(shù)據(jù)的機制更加優(yōu)雅。

看下最后的架構(gòu)圖:

基于Redis 如何構(gòu)建數(shù)據(jù)服務(wù)?


來源:公眾賬號

您還未登錄,請先登錄

熱門帖子

最新帖子

?