朱曄的網際網路架構實踐心得S1E10:資料的權衡和折騰【系列完】

powerzhuye發表於2019-02-25

本文站在資料的維度談一下在架構設計中的一些方案對資料的權衡以及資料流轉過程中的折騰這兩個事情。最後進行系列文章的總結和之後系列文章寫作計劃的一些展望。

資料的權衡

正所謂魚和熊掌不能兼得,舍了才能得。架構或技術設計方案中針對資料這個事情,有太多體現了權衡思想的地方。

空間和時間

我們來想想有哪些廣義上空間換時間(效能)的例子,也就是通過使用更多的儲存或記憶體空間加快了任務的單次執行速度或總體吞吐量:

·
讓資料在更快的地方:也就是快取。速度和價格本來就是矛盾的,我們不可能10萬買到百公里加速在4秒內的高效能跑車。儲存雖便宜但是速度慢,記憶體雖然貴但是速度快,使用級聯的快取儲存方案我們可以在這當中做一個平衡。不僅僅是架構設計上我們幾乎都會用到快取,CPU會有多級快取,OS也有頁面快取機制。

·
讓資料一次性提交:也就是緩衝。在進行IO操作的時候,真正和磁碟和網路互動之前,我們往往都會建立緩衝區。在大多數的時候進行IO操作對於10位元組和100位元組的資料需要的IO時間是一樣的,我們可以在緩衝區進行短時間的資料累積後一次性進行操作,這種做法不一定能提高單次執行效能但是可以增加吞吐(對於繁忙的系統,吞吐達到瓶頸後單次的執行會排隊,所以反過來也可以認為提高單次效能)。

·
讓資料更靠近使用者:CDN就是一個典型應用。讓資料離使用者更近意味資料不需要經過太多的機房和鏈路交換就可以到達使用者終端,顯然可以提高訪問效能。其實說白了就是讓資料在離使用者更近的地方快取一份,在客戶端快取也算。

·
讓資料面向查詢儲存:相對於面向儲存優化。常見的儲存資料結構上,我們知道寫入效能最好的是追加檔案的日誌儲存,然後是LSM樹然後是B+樹,讀取效能則反過來。為了效能我們通常會在儲存資料時候進行一定的排序分類然後按一定的資料結構儲存,而不僅僅是把原始資訊存下來,這樣在查詢搜尋的時候避免了資料全掃。這些特殊的(甚至有的時候是額外的)資料結構的維護也體現了空間換時間。

·
讓資料面向輸出優化:之前說的物化檢視就是一個例子,在儲存資料的時候直接儲存我們最終需要查詢的資料,這樣查詢的時候就不需要做各種關聯。在微博架構設計的時候我們往往會更極端,為每個人維護一個資訊流佇列,在發微博的時候直接為所有的粉絲的佇列追加資料,這樣就避免了首頁查詢時候非常誇張的Join操作。

·
讓資料複製多份:複製多份資料意味著我們可以有多個相同的資料來源來為讀取做服務。之前也提到過,雖然這是為伸縮性考慮,但是一定程度上其實也可以提高效能。

·
讓資料計算默默進行:搞兩份資料,一份資料是使用者現在就在看的,另一份資料是新版本的使用者將要看到的資料,通過在後臺另一塊空間單獨處理這份新資料,處理完成後再展現給使用者就相當於使用者不需要花費時間等待資料的處理加工了。

·
浪費一半的空間倒騰:這裡說的是類似於JVM的新生代的複製演算法。始終有兩塊大小一致的倖存區,在需要回收的時候可以將一個倖存區和伊甸園中的有用物件一次性搬到另一塊區域中。雖然浪費了空間,但是這種每次都給我一張新的紙來畫圖的方式解決了碎片化的問題。

隨著儲存和頻寬的優化,網際網路架構更多考慮空間換時間,在有明顯頻寬和儲存瓶頸的視訊圖片等資源的傳輸上,我們會採取壓縮的方式用時間來換空間。

一致性和可用性

對於分散式儲存,資料複製多份(分片)儲存。在出現網路故障的時候,節點之間的資料無法一致,這個時候如果我們追求資料一致性那麼就只能等待系統恢復再去使用這個系統,這個時候系統就不可用,當然我們也可以放棄一致性的追求先湊合用系統,這個過程中節點之間的資料無法確保一致。分散式的情況下受到外部客觀因素的限制,不可能兩者都保證,我們只能根據業務需求來決定放棄哪個。有關CAP有很多討論,對於各種分散式儲存也有按照CP(偏向於一致性)和AP(偏向於可用性)進行了分類,目前其實大多數的系統都把這個選擇權交給了客戶端交給了使用者,在使用資料的時候我們可以選擇是CP還是AP,所以不能簡單認定某個儲存就是CP或AP的。

資料查詢的專有DSL(領域專用語言)和通用查詢語言

類似Mongodb、ElasticSearch、HBase等儲存系統都有自己的查詢DSL,關係型資料庫大多支援以SQL這種通用查詢語言進行資料查詢。ElasticSearch現已支援了SQL查詢,基於HBase也有很多元件提供SQL查詢(比如Phoenix)。我們知道每一種儲存引擎都有其特點,專有DSL可以做到讓你以引擎最合適的方式來做查詢,支援了通用的SQL後往往會產生濫用導致的效能問題,但是可以帶來無比的便利性(語言和語言之間的翻譯是很痛苦的,而且這個翻譯往往是有損的)。在我們自己做服務設計對外API,設計業務邏輯處理引擎的時候,其實也會遇到這樣的設計層次問題。我們是提供專門的API來允許外部操作我們的資料呢,還是開發出一套DSL允許外部使用這套語言來做複合查詢,甚至直接支援類似SQL這種通用語言(直通資料庫)。比如在做風控規則引擎的時候,我們可以把每一條規則作為寫死的程式碼邏輯來寫,也可以開發出一套DSL讓風控專家可以配置資料欄位和規則,甚至可以直接使用SQL進行配置,開發報表系統、監控系統也是類似的道理。

資料的折騰

從資料的角度我們來看看在網際網路系統中,資料到底經歷了什麼,體現在資料的儲存和流轉兩方面。就來說說使用者的註冊這一過程吧:

1.
使用者填寫了登錄檔單,產生了使用者名稱、密碼這一註冊資料

2.
Web網站服務端從HTTP(S)請求的Body中收到了Key=Value形式的資料

3.
資料通過Thrift二進位制協議編碼傳輸給了使用者服務(RPC呼叫),在這個過程中其實只有 Value編碼了進去參與傳輸

4.
在使用者服務服務端收到資料後根據介面IDL和資料拼在一起反序列化讓不同的資料欄位又有了含義

5.
使用者服務把這個資訊儲存到了關係型資料庫MySQL(以MySQL二進位制協議在網路傳輸),資料成為了一份RedoLog後以B樹形式資料結構在磁碟上儲存

6.
MySQL主庫又把資料以SQL語句(或資料行)的形式把資料複製到從庫

7.
使用者服務隨後又把獲得的使用者Id以及使用者的基本Profile資訊以Key為字串Value為Json文字的形式在Redis中進行快取

8.
使用者服務然後把有新使用者註冊這個事件作為一個訊息(Json序列化)推送到MQ Broker上

9.
(非同步)紅包服務監聽了這個訊息,在收到訊息後重新把位元組流轉換為事件Json物件為新使用者派發紅包

10. (非同步)風控服務也監聽了訊息,從事件中提取出IP地址等資訊,使用CEP複雜事件處理技術對訊息進行處理然後觸發報警

11. (非同步)大資料倉儲服務也監聽了訊息,從資料庫查詢使用者完整的資訊並且把資料儲存到了HBase中以LSM樹儲存

12. 在使用者服務完成處理後,Web網站也就知道了使用者註冊成功了告知使用者註冊成功的訊息,然後使用者嘗試登陸,由於主從同步的延遲,資料庫從庫沒有查詢到新註冊的使用者,使用者迷茫了,我註冊成功了但我是誰呢(這裡開個玩笑,這是一個設計錯誤,一般而言對於自己產生的資料的查詢原則是隻能在主庫查詢)

所以我們看到,網際網路分散式架構的複雜性在於一份資料可能會以不同的形式做轉換不同的通訊結構走傳輸,不用的資料結構做儲存,在每一個環節,資料可能都以不同的形式體現,資料本身和資料代表的意義可能頭尾分離。如果我們對程式記憶體做一份Dump,對各個環節的TCP包做幾份Dump,對資料最終落地的儲存做幾份Dump然後在這幾份二進位制的Dump中找到並且看一下我們的資料長啥樣,看看是否還能理解資料的意義,這是不是會是一件有趣的事情呢?

就這麼簡單的一份資料,為什麼我們要這麼折騰呢?原因在於我們不得不引入某個技術來解決處理某一方面問題的時候又引入了更多的複雜度,然後我們又需要引入更多的技術來處理,迴圈往復:

·
在單體架構中,我們依靠資料庫提供的索引+事務可以解決大部分的效能和一致性問題

·
單體架構在效能、可擴充套件性、可靠性等方面不能滿足需求,我們引入了分散式架構

·
在分散式架構中,我們做了資料複製的分片,不但需要解決因網路、時鐘、資源等問題導致的節點的協調,而且需要在空間分佈和時間錯位兩個層面去考慮之前已經解決的處理的很好的事務性問題

·
我們使用不同的資料庫作為複合資料來源來取長補短,不同的資料庫在分散式架構實現上各不相同,我們需要花大量時間來做高可用的調研,同時它們在儲存模型上大不相同,我們需要進行深度研究瞭解儲存模型對於資料增量的效能衰減以及故障後的恢復等方面(諸如Mongodb、和ElasticSearch這種資料庫越來越有趨勢把自己搞成大而全面的資料儲存解決方案了,官方永遠宣傳的都是自己能幹啥而不是不能幹啥,至於不能幹啥只能靠自己去研究和總結)

