1. 程式人生 > >構建高可用分散式Key-Value儲存服務

構建高可用分散式Key-Value儲存服務

前言

當我們構建服務端應用的時候,都會面臨資料存放的問題。不同的資料型別有不同的存放方式,譬如關係型資料通常使用MySQL來儲存,文件型資料則會考慮使用MongoDB,而這裡,我們僅僅考慮最簡單的kv(key-value)。

kv的使用場景很多,一個很典型的場景就是使用者session的存放,key為使用者當前的session id,而value則是使用者當前會話需要儲存的一些資訊。因為kv的場景很多,所以選擇一個好的kv服務就很重要了。

對於筆者來說,一個不錯的kv服務可能僅僅需要滿足如下幾點就夠了:

  • 協議簡單
  • 高效能
  • 高可用
  • 易擴容

市面上已經有很多滿足條件kv服務,但筆者秉著no zuo no die的精神,決定使用

LedisDB + xcodis + redis-failover來構建一個高可用分散式kv儲存服務。

現有解決方案

在繼續說明之前,筆者想說說曾經考慮使用或者已經使用的一些解決方案。

MySQL

好吧,別笑,我真的說的是MySQL。MySQL作為一個關係型資料庫,用來儲存kv效能真心一點都不差。table的結構很簡單,可能如下:

CREATE TABLE kv (
    k VARBINARY(256),
    v BLOB,
    PRIMARY KEY(k),
) ENGINE=innodb;

當我還在騰訊互動娛樂部門的時候,一些遊戲專案就僅僅將MySQL作為kv來使用,譬如用來存放玩家資料,遊戲伺服器通過玩家id讀取對應的資料,修改,然後更新。鑑於騰訊遊戲恐怖的使用者量,MySQL能撐住直接就能說明將MySQL作為一個kv來用是可行的。

不過不知道現在還有多少遊戲專案仍然採用這種做法,畢竟筆者覺得,將MySQL作為一個kv服務,有點殺雞用牛刀的感覺,MySQL還是有點重了。

Couchbase

Couchbase是一個高效能的分散式NoSQL,它甚至能支援跨data center的備份。筆者研究了很長一段時間,但最終並沒有決定採用,主要筆者沒信心去搞定它的程式碼。

Redis

Redis是一個高效能NoSQL,它不光支援kv,同時還提供了其他的資料結構如hash,list,set,zset供外部使用。

筆者在三年前就開始使用Redis,加之Redis的程式碼簡單,很容易就能理解掌控。所以一直到現在,筆者都會優先使用Redis來儲存很多非關係型資料。自然對於kv,筆者也是採用Redis來存放的。

但Redis也有一些不足,最大的莫過於記憶體限制,Redis儲存的總資料大小最好別超過實體記憶體,不然效能會有問題。同時,筆者覺得Redis的RDB和AOF機制也比較蛋疼,RDB的時候系統可能會出現卡頓,而AOF在rewrite的時候也可能出現類似的問題。

因為記憶體的限制,所以Redis不能儲存超大量的資料,為了解決這個問題,我們只能採用cluster的方案,但是Redis官方的cluster仍然處於開發階段,並不能真正在生產環境中使用。所以筆者開發了LedisDB

LedisDB

開發LedisDB,主要就是為了解決Redis記憶體限制問題,它主要有如下特性:

  • 採用Redis協議,大部分Redis的client都能直接使用。
  • 提供類似Redis的API,支援kv,hash,list,set,zset。
  • 底層採用多種db儲存實際資料,支援rocksdb(推薦),leveldb,goleveldb,boltdb,lmdb,沒有Redis記憶體限制問題,因為將資料放到硬盤裡面了。
  • 高效能,參考benchmark,雖然比Redis略慢,但完全可用於生產環境。

一個簡單地例子:

//start ledis server
ledis-server 

//another shell
ledis-cli -p 6380

ledis> set a 1
OK
ledis> get a
"1"

可以看到,LedisDB非常類似Redis,所以使用者能很方便的從Redis遷移到LedisDB上面。在實際生產環境中,筆者建議底層選擇rocksdb作為其儲存模組,它不光效能高,同時提供了很多配置方便使用者根據特定情況進行調優(當然,理解這一堆配置可是一件很蛋疼的事情)。後續,筆者對於LedisDB的使用說明都會是基於rocksdb的。

資料安全

