Sharding-JDBC 分库分表读写分离

目录

主要概念介绍

垂直分表

垂直分库

水平分表

水平分库

分表规则有哪些?

垂直分表

水平分表

        方式一:range

        方式二:Hash取模

        取模规则:

分库分表中间件介绍

Mycat和Sharding-JDBC的区别

分库分表相关术语名词介绍

数据节点Node

真实表

逻辑表

绑定表

广播表

分片算法有哪些?

SpringBoot整合Sharding-JDBC

如何配置广播表?

如何配置水平分库、水平分表?

如何配置绑定表?

精准分片算法 PreciseShardingAlgorithm

范围分片算法 RangeShardingAlgorithm

Hint分片策略 HintShardingStrategy

分库分表带来的问题及解决办法

Sharding-jdbc 读写分离配置


主要概念介绍

垂直分表

基于列字段进行的

拆分原则一般是表中的字段较多,将不常用的或者数据较大,长度较长的拆分到“扩展表 如text类型字段

访问频次低、字段大的商品描述信息单独存放在一张表中,访问频次较高的商品基本信息单独放在一张表中

垂直分库

  • 垂直分库针对的是一个系统中的不同业务进行拆分, 数据库的连接资源比较宝贵且单机处理能力也有限
  • 没拆分之前全部都是落到单一的库上的,单库处理能力成为瓶颈,还有磁盘空间,内存,tps等限制
  • 拆分之后,避免不同库竞争同一个物理机的CPU、内存、网络IO、磁盘,所以在高并发场景下,垂直分库一定程度上能够突破IO、连接数及单机硬件资源的瓶颈
  • 垂直分库可以更好解决业务层面的耦合,业务清晰,且方便管理和维护
  • 单体项目升级改造为微服务项目,就是垂直分库

水平分表

  • 把一个表的数据分到一个数据库的多张表中,每个表只有这个表的部分数据
  • 核心是把一个大表,分割N个小表,每个表的结构是一样的,数据不一样,全部表的数据合起来就是全部数据
  • 针对数据量巨大的单张表(比如订单表),按照某种规则(RANGE,HASH取模等),切分到多张表里面去
  • 但是这些表还是在同一个库中,所以单数据库操作还是有IO瓶颈,主要是解决单表数据量过大的问题
  • 减少锁表时间,没分表前,如果是DDL(create/alter/add等)语句,当需要添加一列的时候mysql会锁表,期间所有的读写操作只能等待

水平分库

  • 把同个表的数据按照一定规则分到不同的数据库中,数据库在不同的服务器上
  • 水平分库是把不同表拆到不同数据库中,它是对数据行的拆分,不影响表结构
  • 每个库的结构都一样,但每个库的数据都不一样,没有交集,所有库的并集就是全量数据
  • 水平分库的粒度,比水平分表更大

分表规则有哪些?

  • 垂直分表

垂直分表是根据字段来的,有以下几种划分规则

  • 把不常用的字段单独放在一张表;
  • 把text,blob等大字段拆分出来放在附表中;
  • 业务经常组合查询的列放在一张表中
  • 水平分表

        方式一:range

        根据自增ID范围进行分表

1~1000000                     是表一

1000001~2000000         是表二

2000001~3000000         是表三

......

        自增id分表优点:

  • id是自增长,可以无限增长(面试会问自增id用完的问题,注意数字是没有上限的,有上限的是你设计的字段长度)
  • 扩容不用迁移数据,容易理解和维护

        自增id分表缺点:

  • 大部分读和写都访会问新的数据,有IO瓶颈,整体资源利用率低
  • 数据倾斜严重,热点数据过于集中,部分节点有瓶颈

自增id是范围的一种,还有哪些访问?

  • 时间

        t_order_2022_01

        t_order_2022_02

        t_order_2022_03

        ......

  • 地理

        t_order_北京

        t_order_广州

        t_order_上海

        t_order_华东、华南、华北等

        ......

        方式二:Hash取模

        为什么不直接取模?如果取模的字段不是整数型要先hash

        取模规则:

要求:用户ID是整数型的,要分2库,每个库中的表数量4张,一共8张表
库ID = userId % 库数量 2 
表ID = userId / 库数量 2 % 表数量4

        优点:

保证数据较均匀的分散落在不同的库、表中,可以有效的避免热点数据集中问题

        缺点:

