分库分表中间件之ShardingSphere-入门案例 | 字数总计: 3.5k | 阅读时长: 16分钟 | 阅读量: |
ShardingSphere的前尘往事 ShardingSphere的前身是Sharding-JDBC,起源于当当网内部应用框架,于2016年初开源。
2017年,Sharding-JDBC进入2.x阶段,核心功能是数据库治理。
2018年春节前夕,Sharding-JDBC团队于京东数科重新组建,并将其定位为面向云原生的数据库中间件,正式更名为ShardingSphere。Sharding-JDBC进入了3.x时代,功能重心转向了Sharding-proxy及分布式事务上。
美国时间2018年11月10日,分布式数据库中间件生态圈 ShardingSphere正式进入Apache基金会孵化器。
预祝ShardingSphere项目早日成功毕业。
Apache ShardingSphere简介 Apache ShardingSphere (Incubator) 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(规划中)这3款相互独立,却又能够混合部署配合使用的产品组成。它们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、云原生等各种多样化的应用场景。
Apache官方发布从4.0.0版本开始。
Apache ShardingSphere官方文档
Sharding-JDBC简介 定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
Sharding-JDBC快速入门 我们以一库两表的案例来快速入门。
建立数据表 这里我们以简单的订单表为例,作为入门学习使用。
首先建立一库两表的数据库结构。数据库名为sharding_jdbc_db,建立t_order_1和t_order_2两张表。
建表sql语句如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 DROP TABLE IF EXISTS t_order_1;DROP TABLE IF EXISTS t_order_2;CREATE TABLE `sharding_jdbc_db`.`t_order_1` ( `order_id` varchar (32 ) NOT NULL COMMENT '主键:订单id' , `price` decimal (10 , 2 ) NOT NULL COMMENT '订单金额' , `user_id` varchar (32 ) NOT NULL COMMENT '用户id' , `status` varchar (16 ) NOT NULL COMMENT '订单状态' , PRIMARY KEY (`order_id`) ) ENGINE = InnoDB CHARACTER SET = utf8; CREATE TABLE `sharding_jdbc_db`.`t_order_2` ( `order_id` varchar (32 ) NOT NULL COMMENT '主键:订单id' , `price` decimal (10 , 2 ) NOT NULL COMMENT '订单金额' , `user_id` varchar (32 ) NOT NULL COMMENT '用户id' , `status` varchar (16 ) NOT NULL COMMENT '订单状态' , PRIMARY KEY (`order_id`) ) ENGINE = InnoDB CHARACTER SET = utf8;
创建sharding-jdbc-simple项目 使用IDEA创建一个maven的空模板项目,项目名为sharding-jdbc-simple。
在pom.xml文件中需添加的sharding-jdbc-spring-boot-starter依赖如下:
1 2 3 4 5 6 <dependency > <groupId > org.apache.shardingsphere</groupId > <artifactId > sharding-jdbc-spring-boot-starter</artifactId > <version > 4.0.0-RC1</version > </dependency >
由于我的项目使用了自己定义的统一依赖管理,所以没有指定各个jar包的版本号。
读者可以参考 sunchaser-boot-dependencies 进行实现,或者单独指定对应版本号。
完整的pom.xml内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <parent > <artifactId > 中间件</artifactId > <groupId > com.sunchaser</groupId > <version > 1.0-SNAPSHOT</version > </parent > <modelVersion > 4.0.0</modelVersion > <artifactId > sharding-jdbc-simple</artifactId > <dependencyManagement > <dependencies > <dependency > <groupId > com.sunchaser.boot</groupId > <artifactId > sunchaser-boot-dependencies</artifactId > <version > 1.0-SNAPSHOT</version > <type > pom</type > <scope > import</scope > </dependency > </dependencies > </dependencyManagement > <dependencies > <dependency > <groupId > org.apache.shardingsphere</groupId > <artifactId > sharding-jdbc-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <scope > runtime</scope > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <scope > provided</scope > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
编写配置文件 springboot项目可使用properties配置或yaml配置。二选一即可。
基本配置 配置tomcat端口号、应用名和允许重复定义的bean进行覆盖。
properties配置:
1 2 3 4 server.port =520 spring.application.name =sharding-jdbc-simple-demo spring.main.allow-bean-definition-overriding =true
yaml配置:
1 2 3 4 5 6 7 8 server: port: 520 spring: application: name: sharding-jdbc-simple-demo main: allow-bean-definition-overriding: true
配置mybatis 主要配置xml文件映射路径和下划线自动转驼峰。
properties配置:
1 2 3 4 5 6 7 8 9 mybatis.mapper-locations =classpath:mapper/**/*.xml mybatis.configuration.map-underscore-to-camel-case =true
yaml配置:
1 2 3 4 5 6 mybatis: mapper-locations: classpath:mapper/**/*.xml configuration: map-underscore-to-camel-case: true
配置数据源 此入门案例是一库两表,只有一个数据源需要配置。
properties配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 spring.shardingsphere.datasource.names =ds1 spring.shardingsphere.datasource.ds1.type =com.alibaba.druid.pool.DruidDataSource spring.shardingsphere.datasource.ds1.driver-class-name =com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.ds1.url =jdbc:mysql://127.0.0.1:3306/sharding_jdbc_db?useUnicode=true&serverTimezone=Asia/Shanghai spring.shardingsphere.datasource.ds1.username =root spring.shardingsphere.datasource.ds1.password =ll970722
yaml配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 spring: shardingsphere: datasource: names: ds1 ds1: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/sharding_jdbc_db?useUnicode=true&serverTimezone=Asia/Shanghai username: root password: ll970722
配置分片规则 使用默认的inline表达式配置分片策略,分片策略包含分片键和分片算法,需符合groovy语法。
properties配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 spring.shardingsphere.sharding.tables.t_order.actual-data-nodes =ds1.t_order_$->{1..2} spring.shardingsphere.sharding.tables.t_order.key-generator.column =order_id spring.shardingsphere.sharding.tables.t_order.key-generator.type =SNOWFLAKE spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column =order_id spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression =t_order_$->{order_id % 2 + 1}
yaml配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 spring: shardingsphere: sharding: tables: t_order: actual-data-nodes: ds1.t_order_$->{1..2} key-generator: column: order_id type: SNOWFLAKE table-strategy: inline: sharding-column: order_id algorithm-expression: t_order_$->{order_id % 2 + 1 }
配置日志相关 配置打印执行的真实sql与日志级别。
properties配置:
1 2 3 4 5 6 7 spring.shardingsphere.props.sql.show =true logging.level.root =info logging.level.org.springframework.web =info logging.level.com.sunchaser.shardingjdbc =debug logging.level.druid.sql =debug
yaml配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 spring: shardingsphere: props: sql: show: true logging: level: root: info org: springframework: web: info com: sunchaser: shardingjdbc: debug druid: sql: debug
至此,配置部分完成,开始编写后端代码。
后端测试代码编写 项目包结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 └─src ├─main │ ├─java │ │ └─com │ │ └─sunchaser │ │ └─shardingjdbc ------ 存放启动类 │ │ ├─entity -------- 存放实体类 │ │ └─mapper -------- 存放mapper接口 │ └─resources │ ├─db ----------------------- 存放建表语句 │ └─mapper ------------------- 存放xml映射文件 └─test └─java └─com └─sunchaser └─shardingjdbc └─mapper ------ 存放单元测试类
入门案例主要是使用Sharding-JDBC进行新增和查询,mybatis的mapper接口和xml映射文件编写方式保持不变。
编写启动类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.sunchaser.shardingjdbc;import org.springframework.boot.WebApplicationType;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.builder.SpringApplicationBuilder;@SpringBootApplication public class ShardingJdbcSimpleApplication { public static void main (String[] args) { new SpringApplicationBuilder (ShardingJdbcSimpleApplication.class) .web(WebApplicationType.SERVLET) .run(args); } }
编写订单实体类 使用lombok工具包的@Data和@Builder注解生成getter、setter和建造者模式代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package com.sunchaser.shardingjdbc.entity;import lombok.Builder;import lombok.Data;import java.math.BigDecimal;@Data @Builder public class OrderEntity { private Long orderId; private BigDecimal price; private String userId; private String status; }
编写mapper接口 插入订单(注解实现) 根据订单ID的集合查询订单信息集合(注解实现) 根据订单ID的集合查询订单信息集合(XML实现) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package com.sunchaser.shardingjdbc.mapper;import com.sunchaser.shardingjdbc.entity.OrderEntity;import org.apache.ibatis.annotations.Insert;import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Param;import org.apache.ibatis.annotations.Select;import java.util.List;@Mapper public interface OrderMapper { @Insert("insert into t_order(price,user_id,status) values(#{price},#{userId},#{status})") Integer insert (OrderEntity orderEntity) ; @Select(" <script> " + " select * from t_order t where t.order_id in " + " <foreach collection='orderIds' open='(' separator=',' close=')' item='orderId'> " + " #{orderId} " + " </foreach> " + " </script> ") List<OrderEntity> selectByAnnotation (@Param("orderIds") List<Long> orderIds) ; List<OrderEntity> selectByXml (@Param("orderIds") List<Long> orderIds) ; }
OrderMapper的selectByXml方法对应的XML配置如下:
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.sunchaser.shardingjdbc.mapper.OrderMapper" > <select id ="selectByXml" resultType ="com.sunchaser.shardingjdbc.entity.OrderEntity" > select * from t_order where order_id in <foreach collection ='orderIds' item ='orderId' open ='(' separator =',' close =')' > #{orderId} </foreach > </select > </mapper >
编写单元测试代码 首先循环调用OrderMapper的insert方法插入20条订单数据,然后分别选择同一个订单表的两个订单orderId和选择不同订单表的两个orderId进行in
查询。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package com.sunchaser.shardingjdbc.mapper;import com.google.common.collect.Lists;import com.sunchaser.shardingjdbc.entity.OrderEntity;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import javax.annotation.Resource;import java.math.BigDecimal;import java.util.List;@SpringBootTest @RunWith(SpringRunner.class) public class OrderMapperTest { @Resource private OrderMapper orderMapper; @Test public void insert () { for (int i = 0 ; i < 20 ; i++) { OrderEntity orderEntity = OrderEntity.builder() .price(new BigDecimal ("1.11" )) .userId("2019xxxxxxxx" ) .status("SUCCESS" ) .build(); Integer success = orderMapper.insert(orderEntity); System.out.println(success); } } @Test public void selectByAnnotation () { List<Long> orderIds = Lists.newArrayList(406907714078244865L ,407114454203891712L ); List<OrderEntity> orderEntities = orderMapper.selectByAnnotation(orderIds); System.out.println(orderEntities); } @Test public void selectByXml () { List<Long> orderIds = Lists.newArrayList(406907714078244865L ,407114454203891712L ); List<OrderEntity> orderEntities = orderMapper.selectByXml(orderIds); System.out.println(orderEntities); } }
由于前面配置了sql日志打印,我们可在控制台看到执行的逻辑SQL和真实SQL。
以查询方法selectByAnnotation为例,执行日志如下:
1 2 3 4 5 6 7 8 9 2019-12-12 11:05:36.399 DEBUG 17328 --- [ main] c.s.s.m.OrderMapper.selectByAnnotation : ==> Preparing: select * from t_order t where t.order_id in ( ? , ? ) 2019-12-12 11:05:36.459 DEBUG 17328 --- [ main] c.s.s.m.OrderMapper.selectByAnnotation : ==> Parameters: 406907714078244865(Long), 407114454203891712(Long) 2019-12-12 11:05:37.778 INFO 17328 --- [ main] ShardingSphere-SQL : Rule Type: sharding 2019-12-12 11:05:37.783 INFO 17328 --- [ main] ShardingSphere-SQL : Logic SQL: select * from t_order t where t.order_id in ( ? , ? ) 2019-12-12 11:05:37.784 INFO 17328 --- [ main] ShardingSphere-SQL : SQLStatement: SelectStatement(super=DQLStatement(super=AbstractSQLStatement(type=DQL, tables=Tables(tables=[Table(name=t_order, alias=Optional.of(t))]), routeConditions=Conditions(orCondition=OrCondition(andConditions=[AndCondition(conditions=[Condition(column=Column(name=order_id, tableName=t_order), operator=IN, compareOperator=null, positionValueMap={}, positionIndexMap={0=0, 1=1})])])), encryptConditions=Conditions(orCondition=OrCondition(andConditions=[])), sqlTokens=[TableToken(tableName=t_order, quoteCharacter=NONE, schemaNameLength=0)], parametersIndex=2, logicSQL=select * from t_order t where t.order_id in ( ? , ? ))), containStar=true, firstSelectItemStartIndex=7, selectListStopIndex=7, groupByLastIndex=0, items=[StarSelectItem(owner=Optional.absent())], groupByItems=[], orderByItems=[], limit=null, subqueryStatement=null, subqueryStatements=[], subqueryConditions=[]) 2019-12-12 11:05:37.786 INFO 17328 --- [ main] ShardingSphere-SQL : Actual SQL: ds1 ::: select * from t_order_1 t where t.order_id in ( ? , ? ) ::: [406907714078244865, 407114454203891712] 2019-12-12 11:05:37.786 INFO 17328 --- [ main] ShardingSphere-SQL : Actual SQL: ds1 ::: select * from t_order_2 t where t.order_id in ( ? , ? ) ::: [406907714078244865, 407114454203891712] 2019-12-12 11:05:38.062 DEBUG 17328 --- [ main] c.s.s.m.OrderMapper.selectByAnnotation : <== Total: 2 [OrderEntity(orderId=407114454203891712, price=1.11, userId=2019xxxxxxxx, status=SUCCESS), OrderEntity(orderId=406907714078244865, price=1.11, userId=2019xxxxxxxxxxxxxxx, status=SUCCESS)]
可看到执行的Logic SQL为:
1 select * from t_order t where t.order_id in ( ? , ? )
Sharding-JDBC的路由引擎根据解析上下文匹配数据库和表的分片策略,路由到具体的库和表。再经过改写引擎,将逻辑SQL改写为真正执行的SQL。
从日志中可看到改写后的Actual SQL为:
1 2 select * from t_order_1 t where t.order_id in ( ? , ? ) ::: [406907714078244865 , 407114454203891712 ]select * from t_order_2 t where t.order_id in ( ? , ? ) ::: [406907714078244865 , 407114454203891712 ]
结果归并,真实的SQL执行后会返回多个结果集,归并引擎从各个数据节点拿到结果集后组合成一个结果集并返回给客户端。
至此,入门案例就演示结束。源码地址:传送门
下一篇文章将对Sharding-JDBC中的一些核心概念进行具体的解释。
小结 这篇文章主要是对Apache ShardingSphere 4.x与Spring Boot 2.1.x进行整合,使用了内置的SNOWFLAKE雪花算法生成分布式全局唯一ID和使用默认的inline表达式配置分片策略,完成了最简单的一库两表的分库分表,并对新增和查询操作进行了测试。