.Net下的分庫分表幫助類——用分庫的思想來分表

    在大型項目中,我們會遇到分表分庫的情景。 
    分庫,將不同模塊對應的表拆分到對應的數據庫下,其實伴隨着公司內分佈式系統的出現,這個過程也是自然而然就發生了,對應商品模塊和用戶模塊,我們會建立商品服務和用戶服務,各個服務訪問各自的數據庫,系統間的交互,通過遠程調用實現,而不是直接訪問其數據庫。
    但是隨着業務的進一步發展,數據表也會出現瓶頸,比如數據表的記錄已經超過了千萬級,到了這個量級,速度也會慢下來。所以接下來就是分表。 比如用戶表,我們會分user_1,user_2,user_3,....,我們會按照用戶的Id取模的方式來定位表,假如用戶表有3個,則Id是5的用戶信息會落在第二張表。 分表的方式多種多樣,比如商品表就適合按照日期來分表,一個月一張。 (分表還有一種是將不同的字段,分配到不同的表中)
目前我所知道的分表的方式,大概有以下幾種
    1.自己手動控制,來決定操作那張表,比如要查詢Id爲5的用戶信息,則會先5%(表的個數)=N 然後通過字符串拼接user_+"N"的方式得到表名,然後再訪問數據庫。
    2. sql解析替換,比如要查詢Id爲5的用戶信息,sql爲select * from user,這裏user表其實在數據庫中不存在,是一個邏輯表,在調用的更底層,會解析這個sql語句,找出表名,然後根據分表規則,替換成具體的表名。 這種方式比上面的侵入性要底。
    3. 代理方式,其實和上面的類似,只是具體替換工作是代理服務器做的,在連接數據庫服務器的時候,我們連接的是代理,代理再連接數據庫,我們執行一個sql語句,會先發送到代理服務器,代理服務器根據預先指定的分庫分表規則,路由到具體的數據庫。 對於我們系統來說,就是零侵入。 
    4. 數據庫服務器本身的支持,比如sql server本本身就支持分表。
數據分表看似簡單, 其實也非常困難,比如: 
    在應對Join查詢上,我們不能再像原來那麼操作。
    在未使用分表規則時的查詢,比如,用戶表是按照Id取模分表的,但是如果有一個查詢是select * from user where loginid='XX' , 那就相當於要並行查詢多張表。 
    在面對批量插入的時候。 
    等等。 當想要把分表做的更通用,更透明時,都會面對這個問題。
我的想法和上面第一種比較類似,我想做的更通用一些,但是表名是始終繞不過去的,後來索性換了一種思路,既然這樣做如此麻煩,那表名就不替換了,替換庫,這就是我標題裏說的,用分庫的思想來分表,同時還得到另外的一個好處,就是當數據庫服務器IO遇到瓶頸的時候,可以將這些數據庫中一部分遷移到其他機器上。
    比如 用戶表(user)需要分成3個,那我就新建3個數據庫,每個數據庫中各有一張表(user),當我執行select * from user  where id=5 的時候,我會根據規則,切換數據庫連接,這個sql裏面的user表,在對應數據庫裏是真實存在的。 這些數據庫可以在同一臺機器上,當服務器遇到壓力時,可以將這3個數據庫分佈到3臺機器上去,比起遷表,遷庫更容易。 
    有了這個思路,接下來就是如何儘可能的低浸入,這裏我使用.net的Attribute(當然,也可以搞成配置文件方式),通過給方法打標籤來提供一些信息,最後就是如何解析這些標籤,我這裏使用AOP, 當然完全的零侵入是不可能的,但是也只是需要你在訪問數據庫的方法中,多一行代碼,就是獲取數據庫連接的。 
 
 我們先看數據訪問層
這裏數據訪問我用的是dapper,對於需要分庫的的方法,只需要在方法上打上一個標籤ShardingMode,參數包括你分庫的規則,以及你的表的數量,至於需要根據哪個參數來分,則只需要在這個參數上打上ShardingKey的標籤,如果是對象,則可以寫上具體的key,其實也就是屬性名。 
    以上面的爲例,我使用的是取模的的方式來分表(ShardingMode = ShardingMode.Mod),表總共有3張,對於第一個方法,因爲傳進去的是對象,所以需要標示出具體是按照那個字段來的, 對於第二個方法,因爲是簡單類型,則直接打上標籤就可以。每個方法中有一行代碼, IDbConnection connection = ShardingConnUtils.GetConnection();,這個算是侵入的代碼,主要是獲取連接對象的,
 
下面是核心代碼
 
 
其實核心代碼的思想很簡單,首先是獲取方法上的標籤,根據標籤的值來分別選擇不同的分庫規則,然後獲取方法的參數,看參數上是否打了標籤,如果有標籤,再根據參數的值,計算出具體分到哪張表。 注意上面的最後一行,ShardingConnUtils.SetConnectionIndex(),其實就是設置對應的數據庫連接的,具體的值,會放在ThreadLocal中。 在操作數據庫的方法中,就可以通過ShardingConnUtils.GetConnection()方法取得對應的連接。 
 
最後就是如何攔截方法來獲取這些標籤,這裏就該AOP出場了,這裏我使用了sheepaspect,
這裏可以看到我定義了一個切面,主要是攔截方法上有ShardingModeAttribute標籤的方法,當這類方法在執行的時候,會先執行  ShardingCore.Process(jp.Method,jp.Args); 來決定是哪個數據庫連接,最後再執行具體的方法。
 
最後的執行
需要先註冊數據庫連接,以用於後面的切換,剩下的就和普通的方法調用沒什麼區別了。 
 
    1. 可以看到我在定義要攔截哪些方法的時候,是隻有ShardingModeAttribute標籤的方法,我無意做一個通用的數據庫連接管理框架,對於普通的單表操作,數據庫連接還是由你們自己管理。
    2. 對join查詢, 批量插入等操作,還沒有辦法支持,但其實在高併發項目中,Join查詢很少用。 對於未使用分表規則時的查詢,完全可以再建立一個內存映射規則解決,對於批量插入,可以考慮自己控制。 
    3. 對於分庫的規則,我只實現了取模的方式,其他的如果要實現也是非常簡單的,同時分庫規則多種多樣,可以按照自己的需求來實現。 
    這裏AOP框架是關鍵但不是重點, .Net的AOP框架有多種選擇,比如PostSharp,RealProxy,EntLib,可以選擇任意一種。 以前我並沒有關注.Net AOP, 但是通過這次的項目,我的思路一下次都打開了,我們可以實現很多的東西:
    比如在分佈式調用上,我們可以控制方法重試, 因爲分佈式調用有一定機率的失敗,只要保證冪等性,我們可以失敗重試 。
    其次,我們可以定義性能監控,我們可以在方法執行前記錄一個時間,方法執行後記錄一個時間,這樣就可以算出方法執行的耗時,同時記錄方法調用的次數,最後彙總到一起,就可以看出整個系統的性能瓶頸在哪裏,也可以知道系統的繁忙程度。 配合docker,可以自動擴充我們的系統。 
    最後,我們可以對攔截的方法做try catch 來記錄未捕獲的異常,彙集到一起做一個異常報警系統。 
 
代碼我已經上傳到gtihub,地址是 https://github.com/zhaoyb/DBSharding 
如果大家對這個項目感興趣,可以到上面看看。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章