扩容(如果不是2个库,改成4个库了呢)不是很方便,需要数据迁移


分库分表中间件介绍

  • Cobar        阿里的已经被淘汰
  • TDDL        淘宝的 TDDL (Taobao Distributed Data Layer),主要淘宝内部使用,开源功能较少
  • Mycat       
    • 前身是Cobar,Java语言编写的MySQL数据库网络协议的开源中间件
    • 遵守Mysql原生协议,跨语言,跨平台,跨数据库的通用中间件代理
    • 是基于 Proxy,它复写了 MySQL 协议,将 Mycat Server 伪装成一个 MySQL 数据库
  • Sharding-JDBC
    • Apache 开源项目 ShardingSphere 下的组成部分,基于jdbc驱动,不用额外的proxy,支持任意实现 JDBC 规范的数据库
    • 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖
    • 可理解为加强版的 JDBC 驱动,兼容 JDBC 和各类 ORM 框架

Mycat和Sharding-JDBC的区别

  • 两者设计理念相同,主流程都是SQL解析-->SQL路由-->SQL改写-->结果归并
  • sharding-jdbc
    • 基于jdbc驱动,不用额外的proxy,在本地应用层重写Jdbc原生的方法,实现数据库分片形式
    • 是基于 JDBC 接口的扩展,是以 jar 包的形式提供轻量级服务的,性能高
    • 代码有侵入性
  • Mycat
    • 是基于 Proxy,它复写了 MySQL 协议,将 Mycat Server 伪装成一个 MySQL 数据库
    • 客户端所有的jdbc请求都必须要先交给MyCat,再有MyCat转发到具体的真实服务器
    • 缺点是效率偏低,中间包装了一层
    • 代码无侵入性

分库分表相关术语名词介绍

数据节点Node

数据分片的最小单元,由数据源名称和数据表组成
比如:ds_0.t_order_0

真实表

在分片的数据库中真实存在的物理表
比如订单表 t_order_0、t_order_1、t_order_2

逻辑表

水平拆分的数据库(表)的相同逻辑和数据结构表的总称
比如订单表 t_order_0、t_order_1、t_order_2,逻辑表就是t_order

绑定表

指分片规则一致的主表和子表
比如t_order表和t_order_info表,均按照t_order_id分表,则此两张表互为绑定表关系
绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升

广播表

指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致
适用于数据量不大且需要与海量数据的表进行关联查询的场景
例如:字典表、配置表


分片算法有哪些?

数据库表分片(水平库、表)

包含分片键和分片策略

分片键 (PartitionKey)

用于分片的数据库字段,是将数据库(表)水平拆分的关键字段
比如t_order订单表,根据订单号 out_trade_no做哈希取模,则out_trade_no是分片键
除了对单分片字段的支持,ShardingSphere也支持根据多个字段进行分片

分片策略

  • 行表达式分片策略 InlineShardingStrategy(必备)
    • 只支持【单分片键】使用Groovy的表达式,提供对SQL语句中的 =和IN 的分片操作支持
    • 可以通过简单的配置使用,无需自定义分片算法,从而避免繁琐的Java代码开发
    • t_order_$->{user_id % 8}` 表示订单表根据user_id模8,而分成8张表,表名称为`t_order_0`到`t_order_7
  • 标准分片策略StandardShardingStrategy
    • 只支持【单分片键】,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法
    • PreciseShardingAlgorithm 精准分片 是必选的,用于处理=和IN的分片
    • RangeShardingAlgorithm 范围分配 是可选的,用于处理BETWEEN AND分片
    • 如果不配置RangeShardingAlgorithm,如果SQL中用了BETWEEN AND语法,则将按照全库路由处理,性能下降
  • 复合分片策略ComplexShardingStrategy
    • 支持【多分片键(t_order_0_0)】,多分片键之间的关系复杂,由开发者自己实现,提供最大的灵活度
    • 提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持
  • Hint分片策略HintShardingStrategy
    • 这种分片策略无需配置分片健,分片健值也不再从 SQL中解析,外部手动指定分片健或分片库,让 SQL在指定的分库、分表中执行
    • 用于处理使用Hint行分片的场景,通过Hint而非SQL解析的方式分片的策略
    • Hint策略会绕过SQL解析的,对于这些比较复杂的需要分片的查询,Hint分片策略性能可能会更好
    • sql可以到指定的库指定的表执行
  • 不分片策略 NoneShardingStrategy
    • 不分片的策略。

