客戶端分片到Proxy分片,如絲般順滑的平穩遷移

點擊上方藍色字體,選擇“設爲星標”

回覆”學習資料“獲取學習寶典


背景

隨着訂單數量的增多,以及大促時需要扛住比平時多N倍的流量,單庫單表的瓶頸日益顯現。

需要對數據庫進行水平拆分,目前訂單使用的是客戶端分片的方式進行拆分,採用Sharding-Jdbc框架實現。

Sharding-Jdbc後改名爲ShardingSphere,提供client和proxy方式進行數據庫的拆分,目前訂單使用的是client方式。

client方式的優勢是實現簡單,只需要通過簡單的配置即可完成拆分操作。在本地通過分片進行計算,得到真實的庫和表進行路由,性能相對較高。不依賴於三方,沒有單點故障。

client方式的劣勢是每個項目都要去管理分片,讀寫分離等信息,沒辦法統一進行管理。

當需要升級的時候只能所有項目都進行升級,沒辦法統一升級。最難的點在於需要推動各個業務域進行升級,升級的週期較長,對業務方的壓力也比較大。

相對於proxy方式,client方式連接數佔用的比較多,一個數據源10個連接,部署10個實例就是100個連接。

proxy方式指的是部署一個獨立的服務,這個服務會實現Mysql協議,應用中只需要連接這個獨立的proxy服務,把它當做一個完整的獨立的數據庫使用即可。

分庫分表等規則全部由這個proxy去管理,對應用透明。

彩虹橋DAL是得物基礎架構組研發的proxy方式的代理服務,有了彩虹橋就可以統一管理所有數據庫,可以對數據庫操作做限流保護,可以做壓測時的數據自動進入影子庫,可以監控慢SQL等等。

接入方案

直接修改老數據源-改成連接彩虹橋

最簡單,最快速的方式就是直接將所有老的數據源改成新的代理數據源,然後重新發布就可以了。這個方案確實快,但是不夠好,不夠完美,不夠穩定,爲啥?

這其實跟改接口是一樣的道理,當某天加了個新的需求,你會發現直接在老接口的基礎上改一下就完成了。改完後測試發現當前沒問題,但是老版本的產品還在用,然後兼容出問題了。這種場景下如果你是對接口進行升級,之前是v1,現在改成v2,老的邏輯完全不動,就完美了呀。

如果我們直接改成最新的方式,那麼如何平滑的發佈上線呢?

全部發布,萬一彩虹橋那邊配置有問題,或者有其他的問題導致不可用,你這不就尷尬了麼,當然也可以回滾歷史版本。

另一種方式就是灰度發佈一臺機器,進行測試,沒問題後全部發布,有問題就不發,其實也不錯。就怕過了一段時間,萬一彩虹橋有Bug, 我們還是得回滾到接入彩虹橋之前的版本。

新增一套數據源 - 支持開關切換到新數據源,下線老數據源

新增數據源,支持開關切換就跟升級API版本是一樣的邏輯。保持老邏輯不變,上線時還是走老的數據源,然後通過開關動態切換到新的數據源,完成上線動作。

當然這邊也會出現上面提到的問題,比如彩虹橋不可用之類的情況,也可以通過灰度配置的方式來測試。將開關配置灰度到某個節點上,只切這個節點的數據源,進行測試,沒問題後可以擴大範圍。有問題開關一關就還原到之前的邏輯了,無影響。

如果用了一段時間,彩虹橋有Bug的話同樣改下開關的配置即可回滾,也不用重新發布項目。

此方案不足的點就是會佔用一分部數據庫的連接,因爲老的數據源還存在。此時需要改代碼去掉老的數據源,就能釋放了。如果後面還要去改代碼,那還能叫完美的方案麼?

這點我也考慮到了,還是通過配置開關來關閉老的數據源,但是這個操作得重啓服務,重啓後就只有一套數據源了。(請確保以後只用彩虹橋的方式才用此開關)

實現步驟

  • 配置2套新數據源,連接彩虹橋的地址,訂單的庫分爲老訂單庫和新的分庫分表庫。

  • 新增2個動態數據源,用於開關動態切換,使用AbstractRoutingDataSource管理。

  • 老數據源通過@ConditionalOnProperty來控制是否加載,默認加載,用於後期下線老數據源。

  • 讀寫分離兼容client和proxy方式。

  • 分片算法重寫,之前用的Sharding-Jdbc3.X版本,新的彩虹橋基於5.X版本深度定製開發,在自定義算法這塊有變化,目前彩虹橋的分片算法全部在彩虹橋的擴展包中,不在訂單裏面。

注意事項

select last_insert_id()不支持

在insert中通過select last_insert_id()實時返回當前插入的自增ID場景需要修改,目前訂單中就一個地方用到了,而且上層其實沒消費這個ID, 如果後面有其他場景需要獲取剛插入的ID可以手動提前獲取分佈式ID,然後再用這個ID存到表中。

強制走從節點查詢

老的讀寫分離用的Sharding-Jdbc做的,正常情況下默認查詢走從節點,其他走主節點。而現在訂單裏面默認都是走主節點,如果需要強制走從節點需要使用HintManagerHolder.clear() 清空Hint信息來實現。

猜測最開始沒有從節點,業務都走主節點。後面加了從節點,如果用默認的方式查詢都會走從節點,但是在某些業務場景中一致性比較高,必須走主節點查詢,相對於標記出哪些查詢走從節點來說,默認全部走主節點更穩。然後根據業務場景再標記是否要走從節點,這樣幾乎不會影響老的邏輯。

接入彩虹橋後,默認是彩虹橋和Sharding-Jdbc兩套數據源共存的,所以在HintManagerHolder.clear() 這塊也需要做兼容,同時支持彩虹橋和Sharding-Jdbc方式的強制走從節點。

第一步:兼容老代碼

將所有使用HintManagerHolder.clear()改成SqlHintUtils.clear()。

public class SqlHintUtils {
   public static void clear() {        
     // ShardingJdbc        
     HintManagerHolder.clear();        
     // 彩虹橋DAL        
     BifrostContext.clearNotAutoClearPrimary();    
   }
}

第二步:新增註解強制走從節點,比代碼更優雅

以後有新的查詢需要走從節點就在Dao方法上增加@ForceSlave註解即可,此註解只能作用於Dao方法上,加在其它層無效。如果Dao方法上加了註解,那麼方法內所有的查詢操作都將走從節點。

老的clear相關的代碼其實可以用註解代替,但爲了保險起見還是不改變原有的方式,新的可以用註解的方式。

參考文檔 shardingsphere:https://shardingsphere.apache.org/document/current/cn/overview/

後臺回覆 學習資料 領取學習視頻


如有收穫,點個在看,誠摯感謝


本文分享自微信公衆號 - 猿天地(cxytiandi)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章