likes
comments
collection
share

mysql第十话 - mysql+springboot之ShardingSphere-JDBC分表分库实战

作者站长头像
站长
· 阅读数 36

作为一个后端开发,用过mycat的应该不在少数吧。说说作者使用mycat遇到的问题! 需要单独的服务,对于我们根本就只有一串连接地址的概念,有时候挂了都不知道(因为是运维部署的,给我们的只是跳板机连的数据库,并不是mycat),高并发出现读延迟(强制主库解决),但总的来说运维成本比较高。 今天出一个客服端分库分表的实战,其实了解以及很久了,一直没有付出行动! 那么今天就来了!

1.ShardingSphere介绍

官网直通车:Apache ShardingSphere 产品定位为 Database Plus,旨在构建异构数据库上层的标准和生态。 它关注如何充分合理地利用数据库的计算和存储能力,而并非实现一个全新的数据库。ShardingSphere 站在数据库的上层视角,关注他们之间的协作多于数据库自身。 ShardingSphere 已于 2020 年 4 月 16 日成为 Apache 软件基金会的顶级项目。 目前有三大组件:

  • ShardingSphere-JDBC 基于协议层面对JDBC接口的封装,是以jar包客户端的形式存在的
  • ShardingSphere-Proxy 独立的代理服务,类似mycat
  • ShardingSphere-SCALING (EXPERIMENTAL) 实现中

今天主要来分析java端的ShardingSphere-JDBC

2.ShardingSphere-JDBC

2.1 介绍

ShardingSphere-JDBC自身定义为轻量级Java框架,可在Java JDBC层提供额外的服务。客户端直接连接到数据库时,它以jar形式提供服务,并且不需要额外的部署。

2.2 项目详细

  • pom.xml 本文基于springboot-2.5.6,shardingsphere-5.1.5
<!--Mysql连接-->
<dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <scope>runtime</scope>
</dependency>
<!--mybatis-->
<dependency>
 <groupId>org.mybatis.spring.boot</groupId>
 <artifactId>mybatis-spring-boot-starter</artifactId>
 <version>2.2.0</version>
</dependency>
<!--shardingsphere-jdbc-->
<dependency>
 <groupId>org.apache.shardingsphere</groupId>
 <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
 <version>5.1.1</version>
</dependency>

orm我这里用的是mybatis。

  • 表结构 主库:demo_test 从库:demo_test1,demo_test2 分表:shard_test0、shard_test1,字段user_id,name 主从表:user_info(user_id,name),user_mobile(user_id,mobile)

  • 插入代码和sql

@GetMapping("insert")
public Object insert() {
    Map<String, Object> resMap = new HashMap<>();
    for (int i = 0; i < 10; i++) {
        resMap.put("user_id", i);
        shardMapper.insertByUserId(resMap);
    }
    return 1;
}
  • sql
<!--分表插入-->
<insert id="insertByUserId" parameterType="Map">
 insert into shard_test(`user_id`,`name`) values (#{user_id},unix_timestamp())
</insert>
<!--分表查询-->
<select id="selectByUserId" parameterType="Integer" resultType="Map">
 select * from shard_test where user_id=#{userId}
</select>
<!--分库插入 || 全局表-->
<insert id="insertUserByUserId" parameterType="Map">
 insert into user_info(`user_id`,`name`) values (#{user_id},unix_timestamp())
</insert>
<!--分库插入 || 绑定表-->
<insert id="insertMobileByUserId" parameterType="Map">
 insert into user_mobile(`user_id`,`mobile`) values (#{user_id},unix_timestamp())
</insert>

<!--关联查询-->
<select id="selectByUserId" parameterType="Integer" resultType="Map">
 select * from user_info u
 left join user_mobile um on u.user_id=um.user_id
 where u.user_id=#{userId}
</select>

2.3 单库分表

作用:一个库两张表,通过取模的方式数据落到不同的表中。官网直通车

  • shardingsphere配置
spring:
  shardingsphere:
    datasource:
      names: ds0 #数据源名称
      ds0:
        type: com.zaxxer.hikari.HikariDataSource #使用的数据池
        driver-class-name: com.mysql.jdbc.Driver #驱动
        jdbc-url: jdbc:mysql://192.168.0.100:3306/demo_test #连接地址
        username: root #账号
        password: 123456 #密码
    rules: #规则
      sharding: 
        tables:
          shard_test: #表名
          #分片表配置由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。
            actual-data-nodes: ds0.shard_test$->{0..1} 
            table-strategy: #分表策略
              standard: #单分片键的场景
                sharding-column: user_id #分片键 就是根据那个字段分片
                sharding-algorithm-name: my_table #分片算法名称 详细在下面
        sharding-algorithms: #分片算法
          my_table: #上面自定义的算法名称
            type: INLINE
            props:
              #分片算法计算规则
              algorithm-expression: shard_test$->{user_id % 2}     props:
      sql-show: true #开启日志
  • 测试插入和查询日志
//插入日志 根据取模分布到ds0库的shard_test0和shard_test1表了
Actual SQL: ds0 ::: insert into shard_test0(`user_id`,`name`) values (?, unix_timestamp()) ::: [8]
Logic SQL: insert into shard_test(`user_id`,`name`) values (?,unix_timestamp())

Actual SQL: ds0 ::: insert into shard_test1(`user_id`,`name`) values (?, unix_timestamp()) ::: [9]
Logic SQL: select * from shard_test where user_id=?


//user_id  1和2的查询日志
Actual SQL: ds0 ::: select * from shard_test1 where user_id=? ::: [1]
Logic SQL: select * from shard_test where user_id=?

Actual SQL: ds0 ::: select * from shard_test0 where user_id=? ::: [2]

通过日志是可以看到能够正常分片插入和分片查询的。但是这种单库分表一般不怎么推荐,毕竟服务器压力还是在

2.4 分库单表

作用:将数据分片到不同的数据库中的同一张表中 数据库ds0,ds1表user_info

spring:
  shardingsphere:
    datasource:
      names: ds0,ds1
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://192.168.0.100:3306/demo_test
        username: root
        password: 123456
      ds1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://192.168.0.100:3306/demo_test1
        username: root
        password: 123456
    rules:
      sharding:
        tables:
          user_info: #表名
           #分片表配置由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。
            actual-data-nodes: ds$->{0..1}.user_info
   #分库分片键 和分表配置不同的就是这个key
            database-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: my_database
        sharding-algorithms:
          my_database:
            type: INLINE
            props:
              #分片键算法
              algorithm-expression: ds$->{user_id % 2}
    props:
      sql-show: true
  • 插入日志
Actual SQL: ds0 ::: insert into user_info(`user_id`,`name`) values (?, unix_timestamp()) ::: [6]
insert into user_info(`user_id`,`name`) values (?,unix_timestamp())
Actual SQL: ds1 ::: insert into user_info(`user_id`,`name`) values (?, unix_timestamp()) ::: [7]

根据user_id取模分布到不同的数据库中了

2.5 绑定表

作用:分库后的关联查询,避免出现笛卡尔积

#数据源配置省略了
rules:
  sharding:
    tables:
      user_info: #user_info分库策略
        actual-data-nodes: ds$->{0..1}.user_info
        database-strategy:
          standard:
            sharding-column: user_id
            sharding-algorithm-name: my_database
      user_mobile:  #user_mobile分库策略
        actual-data-nodes: ds$->{0..1}.user_mobile
        database-strategy:
          standard:
            sharding-column: user_id
            sharding-algorithm-name: my_database
    sharding-algorithms:
      my_database: #使用的都是my_database这个分片算法
        type: INLINE
        props:
          algorithm-expression: ds$->{user_id % 2}
    #绑定表 这里是关键 如果又分表又分库 关联查询会出现笛卡尔积       
    binding-tables: user_info,user_mobile
  • 测试日志输出
//往两个表插入数据
Actual SQL: ds1 ::: insert into user_info(`user_id`,`name`) values (?, unix_timestamp()) ::: [7]
Logic SQL: insert into user_mobile(`user_id`,`mobile`) values (?,unix_timestamp())
Actual SQL: ds1 ::: insert into user_mobile(`user_id`,`mobile`) values (?, unix_timestamp()) ::: [7]
insert into user_info(`user_id`,`name`) values (?,unix_timestamp())

Actual SQL: ds0 ::: insert into user_info(`user_id`,`name`) values (?, unix_timestamp()) ::: [8]
Logic SQL: insert into user_mobile(`user_id`,`mobile`) values (?,unix_timestamp())
Actual SQL: ds0 ::: insert into user_mobile(`user_id`,`mobile`) values (?, unix_timestamp()) ::: [8]
Logic SQL: insert into user_info(`user_id`,`name`) values (?,unix_timestamp())

//查询关联查询日志user_id 23
Actual SQL: ds0 ::: select * from user_info u
  left join user_mobile um on u.user_id=um.user_id
  where u.user_id=? ::: [2]
Actual SQL: ds1 ::: select * from user_info u
  left join user_mobile um on u.user_id=um.user_id
  where u.user_id=? ::: [3]

关联查询能正常路由到指定的数据库

2.6 广播表(全局表)

作用:全局唯一ID,业务配置冗余,插入的数据均分布到两个库中

#数据源配置省略
rules:
  sharding:
    #只需要声明表名
    broadcast-tables: shard_test0
  • 测试日志输出
//插入日志
Actual SQL: ds0 ::: insert into shard_test0(`user_id`,`name`) values (?, unix_timestamp()) ::: [9]
Actual SQL: ds1 ::: insert into shard_test0(`user_id`,`name`) values (?, unix_timestamp()) ::: [9]
Logic SQL: insert into shard_test0(`user_id`,`name`) values (?,unix_timestamp())

//查询日志输出
Actual SQL: ds0 ::: select * from shard_test0 where user_id=? ::: [5]
Logic SQL: select * from shard_test0 where user_id=?

//第三次查询
Actual SQL: ds1 ::: select * from shard_test0 where user_id=? ::: [5]

可以看到数据会往两个数据源都插入一份,而查询应该是基于随机算法

2.7 读写分离

作用:利用数据库的主从复制实现读写分离,提高数据库读取性能,一主多从和多主多从

  • yaml配置
rules:
  #读写分离
  readwrite-splitting:
    data-sources:
      ds0: #上面定义的数据源名字 也可随便定义不重复的
        type: Static #类型Static,Dynamic
        props:
          #写库
          write-data-source-name: ds0
          #读库 多个从库用逗号分隔
          read-data-source-names: ds1,ds2
#          ds1: #多个主库需要加这个配置 和分库配置
#            type: Static
#            props:
#              write-data-source-name: ds1
#              read-data-source-names: ds3
  • 测试日志输出
//查询日志
Actual SQL: ds1 ::: select * from shard_test0 where user_id=? ::: [7]
Logic SQL: select * from shard_test0 where user_id=?
Actual SQL: ds2 ::: select * from shard_test0 where user_id=? ::: [8]

默认是轮询算法,如需配置随机算法可参考下面配置

  rules:
    readwrite-splitting:
      data-sources:
        ds0:
          type: Static
          props:
            write-data-source-name: ds0
            read-data-source-names: ds1,ds2
          #复制均衡算法名称
          load-balancer-name: load_name
      load-balancers:
        load_name: #名称
          #算法 round_robin/轮询 random/随机 权重
          type: random
          props:
            default: 0 #要有这个 不然启动报错

3.总结

总体来说用起来还是比较舒服的,完美的实现了分库分表,读写分离。 这里贴一下要注意的问题点: 1.有些配置里面的<table-name>是一定要写对应的表名的,有些这种配置就可以随便定义 2.分片键配置的行内表达式如果有比较复杂的,可手写代码实现分片规则 3.读延迟问题,可在读之前执行HintManager.getInstance().setWriteRouteOnly();这个代码强制读主库,但是如果还有后续查询记得清除线程变量。

总结一下和mycat的区别:

SharingSphere-JDBCMycat
工作层面JDBC协议,接口封装Mysql协议,JDBC协议
运行方式Jar包,客服端独立服务,服务端
开发方式代码,配置数据量连接地址
运维方式运维单独维护
性能多线程并发独立服务,需要看服务器配置
支持语言仅支持Java支持JDBC协议的语言

以上就是本章的全部内容了。

上一篇:mysql第九话 - mysql主从复制集群实现 下一篇:通信框架之Netty第一话 - NIO的超神发展之路

天行健,君子以自强不息

转载自:https://juejin.cn/post/7131173578535338015
评论
请登录