1. 程式人生 > >Spring + iBatis 的多庫橫向切分簡易解決思路

Spring + iBatis 的多庫橫向切分簡易解決思路

1.引言 
   筆者最近在做一個網際網路的“類SNS”應用,應用中使用者數量巨大(約4000萬)左右,因此,簡單的使用傳統單一資料庫儲存肯定是不行的。 

   參考了業內廣泛使用的分庫分表,以及使用DAL資料訪問層等的做法,筆者決定使用一種最簡單的資料來源路由選擇方式來解決問題。 

   嚴格的說,目前的實現不能算是一個解決方案,只能是一種思路的簡易實現,筆者也僅花了2天時間來完成(其中1.5天是在看資料和Spring/ibatis的原始碼)。這裡也只是為各位看官提供一個思路參考,順便給自己留個筆記 

2.系統的設計前提 
   我們的系統使用了16個數據庫例項(目前分佈在2臺物理機器上,後期將根據系統負荷的增加,逐步移庫到16臺物理機器上)。16個庫是根據使用者的UserID進行簡單的hash分配。這裡值得一說的是,我們既然做了這樣的橫向切分設計,就已經考慮了系統需求的特性, 
  • 1.不會發生經常性的跨庫訪問。
  • 2.主要的業務邏輯都是圍繞UserID為核心的,在一個單庫事務內即可完成。


   在系統中,我們使用Spring和iBatis。Spring負責資料庫的事務管理AOP,以及Bean間的IOC。選擇iBatis的最大原因是對Sql的效能優化,以及後期如果有分表要求的時,可以很容易實現對sql表名替換。 


3.設計思路 
   首先,要說明一下筆者的思路,其實很簡單,即“在每次資料庫操作前,確定當前要選擇的資料庫物件”而後就如同訪問單庫一樣的訪問當前選中的資料庫即可。 

   其次,要在每次DB訪問前選擇資料庫,需要明確幾個問題,1.iBatis在什麼時候從DataSource中取得具體的資料庫Connection的,2.對取得的Connection,iBatis是否進行快取,因為在多庫情況下Connection被快取就意味著無法及時改變資料庫連結選擇。3.由於我們使用了Spring來管理DB事務,因此必須搞清Spring對DB Connction的開關攔截過程是否會影響多DataSource的情況。 

   幸運的是,研究原始碼的結果發現,iBatis和Spring都是通過標準的DataSource介面來控制 
Connection的,這就為我們省去了很多的麻煩,只需要實現一個能夠支援多個數據庫的DataSource,就能達到我們的目標。 

4.程式碼與實現
 
多資料庫的DataSource實現:MultiDataSource.class 
Java程式碼  收藏程式碼
  1. import java.io.PrintWriter;  
  2. import java.sql.Connection;  
  3. import java.sql.SQLException;  
  4. import java.util.ArrayList;  
  5. import java.util.Collection;  
  6. import java.util.HashMap;  
  7. import java.util.Map;  
  8. import javax.sql.DataSource;  
  9. import org.apache.log4j.Logger;  
  10. import com.xxx.sql.DataSourceRouter.RouterStrategy;  
  11. /** 
  12.  * 複合多資料來源(Alpha) 
  13.  * @author [email protected] 
  14.  * Jul 15, 2010 
  15.  */  
  16. public class MultiDataSource implements DataSource {  
  17.     static Logger logger = Logger.getLogger(MultiDataSource.class);  
  18.     //當前執行緒對應的實際DataSource  
  19.     private ThreadLocal<DataSource> currentDataSourceHolder = new ThreadLocal<DataSource>();  
  20.     //使用Key-Value對映的DataSource  
  21.     private Map<String , DataSource> mappedDataSources;  
  22.     //使用橫向切分的分散式DataSource  
  23.     private ArrayList<DataSource> clusterDataSources;  
  24.     public MultiDataSource(){  
  25.         mappedDataSources = new HashMap<String , DataSource>(4);  
  26.         clusterDataSources = new ArrayList<DataSource>(4);  
  27.     }  
  28.     /** 
  29.      * 資料庫連線池初始化 
  30.      * 該方法通常在web 應用啟動時呼叫 
  31.      */  
  32.     public void initialMultiDataSource(){  
  33.         for(DataSource ds : clusterDataSources){  
  34.             if(ds != null){  
  35.                 Connection conn = null;  
  36.                 try {  
  37.                     conn = ds.getConnection();                    
  38.                 } catch (SQLException e) {  
  39.                     e.printStackTrace();  
  40.                 } finally{  
  41.                     if(conn != null){  
  42.                         try {  
  43.                             conn.close();  
  44.                         } catch (SQLException e) {  
  45.                             e.printStackTrace();  
  46.                         }  
  47.                         conn = null;  
  48.                     }  
  49.                 }  
  50.             }  
  51.         }  
  52.         Collection<DataSource> dsCollection = mappedDataSources.values();  
  53.         for(DataSource ds : dsCollection){  
  54.             if(ds != null){  
  55.                 Connection conn = null;  
  56.                 try {  
  57.                     conn = ds.getConnection();  
  58.                 } catch (SQLException e) {  
  59.                     e.printStackTrace();  
  60.                 } finally{  
  61.                     if(conn != null){  
  62.                         try {  
  63.                             conn.close();  
  64.                         } catch (SQLException e) {  
  65.                             e.printStackTrace();  
  66.                         }  
  67.                         conn = null;  
  68.                     }  
  69.                 }  
  70.             }  
  71.         }  
  72.     }  
  73.     /** 
  74.      * 獲取當前執行緒繫結的DataSource 
  75.      * @return 
  76.      */  
  77.     public DataSource getCurrentDataSource() {  
  78.         //如果路由策略存在,且更新過,則根據路由演算法選擇新的DataSource  
  79.         RouterStrategy strategy = DataSourceRouter.currentRouterStrategy.get();  
  80.         if(strategy == null){  
  81.             throw new IllegalArgumentException("DataSource RouterStrategy No found.");  
  82.         }         
  83.         if(strategy != null && strategy.isRefresh()){             
  84.             if(RouterStrategy.SRATEGY_TYPE_MAP.equals(strategy.getType())){  
  85.                 this.choiceMappedDataSources(strategy.getKey());  
  86.             }else if(RouterStrategy.SRATEGY_TYPE_CLUSTER.equals(strategy.getType())){  
  87.                 this.routeClusterDataSources(strategy.getRouteFactor());  
  88.             }             
  89.             strategy.setRefresh(false);  
  90.         }  
  91.         return currentDataSourceHolder.get();  
  92.     }  
  93.     public Map<String, DataSource> getMappedDataSources() {  
  94.         return mappedDataSources;  
  95.     }  
  96.     public void setMappedDataSources(Map<String, DataSource> mappedDataSources) {  
  97.         this.mappedDataSources = mappedDataSources;  
  98.     }  
  99.     public ArrayList<DataSource> getClusterDataSources() {  
  100.         return clusterDataSources;  
  101.     }  
  102.     public void setClusterDataSources(ArrayList<DataSource> clusterDataSources) {  
  103.         this.clusterDataSources = clusterDataSources;  
  104.     }  
  105.     /** 
  106.      * 使用Key選擇當前的資料來源 
  107.      * @param key 
  108.      */  
  109.     public void choiceMappedDataSources(String key){  
  110.         DataSource ds = this.mappedDataSources.get(key);  
  111.         if(ds == null){  
  112.             throw new IllegalStateException("No Mapped DataSources Exist!");  
  113.         }  
  114.         this.currentDataSourceHolder.set(ds);  
  115.     }  
  116.     /** 
  117.      * 使用取模演算法,在群集資料來源中做路由選擇 
  118.      * @param routeFactor 
  119.      */  
  120.     public void routeClusterDataSources(int routeFactor){  
  121.         int size = this.clusterDataSources.size();  
  122.         if(size == 0){  
  123.             throw new IllegalStateException("No Cluster DataSources Exist!");  
  124.         }  
  125.         int choosen = routeFactor % size;  
  126.         DataSource ds = this.clusterDataSources.get(choosen);  
  127.         if(ds == null){  
  128.             throw new IllegalStateException("Choosen DataSources is null!");  
  129.         }  
  130.         logger.debug("Choosen DataSource No." + choosen+ " : " + ds.toString());  
  131.         this.currentDataSourceHolder.set(ds);  
  132.     }  
  133.     /* (non-Javadoc) 
  134.      * @see javax.sql.DataSource#getConnection() 
  135.      */  
  136.     public Connection getConnection() throws SQLException {  
  137.         if(getCurrentDataSource() != null){  
  138.             return getCurrentDataSource().getConnection();  
  139.         }  
  140.         return null;  
  141.     }  
  142.     /* (non-Javadoc) 
  143.      * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String) 
  144.      */  
  145.     public Connection getConnection(String username, String password)  
  146.             throws SQLException {  
  147.         if(getCurrentDataSource() != null){  
  148.             return getCurrentDataSource().getConnection(username , password);  
  149.         }  
  150.         return null;  
  151.     }  
  152.     /* (non-Javadoc) 
  153.      * @see javax.sql.CommonDataSource#getLogWriter() 
  154.      */  
  155.     public PrintWriter getLogWriter() throws SQLException {  
  156.         if(getCurrentDataSource() != null){  
  157.             return getCurrentDataSource().getLogWriter();  
  158.         }  
  159.         return null;  
  160. 相關推薦

    Spring + iBatis橫向切分簡易解決思路

    1.引言     筆者最近在做一個網際網路的“類SNS”應用,應用中使用者數量巨大(約4000萬)左右,因此,簡單的使用傳統單一資料庫儲存肯定是不行的。     參考了業內廣泛使用的分庫分表,以及使用DAL資料訪問層等的做法,筆者決定使用一種最簡單的資料來源路由選擇方式來解決問題。    

    MyBatis-Plus 部署方式;spring mvc 部署方式

    1、實現mybatis-plus的多個數據庫的切換方式      原始碼地址:https://github.com/baomidou/mybatisplus-spring-mvc 2、因為其文件都是相互依賴的,所以修改配置,就是在已有的配置中修改    

    spring框架個數據操作需統一提交事務回滾機制解析以及解決辦法

    1、遇到的問題   當我們一個方法裡面有多個數據庫儲存操作的時候,中間的資料庫操作發生的錯誤。虛擬碼如下: public method() { Dao1.save(Person1); Dao1.save(Person2); Dao1.sa

    【策略模式】如何結合spring實現一個介面個實現,如何解決介面選擇問題

    1、首先把對映關係放在spring-mvc.xml配置檔案 <bean id="dispatcher" class="com.ms.kai.bms.dispatcher.Abstrac

    spring boot+mybatis+druid 資料來源分散式事務

    廢話不多說,首先貼配置檔案,需要引入pomxml <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter&l

    Tomcat叢集Spring+Quartz次執行解決方案記錄

    由於在叢集環境下定時器會出現併發和重複執行的問題,我再三考慮記錄有5 一、把定時器模組單獨拿出來放到一臺tomcat或者新建一個Java工程手動啟動定時器,這樣定時器的任務就可以從原來的叢集中抽離開來,原來的tomcat叢集不再執行定時器任務,而是交給定時器應用單獨執

    spring mybatis atomikos 分散式事務demo

            最近有點時間 , 就準備搭個多庫事務的例子 , 不過中間碰到一些問題 , 這裡記錄下來 .         我的atomikos   版本是 3.7.0 ; Spring4 mybatis3 ;         碰到問題主要有兩類  :  

    spring+ibatis配置資料來源

    經過上一步對spring配置檔案的配置,接下來我們新建一個介面檔案: public interface IBaseSqlMapClientDaoSupport {     public void choseSqlClient(String name); } 同時也新建一個實現類: public class B

    iOS專案中引用個第三方引發衝突的解決方法

    這個真蛋疼~~~~ 解決方法如下: iOS程式開發過程中引用多個第三方庫時會出現類名重疊,導致衝突,具體的衝突錯誤提示如下: duplicate symbol OBJC_IVAR$_AFHTTPSessionManager._requestSerializer in:

    spring整合個mongodb

    1. 仿照抽象路由資料來源類建立一個抽象mongodb路由模板源的類。因為操作mongodb的是模板。 package com.caiwufei.common.db.mongo; import java.util.HashMap; import java.util.Map

    spring ibatis 整合 abator自動生成的xml檔案報錯及解決方法

    程式中的部分程式碼由abator自動生成, dao及其daoimpl package com.our311.demo.dao; import com.our311.demo.dao.model.TbUser; import com.our311.demo.dao.model

    ubuntu MySQL數據輸入中文亂碼 解決方案

    title str itl alt 查詢 ref cte class nbsp 一、登錄MySQL查看用SHOW VARIABLES LIKE ‘character%’;下字符集,顯示如下:+--------------------------+--------------

    數據水平切分(拆拆表)的實現原理解析(轉)

    數字 一個數據庫 java ins 結果 都對 不同 com 嚴重 第1章 引言 隨著互聯網應用的廣泛普及,海量數據的存儲和訪問成為了系統設計的瓶頸問題。對於一個大型的互聯網應用,每天幾十億的PV無疑對數據庫造成了相當 高的負載。對於系統的穩定性和擴展性造成了極大的問題。

    SQL server觸發器、存儲過程操作遠程數據插入數據,解決服務器已存在的問題

    定義 ims val rom 記錄 插入記錄 其它 pre 項目 近期弄了一個小項目,也不是非常復雜,須要將一個數據庫的一些數據備份到另外一個庫。不是本地,可能是網絡上其它的數據庫。想了一下,用了存儲過程和觸發器。也不是非常復雜,首先我須要操作遠程數據庫,於是寫了一個存

    Maven 搭建spring boot模塊項目

    con pac end ice ces encoding oca 被子 resources Maven 搭建spring boot多模塊項目 備註:所有項目都在idea中創建 1.idea創建maven項目 1-1: 刪除src,target目錄,只保

    python3 寫CSV文件一個空行的解決辦法

    bsp eggs line 參數 lov blog mini csv span Python文檔中有提到: open(‘eggs.csv‘, newline=‘‘) 也就是說,打開文件的時候多指定一個參數。Python文檔中也有這樣的示例: import csvwith

    ssm框架插入mysql數據中文亂碼問題解決

    mar word ref def http reat pro xml文件 framework 1. 檢查web.xml <!-- 編碼過濾器 --> <filter> <filter-name>

    Spring-更DI的知識

    移除 進行 繼續 -c invoke 這就是 ima 不同的 string 3.3.1 延遲初始化Bean 延遲初始化也叫做惰性初始化,指不提前初始化Bean,而是只有在真正使用時才創建及初始化Bean。 配置方式很簡單只需在<bean>標簽上指定 “lazy-

    php寫入數據到mysql數據中出現亂碼解決方法

    names .com http image alt ima utf8 情況 mysql 亂碼情況: 在選擇數據庫前加入一句代碼即可 mysql_query("set names utf8"); 最後效果 php寫入數據到mysql數據庫中出現亂碼解決方法

    spring-boot實戰【05】:Spring Boo環境配置及配置屬性註入到對象

    num java red component 配置 cati 定義 fin row 項目工程結構: 配置文件application.properties文件 com.yucong.blog.name=yucong com.yucong.blog.title=Spring