Sharding-JDBC入门
最近在做一个java后台重构项目时碰到了一个问题,就是系统的数据量越来越大,单表的压力剧增,已经不能满足正常的数据操作了。这个时候就需要有分库分表的处理了,由于资源和业务限制,最终只选择了分表。本来想着分表简单,对数据入库和索引时添加逻辑判断,选择对应表处理就完事了,结果项目里有位老哥说了,我们可以尝试用Sharding-JDBC做分表。这突然引起了我的兴趣,于是乎边开始研究一下Sharding-JDBC了。
本片主要是对Sharding-JDBC做一个入门,想要深入研究见官网(Apache ShardingSphere):
http://shardingsphere.apache.org/index_zh.html
1.基本概念
1.1分库和分表
1.1.1水平分库和垂直分库
垂直分库:指按照业务将表进行分类,分布到不同的数据库上面,每个库可以放在不同的服务器上,它的核心理念
是专库专用。
简单说就是,在数据不断增长的情况向将不同的业务数据放到不同的数据库中,减少数据库压力。例如将订单数据和商品数据放到不同的数据库中。
水平分库:是把同一个表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上。
上面的垂直分表解决了不同业务在同一个数据库访问的压力,但是如果某个业务量非常巨大,对单个业务数据库的访问量也是大数据量级别,那么依然存在问题,这时候就可以将某个业务库的数据库进行水平拆分。入将商品库水平两个库,将商品安装不同规则放入到两个数据库中。
1.1.2水平分表和垂直分表
垂直分表:将一个表按照字段分成多表,每个表存储其中一部分字段。
垂直分表就是根据数据存储的特点将一张表的数据氛围多张表,比如讲商品表氛围商品基本信息(商品id,商品名称)表和商品详情表(商品简介、商品参数等)。
水平分表:是在同一个数据库内,把同一个表的数据按一定规则拆到多个表中。
其实理念和水平分库类似,就是为了把单表的压力分散到多表上。每一张表的结构和字段是一样的。
1.2Sharding-JDBC基本术语
1.2.1逻辑表
水平拆分的数据库(表)的相同逻辑和数据结构表的总称。例:订单数据根据主键尾数拆分为 10 张表,分别是 t_order_0
到 t_order_9
,他们的逻辑表名为 t_order
。
1.2.2真实表
在分片的数据库中真实存在的物理表。即上个示例中的 t_order_0
到 t_order_9
。
1.2.3数据节点
数据分片的最小单元。由数据源名称和数据表组成,例:ds_0.t_order_0
。
1.2.4绑定表
指分片规则一致的主表和子表。例如:t_order
表和 t_order_item
表,均按照 order_id
分片,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。
1.2.5广播表
指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表。
2.简单案例
这里我们主要实现一个基于sharding-jdbc的分表案例。
2.1创建数据库和表
init.sql:
CREATE DATABASE `shardingjdbc_db` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
DROP TABLE IF EXISTS `t_order_1`;
CREATE TABLE `t_order_1` (
`order_id` bigint(20) NOT NULL COMMENT '订单id',
`price` decimal(10, 2) NOT NULL COMMENT '订单价格',
`user_id` bigint(20) NOT NULL COMMENT '下单用户id',
`status` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '订单状态',
PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
DROP TABLE IF EXISTS `t_order_2`;
CREATE TABLE `t_order_2` (
`order_id` bigint(20) NOT NULL COMMENT '订单id',
`price` decimal(10, 2) NOT NULL COMMENT '订单价格',
`user_id` bigint(20) NOT NULL COMMENT '下单用户id',
`status` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '订单状态',
PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
生成数据库和表。
2.2搭建Maven工程
2.2.1新建工程
2.2.2引入Maven依赖
<?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>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.hexin</groupId>
<artifactId>sharding-jdbc-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
2.2.3配置文件
application.properties配置文件:
spring.main.allow‐bean‐definition‐overriding = true
mybatis.configuration.map-underscore-to-camel-case = true
#m1数据源
spring.shardingsphere.datasource.names = m1
spring.shardingsphere.datasource.m1.type = com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m1.url = jdbc:mysql://192.168.142.110:3306/shardingjdbc_db?useUnicode=true
spring.shardingsphere.datasource.m1.username = root
spring.shardingsphere.datasource.m1.password = 123456
# 指定t_order表的数据分布情况,配置数据节点 m1.t_order_1,m1.t_order_2
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes = m1.t_order_$->{1..2}
# 指定t_order表的主键生成策略为SNOWFLAKE
spring.shardingsphere.sharding.tables.t_order.key-generator.column=order_id
spring.shardingsphere.sharding.tables.t_order.key-generator.type=SNOWFLAKE
# 指定t_order表的分片策略,分片策略包括分片键和分片算法
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}
# 打开sql输出日志
spring.shardingsphere.props.sql.show = true
(1)首先定义数据源m1,并对m1进行实际的参数配置。
(2)指定t_order表的数据分布情况,他分布在m1.t_order_1,m1.t_order_2
(3)指定t_order表的主键生成策略为SNOWFLAKE,SNOWFLAKE是一种分布式自增算法,保证id全局唯一
(4)定义t_order分片策略,order_id为偶数的数据落在t_order_1,为奇数的落在t_order_2,分表策略的表达式为t_order_$->{order_id % 2 + 1}
2.2.4主启动类
package com.hexin;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableAutoConfiguration(exclude={DruidDataSourceAutoConfigure.class})
@SpringBootApplication
public class ShardingJDBCApplication {
public static void main(String[] args) {
SpringApplication.run(ShardingJDBCApplication.class, args);
}
}
注意:
需要加上@EnableAutoConfiguration(exclude={DruidDataSourceAutoConfigure.class})。
Spring Boot会自动根据你jar包的依赖来自动配置项目,因为我们导入了druid的jar,Spring Boot会创建默认的内存数据库的数据源DataSource。这时候需要用exclude={DataSourceAutoConfiguration.class}, 让spring-boot不要根据Maven中依赖自动配置了。
2.2.5dao
package com.hexin.dao;
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 org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
@Mapper
@Component
public interface OrderDao {
/**
* 新增订单 * @param price 订单价格
* @param userId 用户id
* @param status 订单状态 * @return
*/
@Insert("insert into t_order(price,user_id,status) value(#{price},#{userId},#{status})")
int insertOrder(@Param("price") BigDecimal price, @Param("userId")Long userId, @Param("status")String status);
/**
* 根据id列表查询多个订单
* @param orderIds 订单id列表
* @return
*/
@Select({"<script>"
+ "select * from t_order t"
+ " where t.order_id in "
+ "<foreach collection='orderIds' item='id' open='(' separator=',' close=')'>"
+ " #{id} "
+ "</foreach>"
+ "</script>"})
List<Map> selectOrderbyIds(@Param("orderIds") List<Long> orderIds);
}
2.2.5测试
package com.hexin;
import com.hexin.dao.OrderDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {ShardingJDBCApplication.class})
public class OrderTest {
@Autowired
private OrderDao orderDao;
@Test
public void testInsertOrder(){
for (int i = 0 ; i<10; i++){
orderDao.insertOrder(new BigDecimal((i+1)*5),1L,"WAIT_PAY");
}
}
@Test
public void testSelectOrderbyIds(){
List<Long> ids = new ArrayList<>();
ids.add(373771636085620736L);
ids.add(373771635804602369L);
List<Map> maps = orderDao.selectOrderbyIds(ids);
System.out.println(maps);
}
}
通过日志可以发现order_id为奇数的被插入到t_order_2表,为偶数的被插入到t_order_1表,达到预期目标。
3.小结
通过上面的案例,我们看一下Sharding-JDBC在拿到用户要执行的sql之后干了哪些事儿:
(1)解析sql,获取片键值,在本例中是order_id
(2)Sharding-JDBC通过规则配置 t_order_$->{order_id % 2 + 1},知道了当order_id为偶数时,应该往
t_order_1表插数据,为奇数时,往t_order_2插数据。
(3)于是Sharding-JDBC根据order_id的值改写sql语句,改写后的SQL语句是真实所要执行的SQL语句。
(4)执行改写后的真实sql语句
(5)将所有真正执行sql的结果进行汇总合并,返回。
以上就是sharding-jdbc的入门案例。