SpringBoot整合Sharding-JDBC

pom.xml中导入springding-jdbc的jar包

<dependency>
	<groupId>org.apache.shardingsphere</groupId>
	<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
	<version>4.1.1</version>
</dependency>

分库分表需求:2库2表

  • shop_order_0        // 第一个库
    • t_order_0
    • t_order_1
  • shop_order_1        // 第二个库
    • t_order_0
    • t_order_1

逻辑表名就叫做 t_order,真实表名叫做 t_order_0/1

代码中把实体类创建好,这里就不贴代码了(使用逻辑表t_order创建实体类)

springboot 的 application.properties 中配置 (水平分表)

### 打印执行的数据库以及语句
spring.shardingsphere.props.sql.show=true

### 指定数据预源 db0,有多个数据源使用英式逗号分隔 db0,db1,db2
spring.shardingsphere.datasource.names=ds0,ds1

### 配置第一个数据库连接信息
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://192.168.189.71:3306/shop_order_0?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456

### 配置第二个数据库连接信息
spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://192.168.189.71:3306/shop_order_1?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=123456

### 指定 order 表的数据分布情况,配置数据节点,行表达式标识符使用 ${...} 或 $->{...},
# 但前者与 Spring 本身的文件占位符冲突,所以在 Spring 环境中建议使用 $->{...}
#                                     t_order:这里写逻辑表名   在ds0数据库,分成 t_order_0、t_order_1 两张表
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ds0.t_order_$->{0..1}

# 指定t_order表的分片策略,分片策略包括【分片键和分片算法】
#                                     t_order:这里写逻辑表名         sharding-column:分片键 (就是使用哪个字段进行分片)
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column=user_id
#                                     t_order:这里写逻辑表名         algorithm-expression:分片算法
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression=t_order_$->{user_id % 2}

写个单元测试看下结果

@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
@Slf4j
public class DBTest {

	@Autowired
	private OrderMapper orderMapper;

	@Test
	public void testSaveOrder() {
		for (int i = 0; i < 10; i++) {
			OrderDO orderDO = new OrderDO();
			orderDO.setCreateTime(new Date());
			orderDO.setUserId((long) i);
			orderMapper.insert(orderDO);
		}
	}
}

可以看到结果就按照 user_id % 2 的方式插入2张表中(注意主键生成策略不要使用自增长)


如何配置广播表?

application.properties 中配置

# 配置广播表, t_config 是表名,有多个广播表使用英式逗号分隔
spring.shardingsphere.sharding.broadcast-tables=t_config

写个单元测试看下结果

@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
@Slf4j
public class DBTest {

	@Autowired
	private ConfigMapper configMapper;

	@Test
	public void testSaveConfig() {
		ConfigDO configDO = new ConfigDO();
		configDO.setConfigKey("xxxKey");
		configDO.setConfigValue("xxxVlue");
		configDO.setType("xxxType");

		configMapper.insert(configDO);
	}
}

可以看到多个库中的配置表插入了相同的数据


如何配置水平分库、水平分表?

分库规则:根据 user_id 进行分库                     (user_id % 库数量)
分表规则:根据 t_order_id 订单号进行分表        (order_id / 库数量 % 表数量)

准备好表

  • shop_order_0        // 第一个库
    • t_order_0
    • t_order_1
  • shop_order_1        // 第二个库
    • t_order_0
    • t_order_1

application.properties 中配置

### 指定数据预源,有多个数据源使用英式逗号分隔 db0,db1,db2
spring.shardingsphere.datasource.names=ds0,ds1

### 配置第一个数据库连接信息
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://192.168.189.71:3306/shop_order_0?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456

### 配置第二个数据库连接信息
spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://192.168.189.71:3306/shop_order_1?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=123456

### 指定 t_order 表的数据分布情况,配置数据节点,行表达式标识符使用 ${...} 或 $->{...},
# 但前者与 Spring 本身的文件占位符冲突,所以在 Spring 环境中建议使用 $->{...}
#                                     t_order:这里写逻辑表名   在ds0数据库,分成 t_order_0、t_order_1 两张表
#spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ds0.t_order_$->{0..1}
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ds$->{0..1}.t_order_$->{0..1}

### 配置默认分库规则,如果有很多表需要分库的话,就可以使用默认的
spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=ds$->{user_id % 2}

### 配置t_order表的分库规则
#                                    t_order 是表名                    sharding-column:指定分库使用t_order表的哪个字段,algorithm-expression:指定分片算法
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.algorithm-expression=ds$->{user_id % 2}

#### 配置t_order表的分表规则【分片键和分片算法】
#                                     t_order:这里写逻辑表名         sharding-column:分片键 (就是使用t_order表的哪个字段进行分表)
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column=id
#                                     t_order:这里写逻辑表名         algorithm-expression:分片算法
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression=t_order_$->{id % 2}

写个单元测试看下结果

@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
public class DBTest {

	@Autowired
	private OrderMapper orderMapper;

	@Test
	public void testSaveOrder2() {
		for (int i = 0; i < 10; i++) {
			OrderDO orderDO = new OrderDO();
			orderDO.setCreateTime(new Date());
			orderDO.setUserId(IdWorker.getId());
			orderMapper.insert(orderDO);
		}
	}
}

可以看到2个库中的表中都插入了数据


如何配置绑定表?

假如订单表 t_order 有订单详情表 t_order_info,外键关联字段是 order_id 字段。

如果要进行关联查询,未分表的情况下是这样的

select * from t_order o left join t_order_info i on o.id = i.order_id         // 真实查询请勿使用 * 号

在分库分表的情况下,没有配置绑定表,会出现笛卡尔积查询

准备表

  • shop_order_0        // 第一个库
    • t_order_0
    • t_order_1
    • t_order_info_0
    • t_order_info_1
  • shop_order_1        // 第二个库
    • t_order_0
    • t_order_1
    • t_order_info_0
    • t_order_info_1

application.properties 中配置

### 配置 t_order_info 表的 分表规则
spring.shardingsphere.sharding.tables.t_order_info.actual-data-nodes=ds$->{0..1}.t_order_info_$->{0..1}
spring.shardingsphere.sharding.tables.t_order_info.table-strategy.inline.sharding-column=order_id
spring.shardingsphere.sharding.tables.t_order_info.table-strategy.inline.algorithm-expression=t_order_info_$->{order_id % 2}

### 配置绑定表
spring.shardingsphere.sharding.binding-tables[0]=t_order,t_order_info

写个单元测试看下结果

@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
public class DBTest {

	@Autowired
	private OrderMapper orderMapper;

	@Test
	public void testBinding () {
		// sql 语句执行的是  @Select("select * from t_order o left join t_order_info i on o.id = i.order_id")
		orderMapper.selectOrderInfoList();
	}
}

未配置绑定表之前的查询效果

配置绑定表后的查询效果


精准分片算法 PreciseShardingAlgorithm

自定义类实现PreciseShardingAlgorithm重写doSharding方法

import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;

import java.util.Collection;

public class CustomDBPreciseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {

	/**
	 * @param dataSourceNames      数据源集合
	 *                             在分库时值为所有分片库的集合 databaseNames
	 *                             分表时为对应分片库中所有分片表的集合 tablesNames
	 * @param preciseShardingValue 分片属性,包括
	 *                             logicTableName 为逻辑表,
	 *                             columnName 分片健(字段),
	 *                             value 为从 SQL 中解析出的分片健的值
	 */
	@Override
	public String doSharding(Collection<String> dataSourceNames, PreciseShardingValue<Long> preciseShardingValue) {
		for (String datasourceName : dataSourceNames) {
			String value = preciseShardingValue.getValue() % dataSourceNames.size() + "";
			if (datasourceName.endsWith(value)) {
				return datasourceName;
			}
		}
		return null;
	}
}
public class CustomTablePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {

	/**
	 * @param dataSourceNames      数据源集合
	 *                             在分库时值为所有分片库的集合 databaseNames
	 *                             分表时为对应分片库中所有分片表的集合 tablesNames
	 * @param preciseShardingValue 分片属性,包括
	 *                             logicTableName 为逻辑表,
	 *                             columnName 分片健(字段),
	 *                             value 为从 SQL 中解析出的分片健的值
	 */
	@Override
	public String doSharding(Collection<String> dataSourceNames, PreciseShardingValue<Long> preciseShardingValue) {
		for (String datasourceName : dataSourceNames) {
			String value = preciseShardingValue.getValue() % dataSourceNames.size() + "";
			if (datasourceName.endsWith(value)) {
				return datasourceName;
			}
		}
		return null;
	}
}

