由于Redis出眾的性能,其在眾多的移動(dòng)互聯(lián)網(wǎng)企業(yè)中得到廣泛的應(yīng)用。Redis在3.0版本前只支持單實(shí)例模式,雖然現(xiàn)在的服務(wù)器內(nèi)存可以到100GB、200GB的規(guī)模,但是單實(shí)例模式限制了Redis沒法滿足業(yè)務(wù)的需求(例如新浪微博就曾經(jīng)用Redis存儲(chǔ)了超過1TB的數(shù)據(jù))。Redis的開發(fā)者Antirez早在博客上就提出在Redis 3.0版本中加入集群的功能,但3.0版本等到2015年才發(fā)布正式版。各大企業(yè)在3.0版本還沒發(fā)布前為了解決Redis的存儲(chǔ)瓶頸,紛紛推出了各自的Redis集群方案。這些方案的核心思想是把數(shù)據(jù)分片(sharding)存儲(chǔ)在多個(gè)Redis實(shí)例中,每一片就是一個(gè)Redis實(shí)例。
下面介紹Redis的集群方案。
客戶端分片是把分片的邏輯放在Redis客戶端實(shí)現(xiàn),通過Redis客戶端預(yù)先定義好的路由規(guī)則,把對(duì)Key的訪問轉(zhuǎn)發(fā)到不同的Redis實(shí)例中,最后把返回結(jié)果匯集。這種方案的模式如圖1所示。
圖1 客戶端分片的模式
客戶端分片的好處是所有的邏輯都是可控的,不依賴于第三方分布式中間件。開發(fā)人員清楚怎么實(shí)現(xiàn)分片、路由的規(guī)則,不用擔(dān)心踩坑。
客戶端分片方案有下面這些缺點(diǎn)。
這是一種靜態(tài)的分片方案,需要增加或者減少Redis實(shí)例的數(shù)量,需要手工調(diào)整分片的程序。
可運(yùn)維性差,集群的數(shù)據(jù)出了任何問題都需要運(yùn)維人員和開發(fā)人員一起合作,減緩了解決問題的速度,增加了跨部門溝通的成本。
在不同的客戶端程序中,維護(hù)相同的分片邏輯成本巨大。例如,系統(tǒng)中有兩套業(yè)務(wù)系統(tǒng)共用一套R(shí)edis集群,一套業(yè)務(wù)系統(tǒng)用Java實(shí)現(xiàn),另一套業(yè)務(wù)系統(tǒng)用PHP實(shí)現(xiàn)。為了保證分片邏輯的一致性,在Java客戶端中實(shí)現(xiàn)的分片邏輯也需要在PHP客戶端實(shí)現(xiàn)一次。相同的邏輯在不同的系統(tǒng)中分別實(shí)現(xiàn),這種設(shè)計(jì)本來就非常糟糕,而且需要耗費(fèi)巨大的開發(fā)成本保證兩套業(yè)務(wù)系統(tǒng)分片邏輯的一致性。
2.Twemproxy
Twemproxy是由Twitter開源的Redis代理,其基本原理是:Redis客戶端把請(qǐng)求發(fā)送到Twemproxy,Twemproxy根據(jù)路由規(guī)則發(fā)送到正確的Redis實(shí)例,最后Twemproxy把結(jié)果匯集返回給客戶端。
Twemproxy通過引入一個(gè)代理層,將多個(gè)Redis實(shí)例進(jìn)行統(tǒng)一管理,使Redis客戶端只需要在Twemproxy上進(jìn)行操作,而不需要關(guān)心后面有多少個(gè)Redis實(shí)例,從而實(shí)現(xiàn)了Redis集群。
Twemproxy集群架構(gòu)如圖2所示。
圖2Twemproxy集群架構(gòu)
Twemproxy的優(yōu)點(diǎn)如下。
客戶端像連接Redis實(shí)例一樣連接Twemproxy,不需要改任何的代碼邏輯。
支持無效Redis實(shí)例的自動(dòng)刪除。
Twemproxy與Redis實(shí)例保持連接,減少了客戶端與Redis實(shí)例的連接數(shù)。
Twemproxy有如下不足。
由于Redis客戶端的每個(gè)請(qǐng)求都經(jīng)過Twemproxy代理才能到達(dá)Redis服務(wù)器,這個(gè)過程中會(huì)產(chǎn)生性能損失。
沒有友好的監(jiān)控管理后臺(tái)界面,不利于運(yùn)維監(jiān)控。
最大的問題是Twemproxy無法平滑地增加Redis實(shí)例。對(duì)于運(yùn)維人員來說,當(dāng)因?yàn)闃I(yè)務(wù)需要增加Redis實(shí)例時(shí)工作量非常大。
Twemproxy作為最被廣泛使用、最久經(jīng)考驗(yàn)、穩(wěn)定性最高的Redis代理,在業(yè)界被廣泛使用。
3.Codis
Twemproxy不能平滑增加Redis實(shí)例的問題帶來了很大的不便,于是豌豆莢自主研發(fā)了Codis,一個(gè)支持平滑增加Redis實(shí)例的Redis代理軟件,其基于Go和C語言開發(fā),并于2014年11月在GitHub上開源。
Codis包含下面4個(gè)部分。
Codis Proxy:Redis客戶端連接到Redis實(shí)例的代理,實(shí)現(xiàn)了Redis的協(xié)議,Redis客戶端連接到Codis Proxy進(jìn)行各種操作。Codis Proxy是無狀態(tài)的,可以用Keepalived等負(fù)載均衡軟件部署多個(gè)Codis Proxy實(shí)現(xiàn)高可用。
CodisRedis:Codis項(xiàng)目維護(hù)的Redis分支,添加了slot和原子的數(shù)據(jù)遷移命令。Codis上層的 Codis Proxy和Codisconfig只有與這個(gè)版本的Redis通信才能正常運(yùn)行。
Codisconfig:Codis管理工具。可以執(zhí)行添加刪除CodisRedis節(jié)點(diǎn)、添加刪除Codis Proxy、數(shù)據(jù)遷移等操作。另外,Codisconfig自帶了HTTP server,里面集成了一個(gè)管理界面,方便運(yùn)維人員觀察Codis集群的狀態(tài)和進(jìn)行相關(guān)的操作,極大提高了運(yùn)維的方便性,彌補(bǔ)了Twemproxy的缺點(diǎn)。
ZooKeeper:分布式的、開源的應(yīng)用程序協(xié)調(diào)服務(wù),是Hadoop和Hbase的重要組件,其為分布式應(yīng)用提供一致性服務(wù),提供的功能包括:配置維護(hù)、名字服務(wù)、分布式同步、組服務(wù)等。Codis依賴于ZooKeeper存儲(chǔ)數(shù)據(jù)路由表的信息和Codis Proxy節(jié)點(diǎn)的元信息。另外,Codisconfig發(fā)起的命令都會(huì)通過ZooKeeper同步到CodisProxy的節(jié)點(diǎn)。
Codis的架構(gòu)如圖3所示。
圖3Codis的架構(gòu)圖
在圖3的Codis的架構(gòu)圖中,Codis引入了Redis Server Group,其通過指定一個(gè)主CodisRedis和一個(gè)或多個(gè)從CodisRedis,實(shí)現(xiàn)了Redis集群的高可用。當(dāng)一個(gè)主CodisRedis掛掉時(shí),Codis不會(huì)自動(dòng)把一個(gè)從CodisRedis提升為主CodisRedis,這涉及數(shù)據(jù)的一致性問題(Redis本身的數(shù)據(jù)同步是采用主從異步復(fù)制,當(dāng)數(shù)據(jù)在主CodisRedis寫入成功時(shí),從CodisRedis是否已讀入這個(gè)數(shù)據(jù)是沒法保證的),需要管理員在管理界面上手動(dòng)把從CodisRedis提升為主CodisRedis。
如果覺得麻煩,豌豆莢也提供了一個(gè)工具Codis-ha,這個(gè)工具會(huì)在檢測(cè)到主CodisRedis掛掉的時(shí)候?qū)⑵湎戮€并提升一個(gè)從CodisRedis為主CodisRedis。
Codis中采用預(yù)分片的形式,啟動(dòng)的時(shí)候就創(chuàng)建了1024個(gè)slot,1個(gè)slot相當(dāng)于1個(gè)箱子,每個(gè)箱子有固定的編號(hào),范圍是1~1024。slot這個(gè)箱子用作存放Key,至于Key存放到哪個(gè)箱子,可以通過算法“crc32(key)%1024”獲得一個(gè)數(shù)字,這個(gè)數(shù)字的范圍一定是1~1024之間,Key就放到這個(gè)數(shù)字對(duì)應(yīng)的slot。例如,如果某個(gè)Key通過算法“crc32(key)%1024”得到的數(shù)字是5,就放到編碼為5的slot(箱子)。1個(gè)slot只能放1個(gè)Redis Server Group,不能把1個(gè)slot放到多個(gè)Redis Server Group中。1個(gè)Redis Server Group最少可以存放1個(gè)slot,最大可以存放1024個(gè)slot。因此,Codis中最多可以指定1024個(gè)Redis Server Group。
Codis最大的優(yōu)勢(shì)在于支持平滑增加(減少)Redis Server Group(Redis實(shí)例),能安全、透明地遷移數(shù)據(jù),這也是Codis 有別于Twemproxy等靜態(tài)分布式 Redis 解決方案的地方。Codis增加了Redis Server Group后,就牽涉到slot的遷移問題。例如,系統(tǒng)有兩個(gè)Redis Server Group,Redis Server Group和slot的對(duì)應(yīng)關(guān)系如下。
當(dāng)增加了一個(gè)Redis Server Group,slot就要重新分配了。Codis分配slot有兩種方法。
第一種:通過Codis管理工具Codisconfig手動(dòng)重新分配,指定每個(gè)Redis Server Group所對(duì)應(yīng)的slot的范圍,例如可以指定Redis Server Group和slot的新的對(duì)應(yīng)關(guān)系如下。
第二種:通過Codis管理工具Codisconfig的rebalance功能,會(huì)自動(dòng)根據(jù)每個(gè)Redis Server Group的內(nèi)存對(duì)slot進(jìn)行遷移,以實(shí)現(xiàn)數(shù)據(jù)的均衡。
Redis 3.0集群采用了P2P的模式,完全去中心化。Redis把所有的Key分成了16384個(gè)slot,每個(gè)Redis實(shí)例負(fù)責(zé)其中一部分slot。集群中的所有信息(節(jié)點(diǎn)、端口、slot等),都通過節(jié)點(diǎn)之間定期的數(shù)據(jù)交換而更新。
Redis客戶端在任意一個(gè)Redis實(shí)例發(fā)出請(qǐng)求,如果所需數(shù)據(jù)不在該實(shí)例中,通過重定向命令引導(dǎo)客戶端訪問所需的實(shí)例。
Redis 3.0集群的工作流程如圖4所示。
圖4Redis 3.0集群的工作流程圖
如圖4所示Redis集群內(nèi)的機(jī)器定期交換數(shù)據(jù),工作流程如下。
(1) Redis客戶端在Redis2實(shí)例上訪問某個(gè)數(shù)據(jù)。
(2) 在Redis2內(nèi)發(fā)現(xiàn)這個(gè)數(shù)據(jù)是在Redis3這個(gè)實(shí)例中,給Redis客戶端發(fā)送一個(gè)重定向的命令。
(3) Redis客戶端收到重定向命令后,訪問Redis3實(shí)例獲取所需的數(shù)據(jù)。
Redis 3.0的集群方案有以下兩個(gè)問題。
一個(gè)Redis實(shí)例具備了“數(shù)據(jù)存儲(chǔ)”和“路由重定向”,完全去中心化的設(shè)計(jì)。這帶來的好處是部署非常簡(jiǎn)單,直接部署Redis就行,不像Codis有那么多的組件和依賴。但帶來的問題是很難對(duì)業(yè)務(wù)進(jìn)行無痛的升級(jí),如果哪天Redis集群出了什么嚴(yán)重的Bug,就只能回滾整個(gè)Redis集群。
對(duì)協(xié)議進(jìn)行了較大的修改,對(duì)應(yīng)的Redis客戶端也需要升級(jí)。升級(jí)Redis客戶端后誰能確保沒有Bug?而且對(duì)于線上已經(jīng)大規(guī)模運(yùn)行的業(yè)務(wù),升級(jí)代碼中的Redis客戶端也是一個(gè)很麻煩的事情。
綜合上面所述的兩個(gè)問題,Redis 3.0集群在業(yè)界并沒有被大規(guī)模使用。
國(guó)內(nèi)的云服務(wù)器提供商阿里云、UCloud等均推出了基于Redis的云存儲(chǔ)服務(wù)。
這個(gè)服務(wù)的特性如下。
(1)動(dòng)態(tài)擴(kuò)容
用戶可以通過控制面板升級(jí)所需的Redis存儲(chǔ)空間,擴(kuò)容的過程中服務(wù)部不需要中斷或停止,整個(gè)擴(kuò)容過程對(duì)用戶透明、無感知,這點(diǎn)是非常實(shí)用的,在前面介紹的方案中,解決Redis平滑擴(kuò)容是個(gè)很煩瑣的任務(wù),現(xiàn)在按幾下鼠標(biāo)就能搞定,大大減少了運(yùn)維的負(fù)擔(dān)。
(2)數(shù)據(jù)多備
數(shù)據(jù)保存在一主一備兩臺(tái)機(jī)器中,其中一臺(tái)機(jī)器宕機(jī)了,數(shù)據(jù)還在另外一臺(tái)機(jī)器上有備份。
(3)自動(dòng)容災(zāi)
主機(jī)宕機(jī)后系統(tǒng)能自動(dòng)檢測(cè)并切換到備機(jī)上,實(shí)現(xiàn)服務(wù)的高可用。
(4)實(shí)惠
很多情況下為了使Redis的性能更高,需要購(gòu)買一臺(tái)專門的服務(wù)器用于Redis的存儲(chǔ)服務(wù),但這樣子CPU、內(nèi)存等資源就浪費(fèi)了,購(gòu)買Redis云存儲(chǔ)服務(wù)就很好地解決了這個(gè)問題。
有了Redis云存儲(chǔ)服務(wù),能使App后臺(tái)開發(fā)人員從煩瑣運(yùn)維中解放出來。App后臺(tái)要搭建一個(gè)高可用、高性能的Redis服務(wù),需要投入相當(dāng)?shù)倪\(yùn)維成本和精力。如果使用云存儲(chǔ)服務(wù),就沒必要投入這些成本和精力,可以讓App后臺(tái)開發(fā)人員更專注于業(yè)務(wù)。