閱讀171 返回首頁    go 阿裏雲 go 技術社區[雲棲]


MySQL存儲引擎之Spider內核深度解析

Spider是為MySQL/MariaDB開發的一個特殊引擎,具有內嵌分片功能。現在它已經被集成到MariaDB10.0及以上版本中,作為MariaDB的一個新的主要特性。Spider的主要功能是將數據分散到多個後端節點,它的作用類似於一個代理。

 

本文主要分成四個部分來介紹Spider:

 

  1. 表鏈接:利用Spider,多個後端節點的表看起來就像存在於單一實例上一樣。

  2. 事務:Spider實現了XA事務/單機事務接口,支持XA事務,以便在多個數據節點之間同步或者更新數據。

  3. 插拔式引擎:Spider作為MySQL/MariaDB的一個插拔式引擎,實現handler類定義的表訪問方法。

  4. 讀寫流程:受MySQL Server層驅動,執行訪問數據的動作。

 

一、表鏈接  

 

Spider的表鏈接的技術參考ISO/IEC 9075-9:2008 SQL/MED標準。利用Spider的這個特性,你可以像操作本地MariaDB實例的表一樣來操作遠程MariaDB實例上的表,也可以像操作本地MariaDB實例的表一樣來操作分布在多個MariaDB實例上的表。

 

當創建一個Spider存儲引擎的表時,該表指向遠程服務器上對應的一張表或者多個實例上的表,就像UNIX/Linux中的軟鏈接一樣。遠程服務器上的表可以是任何存儲引擎的表。在執行CREATE TABLE命令創建Spider引擎的表時,需要添加COMMENT或CONNECTION語法來指定遠程服務器的地址等信息。例如,在遠程服務器(該服務器是數據節點,假設IP為192.168.0.1)上創建了如下一張表: 

 

CREATE TABLE s(id INT NOT NULL AUTO_INCREMENT, code VARHCAR(10), PRIMARY KEY(id));

 

在Spider節點創建一張表指向該表:

 

CREATE TABLE s(id INT NOT NULL AUTO_INCREMENT, code VARHCAR(10), PRIMARY KEY(id)) ENGINE=SPIDER COMMENT ‘host “192.168.0.1”,user “user1”, password “pwd1”, port “3307”’

 

Spider節點,表字段定義可以忽略。Spider第一次訪問表的時候,如果發現沒有表字段定義,會從後端節點拉取相關元數據,然後緩存在本地。

 

Spider的係統表spider_tables記錄了各個數據分片的位置信息,類似於編程語言中指針作用。該係統表可以便利Spider跨節點的join操作:訪問數據所在的機器,然後把數據拉取到本地進行join操作;如果進行join操作字段不是分片字段,那麼需要廣播SQL語句將數據拉取到Spider節點進行join操作。

 

Spider_tables類似圖1所示。

 

20170503093701236.jpg

圖1. Spider表鏈接

 

二、事務  

 

Spider分別針對單機事務與XA事務實現了相應的操作事務的方法。圖2列出了部分實現的方法。

 

20170503093710875.jpg

圖2. Spider部分實現的事務接口

 

上述方法的主要實現是向後端節點發送消息,有些階段同時需要執行記錄係統表的行為。Spider依賴後端數據節點保證事務的持久性以及隔離性。它隻負責開啟事務,以及在適當的時機發送提交或者回滾事務的命令。如果單機事務涉及多個數據節點,Spider需要將相應的連接保存在隊列中。在事務提交或者回滾的時候,逐個發送相應的命令。

 

Spider參照分布式事務DTP/XA模型實現了分布式XA事務(見圖3)。在這個模型中,存在RM(Resource Manager,資源管理器)、TM(Transaction Manager, 事務管理器)以及AP(Application, 應用程序)三種角色。AP通過RM API來操作和管理資源,通過TM接口開啟/終止/結束事務。RM與TM之間需要實現XA接口。XA接口定義了兩階段提交的必要步驟,以及RM與TM之間需要進行的交互。Spider扮演的是TM角色,而後端的數據節點扮演的是RM的角色。

 

20170503093717626.jpg

圖3. 分布式DTP/XA模型

 

為了使用分布式XA事務,業界定義的XA命令如下:

 

XA START 'trx-id';          //開啟XA事務