application.properties 中配置

#精准分片
# 指定t_order表的数据分布情况,配置数据节点,在 Spring 环境中建议使用 $->{...}
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ds$->{0..1}.t_order_$->{0..1}

#指定精准分片算法-水平分库
spring.shardingsphere.sharding.tables.t_order.database-strategy.standard.sharding-column=user_id
spring.shardingsphere.sharding.tables.t_order.database-strategy.standard.precise-algorithm-class-name=com.demo.strategy.CustomDBPreciseShardingAlgorithm

#指定精准分片算法-水平分表
spring.shardingsphere.sharding.tables.t_order.table-strategy.standard.sharding-column=id
spring.shardingsphere.sharding.tables.t_order.table-strategy.standard.precise-algorithm-class-name=com.demo.strategy.CustomTablePreciseShardingAlgorithm

写个单元测试看下结果

@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
public class DBTest {

	@Autowired
	private OrderMapper orderMapper;

	@Test
	public void testSaveOrder2() {
		for (int i = 0; i < 10; i++) {
			OrderDO orderDO = new OrderDO();
			orderDO.setCreateTime(new Date());
			orderDO.setUserId(IdWorker.getId());
			orderMapper.insert(orderDO);
		}
	}
}

范围分片算法 RangeShardingAlgorithm

自定义类实现RangeShardingAlgorithm重写doSharding方法

import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;

import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;

public class CustomRangeShardingAlgorithm implements RangeShardingAlgorithm<Long> {
	/**
	 * @param dataSourceNames 数据源集合
	 *                        在分库时值为所有分片库的集合 databaseNames
	 *                        分表时为对应分片库中所有分片表的集合 tablesNames
	 * @param shardingValue   分片属性,包括
	 *                        logicTableName 为逻辑表,
	 *                        columnName 分片健(字段),
	 *                        value 为从 SQL 中解析出的分片健的值
	 */
	@Override
	public Collection<String> doSharding(Collection<String> dataSourceNames, RangeShardingValue<Long> shardingValue) {
		Set<String> result = new LinkedHashSet<>();

		//between 开始值
		Long lower = shardingValue.getValueRange().lowerEndpoint();

		//between 结束值
		Long upper = shardingValue.getValueRange().upperEndpoint();

		for (long i = lower; i <= upper; i++) {
			for (String datasource : dataSourceNames) {
				String value = i % dataSourceNames.size() + "";
				if (datasource.endsWith(value)) {
					result.add(datasource);
				}
			}

		}
		return result;
	}
}

application.properties 中配置

#范围分片(水平分表)
spring.shardingsphere.sharding.tables.t_order.table-strategy.standard.range-algorithm-class-name=com.demo.strategy.CustomRangeShardingAlgorithm

写个单元测试

@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
public class DBTest {

	@Autowired
	private OrderMapper orderMapper;

	@Test
	public void testBetween() {
		orderMapper.selectList(new QueryWrapper<OrderDO>().between("id", 1L, 10L));
	}
}

Hint分片策略 HintShardingStrategy

自定义类实现接口HintShardingAlgorithm重写doSharding方法

import org.apache.shardingsphere.api.sharding.hint.HintShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.hint.HintShardingValue;

import java.util.ArrayList;
import java.util.Collection;

public class CustomDBHintShardingAlgorithm implements HintShardingAlgorithm<Long> {

	/**
	 * @param dataSourceNames   数据源集合
	 *                          在分库时值为所有分片库的集合 databaseNames
	 *                          分表时为对应分片库中所有分片表的集合 tablesNames
	 * @param hintShardingValue 分片属性,包括
	 *                          logicTableName 为逻辑表,
	 *                          columnName 分片健(字段),hit策略此处为空 ""
	 *                          <p>
	 *                          value 【之前】都是 从 SQL 中解析出的分片健的值,用于取模判断
	 *                          HintShardingAlgorithm不再从SQL 解析中获取值,而是直接通过
	 *                          hintManager.addTableShardingValue("t_order", 1)参数进行指定
	 */
	@Override
	public Collection<String> doSharding(Collection<String> dataSourceNames, HintShardingValue<Long> hintShardingValue) {
		Collection<String> result = new ArrayList<>();
		for (String datasourceName : dataSourceNames) {
			for (Long shardingValue : hintShardingValue.getValues()) {
				String value = shardingValue % dataSourceNames.size() + "";
				if (datasourceName.endsWith(value)) {
					result.add(datasourceName);
				}
			}
		}
		return result;
	}
}
import org.apache.shardingsphere.api.sharding.hint.HintShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.hint.HintShardingValue;

import java.util.ArrayList;
import java.util.Collection;

public class CustomTableHintShardingAlgorithm implements HintShardingAlgorithm<Long> {

	/**
	 * @param dataSourceNames   数据源集合
	 *                          在分库时值为所有分片库的集合 databaseNames
	 *                          分表时为对应分片库中所有分片表的集合 tablesNames
	 * @param hintShardingValue 分片属性,包括
	 *                          logicTableName 为逻辑表,
	 *                          columnName 分片健(字段),hit策略此处为空 ""
	 *                          <p>
	 *                          value 【之前】都是 从 SQL 中解析出的分片健的值,用于取模判断
	 *                          HintShardingAlgorithm不再从SQL 解析中获取值,而是直接通过
	 *                          hintManager.addTableShardingValue("t_order", 1)参数进行指定
	 */
	@Override
	public Collection<String> doSharding(Collection<String> dataSourceNames, HintShardingValue<Long> hintShardingValue) {
		Collection<String> result = new ArrayList<>();
		for (String datasourceName : dataSourceNames) {
			for (Long shardingValue : hintShardingValue.getValues()) {
				String value = shardingValue % dataSourceNames.size() + "";
				if (datasourceName.endsWith(value)) {
					result.add(datasourceName);
				}
			}
		}
		return result;
	}
}

application.properties 中配置

spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ds$->{0..1}.t_order_$->{0..1}
spring.shardingsphere.sharding.tables.t_order.database-strategy.hint.algorithm-class-name=com.demo.strategy.CustomDBHintShardingAlgorithm
spring.shardingsphere.sharding.tables.t_order.table-strategy.hint.algorithm-class-name=com.demo.strategy.CustomTableHintShardingAlgorithm

写个单元测试

@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
public class DBTest {

	@Autowired
	private OrderMapper orderMapper;
	
	@Test
	public void testHit() {
		//清除历史规则
		HintManager.clear();
		//获取对应的实例
		HintManager hintManager = HintManager.getInstance();
		//设置库的分片键值,value是用于库分片取模,参数一:指定逻辑表,参数二:指定的库
		hintManager.addDatabaseShardingValue("t_order", 0L);
		//设置表的分片键值,value是用于表分片取模,参数一:指定逻辑表,参数二:指定的表
		hintManager.addTableShardingValue("t_order", 1L);
		//如果在读写分离数据库中,Hint 可以强制读主库(主从复制存在一定延时,但在业务场景中,可能更需要保证数据的实时性)
		//hintManager.setMasterRouteOnly();
		//对应的value,只做查询,不做sql解析
		orderMapper.selectList(new QueryWrapper<OrderDO>().eq("id", 1L));
	}
}

可以看到 单元测试指定的哪个库哪个表,执行的sql就找到指定的库指定表进行查询


分库分表带来的问题及解决办法

问题一

分库后,数据分布再不同的节点上,跨节点多库进行查询时,会出现limit分页、order by排序等问题。而且当排序字段非分片字段时,更加复杂了,要在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序(也会带来更多的CPU/IO资源损耗)

解决办法

业务上要设计合理,利用好分片键PartitionKey,查询的数据分布同个数据节点上,避免跨节点多库进行查询。sharding-jdbc在结果合并层自动帮我们解决很多问题(流式归并和内存归并)

问题二

数据库id主键重复问题

解决办法

UUID
redis发号器
雪花算法

问题三

跨节点数据库Join关联和多维度查询

  • 数据库切分前,多表关联查询,可以通过sql join进行实现
  • 分库分表后,数据可能分布在不同的节点上,sql join带来的问题就比较麻烦,不同维度查看数据,利用的partitionKey是不一样的

