ShardingSphere 之 Sharding-JDBC 数据分片实践

本文涉及的产品
云原生数据库 PolarDB PostgreSQL 版,企业版 4核16GB
推荐场景:
HTAP混合负载
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
简介: ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar(计划中)这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、云原生等各种多样化的应用场景。

简介


ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar(计划中)这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、云原生等各种多样化的应用场景。


ShardingSphere 定位为关系型数据库中间件,旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。 它与 NoSQL 和 NewSQL 是并存而非互斥的关系。NoSQL 和 NewSQL 作为新技术探索的前沿,放眼未来,拥抱变化,是非常值得推荐的。反之,也可以用另一种思路看待问题,放眼未来,关注不变的东西,进而抓住事物本质。 关系型数据库当今依然占有巨大市场,是各个公司核心业务的基石,未来也难于撼动,我们目前阶段更加关注在原有基础上的增量,而非颠覆。


ShardingSphere 已经在2020年4月16日从Apache孵化器毕业,成为 Apache 顶级项目。


快速入门


创建表


这里需要注意的是字段不要有关键字子类的,尽量长一点。


create database `cloud-order`;
use `cloud-order`;
create database if not exists `cloud-order` default charset utf8mb4 collate utf8mb4_general_ci;
create table t_order_1 (
  `oid` bigint(20) unsigned not null auto_increment comment '主键id',
  `order_code` varchar(64) not null default '' comment '订单号',
  primary key (`oid`),
  key `idx_order_code` (`order_code`)
) comment '订单表';
create table t_order_2 (
  `oid` bigint(20) unsigned not null auto_increment comment '主键id',
  `order_code` varchar(64) not null default '' comment '订单号',
  primary key (`oid`),
  key `idx_order_code` (`order_code`)
)comment '订单表';


添加依赖


我说下项目的基本情况:


  • spring boot 2.6.5


  • spring cloud 2021.0.1


  • mybatis-spring-boot-starter 2.2.2


  • sharding-jdbc-spring-boot-starter 4.1.1


目前来说都是比较新的版本,所以肯定会踩很多的坑。具体的配置如下(由于我的配置是之前的项目修改,不够简洁大家凑合看吧):


plugins {
    id 'java'
    id 'org.springframework.boot' version '2.6.5'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
}
group 'io.zhengsh'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
    mavenLocal()
    maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
    mavenCentral()
}
ext {
    set('springCloudVersion', "2021.0.1")
}
configurations {
    compile.exclude module: 'spring-boot-starter-tomcat'
}
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-undertow'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
    implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j'
    // mysql
    implementation 'mysql:mysql-connector-java'
    // mybatis
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2'
    // shardingsphere
    implementation 'org.apache.shardingsphere:sharding-jdbc-spring-boot-starter:4.1.1'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    compileOnly 'org.projectlombok:lombok:1.18.22'
    annotationProcessor 'org.projectlombok:lombok:1.18.22'
    testCompileOnly 'org.projectlombok:lombok:1.18.22'
    testAnnotationProcessor 'org.projectlombok:lombok:1.18.22'
}
dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}
tasks.named('test') {
    useJUnitPlatform()
}


添加 YML 配置


添加 YML 配置,主要是有一下配置


  • 设置数据源,就是配置有哪些库


  • 设置数据表,参与分片的表


  • 设置分表的策略,我这边使用的是雪花算法生成 id , 并且通过取模的方式指定数据位置。


spring:
  shardingsphere:
    # 设置数据源
    datasource:
      names: o1
      o1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/cloud-order?useUnicode=true&zeroDateTimeBehavior=convertToNull&useSSL=false
        username: root
        password: root123
    props:
      # 打印 sql 
      sql.show: true
    sharding:
      default-data-source-name: o1
      tables:
        t_order:
          # 指定需要分的表
          # 表达式, 实际数据节点: 根据上一个节点找到此值, {0..1}为groovy语言,$会替换成{0..1}的一个值,数据库表是: t_order_1 , t_order_2
          # 这个配置是告诉sharding有多少个表
          actual-data-nodes: o1.t_order_$->{1..2}
          key-generator:
            # 指定主键
            column: oid
            # 生成方式: 雪花算法
            type: snowflake
            props:
              worker:
                id: 1
          # 分表策略
          table-strategy:
            inline:
              # 配置sharding的计算列
              sharding-column: oid
              # 配置sharding的表达式,对应的id必须和sharding-column的值对应,否则报错
              algorithm-expression: t_order_$->{oid % 2 + 1}


Java 代码


1、实体类


public class OrderEntity {
    private Long oid;
    private String orderCode;
    // 此处省略 get、set 方法
}


2、Mapper 文件


@Mapper
public interface OrderEntityMapper {
    @Insert("insert into o1.t_order (`order_code`) values(#{orderCode})")
    @Options(useGeneratedKeys = true, keyProperty = "oid", keyColumn = "oid")
    int insertSelective(OrderEntity record);
    @Select("select * from o1.t_order where oid = #{oid}")
    OrderEntity selectOne(@Param("oid") Long oid);
}


3、 单元测试


