使用Radon構建MySQL統一數據訪問層

這篇也可以說是:RadonDB使用最佳建議,從原理上了解RadonDB的拆分後數據訪問邏輯。Radon中整理架構如下:

在Radon的架構中可以利用「表名」唯一,同時利用global table, 拆分表, single table 特性實現table在RadonDB集群中分布。

為什麼要使用Radon構建數據統一訪問層呢?主要有以下原因:

  • 原生高可用實現
  • 配置自動化,不需要對每個表定義拆分規則
  • 基於golang開發,輕量,易部署,易管理

在上面這個架構中所有用戶請求的SQL需要經過Radon,在Radon中解析並改寫分發給後面的MySQL節點運算,結果返回到Radon進行合併或是匯聚返回給前端。下面我們從Radon的配置,SQL在Radon中改寫情況,全面了解一下如何最佳的使用Radon。

先了解Radon配置項

config

{    "max-connections": 支援前端應用最多少個連接連到Radon上,       [required]    "max-result-size": 返回結果,最大支援多少byte,               [required]    "max-join-rows":   在join運算中,最多有多少行可以參與.        [required]    "ddl-timeout":     DDL超時的時間,                          [required]    "query-timeout":   Radon中DML含select運行超時間(in millisecond),     [required]    "twopc-enable":    是否使用Radon分散式事務提交,如果需要顯式使用begin,commit需要開啟,     [required]    "allowip":         ["allow-ip-1", "allow-ip-2"],         [required]    "audit-mode":      開啟審計日誌, "N": disabled, "R": read enabled, "W": write enabled, "A": read/write enabled,  [required]    }   

更改參數參考:

  curl -i -H 'Content-Type: application/json'-X PUT -d '{"max-connections":1024,     "max-result-size":1073741824, "max-join-rows":65535,     "ddl-timeout":3600000, "query-timeout":60000,     "twopc-enable":true,,"audit-mode":"A"}' http://127.0.0.1:8080/v1/radon/config   

更改完配置後,配置會自動寫入radon.json中。

對於Radon配置方面更詳細的API: https://github.com/radondb/radon/blob/master/docs/api.md

Tips:在配置方面,如果你計劃把Radon用在OLAP環境,需要注意下面,這三個參數:

  • max-result-size 返回結果,最大支援多少byte
  • max-join-rows 在join運算中,最多有多少行可以參與
  • query-timeout Radon中DML含select運行超時間(in millisecond)

Radon支援表類型:

  • single 表
  • global 表
  • 拆分表
  • 可以指定拆分數量的拆分表

single表:默認只會存在第一個節點。這是因為在Radon中很多show語句請求是默認轉發到第一個節點,所以對於single表也默認存在第一個節點上,也可以通過distributed by指定存儲的後端節點。對於寫入讀取Radon可以透明傳輸,性能也是最高的。

創建方法:

    create table tb_single(id ..    ...    )engine =Innodb single  distributed by(backend1);   

對業務不明的情況下,可以考慮所有表建成single table通過distributed by指定single表在分端節點的分布,將來數據量大了,利用Radon的Reshard功能重新拆分即可。

global表: RadonDB後面各個分組上都會存在, 對於寫入Radon使用分散式事務,所有的節點都會寫一份數據,適合在寫少讀多的場景的表。例如,全國地理位置資訊等。創建語法:

    create table tb_global(id ..    ...    )engine =Innodb global;   

分區表: 也可以說是Radon中的拆分表,每個表默認按64個小表進行拆分,默認按該表的主鍵運行hash的方式拆分,而該hash,只能對單個欄位運行,所以不能出現聯合索引的主鍵。創建語法:

    create table tb_part(id ..    ...    )engine =Innodb;   

動態指定拆分的表: 你如果你覺的原生的分庫分表功能約定太死,可以看看一下: *: support specify the number of hash partition tables #587 #588 https://github.com/radondb/radon/pull/588 可以動態的指定每個分表的數量

CREATE TABLE zst_bt (        id INT notnull,        c1 varchar(32) notnull        store_id INT    )    PARTITION BY HASH(id)    PARTITIONS 4;   

創建表時可以利用parttions 指定拆分的數量。目前該功能還沒合併到主分支中,利用開發分支測試,功能都沒問題。估計下一個版本中就會出現了,如果你覺得這個功能是你想要的,你也可以去 https://github.com/radondb/radon/pull/588 點個贊吧。

Radon中SQL執行流程

