阿里雲rds數據庫遷移實戰(多數據源)


  由於某幾個業務表數據量太大,數據由業務寫,數據部門讀。

  寫壓力不大,讀卻很容易導致長時間等待問題(讀由單獨系統進行讀),導致連接被占用,從而容易並發稍稍增長導致全庫卡死!

  於是,就拆庫唄。

  業務系統拆分就不要做了(微服務化),沒那工夫。

  直接原系統拆兩個數據源出來,對某幾個高壓力表的寫就單獨用這個數據源,從而減輕壓力。

所以,分庫工作就變為了兩個步驟:

  1. 兩個數據源讀寫業務;

  2. 將新數據庫寫動作同步回讀庫;

  再由於方便性,數據庫也是使用阿里的rds數據庫,一個變為兩個!

  代碼上做兩個數據源很簡單,尤其是在原有代碼就寫得比較清晰的情況下;

如下是使用springboot和mybatis做的多數據源配置:

  1. 配置多個數據源類;

  2. 啟用mybatis多數據源,加載不同配置bean;

  3. 根據掃描路徑區別使用的數據源;

  4. 根據掃描路徑將需要拆分的表與原表區別;

  5. 測試時可使用同同機器上多庫形式運行,上線后為多實例同庫運行;

  6. 驗證功能可用性;如有問題,及時修改;

  具體配置如下:

// 原數據源配置
@Configuration
@MapperScan(basePackages = MainDataSourceConfig.SCAN_BASE_PACKAGE, sqlSessionFactoryRef = "sqlSessionFactory")
public class MainDataSourceConfig {

    public static final String SCAN_BASE_PACKAGE = "com.xxx.dao.mapper.main";

    /**
     * xml 配置文件掃描路徑
     */
    public static final String SCAN_XML_MAPPER_LOCATION = "classpath:mybatis/mappers/mysql/main/**/*Mapper.xml";

    //jdbcConfig
    @Value("${jdbc.main.url}")
    private String jdbcUrl;
    @Value("${jdbc.main.driver}")
    private String driverName;
    @Value("${pool.main.maxPoolSize}")
    private int maxPoolSize;
    @Value("${jdbc.main.username}")
    private String jdbcUserName;
    @Value("${jdbc.main.password}")
    private String jdbcPwd;
    @Value("${pool.main.maxWait}")
    private int jdbcMaxWait;
    @Value("${pool.main.validationQuery}")
    private String validationQuery;

    @Bean(name = "druidDataSource")
    @Primary
    public DruidDataSource druidDataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setUrl(jdbcUrl);
        ds.setDriverClassName(driverName);
        ds.setMaxActive(maxPoolSize);
        ds.setUsername(jdbcUserName);
        ds.setPassword(jdbcPwd);
        ds.setRemoveAbandoned(true);
        ds.setMaxWait(jdbcMaxWait);
        ds.setValidationQuery(validationQuery);
        return ds;
    }
    
    @Bean(name = "dataSourceTransactionManager")
    @Primary
    public DataSourceTransactionManager dataSourceTransactionManager(){
        DataSourceTransactionManager dm = new DataSourceTransactionManager();
        dm.setDataSource(druidDataSource());
        return dm;
    }

    @Bean(name="sqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource[] mapperXmlResource = resolver.getResources(SCAN_XML_MAPPER_LOCATION);
        sqlSessionFactory.setDataSource(druidDataSource());
        sqlSessionFactory.setMapperLocations(mapperXmlResource);
        return sqlSessionFactory.getObject();
    }
    
}

// 新數據源配置,僅僅改了下配置名,但是還不得不另一個配置類
@Configuration
@MapperScan(basePackages = ExtraDataSourceConfig.SCAN_BASE_PACKAGE, sqlSessionFactoryRef = "sqlSessionFactoryExt")
public class ExtraDataSourceConfig {

    public static final String SCAN_BASE_PACKAGE = "com.xxx.dao.mapper.ext";

    /**
     * xml 配置文件掃描路徑
     */
    public static final String SCAN_XML_MAPPER_LOCATION = "classpath:mybatis/mappers/mysql/ext/**/*Mapper.xml";

    //jdbcConfig
    @Value("${jdbc.ext.url}")
    private String jdbcUrl;
    @Value("${jdbc.ext.driver}")
    private String driverName;
    @Value("${pool.ext.maxPoolSize}")
    private int maxPoolSize;
    @Value("${jdbc.ext.username}")
    private String jdbcUserName;
    @Value("${jdbc.ext.password}")
    private String jdbcPwd;
    @Value("${pool.ext.maxWait}")
    private int jdbcMaxWait;
    @Value("${pool.ext.validationQuery}")
    private String validationQuery;

    @Bean(name = "druidDataSourceExt")
    public DruidDataSource druidDataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setUrl(jdbcUrl);
        ds.setDriverClassName(driverName);
        ds.setMaxActive(maxPoolSize);
        ds.setUsername(jdbcUserName);
        ds.setPassword(jdbcPwd);
        ds.setRemoveAbandoned(true);
        ds.setMaxWait(jdbcMaxWait);
        ds.setValidationQuery(validationQuery);
        return ds;
    }
    
    @Bean(name = "dataSourceTransactionManagerExt")
    public DataSourceTransactionManager dataSourceTransactionManager(){
        DataSourceTransactionManager dm = new DataSourceTransactionManager();
        dm.setDataSource(druidDataSource());
        return dm;
    }

    @Bean(name="sqlSessionFactoryExt")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource[] mapperXmlResource = resolver.getResources(SCAN_XML_MAPPER_LOCATION);
        sqlSessionFactory.setDataSource(druidDataSource());
        sqlSessionFactory.setMapperLocations(mapperXmlResource);
        return sqlSessionFactory.getObject();
    }
    
}

  然后,將需要分離的表操作轉移到相應的包路徑下,即可實現多數據源操作了!

而多數據源配置對於基於xml配置spring來說,可能更加直觀更加簡單,甚至xml文件都不用分離!

    <!-- 原數據源配置 -->
    <bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.main.url}" />
        <property name="username" value="${jdbc.main.username}" />
        <property name="password" value="${jdbc.main.password}" />
        <property name="maxActive" value="${jdbc.main.maxActive}" />
        <property name="maxWait" value="${jdbc.main.maxWait}" />
    </bean>
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:mybatis-config.xml" />
        <property name="mapperLocations">
            <list>
                <value>classpath:mybatis/mappers/mysql/main/**/*Mapper.xml</value>
            </list>
        </property>
    </bean>
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <tx:annotation-driven transaction-manager="transactionManager" /> 
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
      <property name="basePackage" value="com.xxx.dao.automapper.main" />
      <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
    
    <!-- 第二個數據源的配置 -->
    <bean name="dataSourceExt" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.ext.url}" />
        <property name="username" value="${jdbc.ext.username}" />
        <property name="password" value="${jdbc.ext.password}" />
        <property name="maxActive" value="${jdbc.ext.maxActive}" />
        <property name="maxWait" value="${jdbc.ext.maxWait}" />
    </bean>
    <bean id="sqlSessionFactoryExt" class="org.mybatis.spring.SqlSessionFactoryBean">
      <property name="dataSource" ref="dataSourceExt" />
      <property name="configLocation" value="classpath:config/mybatis-config.xml" />
      <property name="mapperLocations">
          <list>
            <value>classpath:mybatis/mappers/mysql/ext/**/*Mapper.xml</value>
          </list>
      </property>
    </bean>
    <bean id="transactionManagerExt" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSourceExt" />
    </bean>
    <tx:annotation-driven transaction-manager="transactionManagerExt" /> 
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryExt"/>
        <property name="basePackage" value="com.xxx.dao.automapper.ext"/>
    </bean>

 

  所以,還是那句話:不是所有聽起來好的東西就一定是好,在這里轉換為不是所有聽起來方便的東西用起來就一定方便!

 

代碼ok后,還剩下一個問題:獨立后的寫動作同步問題!

  如果是自行搭建的mysql服務,我們很自然地考慮使用binlog同步(主從)來做!具體配置方法也不復雜,自行查找資料即可!

 

  如果使用阿里雲服務,則不是binlog那樣的配置了(但其實質仍然是對binlog的訂閱寫)。不過倒也都是頁面操作!(網上不一定好找資料,但是官網上一定是最全的)

 

要進行數據庫遷移,大體步驟分為:

  1. 先要將現有數據遷移到新實例上;

  2. 將部分新數據寫入新實例(部分數據仍直接寫現有實例,做到業務剝離的同時還可以減少數據同步的開銷);

  3. 將新實例數據同步回原實例庫;

 

DTS服務(數據傳輸服務),專門用於做數據遷移和數據同步!

  其打開方式為:

  1. 自然是花錢買服務了,買好后才能進入操作頁面;這里服務要分兩個:一是新實例rds數據庫,二是新實例同步回原實例的同步服務;

  2. 設置ip白名單,以使mysql可訪問;

  3. 創建高權限用戶,如root,以使后續可高權限操作mysql,同步時可使用該賬號或者另用一個普通讀寫賬號;

  4. 將全量數據刷入新實例,這里可以選擇阿里雲免費的數據庫遷移服務,也可以自己將數據dump下來,然后自行導致新庫;(不過服務既然是免費的,那為啥不用呢!)

  5. 設置單向同步,從新實例到原實例,此時是不會有數據同步的,因為沒有新寫入;

  6. 數據刷入,同步設置完成后,就可以發布新代碼了,此時最好將前端入口停止,否則可能出現數據錯亂問題;

  7. 發布代碼后,需要自行驗證。此時,先選擇一台機器進行驗證,可以選擇兩種方式驗證:一是自行調用關鍵接口進行驗證;二是將該機器綁定eip外網,使用該外網進行頁面訪問驗證(更完整的驗證);驗證的方向主要有兩個:1. 接口正常響應,沒有錯誤發生(此處應該要有監控設施,否則只能憑感覺);2. 數據有沒有正常同步(一般同步都是秒級的);

  8. 將代碼發布到集群中,觀察各機器運行情況!此處主要查看數據庫連接情況,是否存在連接失敗情況,應用監控是主要手段,也可以通過mysql的show full processlist; 進行查看應用連接db情況;

  9. 觀察正常后,此時可以將前端應用入口打開,此時如有條件,應限制ip訪問,使變更進行充分測試無誤;

  10. 一切無誤后,完全開放訪問服務;監控用戶數據,遷移完成;

 

至此,整個遷移就完成了,其實思路是很簡單的,關鍵是要小心操作。一個不小心的操作,就可能帶來很大的隱患,畢竟,數據無小事!請保持對數據和代碼的敬畏!

 

臨了臨了,附幾個操作的貼心小技巧,避免入坑!

  1. 買rds數據庫時,盡量買與原實例相同的區域(大區和可用區都相同),否則后期在做同步的時候會花更多的錢,因為跨區的網絡通信會讓你支付更多;

  2. 新實例數據庫容量可以稍微降配以節省錢,因為畢竟你是將原來的部分功能拆分出來的,沒必要一開始就為全部將來買單;

  3. 買同步服務時,注意幾點:1. 按量計算(按小時)比預付費(包月)更貴,但是也更容易訂制化,如果僅僅操作兩個rds間同步,且短時間內不會下線服務,則建議選擇預付費包月形式;2. 將區域選擇正確,比如同區域同步將更便宜;3. 能單向同步就不要雙向同步了,便宜的同時,也減小了誤操作帶來的影響;4. 同步性能一般小流量選擇small即可,高配的同步用不上關鍵是貴;

  4. 同步服務盡早開啟,但是后期對於賬號密碼的變更,一定要及時更改同步配置,否則將帶來數據一致性問題;(人工發現往往較晚,盡量設置監控報警)

  5. 數據庫白名單中,需要加入阿里雲數據傳輸服務的白名單,否則無法檢查數據庫響應性及同步作業;

  6. 選擇同步對象時,盡量以庫作為單位!如果選擇以表為同步單位,將存在后續新增表時,不會同步回原實例情況。如果實在不能以庫作為單位,在后續迭代時,一定記得添加此處同步表;(關注點太多,麻煩)

  7. 后期做數據變更時,注意操作對象所屬實例,別一頓操作猛如虎,然后沒什么卵用,因為我們只是選擇了單向同步;

  8. 自己可以不定期地做checksum檢查,以確認同步功能正常工作;(checksum table test)

  9. 代碼上分庫一定要做准確了,因為這里可能是一定時間內的唯一可信參考資料;(簡單但是關鍵)

 

最后,我還想說下使用別人服務和自己動手的一些個人感覺:

  1. 使用自己搭建的服務,最大的好處在於可以做任意的改變不受限,而且不需要付出額外的可見費用;

  2. 使用自己的服務的可能壞處是:如果你不是這方面的專家,往往會被自己埋下的各種坑難住;遇到問題沒能力處理;考慮方面不周全,容易引發安全問題;對未來的因素沒辦法考慮,使后期運作困難;如果你是專家,那多半這些都不是事兒;

  3. 使用別人的服務,最大的好處就是簡單易用,且有人維護;這些服務往往都是一路填坑過來的,時間越久往往越可靠(百年老字號最佳,哈哈);安全性、擴展性、性能調優、高可用等等;

  4. 使用別人的服務,其壞處主要是錢的問題,這個自不必說。還有個不是錢的問題的壞處,那就是你不能隨意訂制你想要的功能了,你的能力被別人限制住了,這個可能促使你轉場到自己提供服務;另外,各家提供的服務都不一樣,不像自己搭建的服務,網上會有各種資料可查,所以有一定的學習成本,具體取決於官方設計與官方文檔的完整性(當然一般都會很簡單);其實還有一個,就不說了,懂的都懂;

 

  好了,借着數據庫遷移的小事,扯了這些淡。只當是拋磚引玉了!歡迎指教!

 


免責聲明!

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



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