0
点赞
收藏
分享

微信扫一扫

Myqsql使用Sharding-JDBC配置详解



目录

  • ​​1 需求说明​​
  • ​​2 环境搭建​​
  • ​​2.1 环境说明​​
  • ​​2.2 创建数据库​​
  • ​​2.3.引入maven依赖​​
  • ​​3 编写程序​​
  • ​​3.1 分片规则配置​​
  • ​​3.2. 数据操作​​
  • ​​3.3 测试​​
  • ​​4 流程分析​​
  • ​​5 其他集成方式​​
  • ​​5.1 Spring Boot Yaml 配置​​
  • ​​5.2 Java 配置​​
  • ​​5.3 Spring Boot properties配置​​
  • ​​5.4 Spring命名空间配置​​
  • ​​6 sharding-jdbc4种分片策略​​
  • ​​6.1 准备工作​​
  • ​​6.2 标准分片策略​​
  • ​​6.3 精准分片算法​​
  • ​​6.4 范围分片算法​​
  • ​​6.5 复合分片策略​​
  • ​​6.6 行表达式分片策略​​
  • ​​6.7 Hint分片策略​​
  • ​​7 水平分表​​
  • ​​8 水平分库​​
  • ​​9 垂直分库​​
  • ​​10 公共表​​
  • ​​11 配置中遇到的一些问题​​
  • ​​11.1 数据库链接池找不到​​
  • ​​11.2 错误java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.​​

1 需求说明

使用Sharding-JDBC完成对订单表的水平分表,通过快速入门程序的开发,快速体验Sharding-JDBC的使用方法。

人工创建两张表,t_order_1和t_order_2,这两张表是订单表拆分后的表,通过Sharding-Jdbc向订单表插入数据,按照一定的分片规则,主键为偶数的进入t_order_1,另一部分数据进入t_order_2,通过Sharding-Jdbc 查询数据,根据 SQL语句的内容从t_order_1或t_order_2查询数据。

2 环境搭建

2.1 环境说明

操作系统: Win10

数据库: MySQL-5.7.25

JDK :64位 jdk1.8.0_201

应用框架: spring-boot-2.1.3.RELEASE,Mybatis3.5.0

Sharding-JDBC :sharding-jdbc-spring-boot-starter-4.0.0-RC1

2.2 创建数据库

创建订单库 order_db

CREATE DATABASE `order_db` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';

在order_db中创建t_order_1、t_order_2表

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.3.引入maven依赖

引入 sharding-jdbc和SpringBoot整合的Jar包:

<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding‐jdbc‐spring‐boot‐starter</artifactId>
<version>4.0.0‐RC1</version>
</dependency>

具体spring boot相关依赖及配置请参考资料中dbsharding/sharding-jdbc-simple工程,本指引只说明与Sharding-

JDBC相关的内容。

3 编写程序

3.1 分片规则配置

分片规则配置是sharding-jdbc进行对分库分表操作的重要依据,配置内容包括:数据源、主键生成策略、分片策略等。

在application.properties中配置

server.port=56081
spring.application.name = sharding‐jdbc‐simple‐demo
server.servlet.context‐path = /sharding‐jdbc‐simple‐demo
spring.http.encoding.enabled = true
spring.http.encoding.charset = UTF‐8
spring.http.encoding.force = true
spring.main.allow‐bean‐definition‐overriding = true
mybatis.configuration.map‐underscore‐to‐camel‐case = true
# 以下是分片规则配置
# 定义数据源
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://localhost:3306/order_db?useUnicode=true
spring.shardingsphere.datasource.m1.username = root
spring.shardingsphere.datasource.m1.password = root
# 指定t_order表的数据分布情况,配置数据节点
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
swagger.enable = true
logging.level.root = info
logging.level.org.springframework.web = info
logging.level.com.itheima.dbsharding = debug
logging.level.druid.sql = debug
  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}

3.2. 数据操作

@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);
}

3.3 测试

编写单元测试:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {ShardingJdbcSimpleDemoBootstrap.class})
public class OrderDaoTest {
@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);
}
}

执行 testInsertOrder:

Myqsql使用Sharding-JDBC配置详解_数据库

通过日志可以发现order_id为奇数的被插入到t_order_2表,为偶数的被插入到t_order_1表,达到预期目标。

执行testSelectOrderbyIds:

Myqsql使用Sharding-JDBC配置详解_java_02

通过日志可以发现,根据传入order_id的奇偶不同,sharding-jdbc分别去不同的表检索数据,达到预期目标。

4 流程分析

通过日志分析,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的结果进行汇总合并,返回。

5 其他集成方式

Sharding-JDBC不仅可以与spring boot良好集成,它还支持其他配置方式,共支持以下四种集成方式。

5.1 Spring Boot Yaml 配置

Myqsql使用Sharding-JDBC配置详解_sql_03

定义application.yml,内容如下:

server:
port: 56081
servlet:
context‐path: /sharding‐jdbc‐simple‐demo
spring:
application:
name: sharding‐jdbc‐simple‐demo
http:
encoding:
enabled: true
charset: utf‐8
force: true
main:
allow‐bean‐definition‐overriding: true
shardingsphere:
datasource:
names: m1
m1:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/order_db?useUnicode=true
username: root
password: mysql
sharding:
tables:
t_order:
actualDataNodes: m1.t_order_$‐>{1..2}
tableStrategy:
inline:
shardingColumn: order_id
algorithmExpression: t_order_$‐>{order_id % 2 + 1}
keyGenerator:
type: SNOWFLAKE
column: order_id
props:
sql:
show: true
mybatis:
configuration:
map‐underscore‐to‐camel‐case: true
swagger:
enable: true
logging:
level:
root: info
org.springframework.web: info
com.itheima.dbsharding: debug
druid.sql: debug

如果使用 application.yml则需要屏蔽原来的application.properties文件。

5.2 Java 配置

添加配置类:

@Configuration
public class ShardingJdbcConfig {
// 定义数据源
Map<String, DataSource> createDataSourceMap() {
DruidDataSource dataSource1 = new DruidDataSource();
dataSource1.setDriverClassName("com.mysql.jdbc.Driver");
dataSource1.setUrl("jdbc:mysql://localhost:3306/order_db?useUnicode=true");
dataSource1.setUsername("root");
dataSource1.setPassword("root");
Map<String, DataSource> result = new HashMap<>();
result.put("m1", dataSource1);
return result;
}
// 定义主键生成策略
private static KeyGeneratorConfiguration getKeyGeneratorConfiguration() {
KeyGeneratorConfiguration result = new
KeyGeneratorConfiguration("SNOWFLAKE","order_id");
return result;
}
// 定义t_order表的分片策略
TableRuleConfiguration getOrderTableRuleConfiguration() {
TableRuleConfiguration result = new TableRuleConfiguration("t_order","m1.t_order_$‐>
{1..2}");
result.setTableShardingStrategyConfig(new
InlineShardingStrategyConfiguration("order_id", "t_order_$‐>{order_id % 2 + 1}"));
result.setKeyGeneratorConfig(getKeyGeneratorConfiguration());
return result;
}
// 定义sharding‐Jdbc数据源
@Bean
DataSource getShardingDataSource() throws SQLException {
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
shardingRuleConfig.getTableRuleConfigs().add(getOrderTableRuleConfiguration());
//spring.shardingsphere.props.sql.show = true
Properties properties = new Properties();
properties.put("sql.show","true");
return ShardingDataSourceFactory.createDataSource(createDataSourceMap(),
shardingRuleConfig,properties);
}
}

由于采用了配置类所以需要屏蔽原来 application.properties文件中spring.shardingsphere开头的配置信息。

还需要在SpringBoot启动类中屏蔽使用spring.shardingsphere配置项的类:

@SpringBootApplication(exclude = {SpringBootConfiguration.class})
public class ShardingJdbcSimpleDemoBootstrap {....}

5.3 Spring Boot properties配置

此方式同快速入门程序。

# 定义数据源
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://localhost:3306/order_db?useUnicode=true
spring.shardingsphere.datasource.m1.username = root
spring.shardingsphere.datasource.m1.password = root
# 指定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.actual‐data‐nodes = m1.t_order_$‐>{1..2}
# 指定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}

5.4 Spring命名空间配置

此方式使用xml方式配置,不推荐使用。

<?xml version="1.0" encoding="UTF‐8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:sharding="http://shardingsphere.apache.org/schema/shardingsphere/sharding"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring‐beans.xsd
http://shardingsphere.apache.org/schema/shardingsphere/sharding
http://shardingsphere.apache.org/schema/shardingsphere/sharding/sharding.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring‐context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring‐tx.xsd">
<context:annotation‐config />

<!‐‐定义多个数据源‐‐>
<bean id="m1" class="com.alibaba.druid.pool.DruidDataSource" destroy‐method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/order_db_1?useUnicode=true" />
<property name="username" value="root" />
<property name="password" value="root" />
</bean>

<!‐‐定义分库策略‐‐>
<sharding:inline‐strategy id="tableShardingStrategy" sharding‐column="order_id" algorithm‐
expression="t_order_$‐>{order_id % 2 + 1}" />


<!‐‐定义主键生成策略‐‐>
<sharding:key‐generator id="orderKeyGenerator" type="SNOWFLAKE" column="order_id" />

<!‐‐定义sharding‐Jdbc数据源‐‐>
<sharding:data‐source id="shardingDataSource">
<sharding:sharding‐rule data‐source‐names="m1">
<sharding:table‐rules>
<sharding:table‐rule logic‐table="t_order" table‐strategy‐
ref="tableShardingStrategy" key‐generator‐ref="orderKeyGenerator" />
</sharding:table‐rules>
</sharding:sharding‐rule>
</sharding:data‐source>
</beans>

6 sharding-jdbc4种分片策略


如果我一部分表做了分库分表,另一部分未做分库分表的表怎么处理?怎么才能正常访问?


这是一个比较典型的问题,我们知道分库分表是针对某些数据量持续大幅增长的表,比如用户表、订单表等,而不是一刀切将全部表都做分片。那么不分片的表和分片的表如何划分,一般有两种解决方案。

