蘇寧citus分散式資料庫應用實踐

IT大咖說發表於2019-03-04

蘇寧citus分散式資料庫應用實踐

內容來源:2017 年 10 月 20 日,蘇寧雲商IT總部資深技術經理陳華軍在“PostgreSQL 2017中國技術大會”進行《蘇寧citus分散式資料庫應用實踐》演講分享。IT 大咖說(微信id:itdakashuo)作為獨家視訊合作方,經主辦方和講者審閱授權釋出。

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

摘要

本次分享主要介紹瞭如何通過Citus打造分散式資料庫,對具體的部署情況進行了講解。

嘉賓演講視訊回放及PPTt.cn/RdmlKXd

業務場景

蘇寧citus分散式資料庫應用實踐

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

這套系統中我們採用的資料庫是DB 2,平時的CPU負載都達到了50%左右,大促期間更是超過了80%,可以算是不堪重負。

DB負載在哪?

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

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

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

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

由於我們的系統接入的業務需要擴張,預計年內負載還會增加10倍,也就是說原先的每秒5k的明細表隨機更新和3000w明細表資料,將提升為每秒5k的明細表隨機更新和3億明細表資料。

這樣的背景下基於單機的DB 2肯定是搞不定的,我們需要的應該是一種分散式方案。

方案選型

蘇寧citus分散式資料庫應用實踐

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

Citus介紹

Citus架構與原理

蘇寧citus分散式資料庫應用實踐

這張是Citus的架構圖,可以看到它由1個maste多個worker組成,資料表通過hash或者append分片的方式分散到每個worker上。這裡的表被分為分片表和參考表,參考表只有一個分片,並且每個worker上都有一份。

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

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

案例演示

蘇寧citus分散式資料庫應用實踐

這裡演示的是Citus的使用過程。分片表的建立和普通表是一樣的,只不過完成之後需要設定分片數,最後執行create_distributed_table函式,引數為需要分片的表以及分片欄位,還可以指定分片方法,預設是hash方式。參考表的不同在於函式換成了create_reference_table。這兩個函式主要做了兩件事,首先是在每個worker上建立分片,其次是更新後設資料。後設資料定義了分片資訊。

蘇寧citus分散式資料庫應用實踐

後設資料pg_dist_partition中存放的是分片表和分片規則,可以從圖中看到,h代表的hash分片,n表示的是參考表。分片表中有一個partkey,它用來指定哪個欄位做分片以及分片型別。

蘇寧citus分散式資料庫應用實踐

後設資料- pg_dist_shard定義了每個分片以及分片對應的hash範圍,不過參考表由於只有一個分片,所以沒有hash範圍。

蘇寧citus分散式資料庫應用實踐

後設資料-pg_dist_shard_placement定義了每個分片存放的位置,第一列是分片的ID號,後面是所在的worker節點位置和埠號。

蘇寧citus分散式資料庫應用實踐

基於後設資料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

蘇寧citus分散式資料庫應用實踐

上圖展示的是對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上總的併發任務數可以通過引數控制。

蘇寧citus分散式資料庫應用實踐

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

部署方案

痛點

蘇寧citus分散式資料庫應用實踐

我們的系統中首先面臨的痛點就是對隨機更新速度要求高。上圖左邊是Citus官方展示的效能資料,看似接近所需的效能要求,實際上遠遠不夠,因為這裡記錄的是普通的窄表,而我們的是寬表而且還有其他的負載。

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

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

解決方案

蘇寧citus分散式資料庫應用實踐

這是我們最終的解決方案。首先對於插入和更新資料慢的問題,不在走master,直接在worker上更新。在更新之前會現在worker上查詢分片的後設資料,然後再進行更新。

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

輔助工具函式開發

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

連線池

蘇寧citus分散式資料庫應用實踐

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

實際的使用的時候由於pgbounce不支援prepare語句,所以有些應用還是要直連到master。

效果

蘇寧citus分散式資料庫應用實踐

上圖是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。

相關文章