解决办法

  • 冗余字段
    • 比如订单显示的时候需要用户的基本信息,但是订单和用户分布在不同的库上,就可以在订单表中冗余用户昵称头像等字段
  • 广播表
  • NOSQL汇总
    • 用户要查看自己的订单,库是根据用户id分的,那么查询在一个库上很方便
    • 商家要查看店铺的订单,商家没有用户id,数据分布在不同的库上,这时查询就很麻烦。解决方法:可以使用nosql大表汇总,实操:使用canal-server监听数据库数据变更(相当于从库从主库中复制数据),监听的数据写到消息队列中,这里的消息队列用于数据缓冲流量削峰,ElasticSearch监听消息队列,把数据从队列中写到ElasticSearch中,商户查询就从ElasticSearch中查询(平常我们使用淘宝京东购物的时候,搜索商品信息,就是从ElasticSearch中搜索出的数据)

问题四

分布式事务问题

解决办法

常见分布式事务解决方案

  • 2PC 和 3PC
    • 两阶段提交, 基于XA协议
  • TCC
    • Try、Confirm、Cancel
  • 事务消息
    • 最大努力通知型

分布式事务框架

问题五

数据库二次扩容问题

业务发展快,初次分库分表后,满足不了数据存储,导致需要多次扩容,数据迁移问题

解决办法

  • 时间规则分库分表的,没有二次扩容问题
  • 区域规则分库分表的,全量迁移数据
  • hash取模:
    • 成倍扩容,例如2、4、6、8。扩容后需要迁移部分数据。例如2个库的时候,1%2=1,2%2=0,3%2=1,4%2=0,可以看到1、3数据在1库,2、4数据在0库,扩容成4个库的时候,1%4=1,2%4=2,3%4=3,4%4=0,那么数据2就要迁移到第二库,数据3就要迁移到第三个库。1、4数据不用迁移,这样就迁移了部分数据,关于数据迁移解决方案,可以网上搜索其他的博客 数据迁移方案
    • 虚拟节点,从设计分库分表之初,多设计些库表,不要只使用2库2表,减少扩容的数据迁移

Sharding-jdbc 读写分离配置

准备2台配置好主从复制的数据库,主从复制配置教程 请查看

pom.xml中导入springding-jdbc的jar包

<dependency>
	<groupId>org.apache.shardingsphere</groupId>
	<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
	<version>4.1.1</version>
</dependency>

application.yml中配置

spring: 
  shardingsphere:
    props:
      # 展示修改以后的sql语句
      sql-show: true
    datasource:
      names: m1,s1
      m1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://主库ip:3306/test_db?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
        username: root
        password: 123456
      s1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://从库ip:3306/test_db?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
        username: root
        password: 123456
    sharding:
      master-slave-rules:
        ds0:
          master-data-source-name: m1
          slave-data-source-names: s1

写个 测试类,看下效果

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ShardingDemoApplication.class)
public class UserTest {

	@Autowired
	private UserMapper userMapper;

	/**
	 * 更新语句(insert、update、delete),走主库
	 */
	@Test
	public void testSave() {
		UserDB userDB = new UserDB();
		userDB.setUsername("张三");
		userMapper.insert(userDB);
	}

	/**
	 * 查询语句(select),走从库
	 */
	@Test
	public void testSelect() {
		// 在主从复制有延迟的情况下,业务又需要里面查询到数据,可以使用 setMasterRouteOnly() 方法,让查询语句走主库
		/*HintManager hintManager = HintManager.getInstance();
		hintManager.setMasterRouteOnly();*/
		userMapper.selectById(1L);
	}
}

集成sharding-jdbc过程中遇到的坑

1:如果使用了druid数据源,要使用druid,不要使用druid-spring-boot-starter,不然会报错Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required

<!-- druid -->
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid</artifactId>
</dependency>

<!-- shardingsphere -->
<dependency>
	<groupId>org.apache.shardingsphere</groupId>
	<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
</dependency>

2:使用 mybatis 和 sharding-jdbc 在子查询的时候,有sharding-jdbc在解析不到数据的时候,会报NullPointerException,或者  java.lang.IllegalStateException: Can not find owner from table

这个问题将在sharding-jdbc的5.x版本中解决,请参考:

Abount Exception [Can not find owner from table.] · Issue #4810 · apache/shardingsphere · GitHub

出现 Can not find owner from table报错。和shardingsphere一起使用的时候。 · Issue #2585 · baomidou/mybatis-plus · GitHub

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

又逢乱世

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值