·
不同的網路中介軟體都有各自不同的協議和通訊方式,協議在處理前後相容性方面各不相同,不同的語言對於基礎資料型別都有不同的處理方式,在語言和協議的互動轉化中也會有一些相容性問題

·
我們使用各種方式來提高效能,資料會以不同的形態在多處儲存,隨著時間的推移資料必然會產生不一致性,對於不一致的資料我們如何發現,發現後去容忍還是補償同步

·
隨著元件的增多,我們不希望元件的不穩定造成木桶效應,我們會引入各種彈性的方案來允許元件的暫時不穩定,我們不希望出了問題找不到來源,會引入各種監控手段來做全鏈路全元件監控

·
資料往往不能完全掌握在自己手裡,越來越多的外部三方服務會提供資料來源以及垂直領域的解決方案,和外部系統進行互動保留了分散式系統所有的困難之外,還需要考慮安全、隔離等問題

架構的複雜性在於資料,資料的複雜性在於多變的結構、資料的共享同步以及資料的增長。單機單使用者的軟體演變為B/S形式的軟體演變為SAAS形式的軟體,資料從單機到對內分散式到在整個網際網路上形成分散式越來越複雜。架構設計中可能一大半的時間在考慮資料的處理儲存問題。

系列文章總結

系列文章到這裡算是一個結束了,在本系列文章中我們沒有過多涉及具體的技術和演算法,我們從高層架構的角度闡述了我的一些實用性經驗,力求在廣度上都有涉及:

·
第一篇文章,我談了對All-In-One架構起步的看法,談了不同語言的選擇和技術團隊中業務和架構團隊的特性。

·
第二篇文章,我談了我認為網際網路架構中最重要的三要素,微服務+訊息佇列+定時任務,訊息佇列和定時任務其實體現的是資料實時流式處理和非實時批次處理的兩大流派。

·
第三篇文章,我談了如何發揮不同型別資料庫(關係型資料庫MySQL、快取型資料庫Redis、文件型資料庫Mongodb、搜尋型資料庫ElasticSearch、時間型資料庫InfluxDb)所長結合之前說的三要素做複合型資料來源,當然還有更多型別的資料庫,比如圖資料庫等等在需要的時候也可以引入做合適的業務。

·
第四篇文章,我談了如何以開源的一些專案(ElasticSearch+Logstash+Kibana、Telegraf+InfluxDb+Grafana)快速搭建起簡單的監控、日誌和資料分析平臺以及闡述了對打點和異常兩個事情的看法。

·
第五篇文章,我談了隨著專案的發展提煉打造合適的中介軟體的必要性,以及介紹了一些常見中介軟體(配置管理、服務管理、全鏈路監控、資料訪問、分散式快取、任務管理、釋出管理等)的需求功能以及設計上的注意點。

·
第六篇文章,我談了架構升級遷移的步驟和注意點,以及開發人員比較容易忽略的安全方面的問題,我總結了我認為比較重要的安全意識的十個原則(木桶效應、不信任客戶端、資料和程式碼分清楚、使用者看不到不等於黑客看不到、最小化介面許可權設計和複用的矛盾、一開始就要考慮安全、做好防刷防暴破控制、產品邏輯注意一體性、做好異常資料監控報警、對內的資料注意許可權控制和審計)。

·
第七篇和第八篇文章,我針對微軟分享的三十種雲架構設計模式(涉及監控、效能、可擴充套件、資料管理、設計實現、訊息、彈性、安全等方面)給出了自己的看法。

·
第九篇文章,我談了架構設計評審時候我們針對元件選型、效能、可伸縮性、靈活性、可擴充套件性、可靠性、安全性、相容性、彈性處理、事務性、可測試下、可運維性、監控等方面會一起討論和提出的一些點,以及寫好一篇概要的技術文件最主要的五個手段(需求腦圖、系統架構圖、對外API腦圖、互動時序圖和資料庫ER圖)。

·
本文最後從架構中最重要的資料角度展開討論了架構中做的妥協權衡以及分散式架構設計的無限複雜性。

我想了一下,有幾方面的內容可以形成其它的系列文章和大家一起探討,盡請期待:

·
Spring框架越來越完善了,龐大的產品體系和外掛式的架構讓Java開發越來越快速和靈活了,在《你不知道的Spring》系列文章中我們或許可以聊一下Spring那些我們不知道的點滴以及如何以Spring為核心做一些擴充套件。

·
對於分散式架構中的資料複製和分片、高可用、一致性處理,每一個產品都有其演算法和思路,也有非常多的爭論,在《分散式架構細節設計》系列文章中我們或許可以詳細針對微服務和分散式架構具體的一些處理方式挖掘一些細節的點一一展開討論。

·
JVM和JDK經過這麼多年的發展,形成了一些設計模式和技巧,理解這些更多的意義在於我們可以在我們的軟體設計和架構中借鑑,在《軟體內部設計模式》系列文章中可以聊聊這部分的一些所見。

相關文章