do actual work;             //實際的查詢執行語句

XA END 'trx-id';           //XA事務結束

XA PREPARE 'trx-id';      //預提交

XA COMMIT 'trx-id';       //提交

 

Spider會在係統表spider_xa中記錄XA事務的狀態,同時在另外一張係統表spider_xa_members中記錄參與該XA事務的節點,以便進行操作。

 

在Spider中,XA事務分別有四種狀態,如圖4所示,對應於NOT YET,PREPAED,ROLLBACK以及COMMITTED。Spider在開始PREPARE階段之際會在係統表spider_xa中標記該XA事務的狀態為NOT YET。在所有數據節點都接收到PREPARE消息以後,該XA事務的狀態進入到PREPARED階段。假如在PREPARE階段,某一個數據節點發生故障,那麼Spider會回滾該事務。相應地,事務的狀態變成ROLLBACK。

 

最後,如果所有參與事務的節點都返回PREPARE OK,該事務進入提交階段。圖5給出了對應上述命令的每一個步驟,Spider向後端節點發送的消息。

 

20170503093727805.jpg

圖4. Spider XA事務狀態轉換

 

20170503093741914.jpg

圖5. 執行XA事務,Spider與兩個後端節點的交互

 

從圖5可以看到Spider向後端節點發送XA START命令時會設置會話級別的事務特性,同時將XA事務ID發送到後端節點。因為XA事務ID由三部分組成,Spider會將這三個部分的解析出來,然後拚接成對應的字符串發送到後端節點。為了節省網絡開銷,Spider將XA END與XA PREPARE命令合並起來一起發送。也就是在這個階段初始,Spider在係統表裏麵記錄事務的狀態。如果所有的RM都返回OK,那麼Spider進入PREPARED狀態,準備提交事務。否則,事務進入到回滾狀態。

 

三、插拔式引擎  

 

MySQL最強大的功能之一,以及區別於其它關係型數據庫係統的一個主要的特色是不同的表能夠采用不同的存儲引擎。每一個存儲引擎都有其優缺點,用戶能夠根據自己的需要定製MySQL的存儲引擎。存儲引擎能夠控製在哪裏以及如何存放、獲取數據。它代表了下麵物理層提供的抽象邏輯接口,也是數據庫執行實際I/O操作的地方。這是一個組件體係結構。在這個結構中,handler類定義了存儲引擎提供的接口和功能。因為所有的存儲引擎從基類handler繼承而來,所以它們能夠提供相同的功能。

 

總的來說,handler類和handlerton結構在整個體係結構中扮演了中間層的角色。你所編寫的存儲引擎隻有滿足了handler的要求後,才能順利插入到運行的MySQL服務器中。所有的網絡連接、安全認證、解析和優化由MySQL服務器本身完成,與存儲引擎無關。

 

Spider作為MySQL的一個可插拔引擎,實現了handler類定義的相應的存取方法。Spider本身並不存放數據,而是類似一個代理的功能將訪問請求路由到後端的數據節點。Spider提供了兩種途徑訪問後端節點存儲的數據。如圖6所示,Spider可以遵循MySQL傳統的查詢處理流程來訪問數據,也開發了自有的一套來加速數據訪問。在傳統的查詢處理方式下,SQL查詢請求經過查詢解析、查詢重寫、查詢優化等步驟。按照生成的查詢執行計劃,Spider從後端節點拉取數據,交給MySQL服務器處理。Spider在這種查詢處理框架之下的一個缺點是不能很好地利用後端節點可並行化特性,同時需要對SQL查詢進行兩次解析,帶來的性能損耗問題比較嚴重。

 

在我們的測試中,性能損耗約50%左右。基於這個原因,為了加速聚集、統計等查詢,Spider開發團隊提供了DirectSQL方式執行查詢。DirectSQL的原理類似於Map Reduce方案,將查詢直接下發到後端節點,無需在MySQL服務器層進行解析(Map階段);後端節點將結果返回給Spider,由Spider合並結果集。(Reduce階段)。這個方式很好地利用後端節點可並行處理查詢的特點,消除重複解析SQL語句的行為。

 

20170503093751716.jpg

圖6. MySQL體係下的Spider

 

上麵已經談到,Spider本身並不存儲數據,因此需要將數據訪問請求轉換成其它方式,例如Handler、Handler Socket以及SQL方式。前麵兩種訪問方式更像是一種NoSQL的數據訪問方式,允許查詢繞過SQL layer層。Spider允許後端的數據節點可以是不同的數據庫係統,通過2PC保證事務提交的原子性。

 

四、讀寫流程  

 

為了更清楚地了解Spider的讀寫流程,我們有必要研究一下數據庫係統的查詢執行模型,以及MySQL的插拔式引擎如何跟這個模型對接的。

 

數據庫係統基本都采用迭代器模型處理查詢,也叫volcano查詢執行引擎(發明這個詞的學者大概是因為查詢執行計劃樹看起來像一座火山,如圖7)。執行計劃樹的上層節點通過get_next方法驅動子節點獲取一條元組,子節點遞歸調用。在葉子節點也就是基本表將數據返回。

 

這個模型的一個好處就是實現起來很優雅,同時數據流與控製流結合在一起方便程序的調試。這個模型的缺點是函數的大量調用使得進程/線程上下文切換頻繁,程序的局部性受到損害。因此,後來針對OLAP場景,采用了向量查詢執行模型來減少進程上下文的切換以及保證保證高速緩存的命中率。

 

再次以圖7為例子,圖中的SQL語句的功能是查詢一個部門的平均薪資。假如在職工表EMP的員工ID字段Dno上存在索引,MySQL在Server層針對該查詢語句生成的查詢計劃如下:順序掃描部門表,通過索引訪問職工表,然後在兩表join操作之後進行投影操作。下一個階段為分組排序操作。上層的操作算子(例如join),驅動子節點調用get_next方法(表掃描方法)獲取一條元組。底層操作算子(表訪問方法,handler接口定義)將數據返回。至此,我們可以總結一下MySQL體係的工作原理:查詢執行計劃由MySQL Server層生成,存儲引擎受執行計劃驅動而訪問表。MySQL的handler已經定義好表的訪問方法,實現了這些訪問方法的存儲引擎就可以作為MySQL的插件式引擎而存在。

 

下麵我們對Spider的讀寫流程結合Server層代碼進行分析。

 

20170503093807968.jpg

圖7. 查詢計劃樹示例

 

1、SELECT操作

 

上麵提到Spider的作用類似一個proxy,本身並不存儲數據。因此Spider處理SELECT語句(UPDATE與DELETE類似)首先需要根據查詢解析的信息生成一個SELECT語句,發送到查詢涉及的後端節點,將數據從遠端拉到本地,然後進行處理。函數spider_db_append_select_columns根據查詢涉及的讀集以及寫集獲取相應的字段,構造一個SQL語句從後端節點拉取數據到本地。如果涉及多個分片,spider將從不同實例獲取過來的結果集存放在不同的結果集spider_db_result中。類spider_db_fetch提供了fetch_next,  current_row等方法供上層方法調用。Server層調用get_next方法驅動引擎層獲取下一條數據。

 

對於表訪問方法,MySQL實現了索引掃描(ha_index_read)與隨機訪問(ha_rnd_next)的方法。對於切分為多個分片的DB,索引掃描需要借助優先隊列。索引掃描需要區分是否是第一次調用該方法。如果是第一次調用該方法,需要遍曆所有的分片讀取一條記錄,然後插入到優先隊列。對應到Spider,如果第一次調用訪問遠端實例表的方法,需要生成SELECT語句,將遠端實例的數據拉到本地存放。在使用索引掃描的情況,MySQL 為每個分片保留一個key buffer以及record buffer。server 利用隊列頭部的m_top_entry 獲得訪問的分片ID。接著,調用get_next方法獲取相應的元組,將返回的數據存放在record buffer,並插入到優先隊列。函數最後將元組從優先隊列返回。

 

為緩解內存等資源的壓力,Spider實現全表掃描的方法是逐個分片串行掃描(為了加速,spider也提供了並行掃描數據節點的選項)。圖8給出了Spider對於上述兩種表訪問方法的實現機製。

 

20170503093815736.jpg

圖8-1. 索引掃描實現

 

20170503093822670.jpg

圖8-2. 全表掃描

 

2、INSERT操作

 

