<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    jinfeng_wang

    G-G-S,D-D-U!

    BlogJava 首頁 新隨筆 聯系 聚合 管理
      400 Posts :: 0 Stories :: 296 Comments :: 0 Trackbacks
    http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-split-table
    http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table

    在談論數據庫架構和數據庫優化的時候,我們經常會聽到“分庫分表”、“分片”、“Sharding”…這樣的關鍵詞。讓人感到高興的是,這些朋友所服務的公司業務量正在(或者即將面臨)高速增長,技術方面也面臨著一些挑戰。讓人感到擔憂的是,他們系統真的就需要“分庫分表”了嗎?“分庫分表”有那么容易實踐嗎?為此,筆者整理了分庫分表中可能遇到的一些問題,并結合以往經驗介紹了對應的解決思路和建議。

    垂直分表

    垂直分表在日常開發和設計中比較常見,通俗的說法叫做“大表拆小表”,拆分是基于關系型數據庫中的“列”(字段)進行的。通常情況,某個表中的字段比較多,可以新建立一張“擴展表”,將不經常使用或者長度較大的字段拆分出去放到“擴展表”中,如下圖所示:

    小結

    在字段很多的情況下,拆分開確實更便于開發和維護(筆者曾見過某個遺留系統中,一個大表中包含100多列的)。某種意義上也能避免“跨頁”的問題(MySQL、MSSQL底層都是通過“數據頁”來存儲的,“跨頁”問題可能會造成額外的性能開銷,這里不展開,感興趣的朋友可以自行查閱相關資料進行研究)。

    拆分字段的操作建議在數據庫設計階段就做好。如果是在發展過程中拆分,則需要改寫以前的查詢語句,會額外帶來一定的成本和風險,建議謹慎。

    垂直分庫

    垂直分庫在“微服務”盛行的今天已經非常普及了。基本的思路就是按照業務模塊來劃分出不同的數據庫,而不是像早期一樣將所有的數據表都放到同一個數據庫中。如下圖:

    小結

    系統層面的“服務化”拆分操作,能夠解決業務系統層面的耦合和性能瓶頸,有利于系統的擴展維護。而數據庫層面的拆分,道理也是相通的。與服務的“治理”和“降級”機制類似,我們也能對不同業務類型的數據進行“分級”管理、維護、監控、擴展等。

    眾所周知,數據庫往往最容易成為應用系統的瓶頸,而數據庫本身屬于“有狀態”的,相對于Web和應用服務器來講,是比較難實現“橫向擴展”的。數據庫的連接資源比較寶貴且單機處理能力也有限,在高并發場景下,垂直分庫一定程度上能夠突破IO、連接數及單機硬件資源的瓶頸,是大型分布式系統中優化數據庫架構的重要手段。

    然后,很多人并沒有從根本上搞清楚為什么要拆分,也沒有掌握拆分的原則和技巧,只是一味的模仿大廠的做法。導致拆分后遇到很多問題(例如:跨庫join,分布式事務等)。

    水平分表

    水平分表也稱為橫向分表,比較容易理解,就是將表中不同的數據行按照一定規律分布到不同的數據庫表中(這些表保存在同一個數據庫中),這樣來降低單表數據量,優化查詢性能。最常見的方式就是通過主鍵或者時間等字段進行Hash和取模后拆分。如下圖所示:

    小結

    水平分表,能夠降低單表的數據量,一定程度上可以緩解查詢性能瓶頸。但本質上這些表還保存在同一個庫中,所以庫級別還是會有IO瓶頸。所以,一般不建議采用這種做法。

    水平分庫分表

    水平分庫分表與上面講到的水平分表的思想相同,唯一不同的就是將這些拆分出來的表保存在不同的數據中。這也是很多大型互聯網公司所選擇的做法。如下圖:

    某種意義上來講,有些系統中使用的“冷熱數據分離”(將一些使用較少的歷史數據遷移到其他的數據庫中。而在業務功能上,通常默認只提供熱點數據的查詢),也是類似的實踐。在高并發和海量數據的場景下,分庫分表能夠有效緩解單機和單庫的性能瓶頸和壓力,突破IO、連接數、硬件資源的瓶頸。當然,投入的硬件成本也會更高。同時,這也會帶來一些復雜的技術問題和挑戰(例如:跨分片的復雜查詢,跨分片事務等)

    分庫分表的難點

    垂直分庫帶來的問題和解決思路:

    跨庫join的問題

    在拆分之前,系統中很多列表和詳情頁所需的數據是可以通過sql join來完成的。而拆分后,數據庫可能是分布式在不同實例和不同的主機上,join將變得非常麻煩。而且基于架構規范,性能,安全性等方面考慮,一般是禁止跨庫join的。那該怎么辦呢?首先要考慮下垂直分庫的設計問題,如果可以調整,那就優先調整。如果無法調整的情況,下面筆者將結合以往的實際經驗,總結幾種常見的解決思路,并分析其適用場景。

    跨庫Join的幾種解決思路

    全局表

    所謂全局表,就是有可能系統中所有模塊都可能會依賴到的一些表。比較類似我們理解的“數據字典”。為了避免跨庫join查詢,我們可以將這類表在其他每個數據庫中均保存一份。同時,這類數據通常也很少發生修改(甚至幾乎不會),所以也不用太擔心“一致性”問題。

    字段冗余

    這是一種典型的反范式設計,在互聯網行業中比較常見,通常是為了性能來避免join查詢。

    舉個電商業務中很簡單的場景:

    “訂單表”中保存“賣家Id”的同時,將賣家的“Name”字段也冗余,這樣查詢訂單詳情的時候就不需要再去查詢“賣家用戶表”。

    字段冗余能帶來便利,是一種“空間換時間”的體現。但其適用場景也比較有限,比較適合依賴字段較少的情況。最復雜的還是數據一致性問題,這點很難保證,可以借助數據庫中的觸發器或者在業務代碼層面去保證。當然,也需要結合實際業務場景來看一致性的要求。就像上面例子,如果賣家修改了Name之后,是否需要在訂單信息中同步更新呢?

    數據同步

    定時A庫中的tab_a表和B庫中tbl_b有關聯,可以定時將指定的表做同步。當然,同步本來會對數據庫帶來一定的影響,需要性能影響和數據時效性中取得一個平衡。這樣來避免復雜的跨庫查詢。筆者曾經在項目中是通過ETL工具來實施的。

    系統層組裝

    在系統層面,通過調用不同模塊的組件或者服務,獲取到數據并進行字段拼裝。說起來很容易,但實踐起來可真沒有這么簡單,尤其是數據庫設計上存在問題但又無法輕易調整的時候。

    具體情況通常會比較復雜。下面筆者結合以往實際經驗,并通過偽代碼方式來描述。

    簡單的列表查詢的情況

    偽代碼很容易理解,先獲取“我的提問列表”數據,然后再根據列表中的UserId去循環調用依賴的用戶服務獲取到用戶的RealName,拼裝結果并返回。

    有經驗的讀者一眼就能看出上訴偽代碼存在效率問題。循環調用服務,可能會有循環RPC,循環查詢數據庫…不推薦使用。再看看改進后的:

    這種實現方式,看起來要優雅一點,其實就是把循環調用改成一次調用。當然,用戶服務的數據庫查詢中很可能是In查詢,效率方面比上一種方式更高。(坊間流傳In查詢會全表掃描,存在性能問題,傳聞不可全信。其實查詢優化器都是基本成本估算的,經過測試,在In語句中條件字段有索引的時候,條件較少的情況是會走索引的。這里不細展開說明,感興趣的朋友請自行測試)。

    小結

    簡單字段組裝的情況下,我們只需要先獲取“主表”數據,然后再根據關聯關系,調用其他模塊的組件或服務來獲取依賴的其他字段(如例中依賴的用戶信息),最后將數據進行組裝。

    通常,我們都會通過緩存來避免頻繁RPC通信和數據庫查詢的開銷。

    列表查詢帶條件過濾的情況

    在上述例子中,都是簡單的字段組裝,而不存在條件過濾。看拆分前的SQL:

    這種連接查詢并且還帶條件過濾的情況,想在代碼層面組裝數據其實是非常復雜的(尤其是左表和右表都帶條件過濾的情況會更復雜),不能像之前例子中那樣簡單的進行組裝了。試想一下,如果像上面那樣簡單的進行組裝,造成的結果就是返回的數據不完整,不準確。 

    有如下幾種解決思路:

    1. 查出所有的問答數據,然后調用用戶服務進行拼裝數據,再根據過濾字段state字段進行過濾,最后進行排序和分頁并返回。

      這種方式能夠保證數據的準確性和完整性,但是性能影響非常大,不建議使用。

    2. 查詢出state字段符合/不符合的UserId,在查詢問答數據的時候使用in/not in進行過濾,排序,分頁等。過濾出有效的問答數據后,再調用用戶服務獲取數據進行組裝。

      這種方式明顯更優雅點。筆者之前在某個項目的特殊場景中就是采用過這種方式實現。

    跨庫事務(分布式事務)的問題

    按業務拆分數據庫之后,不可避免的就是“分布式事務”的問題。以往在代碼中通過spring注解簡單配置就能實現事務的,現在則需要花很大的成本去保證一致性。這里不展開介紹, 
    感興趣的讀者可以自行參考《分布式事務一致性解決方案》,鏈接地址: 
    http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency

    垂直分庫總結和實踐建議

    本篇中主要描述了幾種常見的拆分方式,并著重介紹了垂直分庫帶來的一些問題和解決思路。讀者朋友可能還有些問題和疑惑。

    1. 我們目前的數據庫是否需要進行垂直分庫?

    根據系統架構和公司實際情況來,如果你們的系統還是個簡單的單體應用,并且沒有什么訪問量和數據量,那就別著急折騰“垂直分庫”了,否則沒有任何收益,也很難有好結果。

    切記,“過度設計”和“過早優化”是很多架構師和技術人員常犯的毛病。

    2. 垂直拆分有沒有原則或者技巧?

    沒有什么黃金法則和標準答案。一般是參考系統的業務模塊拆分來進行數據庫的拆分。比如“用戶服務”,對應的可能就是“用戶數據庫”。但是也不一定嚴格一一對應。有些情況下,數據庫拆分的粒度可能會比系統拆分的粒度更粗。筆者也確實見過有些系統中的某些表原本應該放A庫中的,卻放在了B庫中。有些庫和表原本是可以合并的,卻單獨保存著。還有些表,看起來放在A庫中也OK,放在B庫中也合理。

    如何設計和權衡,這個就看實際情況和架構師/開發人員的水平了。

    3. 上面舉例的都太簡單了,我們的后臺報表系統中join的表都有n個了, 
    分庫后該怎么查?

    有很多朋友跟我提過類似的問題。其實互聯網的業務系統中,本來就應該盡量避免join的,如果有多個join的,要么是設計不合理,要么是技術選型有誤。請自行科普下OLAP和OLTP,報表類的系統在傳統BI時代都是通過OLAP數據倉庫去實現的(現在則更多是借助離線分析、流式計算等手段實現),而不該向上面描述的那樣直接在業務庫中執行大量join和統計。

    由于篇幅關系,下篇中我們再繼續細聊“水平分庫分表”相關的話題。 

    在之前的文章中,我介紹了分庫分表的幾種表現形式和玩法,也重點介紹了垂直分庫所帶來的問題和解決方法。本篇中,我們將繼續聊聊水平分庫分表的一些技巧。

    分片技術的由來

    關系型數據庫本身比較容易成為系統性能瓶頸,單機存儲容量、連接數、處理能力等都很有限,數據庫本身的“有狀態性”導致了它并不像Web和應用服務器那么容易擴展。在互聯網行業海量數據和高并發訪問的考驗下,聰明的技術人員提出了分庫分表技術(有些地方也稱為Sharding、分片)。同時,流行的分布式系統中間件(例如MongoDB、ElasticSearch等)均自身友好支持Sharding,其原理和思想都是大同小異的。

    分布式全局唯一ID

    在很多中小項目中,我們往往直接使用數據庫自增特性來生成主鍵ID,這樣確實比較簡單。而在分庫分表的環境中,數據分布在不同的分片上,不能再借助數據庫自增長特性直接生成,否則會造成不同分片上的數據表主鍵會重復。簡單介紹下使用和了解過的幾種ID生成算法。

     

    1. Twitter的Snowflake(又名“雪花算法”)
    2. UUID/GUID(一般應用程序和數據庫均支持)
    3. MongoDB ObjectID(類似UUID的方式)
    4. Ticket Server(數據庫生存方式,Flickr采用的就是這種方式)

    其中,Twitter 的Snowflake算法是筆者近幾年在分布式系統項目中使用最多的,未發現重復或并發的問題。該算法生成的是64位唯一Id(由41位的timestamp+ 10位自定義的機器碼+ 13位累加計數器組成)。這里不做過多介紹,感興趣的讀者可自行查閱相關資料。

    常見分片規則和策略

    分片字段該如何選擇

    在開始分片之前,我們首先要確定分片字段(也可稱為“片鍵”)。很多常見的例子和場景中是采用ID或者時間字段進行拆分。這也并不絕對的,我的建議是結合實際業務,通過對系統中執行的sql語句進行統計分析,選擇出需要分片的那個表中最頻繁被使用,或者最重要的字段來作為分片字段。

    常見分片規則

    常見的分片策略有隨機分片和連續分片這兩種,如下圖所示:

    當需要使用分片字段進行范圍查找時,連續分片可以快速定位分片進行高效查詢,大多數情況下可以有效避免跨分片查詢的問題。后期如果想對整個分片集群擴容時,只需要添加節點即可,無需對其他分片的數據進行遷移。但是,連續分片也有可能存在數據熱點的問題,就像圖中按時間字段分片的例子,有些節點可能會被頻繁查詢壓力較大,熱數據節點就成為了整個集群的瓶頸。而有些節點可能存的是歷史數據,很少需要被查詢到。

    隨機分片其實并不是隨機的,也遵循一定規則。通常,我們會采用Hash取模的方式進行分片拆分,所以有些時候也被稱為離散分片。隨機分片的數據相對比較均勻,不容易出現熱點和并發訪問的瓶頸。但是,后期分片集群擴容起來需要遷移舊的數據。使用一致性Hash算法能夠很大程度的避免這個問題,所以很多中間件的分片集群都會采用一致性Hash算法。離散分片也很容易面臨跨分片查詢的復雜問題。

    數據遷移,容量規劃,擴容等問題

    很少有項目會在初期就開始考慮分片設計的,一般都是在業務高速發展面臨性能和存儲的瓶頸時才會提前準備。因此,不可避免的就需要考慮歷史數據遷移的問題。一般做法就是通過程序先讀出歷史數據,然后按照指定的分片規則再將數據寫入到各個分片節點中。

    此外,我們需要根據當前的數據量和QPS等進行容量規劃,綜合成本因素,推算出大概需要多少分片(一般建議單個分片上的單表數據量不要超過1000W)。

    如果是采用隨機分片,則需要考慮后期的擴容問題,相對會比較麻煩。如果是采用的范圍分片,只需要添加節點就可以自動擴容。

    跨分片技術問題

    跨分片的排序分頁

    一般來講,分頁時需要按照指定字段進行排序。當排序字段就是分片字段的時候,我們通過分片規則可以比較容易定位到指定的分片,而當排序字段非分片字段的時候,情況就會變得比較復雜了。為了最終結果的準確性,我們需要在不同的分片節點中將數據進行排序并返回,并將不同分片返回的結果集進行匯總和再次排序,最后再返回給用戶。如下圖所示:

    上面圖中所描述的只是最簡單的一種情況(取第一頁數據),看起來對性能的影響并不大。但是,如果想取出第10頁數據,情況又將變得復雜很多,如下圖所示:

    有些讀者可能并不太理解,為什么不能像獲取第一頁數據那樣簡單處理(排序取出前10條再合并、排序)。其實并不難理解,因為各分片節點中的數據可能是隨機的,為了排序的準確性,必須把所有分片節點的前N頁數據都排序好后做合并,最后再進行整體的排序。很顯然,這樣的操作是比較消耗資源的,用戶越往后翻頁,系統性能將會越差。

    跨分片的函數處理

    在使用Max、Min、Sum、Count之類的函數進行統計和計算的時候,需要先在每個分片數據源上執行相應的函數處理,然后再將各個結果集進行二次處理,最終再將處理結果返回。如下圖所示:

    跨分片join

    Join是關系型數據庫中最常用的特性,但是在分片集群中,join也變得非常復雜。應該盡量避免跨分片的join查詢(這種場景,比上面的跨分片分頁更加復雜,而且對性能的影響很大)。通常有以下幾種方式來避免:

    全局表

    全局表的概念之前在“垂直分庫”時提過。基本思想一致,就是把一些類似數據字典又可能會產生join查詢的表信息放到各分片中,從而避免跨分片的join。

    ER分片

    在關系型數據庫中,表之間往往存在一些關聯的關系。如果我們可以先確定好關聯關系,并將那些存在關聯關系的表記錄存放在同一個分片上,那么就能很好的避免跨分片join問題。在一對多關系的情況下,我們通常會選擇按照數據較多的那一方進行拆分。如下圖所示:

    這樣一來,Data Node1上面的訂單表與訂單詳細表就可以直接關聯,進行局部的join查詢了,Data Node2上也一樣。基于ER分片的這種方式,能夠有效避免大多數業務場景中的跨分片join問題。

    內存計算

    隨著spark內存計算的興起,理論上來講,很多跨數據源的操作問題看起來似乎都能夠得到解決。可以將數據丟給spark集群進行內存計算,最后將計算結果返回。

    跨分片事務問題

    跨分片事務也分布式事務,想要了解分布式事務,就需要了解“XA接口”和“兩階段提交”。值得提到的是,MySQL5.5x和5.6x中的xa支持是存在問題的,會導致主從數據不一致。直到5.7x版本中才得到修復。Java應用程序可以采用Atomikos框架來實現XA事務(J2EE中JTA)。感興趣的讀者可以自行參考《分布式事務一致性解決方案》,鏈接地址:

    http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency

    我們的系統真的需要分庫分表嗎

    讀完上面內容,不禁引起有些讀者的思考,我們的系統是否需要分庫分表嗎?

    其實這點沒有明確的判斷標準,比較依賴實際業務情況和經驗判斷。依照筆者個人的經驗,一般MySQL單表1000W左右的數據是沒有問題的(前提是應用系統和數據庫等層面設計和優化的比較好)。當然,除了考慮當前的數據量和性能情況時,作為架構師,我們需要提前考慮系統半年到一年左右的業務增長情況,對數據庫服務器的QPS、連接數、容量等做合理評估和規劃,并提前做好相應的準備工作。如果單機無法滿足,且很難再從其他方面優化,那么說明是需要考慮分片的。這種情況可以先去掉數據庫中自增ID,為分片和后面的數據遷移工作提前做準備。

    很多人覺得“分庫分表”是宜早不宜遲,應該盡早進行,因為擔心越往后公司業務發展越快、系統越來越復雜、系統重構和擴展越困難…這種話聽起來是有那么一點道理,但我的觀點恰好相反,對于關系型數據庫來講,我認為“能不分片就別分片”,除非是系統真正需要,因為數據庫分片并非低成本或者免費的。

    這里筆者推薦一個比較靠譜的過渡技術–“表分區”。主流的關系型數據庫中基本都支持。不同的分區在邏輯上仍是一張表,但是物理上卻是分開的,能在一定程度上提高查詢性能,而且對應用程序透明,無需修改任何代碼。筆者曾經負責優化過一個系統,主業務表有大約8000W左右的數據,考慮到成本問題,當時就是采用“表分區”來做的,效果比較明顯,且系統運行的很穩定。

    小結

    最后,有很多讀者都想了解當前社區中有沒有開源免費的分庫分表解決方案,畢竟站在巨人的肩膀上能省力很多。當前主要有兩類解決方案:

    1. 基于應用程序層面的DDAL(分布式數據庫訪問層) 

      比較典型的就是淘寶半開源的TDDL,當當網開源的Sharding-JDBC等。分布式數據訪問層無需硬件投入,技術能力較強的大公司通常會選擇自研或參照開源框架進行二次開發和定制。對應用程序的侵入性一般較大,會增加技術成本和復雜度。通常僅支持特定編程語言平臺(Java平臺的居多),或者僅支持特定的數據庫和特定數據訪問框架技術(一般支持MySQL數據庫,JDBC、MyBatis、Hibernate等框架技術)。

    2. 數據庫中間件,比較典型的像mycat(在阿里開源的cobar基礎上做了很多優化和改進,屬于后起之秀,也支持很多新特性),基于Go語言實現kingSharding,比較老牌的Atlas(由360開源)等。這些中間件在互聯網企業中大量被使用。另外,MySQL 5.x企業版中官方提供的Fabric組件也號稱支持分片技術,不過國內使用的企業較少。 

      中間件也可以稱為“透明網關”,大名鼎鼎的mysql_proxy大概是該領域的鼻祖(由MySQL官方提供,僅限于實現“讀寫分離”)。中間件一般實現了特定數據庫的網絡通信協議,模擬一個真實的數據庫服務,屏蔽了后端真實的Server,應用程序通常直接連接中間件即可。而在執行SQL操作時,中間件會按照預先定義分片規則,對SQL語句進行解析、路由,并對結果集做二次計算再最終返回。引入數據庫中間件的技術成本更低,對應用程序來講侵入性幾乎沒有,可以滿足大部分的業務。增加了額外的硬件投入和運維成本,同時,中間件自身也存在性能瓶頸和單點故障問題,需要能夠保證中間件自身的高可用、可擴展。

    總之,不管是使用分布式數據訪問層還是數據庫中間件,都會帶來一定的成本和復雜度,也會有一定的性能影響。所以,還需讀者根據實際情況和業務發展需要慎重考慮和選擇。

    posted on 2017-01-17 14:29 jinfeng_wang 閱讀(436) 評論(0)  編輯  收藏 所屬分類: 2016-thinking
    主站蜘蛛池模板: 在线观看亚洲专区| 亚洲日韩在线中文字幕综合| 亚洲一区电影在线观看| 亚洲色大情网站www| 人成免费在线视频| 免费看无码特级毛片| 国产成人精品免费视频动漫| 日韩成人在线免费视频| 亚洲 小说区 图片区 都市| 国产亚洲综合色就色| 亚洲人成影院77777| 美女黄频免费网站| 久久免费精品视频| 性感美女视频免费网站午夜| 亚洲日韩国产一区二区三区| 婷婷精品国产亚洲AV麻豆不片| 久久久久久亚洲精品影院| 国产精品偷伦视频免费观看了| 最近新韩国日本免费观看 | 午夜无码A级毛片免费视频| 免费看国产成年无码AV片| 国产福利电影一区二区三区,亚洲国模精品一区 | 国产大片91精品免费观看男同| 亚洲精品乱码久久久久久按摩 | 亚洲国产精品美女| 一级免费黄色毛片| 性xxxxx免费视频播放| 亚洲男女内射在线播放| 亚洲毛片免费观看| 亚洲免费无码在线| 免费一本色道久久一区| 国产亚洲精品资源在线26u| 亚洲欧洲免费无码| 国产精品免费无遮挡无码永久视频 | 最近中文字幕完整免费视频ww | 亚洲综合精品伊人久久| 91免费国产视频| 免费看又爽又黄禁片视频1000| 亚洲精品成人片在线播放| 亚洲AV无码一区二区三区网址| 久久免费国产精品一区二区|