Q: 什麼叫"自動化執行引擎"?數據庫
A: 一條SQL的生命週期是:從客戶端發起、通過Sharding-Sphere處理、再到底層數據庫執行消化。而在Sharding-Sphere裏過程則是:SQL解析-->SQL優化-->SQL路由-->SQL改寫-->SQL執行-->結果歸併。自動化執行引擎是爲了處理SQL執行問題的,即將路由改寫後的真實SQL如何有控制且高效地傳送到底層數據庫執行。那麼直接經過JDBC發送SQL至數據庫執行難道行不通嗎?還有其餘須要考慮嗎?答案是:確定有其餘考慮,不然我就不用寫這篇文章了。這就體如今它的"自動化"上了。所謂"自動化",實際上是爲了平衡數據庫鏈接建立與結果歸併模式選擇問題,爲了平衡資源控制與執行效率問題。數據結構
需求場景多線程
Q: 爲什麼須要自動化執行引擎呢?併發
A: 在概念介紹部分,咱們介紹了主角-自動化執行引擎。也談到它的自動化是爲了平衡數據庫鏈接建立以及結果歸併模式選擇問題。這是它誕生的宿命,歷史的選擇。下面將爲你們介紹這兩個須要平衡的問題:分佈式
1. 數據庫鏈接建立性能
做爲一位混娛樂圈的DBA出身的Java coder, 多少仍是會從DBA角度考慮問題。好比從資源控制的角度看,業務方訪問數據庫的鏈接數量應當有所限制,這可以有效地防止某一業務操做過多地佔用資源,從而將數據庫鏈接的資源耗盡,以至於影響其餘業務的正常訪問。特別是在一個數據庫實例中存在較多分表的狀況下,一條不包含分片鍵的邏輯SQL將產生落在同庫不一樣表的大量真實SQL,若是每條真實SQL都佔用一個獨立的鏈接,那麼一次查詢確定將會佔用過多的資源。Sharding-Sphere做爲數據庫中間層,若是沒有控制好數據庫鏈接數量而致使鏈接暴增、數據庫壓力過大的話,極有可能被強行背鍋。學習
2. 結果歸併模式選擇優化
可是從執行效率的角度看,爲每一個分片查詢維持一個獨立的數據庫鏈接,能夠更加有效地利用多線程來提高執行效率。爲每一個數據庫鏈接開啓獨立的線程,能夠並行化IO所產生的消耗。獨立的數據庫鏈接,可以保持查詢結果集的引用以及遊標位置,在須要獲取相應數據時移動遊標便可,避免了過早將查詢結果數據加載至內存。這就涉及到告終果歸併模式的選擇問題。經過上一篇文章《剖析Sharding-Sphere系列——結果歸併》介紹,咱們知道當前有兩種結果歸併的模式,分別是:線程
流式歸併:以結果集遊標下移進行結果歸併的方式,稱之爲流式歸併,它無需將結果數據全數加載至內存,能夠有效地節省內存資源,進而減小垃圾回收的頻次。code
內存歸併:以讀取內存中加載的結果集進行歸併的方式,進行數據對比歸併。它須要將結果數據全數加載至內存。
相信只要是智商在線的朋友,必定會選擇流式歸併來處理結果集。但是,若是沒法保證每一個分片查詢持有一個獨立數據庫鏈接的話,那麼就須要在複用該數據庫鏈接、獲取下一張分表的查詢結果集以前,將當前的查詢結果集全數加載至內存。所以,即便能夠採用流式歸併,在此場景下也不得不退化爲內存歸併。
一方面是對數據庫鏈接資源的控制保護,一方面是採用更優的歸併模式達到內存資源節省的目的,如何處理好二者之間的關係,是Sharding-Sphere執行引擎需求解決的問題。具體來講,若是一條SQL在通過Sharding-Sphere的分片後,須要操做某數據庫實例下的200張表,那麼,是選擇建立200個鏈接並行執行,仍是選擇建立一個鏈接串行執行呢?效率與資源控制又應該如何抉擇呢?
進化論
針對上述的場景,Sharding-Sphere在3.0.0.M4以前提供了一種解決思路,即提出了鏈接模式(Connection Mode)的概念,並劃分了兩種模式:內存限制模式(MEMORY_STRICTLY)和鏈接限制模式(CONNECTION_STRICTLY)這兩種類型。
內存限制模式
使用此模式的前提是數據庫對其一次操做所耗費的鏈接數量不作限制。若是實際執行的SQL須要對某數據庫實例中的200張表作操做,則對每張表建立一個新的數據庫鏈接,並經過多線程的方式併發處理,以達成執行效率最大化。而且在SQL知足條件狀況下,優先選擇流式歸併,以防止出現內存溢出或避免頻繁垃圾回收狀況。
鏈接限制模式
使用此模式的前提是數據庫嚴格控制對其一次操做所耗費的鏈接數量。若是實際執行的SQL須要對某數據庫實例中的200張表作操做,那麼只會建立惟一的數據庫鏈接,並對其200張表串行處理。若是分片在不一樣的數據庫,仍然是多線程處理不一樣庫,但每一個庫的每次操做仍然只建立一個惟一的數據庫鏈接。這樣便可以防止對一次請求對數據庫鏈接佔用過多所帶來的問題。該模式始終選擇內存歸併。
內存限制模式適用於OLAP操做,能夠經過放寬對數據庫鏈接的限制提高系統吞吐量;鏈接限制模式適用於OLTP操做,OLTP一般帶有分片鍵,會路由到單一的分片,所以嚴格控制數據庫鏈接,以保證在線系統數據庫資源可以被更多的應用所使用,是明智的選擇。
而Sharding-Sphere最終使用何種模式的決定權就交由用戶。Sharding-Sphere提供對鏈接模式的配置,讓開發者依據本身業務的實際場景需求選擇使用內存限制模式或鏈接限制模式。
但是,將兩難的選擇的決定權甩鍋給用戶,使得用戶必需要了解這兩種模式的利弊,並依據業務場景需求進行選擇。這顯然增長了用戶對Sharding-Sphere的學習和使用的成本,這並非一種最優的解決方案。
此外,這種一分爲二的處理方案,將兩種模式的切換交由靜態的初始化配置,缺少靈活應性。在實際的使用場景中,面對不一樣SQL以及佔位符參數,每次的路由結果是不一樣的。這就意味着某些操做可能須要使用內存歸併,而某些操做則可能選擇流式歸併更優,它們不該該由用戶在Sharding-Sphere啓動以前配置好,而更應該根據SQL和佔位符參數的場景,來動態的決定鏈接模式。
像Sharding-Sphere這樣,老是站在用戶角度考慮問題而且不斷優化精進的七道槓青年是必定要進行相關優化調整的,因而自動化執行引擎就進化出來了。
爲了下降用戶的使用成本以及鏈接模式動態化這兩個問題,Sharding-Sphere提煉出自動化執行引擎的思路,在其內部消化了鏈接模式的概念。用戶無需瞭解所謂的內存限制模式和鏈接限制模式是什麼,而是交由執行引擎根據當前場景自動選擇最優的執行方案。
同時,自動化執行引擎將鏈接模式的選擇粒度細化至每一次SQL的操做。針對每次SQL請求,自動化執行引擎都將根據其路由結果,進行實時的演算和權衡,並自主地採用恰當的鏈接模式執行,以達到資源控制和效率的最優平衡。針對自動化的執行引擎,用戶只需配置maxConnectionSizePerQuery便可,該參數表示一次查詢時每一個數據庫所容許使用的最大鏈接數,剩餘的處理邏輯將由自動化執行引擎爲您負責。
實現解析
整個自動化執行引擎的執行流程以下圖所示。
在路由改寫完成後,咱們會獲得路由結果(SQLRouteResult),這個結果集主要包含了SQL、SQL的參數集、數據庫等信息。其數據結構以下圖所示:
執行引擎的執行過程分爲準備、執行兩個階段。
準備階段
顧名思義,此階段用於準備執行的數據。它分爲結果集分組和執行單元建立兩個步驟。
a. 結果集分組
該步驟是實現內化鏈接模式概念的關鍵。執行引擎根據maxConnectionSizePerQuery配置項,結合當前路由結果,自動選擇恰當的鏈接模式。具體步驟以下:
1. 將SQL的路由結果按照數據庫的名稱進行分組。
2. 經過下圖的公式得到每一個數據庫實例在maxConnectionSizePerQuery的容許範圍內,每一個數據庫鏈接須要執行的SQL路由結果組,並演算出本次請求最優的鏈接模式。
在maxConnectionSizePerQuery容許的範圍內,當一個鏈接須要執行的請求數量大於1時,意味着當前的數據庫鏈接沒法持有相應的數據結果集,則必須採用內存歸併;反之,當一個鏈接須要執行的請求數量等於1時,意味着當前的數據庫鏈接能夠持有相應的數據結果集,則能夠採用流式歸併。
每一次的鏈接模式的選擇,是針對每個物理數據庫的。也就是說,在同一次查詢中,若是路由至一個以上的數據庫,每一個數據庫的鏈接模式不必定同樣,它們多是混合存在的形態。
b. 執行單元建立
該步驟經過上一步驟得到的路由分組結果建立用於執行的單元。執行單元是指爲每一個路由分組結果建立相應的數據庫鏈接。
當數據庫被限制了鏈接資源數量且線上業務出現大量併發操做時,若是不妥善處理併發獲取數據庫鏈接的問題,則頗有可能會發送死鎖。在多個請求相互等待對方釋放數據庫鏈接資源時,就會產生飢餓等待,形成交叉死鎖。
舉個栗子,假設一次查詢須要在某一數據庫上獲取2個數據庫鏈接,用於路由至一庫的2個分表查詢。有可能出現查詢A已獲取到該數據庫的1個數據庫鏈接,並等待獲取另外一個數據庫鏈接;而查詢B則也已經得到了該數據庫上的1個數據庫鏈接,並一樣等待另外一個數據庫鏈接的獲取。若是數據庫鏈接池的容許最大鏈接數是2,那麼這2個查詢請求將永遠孤獨地等待着彼此,圖繪版的解釋可能會更便於你們理解:
爲了不死鎖的出現,Sharding-Sphere在獲取數據庫鏈接時進行了同步處理。它在建立執行單元時,以原子性的方式一次性獲取本次SQL請求所需的所有數據庫鏈接,杜絕了每次查詢請求獲取到部分資源的可能。這種加鎖作法確實能夠解決死鎖問題,只是,同時會帶來必定程度併發性能的損失。爲了展現咱們不同!有啥不同呢?
咱們針對此問題還進行了如下兩方面優化:
1. 避免鎖定一次性只需獲取一個數據庫鏈接的操做。由於每次僅須要獲取一個鏈接,就不會發生兩個請求相互等待的場景,無需鎖定。對於大部分OLTP的操做,都是使用分片鍵路由至惟一的數據節點,此時無需擔憂交叉死鎖問題,也無需考慮加鎖問題,從而減小對併發效率的影響。除了路由至單分片的狀況,讀寫分離也屬於此範疇以內的場景。
2. 僅針對內存限制模式進行連接資源的鎖定。在使用鏈接限制模式時,數據庫鏈接資源在全部查詢結果集裝載至內存以後被釋放掉,所以沒必要考慮死鎖等待、加鎖處理的問題。
執行階段
該階段用於真正的執行SQL,它分爲分組執行和歸併結果集生成兩個步驟。
a. 分組執行
該步驟將準備執行階段生成的執行單元分組下發至底層併發執行引擎,並針對執行過程當中的每一個關鍵步驟發送事件。如:執行開始事件、執行成功事件以及執行失敗事件。執行引擎僅關注事件的發送,它並不關心事件的訂閱者。Sharding-Sphere的其餘模塊,如:分佈式事務、調用鏈路追蹤等,會訂閱感興趣的事件,並進行相應的處理。
b. 歸併結果集生成
Sharding-Sphere經過在執行準備階段的獲取的鏈接模式,生成內存歸併結果集或流式歸併結果集,並將其傳遞至結果歸併引擎,以進行下一步的工做。內存歸併結果集或流式歸併結果集的核心區別是:流式歸併結果集會經過遊標方式獲取結果集的數據,而內存歸併結果集則是從內存裏獲取數據。這也是內存歸併和流式歸併的數據基礎。
經過上述全部步驟就完成了自動化執行引擎的執行流程。其核心目的是自動化平衡數據庫鏈接建立以及結果歸併模式選擇問題,實現細粒度地平衡把控每一次查詢的資源控制與執行效率,從而減小用戶的使用學習成本和業務場景變化的擔心。