MySQL的handler類對於INSERT操作提供的接口函數的名字是write_row。存儲引擎想要支持INSERT操作就必須實現write_row方法。Spider對於write_row方法的實現是簡單地根據查詢解析的信息拚接一條INSERT語句,發往後端節點處理。如果是批量插入操作則需要與MySQL Server層配合,將INSERT語句批量發到後端節點。

 

圖9結合一條批量插入的INSERT語句給出MySQL中INSERT操作的具體實現。

 

mysql_insert調用write_row執行具體的插入操作(第8行)。這是存儲引擎必須實現的方法。對應於spider,spider根據查詢涉及到的列(field)拚成一條INSERT語句(如果是分片數據庫,VALUSE中的列必須包含分區鍵,分區鍵是自增列的情況除外)。圖9中的QUERY將用戶ID(ID)和用戶名(Name)插入到user表,其中ID是分區鍵。mysql_insert根據VALUES包含的元組數目,判斷是否需要進行批量插入操作。該例子的QUERY的VALUES包含4條元組,所有需要進行批量插入操作。MySQL循環調用write_row方法觸發spider生成INSERT語句。Spider的write_row方法實現中會根據分區鍵將INSERT語句進行分組(第5行~第9行)。圖9給出的實例隻有兩個數據分片,所以SQL語句被分成兩組。處理完VALUES以後,Spider的INSERT語句也拚接完成。

 

ha_end_bulk_insert方法通知Spider完成VALUES處理。此時,Spider將INSERT發送到後端節點進行處理(第11行)。

 

20170503093830768.jpg

圖9. Spider中INSERT操作的實現

 

3、DELETE實現

 

Spider想要支持DELETE操作必須實現MySQL handler類提供的ha_delete_row方法。與INSERT操作不同,DELETE操作需要生成一條SELECT語句將查詢涉及的分區鍵拉到Spider節點。這是因為MySQL Server層的“once-a-tuple”的查詢執行模型(實際上基本所有的關係數據庫係統都采用該模型)會驅動Spider逐個拚接DELETE語句,然後發往後端節點。這時候,Spider需要知道對應的DELETE語句該往哪個後端節點發送。為了減少網絡開銷,Spider提供了批量發送DELETE語句的功能。

 

20170503093839209.jpg

圖10. DELETE實現

 

圖10給出了Spiderpider中delete的實現。MySQL Server層首先確定表的訪問方法:采用索引掃描或者全部掃描(第5行)?DELETE方法需要執行一次查找操作,調用get_next方法(info.read_record)獲取一條元組(第10行)。Spider需要判斷是否第一次調用get_next方法。如果是的話,則需要生成SELECT語句,將數據節點的數據拉到本地。否則,Spider直接從本地返回數據給上層調用者。接下來,Server層調用ha_delete_row方法將數據刪除。這是存儲引擎需要具體實現的方法。由於Spider本身並不存儲數據的緣故,其實現delete操作主要思想是利用從後端節點拉取過來的數據(分區鍵,過濾條件等),拚接成一條DELETE語句。然後,發送該請求到數據節點。Spider為了優化網絡開銷,提供了批量發送DELETE語句的選項。

 

UPDATE操作的實現類似DELETE,都需要Spider生成SELECT語句從後端節點拉取數據。隻不過,UPDATE在更新區分鍵的時候,可能需要多一次DELETE操作(刪除原來分區的數據,將新的數據插入到不同的分區)。

 

總結  

 

Spider的最大亮點是為MySQL的使用者提供分庫分表的中間件解決方案,同時在SQL語法上兼容MySQL。這得益於Spider作為MySQL的插拔式引擎而存在。Spider是一個proxy,其本身並沒有存儲數據,因此上層的讀寫表請求需要轉換成SQL語句,重新路由到後端的數據節點。相比其它的中間件解決方案,Spider的查詢解析次數都是兩次,並沒有過多開銷。此外,Spider還針對聚集、排序等操作提供了MAP REDUCE的解決方案。

 

總之,從兼容性、性能上衡量,Spider是MySQL分庫分表一個不錯的選項。

原文發布時間為:2017-05-03

本文來自雲棲社區合作夥伴DBAplus

最後更新:2017-05-17 14:01:47

  上一篇:go  Flex is open sourced!
  下一篇:go  Spring源碼分析:實現AOP(轉載)