在RadonDB中,Radon擔任著SQL解析和結果匯聚類的運算。針對該架構,SQL在Radon中執行,大致分為5大類:

  1. 單表(拆分表)查詢中where條件有拆分鍵,對於global table, single table都是tcp轉發,行為簡單就不在列舉
  2. 單表(拆分表)查詢中where條件不包含拆分鍵
  3. single table和拆分的join操作
  4. global table和拆分的join操作
  5. 兩個拆分的表做join操作

第一類 拆分表where條件中包含主鍵的等值查詢

對於拆分錶帶有明確的拆分運算的語句,可以直接精確的投遞到後面的節點,運算後直接返給前端。例如:select k from sbtest1 where ID=10; 其中id是拆分鍵。還有一類, 例如select k from sbtest1 where ID in (1,2,3,4,5); Radon也可以明確算出來以上數據在那個子表,明確給計算出來。

第二類 單表(拆分表)查詢中where條件不包含拆分鍵

查詢中不包含拆分鍵,同樣表是拆分表的情況下,該sql會發向所有後面的節點上該表的拆分表,進行運算,然後在Radon上進行結果集的排序合併處理,返回給前端。例如:select id, k from sbtest1 where k=499 limit 10; 這類SQL中比較複雜,實質上是走的Radon對SQL分布,然後在中間件匯聚結果,最終展示(這個過程有點類似於google開源的Vitess)。

第三類 single table和拆分的join操作

對於非分表: single table(只在後面某一個節點上存儲,一般是第一個節點)和拆分表做join ,如:

  CREATE TABLE clv2 (    CountryCode char(3) NOT NULL DEFAULT '',    ...,    PRIMARY KEY (CountryCode,Language),    KEY CountryCode (CountryCode)  ) single;

SQL語句:

select a.Name, a.Population , b.Language, b.IsOfficial   from city a, clv2 b where a.CountryCode =b.CountryCode   limit 10

轉化為:

 {  "Query": "select a.Name, a.Population, a.CountryCode from             zst.city_0000 as a order by a.CountryCode asc",  "Backend": "`backend1`",  "Range": "[0-64)"  },  ...  {  "Query": "select a.Name, a.Population, a.CountryCode from              zst.city_0063 as a order by a.CountryCode asc",  "Backend": "`backend2`",  "Range": "[4032-4096)"  },  {  "Query": "select b.language, b.IsOfficial, b.CountryCode from             zst.clv2 as b order by b.CountryCode asc",  "Backend": "`backend1`",  "Range": ""  }  "Join": {          "Type": "INNER JOIN",          "Strategy": "Sort Merge Join"  },  "Limit": {          "Offset": 0,          "Limit": 10  }

從上面可以看出來對於分區表和single的join被拆分成兩步,第一步 分別在backend1,backend2上操作 SQL:

select a.Name, a.Population, a.CountryCode   from zst.city_XXXX as a order by a.CountryCode asc ;

第二步執行:select b.language, b.IsOfficial, b.CountryCode from zst.clv2 as b order by b.CountryCode asc ;

最終在Radon上按流式合併返回。如果需要順序返回則後面需要添加order by ,如:

select a.Name, a.Population , b.Language, b.IsOfficial   from city a, clv2 b where a.CountryCode =b.CountryCode   order by Name limit 10;

在執行計劃中會根據排序欄位排序返回:

"GatherMerge": [          "Name"  ],

第四類 global table和拆分的join操作

global table(後端每個節點上都存在)該類表的查詢行為可以明確約定,對global表的單表操作不用做SQL改寫,目前讀操作按輪詢的機制發向後端節點進行處理。對於join操作,對分區表改寫,global表不動直接下發. 如

CREATE TABLE countrylanguage (    CountryCode char(3) NOT NULL DEFAULT '',    Language char(30) NOT NULL DEFAULT '',    ...    PRIMARY KEY (CountryCode,Language),    KEY CountryCode (CountryCode)  ) global;

測試SQL語句:

select a.Name, a.Population , b.Language, b.IsOfficial   from city a, countrylanguage b where   a.CountryCode =b.CountryCode limit 10;

拆分為:

{  "Query": "select a.Name, a.Population, b.language, b.IsOfficial             from zst.city_0000 as a, zst.countrylanguage as b where             a.CountryCode = b.CountryCode limit 10",  "Backend": "`backend1`",  "Range": "[0-64)"  },  ...  {  "Query": "select a.Name, a.Population, b.language, b.IsOfficial from            zst.city_0063 as a,zst.countrylanguage as b where            a.CountryCode = b.CountryCode limit 10",  "Backend": "`backend2`",  "Range": "[4032-4096)"  }  ],  "Limit": {  "Offset": 0,  "Limit": 10  }

