蘇寧citus分佈式數據庫應用實踐



內容來源:2017 年 10 月 20 日,蘇寧雲商IT總部資深技術經理陳華軍在「PostgreSQL 2017中國技術大會」進行《蘇寧citus分佈式數據庫應用實踐》演講分享。IT 大咖說(微信id:itdakashuo)做爲獨家視頻合做方,經主辦方和講者審閱受權發佈。數據庫

閱讀字數:5089 | 13分鐘閱讀後端

摘要

本次分享主要介紹瞭如何經過Citus打造分佈式數據庫,對具體的部署狀況進行了講解。緩存

嘉賓演講視頻回放及PPTt.cn/RdmlKXd微信

業務場景

上圖的系統架構主要是作訂單的分析,它會定時的從其餘的業務系統中抽取訂單以及訂單的更新信息。每5分鐘進行一次批量的處理,更新10張左右的明細表。在數據庫中一樣也是5分鐘作一次處理,首先會對明細表進行計算,以後的計算結果會被放到報表中。架構外層還有一些其餘系統,好比cognos、智能分析等,它們主要是用來從數據中查報表或明細表。架構

這套系統中咱們採用的數據庫是DB 2,平時的CPU負載都達到了50%左右,大促期間更是超過了80%,能夠算是不堪重負。併發

DB負載在哪?

如此高的負載,到底問題是出在那些地方?其實主要是在明細更新、報表計算、報表查詢/明細查詢上app

明細更新時是5分鐘更新10張明細表,這其中最寬的表有400字段,大概每行2.5kB。每次更新最寬的表約10w記錄,整體上是30w。咱們還要保持最近數天的數據。這樣看下來其實主要的壓力是在隨機更新,換算一下大概每秒要作5k條記錄的更新,關鍵是這 5K條記錄還都是寬表。分佈式

報表計算也是每5分鐘計算30多張報表,要求2分鐘完成,每一個報表平均執行14次明細表集合查詢。估算下來大概是每分鐘200次明細表的聚合運算。函數

報表查詢/明細查詢中要求的併發度是大於30,但正常狀況下沒有這麼高,大概只有10個左右。同時要求的響應時間要小於3秒。工具

因爲咱們的系統接入的業務須要擴張,預計年內負載還會增長10倍,也就是說原先的每秒5k的明細表隨機更新和3000w明細表數據,將提高爲每秒5k的明細表隨機更新和3億明細表數據。

這樣的背景下基於單機的DB 2確定是搞不定的,咱們須要的應該是一種分佈式方案。

方案選型

上圖列出的就是咱們當時所考察的各類方案,由於PG在分析上仍是比較有優點,因此這些方案都和PG相關。第一個Greenplum因爲已經比較成熟了,因此咱們一開始就比較看好,可是它更新慢、併發低的缺陷,不符合明細更新的性能要求,所以被排除在外。第二個postgres_fdw因爲不支持聚合下推和並行查詢,因此不符合明細表查詢性能要求。第三個PG_XL方案咱們並無作深刻的評估,可是GMT對性能是有影響的,估計很難知足咱們對隨機更新的需求。最後的citus的優點在於它是一個擴展,穩定性和可維護性都比較好,同時分片表的管理也很方便,最終咱們選擇的就是這個方案。

Citus介紹

Citus架構與原理

這張是Citus的架構圖,能夠看到它由1個maste多個worker組成,數據表經過hash或者append分片的方式分散到每一個worker上。這裏的表被分爲分片表和參考表,參考表只有一個分片,而且每一個worker上都有一份。

在應用訪問的時候master接收應用的查詢SQL,而後對查詢SQL進行解析生成分佈式執行計劃,再將子執行路徑分發到worker上執行,最後彙總執行結果返回給應用。

Citus主要適用於兩種環境,一種是實時數據分析,一種是多租戶應用。

案例演示

這裏演示的是Citus的使用過程。分片表的建立和普通表是同樣的,只不過完成以後須要設置分片數,最後執行create_distributed_table函數,參數爲須要分片的表以及分片字段,還能夠指定分片方法,默認是hash方式。參考表的不一樣在於函數換成了create_reference_table。這兩個函數主要作了兩件事,首先是在每一個worker上建立分片,其次是更新元數據。元數據定義了分片信息。

元數據pg_dist_partition中存放的是分片表和分片規則,能夠從圖中看到,h表明的hash分片,n表示的是參考表。分片表中有一個partkey,它用來指定哪一個字段作分片以及分片類型。

元數據- pg_dist_shard定義了每一個分片以及分片對應的hash範圍,不過參考表因爲只有一個分片,因此沒有hash範圍。

元數據-pg_dist_shard_placement定義了每一個分片存放的位置,第一列是分片的ID號,後面是所在的worker節點位置和端口號。

基於元數據master能夠生成分佈式執行計劃,好比聚合查詢就會生成如上圖所示的執行計劃。上半部分是在每一個worker上預聚合,每一個分片並行執行,下面則是master對worker的結果作最終的聚合。

SQL限制—查詢

Citus最大的缺陷在於有着SQL限制,並非全部SQL都支持。最典型的就是對Join的限制,它不支持2個非親和分片表的outer join,僅task-tracker執行器支持2個非親和分片表的inner join,對分片表和參考表的outer join,參考表只能出如今left join的右邊或right join的左邊。對子查詢也有着限制,子查詢不能參與join,不能出現order by,limit和offset。一些SQL特性Citus一樣不支持,好比CTE、Window函數、集合操做、非分片列的count(distinct)。最後還有一點須要注意,即本地表不能和分片表(參考表)混用。

這些限制其實均可以使用某些方法繞過,好比經過Hll(HyperLogLog)插件支持count(distinct),對於其餘的一些操做也能夠經過臨時表或dblink中轉。不過臨時表的問題在於會將一個SQL拆成多個SQL。

SQL限制—更新

在更新上也存在一些限制,它不支持跨分片的更新SQL和事務,‘insert into ... select ... from ...’的支持存在部分限制,插入源表和目的表必須是具備親和性的分片表,不容許出現Stable and volatile函數,不支持LIMIT,OFFSET,窗口函數,集合操做,Grouping sets,DISTINCT。

固然這些限制也存在對應的迴避方法,首先是使用copy代替insert,其次是用SELECT master_modify_multiple_shards(‘…’)實現擴分片更新。

SQL限制—DDL

上圖展現的是對DDL的支持狀況,這裏面大部分都是支持的,對於不支持的能夠經過建立對等的惟一索引代替變動主鍵,或者使用`run_command_on_placements`函數,直接在全部分片位置上執行DDL的方式來進行迴避。

兩種執行器

Citus有兩種執行器,經過set citus.task_executor_type='task-tracker'|'real-time'進行切換。

默認的real-time又分爲router和非router方式。router適用於只需在一個shard上執行的SQL,1個master後端進程對每一個worker只建立一個鏈接,並緩存鏈接。非route下master後端進程會對全部worker上的全部shard同時發起鏈接,並執行SQL,SQL完成後斷開鏈接。

若是使用task-tracker執行器。Master是隻和worker上的task-tracker進程交互,task-tracker進程負責worker上的任務調度,任務結束後master從worker上取回結果。worker上總的併發任務數能夠經過參數控制。

這裏對這兩種執行器進行了比較。real-time的優點主要在於響應時間小。task-tracker則是支持數據重分佈,SQL支持也比real-time略好,同時併發數,資源消耗可控。

部署方案

痛點

咱們的系統中首先面臨的痛點就是對隨機更新速度要求高。上圖左邊是Citus官方展現的性能數據,看似接近所需的性能要求,實際上遠遠不夠,由於這裏記錄的是普通的窄表,而咱們的是寬表並且還有其餘的負載。

圖中右邊是我這邊作的性能測試。單機狀態下插入速度是每秒13萬條,使用Citus後降低到了5w多,這主要是因爲master要對SQL進行解析和分發。在嘗試對Citus進行優化後,使Citus不解析SQL,提高也不是很明顯。最後一種方式是不使用master,將每一個worker做爲master,此次的效果達到了每秒30萬條。

第二個痛點就是前面提到的SQL限制問題,雖然這些限制都有方法迴避,可是對應用的改造量比較大。

解決方案

這是咱們最終的解決方案。首先對於插入和更新數據慢的問題,不在走master,直接在worker上更新。在更新以前會如今worker上查詢分片的元數據,而後再進行更新。

另外爲了儘可能減小SQL限制對應用的影響,咱們採用的策略是儘可能少作分片,只對明細表進行分片。應用在查詢的時候會將報表和維表作join,也會將明細表和維表作join,那麼這裏就會出現問題,由於本地表和參考表不能出如今同一個SQL裏。因此咱們作了N份參考表,每一個worker放一份,同時再將一份本地維表放在master上,由報表作join用,最後在更新的時候經過觸發器同步本地維表和參考表。

輔助工具函數開發

爲了支撐前面提到的兩個策略,咱們實現了兩個函數。pg_get_dist_shard_placement()函數用來批量獲取記錄所在分片位置函數。create_sync_trigger_for_table()函數用來自動生成本地維表和參考維表同步觸發器的函數。

鏈接池

由於業務對SQL的響應時間要求較高,因此咱們使用的是real time執行器。可是因爲real time存在的缺陷,所以咱們在master上部署了兩套pgbounce鏈接池。一個在PostgreSQL前面,應用在鏈接PostgreSQL前先鏈接到pgbouncer。另外一個在master和worker之間。

實際的使用的時候因爲pgbounce不支持prepare語句,因此有些應用仍是要直連到master。

效果

上圖是POC壓測的結果,基本上明細更新和報表結算知足了性能要求。測試的時候咱們使用的是8個worker,而在部署的時候實際上是先部署4臺,而後再擴容到8臺。

平常維護

Citus的維護和普通的PG維護在大部分狀況下區別不大,不過有些有時候DDL執行會沒法分發,這時能夠用它的一些公有函數來完成。

另外更新多副本分片表的途中worker發生故障,可能致使該worker上的副本沒有被正確更新。此時citus會在系統表pg_dist_shard_placement 中將其標識爲「失效」狀態。使用master_copy_shard_placement() 函數就可以進行恢復。

Citus對DDL、copy等跨庫操做採用2PC保障事務一致,2PC中途發生故障會產生未決事務。對每一個2PC事務中的操做都記錄到系統表pg_dist_transaction,經過該表就可以判斷哪些事務該回滾或提交。

踩過的坑

在實際的應用中咱們並無碰到什麼大坑,主要是一些小問題。

第一個是因爲master(real-time)到worker用的短鏈接,pgbouncer默認記錄鏈接和斷鏈接事件,致使日誌文件增加太快。後來咱們將其關閉了。

第二個是master(real-time)會瞬間建立大量到worker 的併發鏈接,而默認的unix套接字的 backlog鏈接數偏低, master節點的 PostgreSQL日誌中常常發現大量鏈接出錯的告警。對此的解決辦法是修改修改pgbouncer的listen_backlog,而後硬重啓pgbouncer。

以上爲今天的所有分享內容,謝謝你們!

注:本文內容基於較早的citus 6.x版,當前版本citus中「master」節點的名稱已改成「Coordinator」。另外,本文中描述的SQL限制,除join外大部分限制已經在7.2之後版本中被解除,

詳細參考:https://pan.baidu.com/s/1_tkfp9xLSQuwA2LEInQWTw。

相關文章
相關標籤/搜索