這裡所說的五件套是指關係型資料庫、索引型資料庫、時序型資料庫、文件型資料庫和快取型資料庫。
上圖顯示了一套讀寫服務搭配這五種型別資料庫的例子:
1. 這裡只是說明了我們可以這麼來搭配這些型別的資料庫,不是說我們所有的應用都需要用到這些型別的資料庫。
2. 同步寫服務負責第一時間把重要的資料落地和落快取。
3. 非同步寫服務通過監聽MQ來感知資料的變化,然後重新讀取最新的資料來把資料寫入其它次要資料來源,比如文件性資料庫和索引型資料庫,需要的話可以在快取中回寫一個狀態。
4. 由一個專門的資料查詢服務來根據需求做資料路由,根據需求和效能因素,從不同的資料來源讀取資料。
5. 資料聚合服務根據需求從次要資料來源進一步讀取資料以時間維度進行聚合,聚合到時間序列資料庫,供監控查詢服務查詢。
下面我們來具體說說這些儲存系統。
關係型資料庫
毫無疑問,強事務性的資料寫入MySQL之類的關係型資料庫是最可靠的,搭配SSD盤的使用,關係型資料庫也很容易達到萬級的QPS。對於超大資料量加上超大併發的應用來說,單表的資料量過千萬伴隨著數萬的QPS很難以單體資料庫來支撐,我們需要對資料表進行Sharding分片處理,把資料按照一定的維度切分到比如128個資料表,然後分散在8套甚至16套資料叢集,這樣每一臺MySQL的例項只需要承受1/8或1/16的請求壓力而且資料量更小。隨之帶來的問題是,我們需要對應用進行改造,使之只能按照一定的查詢條件來查詢這個切片後的表,如果不帶條件或帶任意條件的話,我們是無法知道資料實際儲存在哪個表哪個例項上的。
這確實是一個比較麻煩的地方,我們的查詢條件可能有十幾個,只能按照一個維度來查詢滿足不了我們的需求。一個折中的方式是我們引入所謂的Index資料表,也就是在寫入實際的完整資料到Sharding的資料表的同時,我們把資料表裡需要查詢的欄位寫入一個專門的沒有經過Sharding處理的Index資料表,這個資料表裡存放的幾乎沒有varchar型別的資料,全部是各種bigint的各類業務ID或是tinyint型別的各種狀態,以及時間。由於這個表非常親,雖然資料條數多但是表空間幾乎可以在資料庫的快取中容納,效能會高不少。對於實時性要求非常強的基於條件的查詢可以從這個資料表來進行查詢。而Sharding後的資料只能用於按ShardKey來進行查詢。
快取資料庫
Redis是最常用的分散式快取解決方案,幾乎在任何網際網路應用中都會用到,特點是:
1. 能持久化資料,但是我的觀點是快取資料庫還是僅僅作為快取的好,要能夠承受丟失資料的風險,否則可能會死的比較難看。因為RDB或主從複製導致的一些事故也是層出不窮的。
2. 豐富的資料結構是一定要利用的,豐富的資料結構代表了可以依賴豐富的API在服務端做複雜的運算,效能比反序列化取出後運算再序列化存入效率高的多。有的時候甚至可以把這些資料結構和API組合在一起碰撞出絕妙的方案以極高效的方式實現一個高效能的業務邏輯。可以看看《Redis實戰》一書。
3. 超高的效能(當然了,配合一些叢集方案比如codis就更上一層樓了)足以抵擋任何業務請求的直接訪問,很多時候快取的方案掛是掛在因為各種各樣的原因穿透快取而不是Redis檔不住。
4. 豐富的叢集和高可用方案以及各類各種實用的功能(管道、事務、Lua指令碼),5.0的版本還推出了Stream特性來替代少有人關注的Disque值得關注。
所以Redis的應用也很廣泛:
· 資料快取
· 分散式鎖
· 訊息佇列
· 服務端運算
在上圖的架構中,我們通過同步寫服務對資料庫和快取進行雙寫,目的也就是為了讓快取中能有新鮮熱資料,不管是對內還是對外這種單條資料的查詢可以直接路由到快取。
文件型資料庫
文件型資料庫的代表就是耕耘多年的Mongodb,我在一些非重要業務的場景使用過Mongodb幾次,我的評價如下(最近1年多沒有碰過Mongodb,也可能評價有失偏頗):
1. 超高的寫入效能,非常不錯的讀取效能(和Redis是不能比的,性質不同),資料量增多後可能會有很厲害的效能衰退,不是Hbase那種無底洞型的儲存,不維護就往裡面一直堆資料進去最後的效能可能比如MySQL。
2. 因為存的是文件,所以是弱結構的,存一些事先不能確定的資料非常非常合適,而且以後要查的時候可以任何加索引對需要的資料進行搜尋查詢。一個很實用的場景就是作為爬蟲的資料來源,資料變化多端而且不那麼重要,而且寫入效能很重要。
3. 不太可靠和穩定,可能會丟資料,強烈不建議作為核心資料儲存,建議作為一個旁路資料庫用在非關鍵的業務。比如在上圖的架構圖中,我們可能會拿到核心資料後再從其它地方去補一些資料然後進行適當的加工,儲存到Mongodb作為一個監控資料庫或者面向後臺的資料庫來用(MEAN套件之一,可以想象對於簡單的應用來說配合指令碼語言用起來多舒服了),掛了也就掛了,沒掛的話可以分擔很多MySQL的壓力。
4. 玩法雖然多,什麼Sharding、複製、叢集都有,但隨著資料量的增多運維可能是一個大坑,很可能遇到叢集全軍覆沒無法啟動的情況,資料的恢復耗時很長。記憶體的使用相當瘋狂,對硬體的使用總感覺價效比不高。
索引型資料庫
ElasticSearch作為其代表是最近幾年的黑馬。ELK叢集各大網際網路公司都有使用,只要叢集配置得當,每秒幾十萬的寫入不是大問題,畢竟徹底的分散式化理論上可以有無限高的寫入能力。ES的特點如下:
1. 非常豐富的查詢API,不僅僅是全文索引查詢,普通的查詢API豐富多樣,組合起來可以在服務端完成各種業務邏輯,基本上SQL+MySQL可以實現的,ES查詢都可以實現,而且還多了更強大的全文搜尋。當然,查詢的語法稍顯晦澀肯定沒有SQL來的直掛。
2. 類似於Mongodb的schema-free,無需實現定義表結構。
3. 還算強大的寫入和讀取能力,當然,索引多的話寫入文件的效率肯定會降低。這也是圖中對於ES的寫入由專門的非同步流程進行的原因。
4. ES天生的分散式配置決定了,在寫入億、十億的資料量之後,還能在相當可以接受的時間內(比如10秒)完成一個多條件複雜查詢,對於MySQL這個量級下這樣的查詢可能需要10分鐘甚至100分鐘的時間來執行,完全不能接受。
5. ES對巢狀型資料的查詢支援不錯,經過測試我們傾向於把多標關聯的資料作為一個大的巢狀的JSON拍扁了直接存入ES,比如我們可以把使用者個人唯獨的基本資訊+充值訂單+提現訂單+投資訂單,一人一個JSON存進去,然後對於巢狀的下層JSON資料也是可以方便的利用查詢API進行查詢。
因為這些特點,在這個架構圖上,我們把ES也作為了查詢服務的資料來源,對於滿足下面這些條件的查詢,我們可以走ES:
· 對資料延遲不敏感,可以接受一段時間查不到新鮮資料
· 查詢特別複雜,或是全文搜尋,不能走Sharding後的RouteKey,Index表也無法滿足需求
· 查詢的結果也不僅僅是單表的資料而是比較豐富的資料,查詢資料庫需要查詢多個表多次
索引型資料庫和文件型資料庫的底層儲存結構是截然不同的,雖然現在有很多人使用ES來完全替代Mongodb,但是個人覺得ES適合存比Mongodb更大的一個資料量,分散式不利用起來發揮不了ES,Mongodb還是適合中型資料非Sharding的儲存。
時序型資料庫
InfluxDb是時序型資料庫的代表。對於按照時間段進行Group By查詢的話,不管是ES還是MySQL還是Mongodb在API層面當然都是支援的,但是查詢效率不堪入目。因此對於諸如下面的需求首當其中可以考慮時序型資料庫:
· 監控圖表
· 按時間維度聚合
· 查詢的時間維度可以跨度很長
· 需要定期歸檔
如果使用傳統方案的話,我們往往會以固定的時間維度來聚合儲存資料,如果我們要查1小時和1年的維度,都使用5秒的聚合粒度顯然不合適,我們需要在寫入資料到時候針對不同的粒度進行聚合,需要一定的工作量,使用時間序列資料庫可以少一些這樣的煩惱。而且InfluxDb之類的資料庫的效能是非常高的,寫入資料的效能堪比Redis,單節點甚至可以承受十萬指標的寫入,基本可以滿足大部分應用場景的需求。對於一些業務指標的監控,業務事件的打點,業務資料的時間維度聚合,我們完全可以考慮引入專門的時序型資料庫。
綜上所述,這裡的架構圖只是體現了幾個重要思想:
1. 使用專門的服務來做資料的寫入和讀取,方便進行路由。
2. 合理規劃好Sharding的方式,以及想好RDBMS在Sharding後的全套查詢方案。
3. 資料的寫入區分主要資料來源的同步寫入和次要資料來源的非同步寫入,讓主流程更快。
4. 合理利用不同資料來源的特性,組合使用發揮所長,避免所短。
5. 資料的加工可以是一個層級的關係,可以由專門業務中介軟體來進行資料加工。
6. RDBMS以外的資料庫如果打算作為主核心儲存引擎的話千萬慎重思考。
7. 採用豐富的資料來源意味著維護成本的增多,資料不同步的問題在所難免,需要考慮一下我們是否可以接受一定層度的資料不一致。