雖然LedisDB能儲存大量資料,並且易於使用,但是作為一個數據儲存服務,資料的安全性是一個非常需要考慮的問題。

  • LedisDB提供了dump和load工具,我們可以很方便的對其備份。在dump的時候,我們僅僅使用的是rocksdb的snapshot機制,非常快速,同時不會阻塞當前服務。這點可能是相對於Redis RDB的優勢。雖然Redis的RDB在save的時候也是fork一個子程序進行處理,但如果Redis的資料量巨大,仍然可能造成Redis的卡頓。
  • LedisDB提供類似MySQL的binlog支援,任何操作都是寫入binlog之後再最終提交到底層db的。如果服務崩潰,我們能通過binlog進行資料恢復。binlog檔案有大小限制,當超過閥值之後,LedisDB會寫入一個新的binlog中,而不是像Redis的AOF一樣進行rewrite處理。
  • LedisDB支援同步或者非同步replication,同步複製能保證資料的強一致,但是會犧牲系統的效能,而非同步複製雖然高效,但可能會面對資料丟失問題。這其實就是一個CAP選擇問題,在P(partition tolerance)鐵定存在的情況下,選擇C(consistency)還是選擇A(availability)?通常情況下,筆者會選擇A。

故障轉移

在生產環境中,為了保證資料安全,一個master我們會通常配備一個或者多個slave(筆者喜歡將其稱為replication topology),當master當掉的時候,監控系統會選擇一個最優的slave(也就是擁有master資料最多的那個),將其提升為新的master,並且將其他slave指向該new master。這套流程也就是我們通常說的failover。

Redis提供了sentinel機制來實現整個replication topology的failover。但sentinel是跟redis繫結的,所以不能直接在LedisDB上面使用,所以筆者開發了redis-failover,一個能支援redis,或者LedisDB failover的sentinel。

redis-failover通過定期向master傳送role命令來獲知當前replication topology,主要是slaves的資訊。當master當掉之後,redis-failover就會從先前獲取的slaves裡面選擇一個最優的slave,提升為master,選擇最優的演算法很簡單,通過info命令得到”slave_priority”和”slave_repl_offset”,如果哪個slave的priority最大,就選擇那個,如果priority都一樣,則選擇replication offset最大的那個。

redis-failover會存在單點問題,所以redis-failover自身需要支援cluster。redis-failover的cluster在內部選舉一個leader用來進行實際的monitor以及failover處理,當leader當掉之後,則進行重新選舉。

現階段,redis-failover可以通過外部的zookeeper進行leader選舉,同時也支援內部自身通過raft演算法進行leader選舉。

分散式叢集

隨著資料量的持續增大,單臺機器最終無法儲存所有資料,我們不得不考慮通過cluster的方式來解決,也就是將資料放到不同的機器上面去。

要構建LedisDB的cluster,筆者考慮瞭如下三種方案,這裡,我們不說啥hash取模或者consistency hash了,如果cluster真能通過這兩種技術簡單搞定,那還要這麼費力幹啥。

  • Redis cluster。

    redis cluster是redis官方提供的cluster解決方案,效能高,並且能支援resharding。可是直到現在,redis cluster仍處於開發階段,至少筆者是不敢將其用於生產環境中。另外,筆者覺得它真的很複雜,還是別浪費腦細胞去搞定這套架構了。

  • 定製client。

    通過定製client,我們可以知道不同key的路由規則,自然就能找到實際的資料了。這方面的工作我的一位盆友正在進行,但定製client有一個很嚴重的問題在於所有的client都必須自己實現,其實不算是一個通用的解決方案。

  • Proxy

    記得有人說過,電腦科學領域的任何問題, 都可以通過新增一箇中間層來解決。而proxy則是用來解決cluster問題的一箇中間層。

    Twemproxy是一個很不錯的選擇,但是它不能支援resharding,而且貌似twitter內部也沒在使用了,所以筆者並不考慮使用。

    本來筆者打算自己寫一個proxy,但這時候,codis橫空出世,它是一個分散式的proxy,同時支援resharding,並且在豌豆莢的生產環境中得到驗證,筆者立刻就決定使用codis了。

    但codis並不支援LedisDB,同時為了滿足他們自身的需求,使用的也是一個修改版的redis,鑑於此,筆者實現了xcodis,一個基於codis的,支援LedisDB以及原生redis的proxy。

架構

最終的架構如下。

kv architecture

我們通過使用LedisDB來解決了Redis單機資料容量問題,通過replication機制保證資料安全性,通過redis-failover用來進行failover處理,最後通過xcodis進行叢集管理。

當前,這套架構並沒有在生產環境中得到驗證,但我們一直在內部不斷測試,而且國外也有使用者在幫助筆者驗證這套架構,所以筆者對其還是很有信心的,希望能早日上線。如果有哪位童鞋也對這套架構感興趣,想吃螃蟹的,筆者非常願意提供支援。