首页 热点资讯 义务教育 高等教育 出国留学 考研考公
您的当前位置:首页正文

Spring+Ibatis数据库水平分库

2023-11-09 来源:花图问答

   笔者最近在做一个互联网的“类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代码

      

    [java] view plaincopy

    1. import java.io.PrintWriter;     

    2.   

    3. import java.sql.Connection;     

    4.   

    5. import java.sql.SQLException;     

    6.   

    7. import java.util.ArrayList;     

    8.   

    9. import java.util.Collection;     

    10.   

    11. import java.util.HashMap;     

    12.   

    13. import java.util.Map;     

    14.   

    15.     

    16.   

    17. import javax.sql.DataSource;     

    18.   

    19.     

    20.   

    21. import org.apache.log4j.Logger;     

    22.   

    23.     

    24.   

    25. import com.xxx.sql.DataSourceRouter.RouterStrategy;     

    26.   

    27.     

    28.   

    29. /**   

    30.  

    31.  * 复合多数据源(Alpha)   

    32.  

    33.  * @author linliangyi2005@gmail.com   

    34.  

    35.  * Jul 15, 2010   

    36.  

    37.  */    

    38.   

    39. public class MultiDataSource implements DataSource {     

    40.   

    41.          

    42.   

    43.     static Logger logger = Logger.getLogger(MultiDataSource.class);     

    44.   

    45.          

    46.   

    47.     //当前线程对应的实际DataSource     

    48.   

    49.     private ThreadLocal<DataSource> currentDataSourceHolder = new ThreadLocal<DataSource>();     

    50.   

    51.     //使用Key-Value映射的DataSource     

    52.   

    53.     private Map<String , DataSource> mappedDataSources;     

    54.   

    55.     //使用横向切分的分布式DataSource     

    56.   

    57.     private ArrayList<DataSource> clusterDataSources;     

    58.   

    59.          

    60.   

    61.     public MultiDataSource(){     

    62.   

    63.         mappedDataSources = new HashMap<String , DataSource>(4);     

    64.   

    65.         clusterDataSources = new ArrayList<DataSource>(4);     

    66.   

    67.     }     

    68.   

    69.          

    70.   

    71.     /**   

    72.  

    73.      * 数据库连接池初始化   

    74.  

    75.      * 该方法通常在web 应用启动时调用   

    76.  

    77.      */    

    78.   

    79.     public void initialMultiDataSource(){     

    80.   

    81.         for(DataSource ds : clusterDataSources){     

    82.   

    83.             if(ds != null){     

    84.   

    85.                 Connection conn = null;     

    86.   

    87.                 try {     

    88.   

    89.                     conn = ds.getConnection();                       

    90.   

    91.                 } catch (SQLException e) {     

    92.   

    93.                     e.printStackTrace();     

    94.   

    95.                 } finally{     

    96.   

    97.                     if(conn != null){     

    98.   

    99.                         try {     

    100.   

    101.                             conn.close();     

    102.   

    103.                         } catch (SQLException e) {     

    104.   

    105.                             e.printStackTrace();     

    106.   

    107.                         }     

    108.   

    109.                         conn = null;     

    110.   

    111.                     }     

    112.   

    113.                 }     

    114.   

    115.             }     

    116.   

    117.         }     

    118.   

    119.         Collection<DataSource> dsCollection = mappedDataSources.values();     

    120.   

    121.        for(DataSource ds : dsCollection){     

    122.   

    123.             if(ds != null){     

    124.   

    125.                 Connection conn = null;     

    126.   

    127.                 try {     

    128.   

    129.                     conn = ds.getConnection();     

    130.   

    131.                 } catch (SQLException e) {     

    132.   

    133.                     e.printStackTrace();     

    134.   

    135.                 } finally{     

    136.   

    137.                     if(conn != null){     

    138.   

    139.                         try {     

    140.   

    141.                             conn.close();     

    142.   

    143.                         } catch (SQLException e) {     

    144.   

    145.                             e.printStackTrace();     

    146.   

    147.                         }     

    148.   

    149.                         conn = null;     

    150.   

    151.                     }     

    152.   

    153.                 }     

    154.   

    155.             }     

    156.   

    157.         }     

    158.   

    159.     }     

    160.   

    161.     /**   

    162.  

    163.      * 获取当前线程绑定的DataSource   

    164.  

    165.      * @return   

    166.  

    167.      */    

    168.   

    169.     public DataSource getCurrentDataSource() {     

    170.   

    171.         //如果路由策略存在,且更新过,则根据路由算法选择新的DataSource     

    172.   

    173.         RouterStrategy strategy = DataSourceRouter.currentRouterStrategy.get();     

    174.   

    175.         if(strategy == null){     

    176.   

    177.             throw new IllegalArgumentException("DataSource RouterStrategy No found.");     

    178.   

    179.         }            

    180.   

    181.         if(strategy != null && strategy.isRefresh()){                

    182.   

    183.            if(RouterStrategy.SRATEGY_TYPE_MAP.equals(strategy.getType())){     

    184.   

    185.                 this.choiceMappedDataSources(strategy.getKey());     

    186.   

    187.                     

    188.   

    189.             }else if(RouterStrategy.SRATEGY_TYPE_CLUSTER.equals(strategy.getType())){     

    190.   

    191.                 this.routeClusterDataSources(strategy.getRouteFactor());     

    192.   

    193.             }                

    194.   

    195.             strategy.setRefresh(false);     

    196.   

    197.         }     

    198.   

    199.         return currentDataSourceHolder.get();     

    200.   

    201.     }     

    202.   

    203.     

    204.   

    205.     public Map<String, DataSource> getMappedDataSources() {     

    206.   

    207.         return mappedDataSources;     

    208.   

    209.     }     

    210.   

    211.    

    212.   

    213.     public void setMappedDataSources(Map<String, DataSource> mappedDataSources) {     

    214.   

    215.         this.mappedDataSources = mappedDataSources;     

    216.   

    217.     }     

    218.   

    219.     

    220.   

    221.     public ArrayList<DataSource> getClusterDataSources() {     

    222.   

    223.         return clusterDataSources;     

    224.   

    225.     }     

    226.   

    227.     

    228.   

    229.     public void setClusterDataSources(ArrayList<DataSource> clusterDataSources) {     

    230.   

    231.         this.clusterDataSources = clusterDataSources;     

    232.   

    233.     }     

    234.   

    235.          

    236.   

    237.     /**   

    238.  

    239.      * 使用Key选择当前的数据源   

    240.  

    241.      * @param key   

    242.  

    243.      */    

    244.   

    245.     public void choiceMappedDataSources(String key){     

    246.   

    247.         DataSource ds = this.mappedDataSources.get(key);     

    248.   

    249.         if(ds == null){     

    250.   

    251.             throw new IllegalStateException("No Mapped DataSources Exist!");     

    252.   

    253.         }     

    254.   

    255.         this.currentDataSourceHolder.set(ds);     

    256.   

    257.     }     

    258.   

    259.          

    260.   

    261.     /**   

    262.  

    263.      * 使用取模算法,在群集数据源中做路由选择   

    264.  

    265.      * @param routeFactor   

    266.  

    267.      */    

    268.   

    269.     public void routeClusterDataSources(int routeFactor){     

    270.   

    271.         int size = this.clusterDataSources.size();     

    272.   

    273.         if(size == 0){     

    274.   

    275.             throw new IllegalStateException("No Cluster DataSources Exist!");     

    276.   

    277.         }     

    278.   

    279.         int choosen = routeFactor % size;     

    280.   

    281.        DataSource ds = this.clusterDataSources.get(choosen);     

    282.   

    283.         if(ds == null){     

    284.   

    285.             throw new IllegalStateException("Choosen DataSources is null!");     

    286.   

    287.         }     

    288.   

    289.         logger.debug("Choosen DataSource No." + choosen+ " : " + ds.toString());     

    290.   

    291.         this.currentDataSourceHolder.set(ds);     

    292.   

    293.     }     

    294.   

    295.     

    296.   

    297.     /* (non-Javadoc)   

    298.  

    299.      * @see javax.sql.DataSource#getConnection()   

    300.  

    301.      */    

    302.   

    303.     public Connection getConnection() throws SQLException {     

    304.   

    305.        if(getCurrentDataSource() != null){     

    306.   

    307.            return getCurrentDataSource().getConnection();     

    308.   

    309.         }     

    310.   

    311.         return null;     

    312.   

    313.     }       

    314.   

    315.     /* (non-Javadoc)   

    316.  

    317.      * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String)   

    318.  

    319.      */    

    320.   

    321.     public Connection getConnection(String username, String password)     

    322.   

    323.             throws SQLException {     

    324.   

    325.         if(getCurrentDataSource() != null){     

    326.   

    327.           return getCurrentDataSource().getConnection(username , password);     

    328.   

    329.        }     

    330.   

    331.         return null;     

    332.   

    333.     }     

    334.   

    335.     

    336.   

    337.     /* (non-Javadoc)   

    338.  

    339.      * @see javax.sql.CommonDataSource#getLogWriter()   

    340.  

    341.      */    

    342.   

    343.    public PrintWriter getLogWriter() throws SQLException {     

    344.   

    345.         if(getCurrentDataSource() != null){     

    346.   

    347.            return getCurrentDataSource().getLogWriter();     

    348.   

    349.         }     

    350.   

    351.        return null;     

    352.   

    353.     }     

    354.   

    355.     

    356.   

    357.     /* (non-Javadoc)   

    358.  

    359.      * @see javax.sql.CommonDataSource#getLoginTimeout()   

    360.  

    361.      */    

    362.   

    363.     public int getLoginTimeout() throws SQLException {     

    364.   

    365.         if(getCurrentDataSource() != null){     

    366.   

    367.             return getCurrentDataSource().getLoginTimeout();     

    368.   

    369.         }     

    370.   

    371.         return 0;     

    372.   

    373.     }     

    374.   

    375.     

    376.   

    377.     /* (non-Javadoc)   

    378.  

    379.      * @see javax.sql.CommonDataSource#setLogWriter(java.io.PrintWriter)   

    380.  

    381.      */    

    382.   

    383.     public void setLogWriter(PrintWriter out) throws SQLException {     

    384.   

    385.         if(getCurrentDataSource() != null){     

    386.   

    387.             getCurrentDataSource().setLogWriter(out);     

    388.   

    389.         }     

    390.   

    391.     }     

    392.   

    393.     

    394.   

    395.     /* (non-Javadoc)   

    396.  

    397.      * @see javax.sql.CommonDataSource#setLoginTimeout(int)   

    398.  

    399.      */    

    400.   

    401.     public void setLoginTimeout(int seconds) throws SQLException {     

    402.   

    403.         if(getCurrentDataSource() != null){     

    404.   

    405.             getCurrentDataSource().setLoginTimeout(seconds);     

    406.   

    407.         }     

    408.   

    409.     }     

    410.   

    411.     

    412.   

    413.     /* (non-Javadoc)   

    414.  

    415.      * 该接口方法since 1.6   

    416.  

    417.      * 不是所有的DataSource都实现有这个方法   

    418.  

    419.     * @see java.sql.Wrapper#isWrapperFor(java.lang.Class)   

    420.  

    421.      */    

    422.   

    423.     public boolean isWrapperFor(Class<?> iface) throws SQLException {     

    424.   

    425.              

    426.   

    427. //      if(getCurrentDataSource() != null){     

    428.   

    429. //          return getCurrentDataSource().isWrapperFor(iface);     

    430.   

    431. //      }     

    432.   

    433.         return false;     

    434.   

    435.    }     

    436.   

    437.     

    438.   

    439.     /* (non-Javadoc)   

    440.  

    441.      * 该接口方法since 1.6   

    442.  

    443.      * 不是所有的DataSource都实现有这个方法   

    444.  

    445.      * @see java.sql.Wrapper#unwrap(java.lang.Class)   

    446.  

    447.      */    

    448.   

    449.     public <T> T unwrap(Class<T> iface) throws SQLException {     

    450.   

    451. //      if(getCurrentDataSource() != null){     

    452.   

    453. //          return getCurrentDataSource().unwrap(iface);     

    454.   

    455. //      }     

    456.   

    457.        return null;     

    458.   

    459.     }    

    460.   

    461. import java.io.PrintWriter;  

    462. import java.sql.Connection;  

    463. import java.sql.SQLException;  

    464. import java.util.ArrayList;  

    465. import java.util.Collection;  

    466. import java.util.HashMap;  

    467. import java.util.Map;  

    468.   

    469. import javax.sql.DataSource;  

    470.   

    471. import org.apache.log4j.Logger;  

    472.   

    473. import com.xxx.sql.DataSourceRouter.RouterStrategy;  

    474.   

    475. /** 

    476.  * 复合多数据源(Alpha) 

    477.  * @author linliangyi2005@gmail.com 

    478.  * Jul 15, 2010 

    479.  */  

    480. public class MultiDataSource implements DataSource {  

    481.       

    482.     static Logger logger = Logger.getLogger(MultiDataSource.class);  

    483.       

    484.     //当前线程对应的实际DataSource  

    485.     private ThreadLocal<DataSource> currentDataSourceHolder = new ThreadLocal<DataSource>();  

    486.     //使用Key-Value映射的DataSource  

    487.     private Map<String , DataSource> mappedDataSources;  

    488.     //使用横向切分的分布式DataSource  

    489.     private ArrayList<DataSource> clusterDataSources;  

    490.       

    491.     public MultiDataSource(){  

    492.         mappedDataSources = new HashMap<String , DataSource>(4);  

    493.         clusterDataSources = new ArrayList<DataSource>(4);  

    494.     }  

    495.       

    496.     /** 

    497.      * 数据库连接池初始化 

    498.      * 该方法通常在web 应用启动时调用 

    499.      */  

    500.     public void initialMultiDataSource(){  

    501.         for(DataSource ds : clusterDataSources){  

    502.             if(ds != null){  

    503.                 Connection conn = null;  

    504.                 try {  

    505.                     conn = ds.getConnection();                    

    506.                 } catch (SQLException e) {  

    507.                     e.printStackTrace();  

    508.                 } finally{  

    509.                     if(conn != null){  

    510.                         try {  

    511.                             conn.close();  

    512.                         } catch (SQLException e) {  

    513.                             e.printStackTrace();  

    514.                         }  

    515.                         conn = null;  

    516.                     }  

    517.                 }  

    518.             }  

    519.         }  

    520.         Collection<DataSource> dsCollection = mappedDataSources.values();  

    521.         for(DataSource ds : dsCollection){  

    522.             if(ds != null){  

    523.                 Connection conn = null;  

    524.                 try {  

    525.                     conn = ds.getConnection();  

    526.                 } catch (SQLException e) {  

    527.                     e.printStackTrace();  

    528.                 } finally{  

    529.                     if(conn != null){  

    530.                         try {  

    531.                             conn.close();  

    532.                         } catch (SQLException e) {  

    533.                             e.printStackTrace();  

    534.                         }  

    535.                         conn = null;  

    536.                     }  

    537.                 }  

    538.             }  

    539.         }  

    540.     }  

    541.     /** 

    542.      * 获取当前线程绑定的DataSource 

    543.      * @return 

    544.      */  

    545.     public DataSource getCurrentDataSource() {  

    546.         //如果路由策略存在,且更新过,则根据路由算法选择新的DataSource  

    547.         RouterStrategy strategy = DataSourceRouter.currentRouterStrategy.get();  

    548.         if(strategy == null){  

    549.             throw new IllegalArgumentException("DataSource RouterStrategy No found.");  

    550.         }         

    551.         if(strategy != null && strategy.isRefresh()){             

    552.             if(RouterStrategy.SRATEGY_TYPE_MAP.equals(strategy.getType())){  

    553.                 this.choiceMappedDataSources(strategy.getKey());  

    554.                   

    555.             }else if(RouterStrategy.SRATEGY_TYPE_CLUSTER.equals(strategy.getType())){  

    556.                 this.routeClusterDataSources(strategy.getRouteFactor());  

    557.             }             

    558.             strategy.setRefresh(false);  

    559.         }  

    560.         return currentDataSourceHolder.get();  

    561.     }  

    562.   

    563.     public Map<String, DataSource> getMappedDataSources() {  

    564.         return mappedDataSources;  

    565.     }  

    566.   

    567.     public void setMappedDataSources(Map<String, DataSource> mappedDataSources) {  

    568.         this.mappedDataSources = mappedDataSources;  

    569.     }  

    570.   

    571.     public ArrayList<DataSource> getClusterDataSources() {  

    572.         return clusterDataSources;  

    573.     }  

    574.   

    575.     public void setClusterDataSources(ArrayList<DataSource> clusterDataSources) {  

    576.         this.clusterDataSources = clusterDataSources;  

    577.     }  

    578.       

    579.     /** 

    580.      * 使用Key选择当前的数据源 

    581.      * @param key 

    582.      */  

    583.     public void choiceMappedDataSources(String key){  

    584.         DataSource ds = this.mappedDataSources.get(key);  

    585.         if(ds == null){  

    586.             throw new IllegalStateException("No Mapped DataSources Exist!");  

    587.         }  

    588.         this.currentDataSourceHolder.set(ds);  

    589.     }  

    590.       

    591.     /** 

    592.      * 使用取模算法,在群集数据源中做路由选择 

    593.      * @param routeFactor 

    594.      */  

    595.     public void routeClusterDataSources(int routeFactor){  

    596.         int size = this.clusterDataSources.size();  

    597.         if(size == 0){  

    598.             throw new IllegalStateException("No Cluster DataSources Exist!");  

    599.         }  

    600.         int choosen = routeFactor % size;  

    601.         DataSource ds = this.clusterDataSources.get(choosen);  

    602.         if(ds == null){  

    603.             throw new IllegalStateException("Choosen DataSources is null!");  

    604.         }  

    605.         logger.debug("Choosen DataSource No." + choosen+ " : " + ds.toString());  

    606.         this.currentDataSourceHolder.set(ds);  

    607.     }  

    608.   

    609.     /* (non-Javadoc) 

    610.      * @see javax.sql.DataSource#getConnection() 

    611.      */  

    612.     public Connection getConnection() throws SQLException {  

    613.         if(getCurrentDataSource() != null){  

    614.             return getCurrentDataSource().getConnection();  

    615.         }  

    616.         return null;  

    617.     }  

    618.   

    619.     /* (non-Javadoc) 

    620.      * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String) 

    621.      */  

    622.     public Connection getConnection(String username, String password)  

    623.             throws SQLException {  

    624.         if(getCurrentDataSource() != null){  

    625.             return getCurrentDataSource().getConnection(username , password);  

    626.         }  

    627.         return null;  

    628.     }  

    629.   

    630.     /* (non-Javadoc) 

    631.      * @see javax.sql.CommonDataSource#getLogWriter() 

    632.      */  

    633.     public PrintWriter getLogWriter() throws SQLException {  

    634.         if(getCurrentDataSource() != null){  

    635.             return getCurrentDataSource().getLogWriter();  

    636.         }  

    637.         return null;  

    638.     }  

    639.   

    640.     /* (non-Javadoc) 

    641.      * @see javax.sql.CommonDataSource#getLoginTimeout() 

    642.      */  

    643.     public int getLoginTimeout() throws SQLException {  

    644.         if(getCurrentDataSource() != null){  

    645.             return getCurrentDataSource().getLoginTimeout();  

    646.         }  

    647.         return 0;  

    648.     }  

    649.   

    650.     /* (non-Javadoc) 

    651.      * @see javax.sql.CommonDataSource#setLogWriter(java.io.PrintWriter) 

    652.      */  

    653.     public void setLogWriter(PrintWriter out) throws SQLException {  

    654.         if(getCurrentDataSource() != null){  

    655.             getCurrentDataSource().setLogWriter(out);  

    656.         }  

    657.     }  

    658.   

    659.     /* (non-Javadoc) 

    660.    &nbs

      小编还为您整理了以下内容,可能对您也有帮助:

      数据库水平分库和垂直分库有什么区别

      常见的分库方式有水平性和垂直性。一般来说,就是按照用户属性(地市或者ID的hash)进行分库,或者按照业务功能块进行分库。
      水平分库方式主要根据用户属性(如地市)拆分物理数据库。一种常见的方式是将全省划分为个大区。
      垂直分库方式:根据业务维度和数据的访问量等,进行数据的分离,剥离为多个数据库。例如,将一些公用的配置信息存储到一个数据库中进行单独维护。

      数据库水平分库和垂直分库有什么区别

      常见的分库方式有水平性和垂直性。一般来说,就是按照用户属性(地市或者ID的hash)进行分库,或者按照业务功能块进行分库。
      水平分库方式主要根据用户属性(如地市)拆分物理数据库。一种常见的方式是将全省划分为个大区。
      垂直分库方式:根据业务维度和数据的访问量等,进行数据的分离,剥离为多个数据库。例如,将一些公用的配置信息存储到一个数据库中进行单独维护。

      浅谈mysql数据库分库分表那些事-亿级数据存储方案

      mysql分库分表一般有如下场景

      其中1,2相对较容易实现,本文重点讲讲水平拆表和水平拆库,以及基于mybatis插件方式实现水平拆分方案落地。

      在 《聊一聊扩展字段设计》 一文中有讲解到基于KV水平存储扩展字段方案,这就是非常典型的可以水平分表的场景。主表和kv表是一对N关系,随着主表数据量增长,KV表最大N倍线性增长。

      这里我们以分KV表水平拆分为场景

      对于kv扩展字段查询,只会根据id + key 或者 id 为条件的方式查询,所以这里我们可以按照id 分片即可

      分512张表(实际场景具体分多少表还得根据字段增加的频次而定)

      分表后表名为kv_000 ~ kv_511

      id % 512 = 1 .... 分到 kv_001,

      id % 512 = 2 .... 分到 kv_002

      依次类推!

      水平分表相对比较容易,后面会讲到基于mybatis插件实现方案

      场景:以下我们基于博客文章表分库场景来分析

      目标:

      表结构如下(节选部分字段):

      按照user_id sharding

      假如分1024个库,按照user_id % 1024 hash

      user_id % 1024 = 1 分到db_001库

      user_id % 1024 = 2 分到db_002库

      依次类推

      目前是2个节点,假如后期达到瓶颈,我们可以增加至4个节点

      最多可以增加只1024个节点,性能线性增长

      对于水平分表/分库后,非shardingKey查询首先得考虑到

      基于mybatis分库分表,一般常用的一种是基于spring AOP方式, 另外一种基于mybatis插件。其实两种方式思路差不多。

      为了比较直观解决这个问题,我分别在Executor 和StatementHandler阶段2个

      实现动态数据源获取接口

      测试结果如下

      由此可知,我们需要在Executor阶段 切换数据源

      对于分库:

      原始sql:

      目标sql:

      其中定义了三个注解

      @useMaster 是否强制读主

      @shardingBy 分片标识

      @DB 定义逻辑表名 库名以及分片策略

      1)编写entity

      Insert

      select

      以上顺利实现mysql分库,同样的道理实现同时分库分表也很容易实现。

      此插件具体实现方案已开源: https://github.com/bytearch/mybatis-sharding

      目录如下:

      mysql分库分表,首先得找到瓶颈在哪里(IO or CPU),是分库还是分表,分多少?不能为了分库分表而拆分。

      原则上是尽量先垂直拆分 后 水平拆分。

      以上基于mybatis插件分库分表是一种实现思路,还有很多不完善的地方,

      例如:

      数据库水平拆分是在单库中拆出多个表,每个表是表名+规则。还是分出多个库,每个库是库名+规则,表名不变

      1 基本思想之什么是分库分表?

      从字面上简单理解,就是把原本存储于一个库的数据分块存储到多个库上,把原本存储于一个表的数据分块存储到多个表上。

      2 基本思想之为什么要分库分表?

      数据库中的数据量不一定是可控的,在未进行分库分表的情况下,随着时间和业务的发展,库中的表会越来越多,表中的数据量也会越来越大,相应地,数据操作,增删改查的开销也会越来越大;另外,由于无法进行分布式式部署,而一台服务器的资源(CPU、磁盘、内存、IO等)是有限的,最终数据库所能承载的数据量、数据处理能力都将遭遇瓶颈。

      3 分库分表的实施策略。

      分库分表有垂直切分和水平切分两种。

      3.1 何谓垂直切分,即将表按照功能模块、关系密切程度划分出来,部署到不同的库上。例如,我们会建立定义数据库workDB、商品数据库payDB、用户数据库userDB、日志数据库logDB等,分别用于存储项目数据定义表、商品定义表、用户数据表、日志数据表等。

      3.2 何谓水平切分,当一个表中的数据量过大时,我们可以把该表的数据按照某种规则,例如userID散列,进行划分,然后存储到多个结构相同的表,和不同的库上。例如,我们的userDB中的用户数据表中,每一个表的数据量都很大,就可以把userDB切分为结构相同的多个userDB:part0DB、part1DB等,再将userDB上的用户数据表userTable,切分为很多userTable:userTable0、userTable1等,然后将这些表按照一定的规则存储到多个userDB上。

      3.3 应该使用哪一种方式来实施数据库分库分表,这要看数据库中数据量的瓶颈所在,并综合项目的业务类型进行考虑。

      如果数据库是因为表太多而造成海量数据,并且项目的各项业务逻辑划分清晰、低耦合,那么规则简单明了、容易实施的垂直切分必是首选。

      而如果数据库中的表并不多,但单表的数据量很大、或数据热度很高,这种情况之下就应该选择水平切分,水平切分比垂直切分要复杂一些,它将原本逻辑上属于一体的数据进行了物理分割,除了在分割时要对分割的粒度做好评估,考虑数据平均和负载平均,后期也将对项目人员及应用程序产生额外的数据管理负担。

      在现实项目中,往往是这两种情况兼而有之,这就需要做出权衡,甚至既需要垂直切分,又需要水平切分。我们的游戏项目便综合使用了垂直与水平切分,我们首先对数据库进行垂直切分,然后,再针对一部分表,通常是用户数据表,进行水平切分。

    显示全文