分佈式架構之數據庫分庫分表,Sharding-JDBC實戰

一、爲什麼要分庫分表

1、高併發:數據庫單實例扛不住高併發,需多實例承受

2、數據量大:單機磁盤容量有限,數據庫數據量大時撐滿磁盤

3、sql執行速度:單表數據過大,sql執行速度極慢

二、有哪些技術支持

1、技術

  • sharding-jdbc(sharding-sphere):噹噹開源的,client層方案,支持分庫分表、讀寫分離、分佈式 id 生成、柔性事務(最大努力送達型事務、TCC 事務)
  • mycat:基於 cobar 改造的,proxy 層方案
  • TDDL:淘寶團隊開發的,client 層方案。支持基本的 crud 語法和讀寫分離,但不支持 join、多表查詢等語法
  • cobar:阿里 b2b 團隊開發和開源的,proxy 層方案,就是介於應用服務器和數據庫服務器之間。應用程序通過 JDBC 驅動訪問 cobar 集羣,cobar 根據 SQL 和分庫規則對 SQL 做分解,然後分發到 MySQL 集羣不同的數據庫實例上執行,不支持讀寫分離、存儲過程、跨庫 join 和分頁等操作。
  • atlas:360 開源的,proxy 層方案

2、選型

sharding-jdbc和mycat是現在最火的,另外三種基本很少人用,然後sharding-jdbc呢不需要部署一套中間件,比較適合中小型企業,而mycat需要部署中間件,需要額外的維護但對項目透明不需要每個項目依賴,適合大型公司

三、如何進行拆分

1、拆分方式

1)水平拆分

水平拆分即把一個表數據按照行的級別進行拆分,比如表中按每一千萬行爲單位,拆成多個庫表,而它們加起來就是全部數據,特點在於每個分表的表結構都是一樣的。這樣拆分使得每個表數據限制在一定的範圍內,以此來支撐高併發和保證sql的執行效率,一般sql越複雜,要求數據行數約少

2)垂直拆分

垂直拆分即是把一個表中不同列拆分出來,每個庫表包含部分字段,特點就是每個分表的表結構是不同的,一般拆分是將訪問較多的字段拆分出來,數據庫是有緩存的,你訪問頻率高的行字段越少,就可以在緩存裏緩存更多的行,性能就越好

2、拆分策略

1)range方式

就是每庫存一段連續的數據,比如按時間範圍或者連續主鍵分類,如上個月的存在一個庫表,下一月的存在另一個庫表,但這種容易產生熱點問題,因爲多數系統查詢的都是較新的數據,那部分會經常使用而較久遠的數據很少被訪問到

2)hash方式

這種是基於hash算法,根據hash值分配到不同庫表,均勻分散,但可能在擴容的時候沒有上一種方式來的方便

四、拆分思考

1、只選一個sharding column進行分庫分表

2、多個sharding column多個分庫分表

  • 冗餘全量
  • 冗餘關係

3、sharding column分庫分表 + es

五、使用Sharding-JDBC分庫分表實戰

1、集成過程,大部分配置類似上一篇文章讀寫分離 https://blog.csdn.net/qq_20475615/article/details/99657628

  • 首先引入依賴,這裏使用springboot+mybatis-plus+druid+mysql+sharding-jdbc
<!-- starter -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- test -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-test</artifactId>
	<scope>test</scope>
</dependency>
<!-- Mysql Connector -->
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>5.1.44</version>
</dependency>
<!-- druid -->
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid-spring-boot-starter</artifactId>
	<version>1.1.10</version>
</dependency>
<!-- MybatisPlus -->
<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>mybatis-plus-boot-starter</artifactId>
	<version>3.0.1</version>
</dependency>
<!-- sharding-jdbc -->
<dependency>
	<groupId>io.shardingsphere</groupId>
	<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
	<version>3.1.0.M1</version>
</dependency>
  • application.yml文件配置,配置規則在後面測試過程有解釋
mybatis-plus:
  # 放在resource目錄 classpath:/mapper/*Mapper.xml
  mapper-locations: classpath:/mapper/*.xml
  # 實體掃描,多個package用逗號或者分號分隔
  typeAliasesPackage: com.example.project.*.*.mapper
  global-config:
    # 主鍵類型  0:"數據庫ID自增", 1:"用戶輸入ID",2:"全局唯一ID (數字類型唯一ID)", 3:"全局唯一ID UUID";
    id-type: 2
    # 字段策略 0:"忽略判斷",1:"非 NULL 判斷",2:"非空判斷"
    field-strategy: 2
    # 駝峯下劃線轉換
    db-column-underline: true
    # 刷新mapper 調試神器
    refresh-mapper: true
    # 數據庫大寫下劃線轉換
    #capital-mode: true
    # 邏輯刪除配置(下面3個配置)
    logic-delete-value: 0
    logic-not-delete-value: 1
    # SQL 解析緩存,開啓後多租戶 @SqlParser 註解生效
    sql-parser-cache: true
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: false


sharding:
  jdbc:
    datasource:
      names: ds0,ds1
      ds0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://192.168.1.60:23306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: root
        password: 123456
      ds1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://192.168.1.60:63306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: root
        password: 123456
    config:
      sharding:
        props:
          sql.show: true
        tables:
          org_user:  #表名
            key-generator-column-name: id  #主鍵
#            actual-data-nodes: ds${0..1}.org_user${0..1}  #數據節點
            actual-data-nodes: ds${0..1}.org_user${0..1}  #數據節點
            #分庫策略
            database-strategy:
              inline:
                sharding-column: age
                algorithm-expression: ds${age % 2}
            #分表策略
            table-strategy:
              inline:
                shardingColumn: sex
                algorithm-expression: org_user${sex % 2}
            #配置另一個表加多個節點即可
#          org_address:
#            key-generator-column-name: id
#            actual-data-nodes: ds${0..1}.org_address
#            database-strategy:
#              inline:
#                shardingColumn: lit
#                algorithm-expression: ds${lit % 2}
  • 業務代碼,這裏要說的就是分庫分表後最重要的就是表 id的生成,如何保證它的唯一性如雪花算法之類的,當然這部分不在這裏詳述,這裏直接寫死,重點在於分庫分表部分
//mybatis-plus 配置類
@Configuration
@MapperScan("com.example.project.*.*.mapper") //這裏千萬注意只寫mapper 所在文件夾,mybatis會進行代理,免得誤傷其他文件夾
public class MybatisPlusConfig {
    /**
     * 分頁插件,自動識別數據庫類型
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
}

//簡單實體類
@TableName("org_user")
public class User {
    private Integer id;
    private String name;
    private Integer age;
    private Integer sex;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getSex() {
        return sex;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }
}

//mapper
public interface UserMapper extends BaseMapper<User> {
}

//簡單的服務代碼
@Service
public class UserService {
    @Autowired
    UserMapper userMapper;

    /**
     * 測試讀
     */
    public void getUser(){
        System.out.println(null != userMapper.selectById(1) ? userMapper.selectById(1).getName():"1空");
        System.out.println(null != userMapper.selectById(2) ? userMapper.selectById(2).getName():"2空");
        System.out.println(null != userMapper.selectById(3) ? userMapper.selectById(3).getName():"3空");
        System.out.println(null != userMapper.selectById(4) ? userMapper.selectById(4).getName():"4空");
    }
    /**
     * 測試寫
     */
    public void saveUser(int id,int age,int sex,String name){
        User user = new User();
        user.setId(id);
        user.setAge(age);
        user.setSex(sex);
        user.setName(name);
        userMapper.insert(user);
    }

}
//測試類
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestService {
    @Autowired
	UserService userService;
    @Test
    public void test(){
	    userService.saveUser(1,2,0,"小米1");//按道理應該去的庫0,org_user0表
        userService.saveUser(2,1,0,"小米2");//按道理應該去的庫1,org_user0表
        userService.saveUser(3,2,1,"小米3");//按道理應該去的庫0,org_user1表
        userService.saveUser(4,1,1,"小米4");//按道理應該去的庫1,org_user1表
//        userService.getUser();
    }
}
  • 在兩個數據庫創建好對應的表,字段對應着實體來就可以,四個表保持結構一致,庫0也就是23306端口,庫1也就是63306端口

      

    

  • 測試過程,我們配置的庫0 是23306端口,庫1 是63306端口,按照我們配置的規則是,年齡雙數是分到庫0,年齡單數的是分到庫1 ,性別我們用 0 代表男,1 代表女,所以規則是男的分到 org_user0 表,女的分到 org_user1 表,看一下結果是否如我們所想

①測試寫,我們查看結果

   

  

 

②測試讀,查看結果是否能自動路由,正常取出對應的值

2、拓展,類似上一篇文章讀寫分離 https://blog.csdn.net/qq_20475615/article/details/99657628,我們用另一種依賴來配置

  • 上面的集成方法中我們用的sharding-jdbc是<artifactId>sharding-jdbc-spring-boot-starter</artifactId>,下面我們換一種依賴來集成,其他的依賴還是跟上面一樣,只要修改sharding-jdbc的
<dependency>
	<groupId>org.apache.shardingsphere</groupId>
	<artifactId>sharding-jdbc-core</artifactId>
	<version>4.0.0-RC1</version>
</dependency>
  • 接着增加一個sharding-druid.yml,放到和application.yml平級,內容如下,可以看到配置方式跟上面是不一樣的(記住不要把該配置放到application.yml,否則會引起衝突導致啓動失敗),application.yml裏去掉sharding節點那部分,至於mybatis的配置還是跟上面一樣
dataSources:
  ds0: !!com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.1.60:23306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456
  ds1: !!com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.1.60:63306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456

shardingRule:
  tables:
    #表名
    org_user:
      actualDataNodes: ds${0..1}.org_user${0..1}
      #分庫策略
      databaseStrategy:
        inline:
          shardingColumn: age
          algorithmExpression: ds${age % 2}
      #分表策略
      tableStrategy:
        inline:
          shardingColumn: sex
#這裏不同版本會有不同,開始按官網的配置是algorithmInlineExpression,發現在我的這個版本里報錯,具體看YamlInlineShardingStrategyConfiguration裏面是什麼屬性
          algorithmExpression: org_user${sex % 2}
  • 增加一個配置類(上面的方式是不需要的)
@Configuration
public class DataSourceConfig {

    @Bean
    public DataSource dataSource() throws Exception {
        return YamlShardingDataSourceFactory.createDataSource(ResourceUtils.getFile("classpath:sharding-druid.yml"));
    }
}
  • 代碼部分跟上面一樣測試即可

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章