從執行計划上可以看出來對於global表的join操作,相當於把分區表,表名改寫後進行下推運算,如果沒有排序,直接流式返回,如果有排序,則需要全部數據執行完畢後排序返回給前段。

第五類 兩個分區表join操作

兩個分區表join操作:

select a.Name, a.District, a.Population, b.Name from city a ,   country b where a.CountryCode=b.Code     and b.name='China' order by a.Population desc limit 10;

對於該類SQL Radon也會給轉為表的請求,如:

{   "Query": "select a.Name, a.District, a.Population, a.CountryCode                from `zst.city_0000` as a order by a.CountryCode asc",   "Backend": "backend1",   "Range": "[0-64)"  },   ...  {    "Query": "select b.Name, b.Code from `zst.country_0063` as b              where b.name = 'China' order by b.Code asc",    "Backend": "backend2",    "Range": "[4032-4096)"  }  ...  "Join": {    "Type": "INNER JOIN",    "Strategy": "Sort Merge Join"  },  "GatherMerge": [          "a.Population"  ],  "Limit": {          "Offset": 0,          "Limit": 10  }

從上面可以看出來,一個join語句也被拆分成兩個單個SQL執行,最終在Radon上做結果的合併,返回給前端。

基於RadonDB Schema實現及最佳建議

因為以上的設計,為了在Radon中獲取最佳的性能,建議遵循以下規則

  1. 對於數據量較小的表,有頻繁更新讀的表,建議使用single表,即可。
  2. 對於寫量少,但讀取量大,或是經常需要和其它表進行關聯查詢的,可以使用global表。
  3. 對於數據量大,寫入量也大,且有高並發寫入的業務,可以使用分區表。對於高速數據寫入RadonDB表現比較好。對於分區表,實質上最需要注意的是分區鍵選擇。默認官方建議是使用id bigint auto_increment 做分為分區鍵,而且Radon中程式碼寫入,對於是Id的列名(不論大小寫),全部要使用bigint定義。但實際使用,減少業務中讀放大的問題,建議使用業務中的主鍵做為分區鍵,例如:UserId ,OrderId,MsgId, ImageId等等有意義的欄位,該欄位需要定義成為主鍵或是唯一索引。對於分表數量,建議從物理表大小,子表單表10G以內,子表總行數來考慮1500萬以內,避免拆分過多的問題。
  4. 在SQL編上,盡量減少讀放大的問題。對於多表join在Radon上限制有兩個限制配置:總共行數不能超過< max-join-rows: 32768 ,結果集大小不能超過< max-result-size:1073741824
  5. 對於single table, global table建議物理大小不超過5G,單錶行數在1千萬以下。
  6. 從拆分的角度理解MySQL最佳實踐中表的總數量,例如,我們約定一個MySQL實例上可以放500個表,Radon默認分區64個,如果只有一個Backend的情況下,建議該節點最多可以放8個分區表,需要在多的分區表時,建議擴展新的backend。

RadonDB SQL限制:

目前RadonDB中,為性能和安全,約束還比較多。在本次測試中遇到的,限制如下:

  1. Radon中SQL區分大小寫,如 select * from tb1 where id=XX 和select * from tb1 where ID=XX 和後面表的結構定義的欄位非常敏感。如果後面表tb1的欄位定義成:ID,則 id=XX 會拆分成和後端分表數量一致的多條SQL執行, 而ID=XX,可以明確計算出來屬於那張表進行計算。這個從Radon好修復,大概了解就可以。
  2. 分區表不支援外鍵 (點贊)
  3. 分區表不支援聯合主鍵
  4. 不支援lock table/unlock table操作
  5. join查詢不能使用 select * ,需要明確欄位,和官方交流後,該功能已經修復。
  6. 不支援insert into c1(id, c1, c2) select id,c1,c2 from c limit 10;
  7. delete 和update必須帶where條件
  8. Radon對MySQL的一些函數支援不夠友好
  9. ... 估計使用還會遇到,歡迎總結 :)

總結

Radon從開發和設計上都比較輕量,從整體使用上考,Radon在設計上對於安全和性能,準確性要求比較高。這個可能和青雲把RadonDB定位在金融環境的中間件有關係。大家有興趣可以去嘗試使用,如果遇到問題,也可以第一時間在 https://github.com/radondb/radon/issues 提交問題,Radon開發團隊目前響應比較快,歡迎嘗試。

Tags: