.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 
如果大家對這個項目感興趣,可以到上面看看。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM