朱曄的網際網路架構實踐心得S1E10:數據的權衡和折騰「系列完」

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

第一部分:數據的權衡

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

空間和時間

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

· 讓數據在更快的地方:也就是緩存。速度和價格本來就是矛盾的,我們不可能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經過這麼多年的發展,形成了一些設計模式和技巧,理解這些更多的意義在於我們可以在我們的軟件設計和架構中借鑑,在《軟件內部設計模式》系列文章中可以聊聊這部分的一些所見。


分享到:


相關文章: