分庫分表系列: 到底該怎麼拆分?

架構擺渡人發表於2022-03-26

大家好,我是【架構擺渡人】,一隻十年的程式猿。這是分庫分表系列的第一篇文章,這個系列會給大家分享很多在實際工作中有用的經驗,如果有收穫,還請分享給更多的朋友。

其實這個系列有錄過視訊給大家學習,但很多讀者反饋說看視訊太慢了。也不好沉澱為文件資料,希望能有一系列文字版本的講解,要用的時候可以快速瀏覽關鍵的知識點。那麼它就來了,我再花點時間寫成幾篇連續的文章供大家學習。

在實際業務實踐過程中,垂直拆分和水平拆分都必須用才行。拆分之前我們先來了解有哪些常用的拆分方式。

分表演算法這麼多,我該怎麼選?

時間

按時間拆分,適合一些與歸檔資料的需求,然後查詢的場景基本上都是最新的資料。如果說你的業務規模不是很固定,就會導致每個時間範圍內的資料不一樣,資料傾斜比較嚴重,那麼查詢比較麻煩,需要進行遍歷多庫多表才能拿到最終想要的結果。

時間拆可以以天,周,月,年等進行,具體採用哪種取決於你每天的資料量。假設我們一個月一張表,那麼就可以建立 table_202201, table_202202, table_202203 等,插入的時候就根據當前時間匹配對應的表。

範圍

適用於數字型別的欄位進行分表,一般像自增的主鍵ID。其實範圍跟時間的拆分有點類似,比如id 1~10000的在table1, 10001~20000的在table2,依次類推下去。

取模

用的比較多,直接用ID取模即可,比如 userId % 10=你的資料在哪個表,這樣就可以將同一個使用者的資料放在同一張表中,查詢的時候就不用同時查詢多個表獲取結果。但是問題也很明顯,就是會出現資料傾斜的問題,比如某個使用者的資料就是很多。其次還會出現熱點問題,比如某個使用者的訪問量就是很大,SQL就會一直落在某張表裡面。

到底該怎麼拆分呢?

多拆庫,少拆表

首先垂直拆分肯定是要的,要根據業務把對應的庫拆出來,比如我們拆出了一個訂單庫。然後針對訂單庫進行水平拆分。

無論我們用上面講的哪種拆分方式,都是把單庫單表拆分成多庫多表。假設我們將訂單庫的訂單表拆分成5庫5表,總共也就是50張訂單表。如下圖所示:

這裡的5個資料庫,你可以根據情況來決定是否需要5個獨立的資料庫例項。如果前期查詢量沒那麼大,並且想節省成本的話,那麼可以將order_00,order_01放在一個例項上。order_02,order_03,order_04放在一個例項上。這樣你就只需要2個資料庫例項。

假設過了一段時間,磁碟不夠了,或者說查詢壓力太大了。那麼你可以將其他的資料移到一個新的例項上面去,此時分庫分表的演算法是沒有改變的,只需要程式中將資料庫的連結地址改下就搞定了。

所以這裡也得出一個比較有價值的經驗:就是儘量多拆庫,少拆表。因為表多了其實資料還是在一個伺服器上面,請求量還是在一個伺服器上面。但是你庫拆的比較多,是可以變成獨立的資料庫例項,這個資料庫部署在獨立的伺服器上,大家懂我意思了吧。

合理分類

第二個經驗值就是:合理分類,因為訂單庫裡面還有一些表可能資料量沒那麼大,其實沒必要拆分成這麼多庫和表。這種情況通常可以單獨用一個庫將這些表放在一起。在我們的應用中就需要配置2個資料來源。

分類需要注意一點,除了表的資料量作為劃分條件,另一個條件就是這個表跟分表的那些表是否會在一個事務中操作,如果有事務需求,也需要考慮進去。如果你放到單獨的庫中去了,就沒辦法用資料庫的事務。

多資料來源的配置

一般我們在專案中都用一個資料來源,而且現在用Spring Boot還都是自動裝配的,突然要搞多個資料來源好像不知道從何下手,這裡給大家一點思路。

可以直接採用配置類的形式進行配置,無非就是配置多個DataSource,sqlSessionFactory,sqlSessionTemplate罷了。

我們舉個例子:

@MapperScan(sqlSessionFactoryRef = "sqlSessionFactoryOrder", basePackages = {"jiagoubaiduren.mapper.order"})
@Configuration
public class OrderMybatisAutoConfiguration {
    private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
    private final String[] MAPPER_XML_PATH = new String[] {"classpath*:ordermapper/*.xml"};
    @Bean(name = "dataSourceOrder")
    @Primary
    @ConfigurationProperties("spring.datasource.order")
    public DataSource dataSourceOrder(){
        return DruidDataSourceBuilder.create().build();
    }
    @Bean(name = "sqlSessionFactoryOrder")
    @Primary
    public SqlSessionFactory sqlSessionFactoryOrder() throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSourceOrder());
        factory.setVfs(SpringBootVFS.class);
        factory.setMapperLocations(resolveMapperLocations());
        return factory.getObject();
    }
    @Bean(name = "sqlSessionTemplateOrder")
    @Primary
    public SqlSessionTemplate sqlSessionTemplateOrder() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactoryOrder());
    }
    public Resource[] resolveMapperLocations() {
        return Stream.of(Optional.ofNullable(MAPPER_XML_PATH).orElse(new String[0]))
                .flatMap(location -> Stream.of(getResources(location))).toArray(Resource[]::new);
    }
    private Resource[] getResources(String location) {
        try {
            return resourceResolver.getResources(location);
        } catch (IOException e) {
            return new Resource[0];
        }
    }
}

裡面其實指定了對應的mapper檔案路徑和包路徑,如果要配置另一個資料來源,複製一份,然後包路徑改下就可以了。

總結

分庫分表主要是用於解決高併發,大資料量的場景,如果沒達到這個條件,千萬不要去做分庫分表。這會使複雜度變高,成本變高,當然你是能夠收貨一些實戰經驗。

既然要分庫分表,那麼必然需要一箇中介軟體來支撐,不可能通過硬編碼的形式去進行SQL的路由,下篇文章將為你分析用什麼形式的中介軟體來實現分庫分表比較合適。

原創:架構擺渡人(公眾號ID:jiagoubaiduren),歡迎分享,轉載請保留出處。

本文已收錄至學習網站 http://cxytiandi.com/ ,裡面有Spring Boot, Spring Cloud,分庫分表,微服務,面試等相關內容。

相關文章