支持自動水平拆分的高性能分布式數據庫TDSQL


隨着互聯網應用的廣泛普及,海量數據的存儲和訪問成為系統設計的瓶頸問題。對於大型的互聯網應用,每天幾十億的PV無疑對數據庫造成了相當高的負載。給系統的穩定性和擴展性造成了極大的問題。通過數據的切分來提高系統整體性能,擴充系統整體容量,橫向擴展數據層已經成為架構研發人員首選的方式。

2004年,騰訊開始逐步上線互聯網增值服務,業務量開始第一次爆炸。計費成為所有業務都需要的一個公共服務,不再是某個服務的專屬。業務量的爆炸給DB層帶來了巨大的壓力,原來的單機模式已經無法支撐。伴隨計費公共平台的整合建設,在DB層開始引入分庫分表機制:針對大的表,按照某個key預先拆成n個子表,分布在不同的機器節點上。邏輯層在訪問DB時,自己根據分表邏輯將請求分發到不同的節點。在擴容時,需要手工完成子表數據的搬遷和訪問路由的修改。DB層在業務狂潮之下,增加各種工具和補丁來解決容量水平擴展的問題。2012年TDSQL項目立項,目標為金融聯機交易數據庫。

TDSQL(Tencent Distributed MySQL,騰訊分布式MySQL)是針對金融聯機交易場景推出的高一致性、分布式數據庫解決方案。產品形態為一個數據庫集群,底層基於MySQL,對外的功能表現上與MySQL兼容。截至2017年,TDSQL已在公司內部關鍵數據領域獲得廣泛應用,其中之一作為Midas(米大師)核心數據庫,經受了互聯網交易場景的考驗。Midas作為騰訊官方唯一數字業務支付平台,為公司移動App(iOS、Android、Win phone等)、PC客戶端、Web等不同場景提供一站式計費解決方案。

TDSQL規定shardkey為表拆分的依據,即進行SQL查詢時,shardkey作為查詢字段指明該SQL發往哪個Set(數據分片)。在分庫分表之前需要Schedule初始化集群,我們這里稱作一個Group。在初始化Group時要確定最初的分片大小,因而需要確定准備幾套Set。例如,我們需要對邏輯表拆分成四張子表,需要我們在初始化集群時准備四個Set,同時指定每個Set的路由信息,並將這些路由信息寫入ZK,如圖1所示。

圖1 TDSQL分庫分表

完成集群初始化后,Proxy監控ZooKeeper中的路由節點,當發現新的路由信息后,更新新的路由到本地。當用戶通過Proxy創建表時,一個建表語句發給Proxy必須指定shardkey,例如create table test_shard(a int, b int) shardkey=a。然后,Proxy改寫SQL,根據路由信息,在最后增加對應的partition clause,然后發到所有的后端Set,如圖2所示。

圖2 Proxy建表語句

這樣,就完成了一次建表任務,用戶看到的是一張邏輯表test_shard,但是在后端創建了4個實體表test_shard,后續用戶通過網關進行帶shardkey的增刪改查時,Proxy便會根據shardkey的路由將SQL發往指定Set。

在單實例MySQL中,用戶可以通過auto_increment屬性生成一個唯一的值,在分布式數據庫下,利用MySQL的自增屬性,只能保證在一個后端實例內實現自增和全局唯一,無法保證整個集群的唯一。

為了保證整個集群的唯一性,很顯然不能依賴於后端的數據庫,而需要Proxy生成對應的值。同時在實際運行中,Proxy可能有多個,並且可能有重啟等操作,通過Proxy自身也很難做到全局唯一,因此選用了ZooKeeper作為唯一值的生成工具。

通過ZooKeeper的分布式特性,可以保證即使多個Proxy同時訪問,每次只會有一個Proxy能夠成功拿到,使得生成的值是全局唯一。從性能上考慮,不可能每次都與ZooKeeper進行交互獲取,因此每個Proxy每次都會申請一段值,都用完后才會向ZooKeeper進行申請。

圖3 全局唯一字段表創建過程

這種設計方式實現了分布式環境下的自增屬性全局唯一。每個Proxy緩存一定數量的值,並且增加單獨線程負責向ZooKeeper申請值,使得性能影響降到最低,同時具有容災特性,即使Proxy掛了或者重啟,都能保證全局唯一。但是缺點是:多個Proxy一起使用的時候,只能保證全局唯一,不能保證單調遞增。

全局唯一字段的創建方式和普通的自增字段一樣:

create table auto_inc(a int auto_increment,b int) shardkey=b;

使用方式也相同:

insert into shard.auto_inc ( a,b,d,c) values(1,2,3,0),(1,2,3,0);

對應的字段如果賦值為0或者NULL時,由Proxy生成唯一的值,然后修改對應的SQL發送到后端。同時也支持select last_insert_id(),返回上次插入的值,每個線程互相獨立。

在分布式數據庫中,數據根據shardkey拆分到后端多個Set中,每個后端Set保存的都只是一部分數據。我們可以方便地在一個Set內做各種復雜的操作,如JOIN、子查詢等。分布式JOIN依賴於網關的語法分析,何為語法分析?簡單來說,語法分析主要做兩方面的事:判斷輸入是否滿足指定的語法規則,同時生成抽象語法樹。對於詞法分析以及語法分析,開源有多種現成的工具,不需要從頭開始做,Linux下用的比較多的是Flex和Bison。

圖4 語法分析過程