@Test
public void insertOrder() {
  for (int i = 0; i < 10; i++) {
    OrderEntity order = new OrderEntity();
    order.setOrderCode("OR000" + i);
    int row = orderEntityMapper.insertSelective(order);
    Assert.assertTrue(row > 0);
  }
}
@Test
public void selectOne() {
  OrderEntity order1 = orderEntityMapper.selectOne(
715721929377452033L);
  Assert.assertNotNull(order1);
}


执行结果


1、插入数据


image.png


2、查询结果


image.png


常见问题


健康检测出错


核心就是 yml  配置。我之前遇到一个问题就是健康检测出错,无法配置,所以采用 Java config 方式的配置来规避这个启动报错


添加 Java config 配置如下所示:


@Configuration
public class DataSourceConfig {
    @Bean
    public HealthIndicator dbHealthIndicator(DataSource dataSource) {
        DataSourceHealthIndicator indicator = new DataSourceHealthIndicator(dataSource);
        indicator.setQuery("select 1");
        return indicator;
    }
}


空指针异常


MyBatis 查询时,报错了,报了一个空指针。查询别的表的时候不报错,就这个表报错,如下所示:


### Cause: java.lang.NullPointerException
  at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
  at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:149)
  at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)
  at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:76)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:498)
  at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:426)
  ... 75 common frames omitted
Caused by: java.lang.NullPointerException: null
  at org.antlr.v4.runtime.tree.AbstractParseTreeVisitor.visit(AbstractParseTreeVisitor.java:18)
  at org.apache.shardingsphere.sql.parser.mysql.visitor.impl.MySQLDMLVisitor.createProjection(MySQLDMLVisitor.java:446)
// 省略部分异常  


最开始以为是分表的原因,后来查了网上的资料,是表里面有关键字导致的。查看异常信息时,忽略了最上面的提示:


line 1:176 mismatched input 'order' expecting {..... 忽略一些关键字}


invoke method mod() on null object 异常


后面我又遇到了这个问题  Cannot invoke method mod() on null object] with root cause 异常,完整的错误信息如下:


### SQL: insert into o1.t_order (`order_code`, `code`) values(?, ?)
### Cause: java.lang.NullPointerException: Cannot invoke method mod() on null object
  at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:96)
  at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:441)
  at com.sun.proxy.$Proxy112.insert(Unknown Source)
  at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:272)
// 省略部分异常


问题原因:分片键和分片策略中的字段不一致导致


image.png


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2月前
|
安全 Java 数据库连接
jdbc解析excel文件,批量插入数据至库中
jdbc解析excel文件,批量插入数据至库中
|
2月前
|
Java 数据库连接 数据库
使用原生JDBC动态解析并获取表格列名和数据
使用原生JDBC动态解析并获取表格列名和数据
|
9月前
|
SQL Java 关系型数据库
JDBC插入数据详解
在Java应用程序中,与数据库交互是一项常见的任务。其中,插入数据操作是一种基本的数据库操作之一。本文将详细介绍如何使用Java JDBC(Java Database Connectivity)来执行插入数据操作。无论您是初学者还是有一定经验的开发人员,都能从本文中获得有关插入数据的重要信息。
110 0
|
9月前
|
SQL Java 大数据
Hive实战(03)-深入了解Hive JDBC:在大数据世界中实现数据交互
Hive实战(03)-深入了解Hive JDBC:在大数据世界中实现数据交互
343 1
|
2月前
|
Java 关系型数据库 MySQL
JDBC实现往MySQL插入百万级数据
JDBC实现往MySQL插入百万级数据
|
2月前
|
Java 数据库连接 数据库
实时计算 Flink版操作报错合集之flink jdbc写入数据时,长时间没写入后报错,是什么原因导致的
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
59 9
|
2月前
|
SQL 流计算 API
实时计算 Flink版产品使用合集之ClickHouse-JDBC 写入数据时,发现写入的目标表名称与 PreparedStatement 中 SQL 的表名不一致如何解决
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
35 0
|
2月前
|
SQL Java 数据库连接
Springboot框架整合Spring JDBC操作数据
JDBC是Java数据库连接API,用于执行SQL并访问多种关系数据库。它包括一系列Java类和接口,用于建立数据库连接、创建数据库操作对象、定义SQL语句、执行操作并处理结果集。直接使用JDBC涉及七个步骤,包括加载驱动、建立连接、创建对象、定义SQL、执行操作、处理结果和关闭资源。Spring Boot的`spring-boot-starter-jdbc`简化了这些步骤,提供了一个在Spring生态中更便捷使用JDBC的封装。集成Spring JDBC需要添加相关依赖,配置数据库连接信息,并通过JdbcTemplate进行数据库操作,如插入、更新、删除和查询。
|
2月前
|
SQL Java 关系型数据库
JDBC批量插入mysql数据
JDBC批量插入mysql数据
|
2月前
|
SQL 存储 Java
Sharding-JDBC 如何实现分片
以上是V 哥在教学过程中实现分片的示例步骤,Sharding-JDBC能够实现SQL的分片操作,将请求路由到正确的数据库和表中,从而实现数据的水平扩展,这是在使用例如 MySQL作为数据库的场景中经常会使用到的,但如果你的企业正在考虑分布式数据库迁移,V 哥建议可以考虑 TiDB 或 OceanBase 这样的分布式数据库,因为它们天然就支持分布式,而不需要考虑这些。