  • 严格划分功能库,分片的库与不分片的库剥离开,业务代码中按需切换数据源访问
  • 设置默认数据源,以​​Sharding-JDBC​​ 为例,不给未分片表设置分片规则,它们就不会执行,因为找不到路由规则,这时我们设置一个默认数据源,在找不到规则时一律访问默认库。
# 配置数据源 ds-0
spring.shardingsphere.datasource.ds-0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds-0.driverClassName=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds-0.url=jdbc:mysql://47.94.6.5:3306/ds-0?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
spring.shardingsphere.datasource.ds-0.username=root
spring.shardingsphere.datasource.ds-0.password=root

# 默认数据源,未分片的表默认执行库
spring.shardingsphere.sharding.default-data-source-name=ds-0

这篇我们针对具体的SQL使用场景,实践一下4种分片策略的用法,开始前先做点准备工作。

  • 标准分片策略
  • 复合分片策略
  • 行表达式分片策略
  • Hint分片策略

6.1 准备工作

先创建两个数据库 ​​ds-0​​​、​​ds-1​​​,两个库中分别建表 ​​t_order_0​​​、​​t_order_1​​​、​​t_order_2​​​ 、​​t_order_item_0​​​、​​t_order_item_1​​​、​​t_order_item_2​​​ 6张表,下边实操看看如何在不同场景下应用 ​​sharding-jdbc​​ 的 4种分片策略。

​t_order_n​​ 表结构如下:

CREATE TABLE `t_order_0` (
`order_id` bigint(200) NOT NULL,
`order_no` varchar(100) DEFAULT NULL,
`user_id` bigint(200) NOT NULL,
`create_name` varchar(50) DEFAULT NULL,
`price` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

​t_order_item_n​​ 表结构如下:

CREATE TABLE `t_order_item_0` (
`item_id` bigint(100) NOT NULL,
`order_id` bigint(200) NOT NULL,
`order_no` varchar(200) NOT NULL,
`item_name` varchar(50) DEFAULT NULL,
`price` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

分片策略分为​​分表策略​​​和​​分库策略​​​,它们实现分片算法的方式基本相同,不同是一个对库​​ds-0​​​、​​ds-1​​​,一个对表 ​​t_order_0​​​ ··· ​​t_order_n​​ 等做处理。

6.2 标准分片策略

使用场景:SQL 语句中有​​>​​​,​​>=​​​, ​​<=​​​,​​<​​​,​​=​​​,​​IN​​​ 和 ​​BETWEEN AND​​ 操作符,都可以应用此分片策略。

标准分片策略(​​StandardShardingStrategy​​​),它只支持对单个分片健(字段)为依据的分库分表,并提供了两种分片算法 ​​PreciseShardingAlgorithm​​​(精准分片)和 ​​RangeShardingAlgorithm​​(范围分片)。

在使用标准分片策略时,精准分片算法是必须实现的算法,用于 SQL 含有 ​​=​​​ 和 ​​IN​​​ 的分片处理;范围分片算法是非必选的,用于处理含有 ​​BETWEEN AND​​ 的分片处理。


一旦我们没配置范围分片算法,而 SQL 中又用到 ​​BETWEEN AND​​​ 或者 ​​like​​等,那么 SQL 将按全库、表路由的方式逐一执行,查询性能会很差需要特别注意。


接下来自定义实现 ​​精准分片算法​​​ 和 ​​范围分片算法​​。

6.3 精准分片算法

​精准分库算法​

实现自定义精准分库、分表算法的方式大致相同,都要实现 ​​PreciseShardingAlgorithm​​​ 接口,并重写 ​​doSharding()​​ 方法,只是配置稍有不同,而且它只是个空方法,得我们自行处理分库、分表逻辑。其他分片策略亦如此

SELECT * FROM t_order where  order_id = 1 or order_id in (1,2,3;

下边我们实现精准分库策略,通过对分片健 ​​order_id​​ 取模的方式(怎么实现看自己喜欢)计算出 SQL 该路由到哪个库,计算出的分片库信息会存放在分片上下文中,方便后续分表中使用。

/**
* @author
* @description 自定义标准分库策略
*/
public class MyDBPreciseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {

@Override
public String doSharding(Collection<String> databaseNames, PreciseShardingValue<Long> shardingValue) {

/**
* databaseNames 所有分片库的集合
* shardingValue 为分片属性,其中 logicTableName 为逻辑表,columnName 分片健(字段),value 为从 SQL 中解析出的分片健的值
*/
for (String databaseName : databaseNames) {
String value = shardingValue.getValue() % databaseNames.size() + "";
if (databaseName.endsWith(value)) {
return databaseName;
}
}
throw new IllegalArgumentException();
}
}

其中 ​​Collection<String>​​​ 参数在几种分片策略中使用一致,在分库时值为所有分片库的集合 ​​databaseNames​​​,分表时为对应分片库中所有分片表的集合 ​​tablesNames​​​;​​PreciseShardingValue​​​ 为分片属性,其中 ​​logicTableName​​​ 为逻辑表,​​columnName​​​ 分片健(字段),​​value​​ 为从 SQL 中解析出的分片健的值。

Myqsql使用Sharding-JDBC配置详解_java_04

而 ​​application.properties​​​ 配置文件中只需修改分库策略名 ​​database-strategy​​​ 为标准模式 ​​standard​​​,分片算法 ​​standard.precise-algorithm-class-name​​ 为自定义的精准分库算法类路径。

### 分库策略
# 分库分片健
spring.shardingsphere.sharding.tables.t_order.database-strategy.standard.sharding-column=order_id
# 分库分片算法
spring.shardingsphere.sharding.tables.t_order.database-strategy.standard.precise-algorithm-class-name=com.oldlu.sharding.algorithm.dbAlgorithm.MyDBPreciseShardingAlgorithm

​精准分表算法​

精准分表算法同样实现 ​​PreciseShardingAlgorithm​​​ 接口,并重写 ​​doSharding()​​ 方法。

/**
* @description 自定义标准分表策略
*/
public class MyTablePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {

@Override
public String doSharding(Collection<String> tableNames, PreciseShardingValue<Long> shardingValue) {

/**
* tableNames 对应分片库中所有分片表的集合
* shardingValue 为分片属性,其中 logicTableName 为逻辑表,columnName 分片健(字段),value 为从 SQL 中解析出的分片健的值
*/
for (String tableName : tableNames) {
/**
* 取模算法,分片健 % 表数量
*/
String value = shardingValue.getValue() % tableNames.size() + "";
if (tableName.endsWith(value)) {
return tableName;
}
}
throw new IllegalArgumentException();
}
}

分表时 ​​Collection<String>​​​ 参数为上边计算出的分片库,对应的所有分片表的集合 ​​tablesNames​​​;​​PreciseShardingValue​​​ 为分片属性,其中 ​​logicTableName​​​ 为逻辑表,​​columnName​​​ 分片健(字段),​​value​​ 为从 SQL 中解析出的分片健的值。

Myqsql使用Sharding-JDBC配置详解_spring boot_05

​application.properties​​​ 配置文件也只需修改分表策略名 ​​database-strategy​​​ 为标准模式 ​​standard​​​,分片算法 ​​standard.precise-algorithm-class-name​​ 为自定义的精准分表算法类路径。

# 分表策略
# 分表分片健
spring.shardingsphere.sharding.tables.t_order.table-strategy.standard.sharding-column=order_id
# 分表算法
spring.shardingsphere.sharding.tables.t_order.table-strategy.standard.precise-algorithm-class-name=com.oldlu.sharding.algorithm.tableAlgorithm.MyTablePreciseShardingAlgorithm


看到这不难发现,自定义分库和分表算法的实现基本是一样的,所以后边我们只演示分库即可


6.4 范围分片算法

使用场景:当我们 SQL中的分片健字段用到 ​​BETWEEN AND​​操作符会使用到此算法,会根据 SQL中给出的分片健值范围值处理分库、分表逻辑。

SELECT * FROM t_order where  order_id BETWEEN 1 AND 100;

自定义范围分片算法需实现 ​​RangeShardingAlgorithm​​​ 接口,重写 ​​doSharding()​​ 方法,下边我通过遍历分片健值区间,计算每一个分库、分表逻辑。

/**
* @description 范围分库算法
*/
public class MyDBRangeShardingAlgorithm implements RangeShardingAlgorithm<Integer> {

@Override
public Collection<String> doSharding(Collection<String> databaseNames, RangeShardingValue<Integer> rangeShardingValue) {

Set<String> result = new LinkedHashSet<>();
// between and 的起始值
int lower = rangeShardingValue.getValueRange().lowerEndpoint();
int upper = rangeShardingValue.getValueRange().upperEndpoint();
// 循环范围计算分库逻辑
for (int i = lower; i <= upper; i++) {
for (String databaseName : databaseNames) {
if (databaseName.endsWith(i % databaseNames.size() + "")) {
result.add(databaseName);
}
}
}
return result;
}
}

和上边的一样 ​​Collection<String>​​​ 在分库、分表时分别代表分片库名和表名集合,​​RangeShardingValue​​​ 这里取值方式稍有不同, ​​lowerEndpoint​​​ 表示起始值, ​​upperEndpoint​​ 表示截止值。

Myqsql使用Sharding-JDBC配置详解_java_06

在配置上由于范围分片算法和精准分片算法,同在标准分片策略下使用,所以只需添加上 ​​range-algorithm-class-name​​ 自定义范围分片算法类路径即可。

# 精准分片算法
spring.shardingsphere.sharding.tables.t_order.database-strategy.standard.precise-algorithm-class-name=com.oldlu.sharding.algorithm.dbAlgorithm.MyDBPreciseShardingAlgorithm
# 范围分片算法
spring.shardingsphere.sharding.tables.t_order.database-strategy.standard.range-algorithm-class-name=com.oldlu.sharding.algorithm.dbAlgorithm.MyDBRangeShardingAlgorithm

6.5 复合分片策略

使用场景:SQL 语句中有​​>​​​,​​>=​​​, ​​<=​​​,​​<​​​,​​=​​​,​​IN​​​ 和 ​​BETWEEN AND​​ 等操作符,不同的是复合分片策略支持对多个分片健操作。

下面我们实现同时以 ​​order_id​​​、​​user_id​​ 两个字段作为分片健,自定义复合分片策略。

SELECT * FROM t_order where  user_id =0  and order_id = 1;

我们先修改一下原配置,​​complex.sharding-column​​​ 切换成 ​​complex.sharding-columns​​​ 复数,分片健上再加一个 ​​user_id​​​ ,分片策略名变更为 ​​complex​​​ ,​​complex.algorithm-class-name​​ 替换成我们自定义的复合分片算法。

### 分库策略
# order_id,user_id 同时作为分库分片健
spring.shardingsphere.sharding.tables.t_order.database-strategy.complex.sharding-column=order_id,user_id
# 复合分片算法
spring.shardingsphere.sharding.tables.t_order.database-strategy.complex.algorithm-class-name=com.oldlu.sharding.algorithm.dbAlgorithm.MyDBComplexKeysShardingAlgorithm

自定义复合分片策略要实现 ​​ComplexKeysShardingAlgorithm​​​ 接口,重新 ​​doSharding()​​方法。

/**
* @description 自定义复合分库策略
*/
public class MyDBComplexKeysShardingAlgorithm implements ComplexKeysShardingAlgorithm<Integer> {


@Override
public Collection<String> doSharding(Collection<String> databaseNames, ComplexKeysShardingValue<Integer> complexKeysShardingValue) {

// 得到每个分片健对应的值
Collection<Integer> orderIdValues = this.getShardingValue(complexKeysShardingValue, "order_id");
Collection<Integer> userIdValues = this.getShardingValue(complexKeysShardingValue, "user_id");

List<String> shardingSuffix = new ArrayList<>();
// 对两个分片健同时取模的方式分库
for (Integer userId : userIdValues) {
for (Integer orderId : orderIdValues) {
String suffix = userId % 2 + "_" + orderId % 2;
for (String databaseName : databaseNames) {
if (databaseName.endsWith(suffix)) {
shardingSuffix.add(databaseName);
}
}
}
}
return shardingSuffix;
}

private Collection<Integer> getShardingValue(ComplexKeysShardingValue<Integer> shardingValues, final String key) {
Collection<Integer> valueSet = new ArrayList<>();
Map<String, Collection<Integer>> columnNameAndShardingValuesMap = shardingValues.getColumnNameAndShardingValuesMap();
if (columnNameAndShardingValuesMap.containsKey(key)) {
valueSet.addAll(columnNameAndShardingValuesMap.get(key));
}
return valueSet;
}
}

​Collection<String>​​​ 用法还是老样子,由于支持多分片健 ​​ComplexKeysShardingValue​​​ 分片属性内用一个分片健为 ​​key​​​,分片健值为 ​​value​​​ 的 ​​map​​来存储分片键属性。

Myqsql使用Sharding-JDBC配置详解_java_07

6.6 行表达式分片策略

行表达式分片策略(​​InlineShardingStrategy​​​),在配置中使用 ​​Groovy​​​ 表达式,提供对 SQL语句中的 ​​=​​​ 和 ​​IN​​ 的分片操作支持,它只支持单分片健。

行表达式分片策略适用于做简单的分片算法,无需自定义分片算法,省去了繁琐的代码开发,是几种分片策略中最为简单的。

它的配置相当简洁,这种分片策略利用​​inline.algorithm-expression​​书写表达式。

比如:​​ds-$->{order_id % 2}​​​ 表示对 ​​order_id​​​ 做取模计算,​​$​​​ 是个通配符用来承接取模结果,最终计算出分库​​ds-0​​​ ··· ​​ds-n​​,整体来说比较简单。

# 行表达式分片键
sharding.jdbc.config.sharding.tables.t_order.database-strategy.inline.sharding-column=order_id
# 表达式算法
sharding.jdbc.config.sharding.tables.t_order.database-strategy.inline.algorithm-expression=ds-$->{order_id % 2}

6.7 Hint分片策略

Hint分片策略(​​HintShardingStrategy​​​)相比于上面几种分片策略稍有不同,这种分片策略无需配置分片健,分片健值也不再从 SQL中解析,而是由外部指定分片信息,让 SQL在指定的分库、分表中执行。​​ShardingSphere​​​ 通过 ​​Hint​​​ API实现指定操作,实际上就是把分片规则​​tablerule​​​ 、​​databaserule​​由集中配置变成了个性化配置。

举个例子,如果我们希望订单表​​t_order​​​用 ​​user_id​​​ 做分片健进行分库分表,但是 ​​t_order​​​ 表中却没有 ​​user_id​​ 这个字段,这时可以通过 Hint API 在外部手动指定分片健或分片库。

下边我们这边给一条无分片条件的SQL,看如何指定分片健让它路由到指定库表。

SELECT * FROM t_order;

使用 Hint分片策略同样需要自定义,实现 ​​HintShardingAlgorithm​​​ 接口并重写 ​​doSharding()​​方法。

/**
* @description hit分表算法
*/
public class MyTableHintShardingAlgorithm implements HintShardingAlgorithm<String> {

@Override
public Collection<String> doSharding(Collection<String> tableNames, HintShardingValue<String> hintShardingValue) {

Collection<String> result = new ArrayList<>();
for (String tableName : tableNames) {
for (String shardingValue : hintShardingValue.getValues()) {
if (tableName.endsWith(String.valueOf(Long.valueOf(shardingValue) % tableNames.size()))) {
result.add(tableName);
}
}
}
return result;
}
}

自定义完算法只实现了一部分,还需要在调用 SQL 前通过 ​​HintManager​​​ 指定分库、分表信息。由于每次添加的规则都放在 ​​ThreadLocal​​​ 内,所以要先执行 ​​clear()​​​ 清除掉上一次的规则,否则会报错;​​addDatabaseShardingValue​​​ 设置分库分片健键值,​​addTableShardingValue​​​设置分表分片健键值。​​setMasterRouteOnly​​ 读写分离强制读主库,避免造成主从复制导致的延迟。

// 清除掉上一次的规则,否则会报错
HintManager.clear();
// HintManager API 工具类实例
HintManager hintManager = HintManager.getInstance();
// 直接指定对应具体的数据库
hintManager.addDatabaseShardingValue("ds",0);
// 设置表的分片健
hintManager.addTableShardingValue("t_order" , 0);
hintManager.addTableShardingValue("t_order" , 1);
hintManager.addTableShardingValue("t_order" , 2);

// 在读写分离数据库中,Hint 可以强制读主库
hintManager.setMasterRouteOnly();

debug 调试看到,我们对 ​​t_order​​​ 表设置分表分片健键值,可以在自定义的算法 ​​HintShardingValue​​ 参数中成功拿到。

Myqsql使用Sharding-JDBC配置详解_数据库_08

​properties​​ 文件中配置无需再指定分片健,只需自定义的 Hint分片算法类路径即可。

# Hint分片算法
spring.shardingsphere.sharding.tables.t_order.table-strategy.hint.algorithm-class-name=com.oldlu.sharding.algorithm.tableAlgorithm.MyTableHintShardingAlgorithm

7 水平分表

上述案例为水平分表是在同一个数据库内,把同一个表的数据按一定规则拆到多个表中,我们已经对水平分库进行实现,这里不再重复介绍。

8 水平分库

前面已经介绍过,水平分库是把同一个表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器

上。接下来看一下如何使用Sharding-JDBC实现水平分库,咱们继续对快速入门中的例子进行完善。

(1)将原有order_db库拆分为order_db_1、order_db_2

Myqsql使用Sharding-JDBC配置详解_spring_09

Myqsql使用Sharding-JDBC配置详解_spring boot_10

(2)分片规则修改

由于数据库拆分了两个,这里需要配置两个数据源。

分库需要配置分库的策略,和分表策略的意义类似,通过分库策略实现数据操作针对分库的数据库进行操作。

# 定义多个数据源
spring.shardingsphere.datasource.names = m1,m2
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://localhost:3306/order_db_1?useUnicode=true
spring.shardingsphere.datasource.m1.username = root
spring.shardingsphere.datasource.m1.password = root
spring.shardingsphere.datasource.m2.type = com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver‐class‐name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m2.url = jdbc:mysql://localhost:3306/order_db_2?useUnicode=true
spring.shardingsphere.datasource.m2.username = root
spring.shardingsphere.datasource.m2.password = root
...
# 分库策略,以user_id为分片键,分片策略为user_id % 2 + 1,user_id为偶数操作m1数据源,否则操作m2。
spring.shardingsphere.sharding.tables.t_order.database‐strategy.inline.sharding‐column = user_id
spring.shardingsphere.sharding.tables.t_order.database‐strategy.inline.algorithm‐expression =
m$‐>{user_id % 2 + 1}

分库策略定义方式如下:

#分库策略,如何将一个逻辑表映射到多个数据源
spring.shardingsphere.sharding.tables.<逻辑表名称>.database‐strategy.<分片策略>.<分片策略属性名>= #
分片策略属性值
#分表策略,如何将一个逻辑表映射为多个实际表
spring.shardingsphere.sharding.tables.<逻辑表名称>.table‐strategy.<分片策略>.<分片策略属性名>= #分
片策略属性值

Sharding-JDBC支持以下几种分片策略:

不管理分库还是分表,策略基本一样。

​standard :标准分片策略​​,对应StandardShardingStrategy。提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和

RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。

RangeShardingAlgorithm是可选的,用于处理BETWEEN AND分片,如果不配置

RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。

​complex :符合分片策略​​,对应ComplexShardingStrategy。复合分片策略。提供对SQL语句中的=, IN

和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。

​inline :行表达式分片策略​​,对应InlineShardingStrategy。使用Groovy的表达式,提供对SQL语句中的=和

IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如: t_user_$ ->{u_id % 8} 表示t_user表根据u_id模8,而分成8张表,表名称为 t_user_0 到t_user_7 。

​hint :Hint分片策略​​,对应HintShardingStrategy。通过Hint而非SQL解析的方式分片的策略。对于分片字段非SQL决定,而由其他外置条件决定的场景,可使用SQL Hint灵活的注入分片字段。例:内部系统,按照员工登录主键分库,而数据库中并无此字段。SQL Hint支持通过Java API和SQL注释(待实现)两种方式使用。none :不分片策略,对应NoneShardingStrategy。不分片的策略。

(3) 插入测试

修改testInsertOrder方法,插入数据中包含不同的user_id

@Test
public void testInsertOrder(){
for (int i = 0 ; i<10; i++){
orderDao.insertOrder(new BigDecimal((i+1)*5),1L,"WAIT_PAY");
}
for (int i = 0 ; i<10; i++){
orderDao.insertOrder(new BigDecimal((i+1)*10),2L,"WAIT_PAY");
}
}

执行testInsertOrder:

Myqsql使用Sharding-JDBC配置详解_spring boot_11

通过日志可以看出,根据user_id的奇偶不同,数据分别落在了不同数据源,达到目标。

(4)查询测试

调用快速入门的查询接口进行测试:

​List<Map> selectOrderbyIds(@Param("orderIds")List<Long> orderIds); ​

通过日志发现,sharding-jdbc将sql路由到m1和m2:

Myqsql使用Sharding-JDBC配置详解_spring boot_12

问题分析:

由于查询语句中并没有使用分片键user_id,所以sharding-jdbc将广播路由到每个数据结点。

下边我们在sql中添加分片键进行查询。

在OrderDao中定义接口:

@Select({"<script>", 
" select",
" * ",
" from t_order t ",
"where t.order_id in",
"<foreach collection='orderIds' item='id' open='(' separator=',' close=')'>",
"#{id}",
"</foreach>",
" and t.user_id = #{userId} ",
"</script>"
})
List<Map> selectOrderbyUserAndIds(@Param("userId") Integer userId,@Param("orderIds")List<Long>
orderIds);

编写测试方法:

@Test 
public void testSelectOrderbyUserAndIds(){
List<Long> orderIds = new ArrayList<>();
orderIds.add(373422416644276224L);
orderIds.add(373422415830581248L);
//查询条件中包括分库的键user_id
int user_id = 1;
List<Map> orders = orderDao.selectOrderbyUserAndIds(user_id,orderIds);
JSONArray jsonOrders = new JSONArray(orders);
System.out.println(jsonOrders);
}

执行testSelectOrderbyUserAndIds:

Myqsql使用Sharding-JDBC配置详解_sql_13

查询条件user_id为1,根据分片策略m$->{user_id % 2 + 1}计算得出m2,此sharding-jdbc将sql路由到m2,见上

图日志。

9 垂直分库

前面已经介绍过,垂直分库是指按照业务将表进行分类,分布到不同的数据库上面,每个库可以放在不同的服务器

上,它的核心理念是专库专用。接下来看一下如何使用Sharding-JDBC实现垂直分库。

(1)创建数据库

创建数据库user_db

​CREATE DATABASE ​​user_db​​ CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';​

在user_db中创建t_user表

DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`user_id` bigint(20) NOT NULL COMMENT '用户id',
`fullname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户姓名',
`user_type` char(1) DEFAULT NULL COMMENT '用户类型',
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

(2)在Sharding-JDBC规则中修改

# 新增m0数据源,对应user_db
spring.shardingsphere.datasource.names = m0,m1,m2
...
spring.shardingsphere.datasource.m0.type = com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m0.driver‐class‐name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m0.url = jdbc:mysql://localhost:3306/user_db?useUnicode=true
spring.shardingsphere.datasource.m0.username = root
spring.shardingsphere.datasource.m0.password = root
....
# t_user分表策略,固定分配至m0的t_user真实表
spring.shardingsphere.sharding.tables.t_user.actual‐data‐nodes = m$‐>{0}.t_user
spring.shardingsphere.sharding.tables.t_user.table‐strategy.inline.sharding‐column = user_id
spring.shardingsphere.sharding.tables.t_user.table‐strategy.inline.algorithm‐expression = t_user

(3) 数据操作

新增UserDao:

@Mapper
@Component
public interface UserDao {
/**
* 新增用户
* @param userId 用户id
* @param fullname 用户姓名
* @return
*/
@Insert("insert into t_user(user_id, fullname) value(#{userId},#{fullname})")
int insertUser(@Param("userId")Long userId,@Param("fullname")String fullname);
/**
* 根据id列表查询多个用户
* @param userIds 用户id列表
* @return
*/
@Select({"<script>",
" select",
" * ",
" from t_user t ",
" where t.user_id in",
"<foreach collection='userIds' item='id' open='(' separator=',' close=')'>",
"#{id}",
"</foreach>",
"</script>"
})
List<Map> selectUserbyIds(@Param("userIds")List<Long> userIds);
}

(4)测试

新增单元测试方法:

@Test
public void testInsertUser(){
for (int i = 0 ; i<10; i++){
Long id = i + 1L;
userDao.insertUser(id,"姓名"+ id );
}
}
@Test
public void testSelectUserbyIds(){
List<Long> userIds = new ArrayList<>();
userIds.add(1L);
userIds.add(2L);
List<Map> users = userDao.selectUserbyIds(userIds);
System.out.println(users);
}

执行 testInsertUser:

Myqsql使用Sharding-JDBC配置详解_spring_14

通过日志可以看出t_user表的数据被落在了m0数据源,达到目标。

执行testSelectUserbyIds:

Myqsql使用Sharding-JDBC配置详解_数据库_15通过日志可以看出t_user表的查询操作被落在了m0数据源,达到目标。

10 公共表

公共表属于系统中数据量较小,变动少,而且属于高频联合查询的依赖表。参数表、数据字典表等属于此类型。可

以将这类表在每个数据库都保存一份,所有更新操作都同时发送到所有分库执行。接下来看一下如何使用

Sharding-JDBC实现公共表。

(1)创建数据库

分别在user_db、order_db_1、order_db_2中创建t_dict表:

CREATE TABLE `t_dict`  (
`dict_id` bigint(20) NOT NULL COMMENT '字典id',
`type` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典类型',
`code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典编码',
`value` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典值',
PRIMARY KEY (`dict_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

(2)在Sharding-JDBC规则中修改

# 指定t_dict为公共表
spring.shardingsphere.sharding.broadcast‐tables=t_dict

(3)数据操作

新增DictDao:

@Mapper
@Component
public interface DictDao {
/**
* 新增字典
* @param type 字典类型
* @param code 字典编码
* @param value 字典值
* @return
*/
@Insert("insert into t_dict(dict_id,type,code,value) value(#{dictId},#{type},#{code},#
{value})")
int insertDict(@Param("dictId") Long dictId,@Param("type") String type, @Param("code")String
code, @Param("value")String value);
/**
* 删除字典
* @param dictId 字典id
* @return
*/
@Delete("delete from t_dict where dict_id = #{dictId}")
int deleteDict(@Param("dictId") Long dictId);
}

(4)字典操作测试

新增单元测试方法:

@Test
public void testInsertDict(){
dictDao.insertDict(1L,"user_type","0","管理员");
dictDao.insertDict(2L,"user_type","1","操作员");
}
@Test
public void testDeleteDict(){
dictDao.deleteDict(1L);
dictDao.deleteDict(2L);
}

执行testInsertDict:

Myqsql使用Sharding-JDBC配置详解_java_16

通过日志可以看出,对t_dict的表的操作被广播至所有数据源。

测试删除字典,观察是否把所有数据源中该 公共表的记录删除。

(5)字典关联查询测试

字典表已在各各分库存在,各业务表即可和字典表关联查询。

定义用户关联查询dao:

在UserDao中定义:

/**
* 根据id列表查询多个用户,关联查询字典表
* @param userIds 用户id列表
* @return
*/
@Select({"<script>",
" select",
" * ",
" from t_user t ,t_dict b",
" where t.user_type = b.code and t.user_id in",
"<foreach collection='userIds' item='id' open='(' separator=',' close=')'>",
"#{id}",
"</foreach>",
"</script>"
})
List<Map> selectUserInfobyIds(@Param("userIds")List<Long> userIds);

定义测试方法:

@Test 
public void testSelectUserInfobyIds(){
List<Long> userIds = new ArrayList<>();
userIds.add(1L);
userIds.add(2L);
List<Map> users = userDao.selectUserInfobyIds(userIds);
JSONArray jsonUsers = new JSONArray(users);
System.out.println(jsonUsers);
}

执行测试方法,查看日志,成功关联查询字典表:

Myqsql使用Sharding-JDBC配置详解_java_17

11 配置中遇到的一些问题

11.1 数据库链接池找不到

springboot2.0之后,采用的默认数据库连接池就是Hikari

Myqsql使用Sharding-JDBC配置详解_sql_18

11.2 错误java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.

原因

HikariConfig校验配置中​​没有jdbcUrl配置​

处理方式

springboot 1.x 版本中,数据源配置是 xxxx.url=

在2.x中,更改为 jdbc-url



举报

相关推荐

0 条评论