有了語法分析的支持,對於涉及分布式JOIN的查詢,例如表t1和t2要做JOIN操作,可能使用不同的字段作為shardkey,這樣根據shardkey路由后,相關記錄可能分布在兩個Set,網關分析后先將數據表t1數據取出,然后再根據t1的shardkey去獲取t2的數據,網關在這個過程中先做語法解析再進行數據聚合,最后返回給用戶結果集。此外,在實際業務中,有一些特殊的配置表,這些表都比較小,並且變動不多,但是會和很多其他表有關聯,對於這類表沒必要進行分片,因此支持一種叫做全局表的特殊表。如果用戶創建時指定是全局表的話(g1),該表全量存放在后端的所有Set中,查詢時隨機選擇一個Set,修改時修改所有Set。如果對全局表進行JOIN的話,就不需要限制條件,即支持select * from t1 join g1。

針對分布式事務,TDSQL采用兩階段提交算法來實現分布式事務,其中Proxy作為協調者,狀態數據持久化到全局事務管理系統中,目前選用的是TDSQL本身的一個InnoDB表來保存(gtid_log);所有的Group作為參與者來負責具體子事務的執行。

圖5 分布式事務

Begin;

Statment1;

Statment2;

Proxy為該事務分配一個ID,並將SQL轉為:

Xa begin “id”

Statment1;

Statment2;

Client最終向Proxy發送commit。

Proxy向所有參與該事務的Set發送:

Xa end “id” 標識該事務的結束;

Xa prepare “id” mysql將事務計入Binlog,並通過Binlog傳遞給Slave,不同於普通事務,寫入Binlog之后該事務仍然沒有提交;

如果任意Set在 prepare過程中失敗或者超時,由於此時還沒有寫存儲引擎日志,MySQL自動rollback這個事務,並向Client返回相應錯誤信息。

當Proxy收到所有Set的prepare響應之后,Proxy更新gtid_log表將對應XID的事務置為commit狀態;Proxy隨后向所有Set發送Xa commit “id”,Set收到該請求之后提交該事務。

Proxy等待所有Set的commit響應,當所有Set返回成功,Proxy返回前台成功。若其中一台返回失敗(當Set發生重啟等故障時,需要等待Agent補提交該事務,因而當前屬於未提交狀態),Proxy返回前台狀態未知,稍后請繼續查詢事務狀態。

當Proxy在第四步寫完commit后,開始逐個Set提交事務,當還沒有完成所有Set提交時Proxy發生宕機,剩余Set中未提交的事務由Agent來提交,以此來保證事務的一致性。Agent會定期通過命令Xa recover查詢MySQL中處於prepare狀態的事務,再對照gtid-log表查詢該事務是否處於commit狀態,如果是則comimt。否則可能由於prepare成功后寫gtid_log失敗,因而Agent需要將該事務abort。

TDSQL支持三種模式的讀寫分離。第一種模式下網關開啟語法解析的配置,通過語法解析過濾出用戶的select讀請求,默認把讀請求直接發給備機。這種方案的缺點有兩個:1. 網關需要對SQL進行分析,降低整體性能;2. 當主備延遲較大時,直接從備機獲取數據可能會得到錯誤的數據。

除了上述模式,TDSQL支持通過增加Slave注釋標記,將指定的SQL發往備機。即在SQL中添加/slave/這樣的標記,該SQL會發送給備機,即用戶能夠根據業務場景可控地選擇讀寫分離,即使主備延遲比較大,用戶也能夠根據需要靈活選擇從主機還是備機讀取數據,這種模式下網關不需要進行詞法解析,因而相比第一種方案提高了整體性能。但是,這種方案的缺陷是改寫了正常SQL,需要調整已有用戶代碼,成本較高,用戶可能不太願意接受。

表1 三種讀寫分離模式比較

針對前兩種讀寫分離的不足,最新版本的TDSQL增加了基於只讀帳號的讀寫分離模式。這種模式下,由只讀帳號發送的請求會根據配置的屬性發給備機。有兩種可配屬性,IDC屬性和備機延遲屬性。IDC屬性可配置三種屬性:1. 為同IDC屬性,即只讀帳號的請求必須發往同IDC的備機;2. 優先發給同IDC的備機,但當同IDC的備機不存在或宕機時,發往不同IDC的備機;3. 如果找不到滿足條件的備機,則發往主機。延遲屬性:如果延遲超過閥值,認為該備機不可用。只讀帳號能夠在既不改變原有用戶代碼,又不影響系統整體性能的前提下,同時提供多種可配參數解決讀寫分離的問題,更具有靈活性和實用性。

2014年微眾銀行設立之初,在其分布式的去IOE架構中,TDSQL承擔了去O的角色,以TDSQL作為交易的核心DB,承載全行所有OLTP業務。2015年,TDSQL和騰訊雲攜手,正式啟動“TDSQL上雲”的工作,接入了一系列傳統以及新型金融企業,覆蓋保險、證券、理財、咨詢等金融行業。目前,分布式TDSQL正作為騰訊日益重要的金融級數據庫,搭建着上百個實例,部署於上千台機器,日均產生TB級數據,承載着公司內外各種關鍵業務。

在未來,TDSQL重點會在數據庫性能、分布式事務、語法兼容三個方面做改進。目前TDSQL基於MariaDB 10.1.x、Percona-MySQL 5.7.x兩個分支版本,后續我們會緊密跟進社區並及時應用官方補丁,同時不斷針對金融場景的特性對數據庫內核進行優化,以此來提升數據庫性能和穩定性。當前分布式事務處於初級階段,對ZooKeeper的依賴性較強,后續可能針對分布式事務的可靠性做持續改進。由於TDSQL在分表環節對語法層做了一些限制,將來我們希望通過對網關解析器的改進,使其能夠支持更豐富的語法、詞法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM