单机
主从
双主
分库和分表
这边有个数据库单表1千万数据,未来1年还会增长多500万,性能比较慢,说下你的优化思路?
1、解决数据库本身瓶颈
2、解决系统本身IO、CPU瓶颈
磁盘读写IO瓶颈,热点数据太多,尽管使用了数据库本身缓存,但是依旧有大量IO,导致sql执行速度慢。
网络IO瓶颈,请求的数据太多,数据传输大,网络带宽不够,链路响应时间变长
CPU瓶颈,尤其在基础数据量大单机复杂SQL计算,SQL语句执行占用CPU使用率高,也有扫描行数大、锁冲突、锁等待等原因
1、垂直分表介绍
2、垂直拆分原则
1、垂直分库介绍
1、水平分表简介
1、水平分库简介
1、垂直角度(表结构不一样)
2、水平角度(表结构一样)
方案一:自增id,根据ID范围进行分表(左闭右开)
1、范围角度思考问题 (范围的话更多是水平分表)
2、基于Range范围分库分表业务场景
方案二:hash取模(Hash分库分表是最普遍的方案)
案例规则
A库ID = userId % 库数量 2
表ID = userId / 库数量 2 % 表数量4
userId | id % 2 (库-取余) | id /2 % 4 (表) |
---|---|---|
1 | 1 | 0 |
2 | 0 | 1 |
3 | 1 | 1 |
4 | 0 | 2 |
5 | 1 | 2 |
6 | 0 | 3 |
7 | 1 | 3 |
8 | 0 | 0 |
9 | 1 | 0 |
Cobar(已经被淘汰没使用了)
TDDL
Mycat
1、什么是ShardingSphere
2、三大构成
ShardingSphere-JDBC | ShardingSphere-Proxy | ShardingSphere-Sidecar | |
---|---|---|---|
数据库 | 任意 | Mysql/PostgreSQL | Mysql/PostgreSQL |
连接消耗数 | 高 | 低 | 高 |
异构语言 | 仅Java | 任意 | 任意 |
性能 | 损耗低 | 损耗略高 | 损耗低 |
无中心化 | 是 | 否 | 是 |
静态入口 | 无 | 有 | 无 |
数据库表分片(水平库、表)
分片键 (PartitionKey)
分片策略(先了解,后面有案例实战)
行表达式分片策略 InlineShardingStrategy(必备)
只支持【单分片键】使用Groovy的表达式,提供对SQL语句中的 =和IN 的分片操作支持
可以通过简单的配置使用,无需自定义分片算法,从而避免繁琐的Java代码开发
prouduct_order_$->{user_id % 8}` 表示订单表根据user_id模8,而分成8张表,表名称为`prouduct_order_0`到`prouduct_order_7
标准分片策略StandardShardingStrategy(需了解)
复合分片策略ComplexShardingStrategy(需了解)
Hint分片策略HintShardingStrategy(需了解)
这种分片策略无需配置分片健,分片健值也不再从 SQL中解析,外部手动指定分片健或分片库,让 SQL在指定的分库、分表中执行
用于处理使用Hint行分片的场景,通过Hint而非SQL解析的方式分片的策略
Hint策略会绕过SQL解析的,对于这些比较复杂的需要分片的查询,Hint分片策略性能可能会更好
不分片策略 NoneShardingStrategy(需了解)
1、创建maven项目,pom.xml引入依赖
<properties>
<java.version>1.8java.version>
<maven.compiler.source>1.8maven.compiler.source>
<maven.compiler.target>1.8maven.compiler.target>
<spring.boot.version>2.5.5spring.boot.version>
<mybatisplus.boot.starter.version>3.4.0mybatisplus.boot.starter.version>
<lombok.version>1.18.16lombok.version>
<sharding-jdbc.version>4.1.1sharding-jdbc.version>
<junit.version>4.12junit.version>
<druid.version>1.1.16druid.version>
<skipTests>trueskipTests>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>${spring.boot.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<version>${spring.boot.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>${mybatisplus.boot.starter.version}version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.27version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${lombok.version}version>
dependency>
<dependency>
<groupId>org.apache.shardingspheregroupId>
<artifactId>sharding-jdbc-spring-boot-starterartifactId>
<version>${sharding-jdbc.version}version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>${junit.version}version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<version>${spring.boot.version}version>
<configuration>
<fork>truefork>
<addResources>trueaddResources>
configuration>
plugin>
plugins>
build>
2、创建application.properties
spring.application.name=sharding-jdbc
server.port=8080
3、创建主启动类
@MapperScan("com.lixiang.mapper")
@EnableTransactionManagement
@SpringBootApplication
public class ShardingApplication {
public static void main(String[] args) {
SpringApplication.run(ShardingApplication.class);
}
}
CREATE TABLE `product_order_0` (
`id` bigint NOT NULL AUTO_INCREMENT,
`out_trade_no` varchar(64) DEFAULT NULL COMMENT '订单唯一标识',
`state` varchar(11) DEFAULT NULL COMMENT 'NEW 未支付订单,PAY已经支付订单,CANCEL超时取消订单',
`create_time` datetime DEFAULT NULL COMMENT '订单生成时间',
`pay_amount` decimal(16,2) DEFAULT NULL COMMENT '订单实际支付价格',
`nickname` varchar(64) DEFAULT NULL COMMENT '昵称',
`user_id` bigint DEFAULT NULL COMMENT '用户id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("product_order")
public class ProductOrderDO {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String outTradeNo;
private String state;
private Date createTime;
private Double payAmount;
private String nickname;
private Long userId;
}
//Mapper类
public interface ProductOrderMapper extends BaseMapper<ProductOrderDO> {
}
spring.application.name=sharding-jdbc
server.port=8080
spring.shardingsphere.props.sql.show=true
# 配置数据源
spring.shardingsphere.datasource.names=ds0,ds1
# 配置ds0库
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.159.101:3306/shop_order_0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456
# 配置ds1库
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.159.101:3306/shop_order_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=123456
# 指定product_order表的数据分布情况,配置数据节点,行表达式标识符使用${...}或$->{...}
# 但前者与 Spring 本身的文件占位符冲突,所以在 Spring 环境中建议使用 $->{...}
spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds0.product_order_$->{0..1}
# 指定product_order表的分片策略,分片策略包括【分片键和分片算法】
# 使用user_id作为分片健
spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.sharding-column=user_id
# 分片策略为user_id和2取余,进入到某一张product_order_?表
spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.algorithm-expression=product_order_$->{user_id % 2}
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ShardingApplication.class)
@Slf4j
public class DBTest {
@Resource
private ProductOrderMapper productOrderMapper;
@Test
public void testSaveProductOrder(){
for(int i=0;i<10;i++){
ProductOrderDO productOrder = new ProductOrderDO();
productOrder.setCreateTime(new Date());
productOrder.setNickname("李祥:i="+i);
productOrder.setOutTradeNo(UUID.randomUUID().toString().substring(0,32));
productOrder.setPayAmount(100.00);
productOrder.setState("PAY");
productOrder.setUserId(Long.valueOf(i+""));
productOrderMapper.insert(productOrder);
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2YiIijUe-1668057977987)(images/4.2(3).jpg)]
(1)数据库自增ID
DB1:单数
//从1开始、每次加2
DB2:偶数
//从2开始,每次加2
(2)UUID
(3)Redis发号器
(4)Snowflake雪花算法
雪花算法生成的数字,long类,所以就是8个byte,64bit
(1)分布式部署就需要分配不同的workId, 如果workId相同,可能会导致生成的id相同
spring.shardingsphere.sharding.tables.product_order.key-generator.props.worker.id=1
(2)分布式情况下,需要保证各个系统时间一致,如果服务器的时钟回拨,就会导致生成的 id 重复
@TableId(value = "id", type = IdType.ASSIGN_ID)
默认实现类为DefaultIdentifierGenerator雪花算法
#id生成策略
spring.shardingsphere.sharding.tables.product_order.key-generator.column=id
spring.shardingsphere.sharding.tables.product_order.key-generator.type=SNOWFLAKE
1、什么是广播表
2、配置实战
(1)增加ad_config表
CREATE TABLE `ad_config` (
`id` bigint unsigned NOT NULL COMMENT '主键id',
`config_key` varchar(1024) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '配置key',
`config_value` varchar(1024) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '配置value',
`type` varchar(128) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '类型',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
(2)POJO类、Mapper
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("ad_config")
public class AdConfigDO {
private Long id;
private String configKey;
private String configValue;
private String type;
}
public interface AdConfigMapper extends BaseMapper<AdConfigDO> {
}
(3)配置文件
# 配置广播表
spring.shardingsphere.sharding.broadcast-tables=ad_config
spring.shardingsphere.sharding.tables.ad_config.key-generator.column=id
spring.shardingsphere.sharding.tables.ad_config.key-generator.type=SNOWFLAKE
(4)测试代码
@Test
public void testSaveAdConfig(){
AdConfigDO adConfigDO = new AdConfigDO();
adConfigDO.setConfigKey("key");
adConfigDO.setConfigValue("value");
adConfigDO.setType("type");
adConfigMapper.insert(adConfigDO);
}
1、分库分表配置
spring.application.name=sharding-jdbc
server.port=8080
spring.shardingsphere.props.sql.show=true
# 配置数据源
spring.shardingsphere.datasource.names=ds0,ds1
# 配置ds0库
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.159.101:3306/shop_order_0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456
# 配置ds1库
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.159.101:3306/shop_order_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=123456
# id生成策略(雪花算法)
spring.shardingsphere.sharding.tables.product_order.key-generator.column=id
spring.shardingsphere.sharding.tables.product_order.key-generator.type=SNOWFLAKE
# 指定product_order表的数据分布情况,配置数据节点,行表达式标识符使用${...}或$->{...}
# 但前者与 Spring 本身的文件占位符冲突,所以在 Spring 环境中建议使用 $->{...}
spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds$->{0..1}.product_order_$->{0..1}
spring.shardingsphere.sharding.tables.product_order.database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.product_order.database-strategy.inline.algorithm-expression=ds$->{user_id % 2}
# 指定product_order表的分片策略,分片策略包括【分片键和分片算法】
# 使用id作为分片健
spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.sharding-column=id
# 分片策略为id和2取余,进入到某一张product_order_?表
spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.algorithm-expression=product_order_$->{id % 2}
# 配置广播表
spring.shardingsphere.sharding.broadcast-tables=ad_config
spring.shardingsphere.sharding.tables.ad_config.key-generator.column=id
spring.shardingsphere.sharding.tables.ad_config.key-generator.type=SNOWFLAKE
2、测试代码
@Test
public void testSaveProductOrder(){
Random random = new Random();
for(int i=0;i<10;i++){
ProductOrderDO productOrder = new ProductOrderDO();
productOrder.setCreateTime(new Date());
productOrder.setNickname("李祥:i="+i);
productOrder.setOutTradeNo(UUID.randomUUID().toString().substring(0,32));
productOrder.setPayAmount(100.00);
productOrder.setState("PAY");
productOrder.setUserId(Long.valueOf(random.nextInt(50)));
productOrderMapper.insert(productOrder);
}
}
1、什么是绑定表
2、创建product_order_item表,一个库创建两个
CREATE TABLE `product_order_item_0` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`product_order_id` bigint DEFAULT NULL COMMENT '订单号',
`product_id` bigint DEFAULT NULL COMMENT '产品id',
`product_name` varchar(128) DEFAULT NULL COMMENT '商品名称',
`buy_num` int DEFAULT NULL COMMENT '购买数量',
`user_id` bigint DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
3、编写实体ProductOrderItemDO,Mapper
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("product_order_item")
public class ProductOrderItemDO {
private Long id;
private Long productOrderId;
private Long productId;
private String productName;
private Integer buyNum;
private Long userId;
}
public interface ProductOrderItemMapper extends BaseMapper<ProductOrderItemDO> {
@Select("select o.id from product_order o left join product_order_item i on o.id = i.product_order_id")
List<Map<String,Object>> listProductOrderDetail();
}
4、编写配置文件
spring.application.name=sharding-jdbc
server.port=8080
spring.shardingsphere.props.sql.show=true
# 配置数据源
spring.shardingsphere.datasource.names=ds0,ds1
# 配置ds0库
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.159.101:3306/shop_order_0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456
# 配置ds1库
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.159.101:3306/shop_order_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=123456
# 配置默认分库规则
spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression = ds$->{user_id % 2}
# 配置product_order表 id生成策略(雪花算法)
spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds$->{0..1}.product_order_$->{0..1}
spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.sharding-column=id
spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.algorithm-expression=product_order_$->{id % 2}
spring.shardingsphere.sharding.tables.product_order.key-generator.column=id
spring.shardingsphere.sharding.tables.product_order.key-generator.type=SNOWFLAKE
# 配置广播表 id生成策略(雪花算法)
spring.shardingsphere.sharding.broadcast-tables=ad_config
spring.shardingsphere.sharding.tables.ad_config.key-generator.column=id
spring.shardingsphere.sharding.tables.ad_config.key-generator.type=SNOWFLAKE
# 配置product_order_item表 id生成策略(雪花算法)
spring.shardingsphere.sharding.tables.product_order_item.actual-data-nodes=ds$->{0..1}.product_order_item_$->{0..1}
spring.shardingsphere.sharding.tables.product_order_item.table-strategy.inline.sharding-column=product_order_id
spring.shardingsphere.sharding.tables.product_order_item.table-strategy.inline.algorithm-expression=product_order_item_$->{product_order_id % 2}
spring.shardingsphere.sharding.tables.product_order_item.key-generator.column=id
spring.shardingsphere.sharding.tables.product_order_item.key-generator.type=SNOWFLAKE
# 配置绑定表 让对应的主表为1的找副表为1的关联,注意不加这个,会产生笛卡尔积
spring.shardingsphere.sharding.binding-tables[0]=product_order,product_order_item
5、测试代码
@Test
public void testFindOrderItem(){
List<Map<String, Object>> list = productOrderItemMapper.listProductOrderDetail();
list.forEach(map -> System.out.println(map));
}
1、查询操作
@Test
public void testSelectHavePartitionKey(){
//id为分片键
ProductOrderDO orderDO = productOrderMapper.selectOne(new QueryWrapper<ProductOrderDO>().eq("id", 678627813053431809L));
System.out.println(orderDO);
}
@Test
public void testSelectNoHavePartitionKey(){
//id为分片键
ProductOrderDO orderDO = productOrderMapper.selectOne(new QueryWrapper<ProductOrderDO>().eq("out_trade_no", "59c66f86-55f7-4b80-b803-13a76048"));
System.out.println(orderDO);
}
2、删除操作
@Test
public void testDeleteHavePartitionKey(){
//id为分片键
productOrderMapper.delete(new QueryWrapper<ProductOrderDO>().eq("id", 678627813053431809L));
}
@Test
public void testDeleteNoHavePartitionKey(){
//id为分片键
productOrderMapper.delete(new QueryWrapper<ProductOrderDO>().eq("out_trade_no", "59c66f86-55f7-4b80-b803-13a76048"));
}
StandardShardingStrategy(标准分片策略)
代码案例
@Component
public class CustomTablePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
/**
* @param dataSourceNames 数据源集合
* 在分库时值为所有分片库的集合 dataSourceNames
* 分表时为对应分片库种所有分片表的集合 tableNames
* @param preciseShardingValue 分片属性
* logicTableName:逻辑表名
* columnName:分片健(字段)
* value:从SQL中解析出的分片健的值
* @return
*/
@Override
public String doSharding(Collection<String> dataSourceNames, PreciseShardingValue<Long> preciseShardingValue) {
for (String dataSourceName : dataSourceNames) {
//从preciseShardingValue中拿出分片健,和表的个数取模,确定落在哪个表中
String value = preciseShardingValue.getValue() % dataSourceNames.size() + "";
if (dataSourceName.endsWith(value)){
return dataSourceName;
}
}
throw new IllegalArgumentException();
}
}
配置文件
spring.application.name=sharding-jdbc
server.port=8080
spring.shardingsphere.props.sql.show=true
# 配置数据源
spring.shardingsphere.datasource.names=ds0,ds1
# 配置ds0库
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.159.101:3306/shop_order_0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456
spring.shardingsphere.sharding.tables.product_order.key-generator.column=id
spring.shardingsphere.sharding.tables.product_order.key-generator.type=SNOWFLAKE
spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds0.product_order_$->{0..1}
spring.shardingsphere.sharding.tables.product_order.table-strategy.standard.sharding-column=id
#配置分片策略类的全路径包名
spring.shardingsphere.sharding.tables.product_order.table-strategy.standard.precise-algorithm-class-name=com.lixiang.strategy.CustomTablePreciseShardingAlgorithm
测试代码
@Test
public void testSaveProductOrder(){
Random random = new Random();
for(int i=0;i<10;i++){
ProductOrderDO productOrder = new ProductOrderDO();
productOrder.setCreateTime(new Date());
productOrder.setNickname("李祥:i="+i);
productOrder.setOutTradeNo(UUID.randomUUID().toString().substring(0,32));
productOrder.setPayAmount(100.00);
productOrder.setState("PAY");
productOrder.setUserId(Long.valueOf(random.nextInt(50)));
productOrderMapper.insert(productOrder);
}
}
新增分库策略类
public class CustomDBPreciseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
/**
* @param dataSourceNames 数据源集合
* 在分库时值为所有分片库的集合 dataSourceNames
* 分表时为对应分片库种所有分片表的集合 tableNames
* @param preciseShardingValue 分片属性
* logicTableName:逻辑表名
* columnName:分片健(字段)
* value:从SQL中解析出的分片健的值
* @return
*/
@Override
public String doSharding(Collection<String> dataSourceNames, PreciseShardingValue<Long> preciseShardingValue) {
for (String dataSourceName : dataSourceNames) {
//从preciseShardingValue中拿出分片健,和表的个数取模,确定落在哪个表中
String value = preciseShardingValue.getValue() % dataSourceNames.size() + "";
if (dataSourceName.endsWith(value)){
return dataSourceName;
}
}
throw new IllegalArgumentException();
}
}
配置文件
spring.application.name=sharding-jdbc
server.port=8080
spring.shardingsphere.props.sql.show=true
# 配置数据源
spring.shardingsphere.datasource.names=ds0,ds1
# 配置ds0库
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.159.101:3306/shop_order_0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456
# 配置ds1库
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.159.101:3306/shop_order_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=123456
spring.shardingsphere.sharding.tables.product_order.key-generator.column=id
spring.shardingsphere.sharding.tables.product_order.key-generator.type=SNOWFLAKE
spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds$->{0..1}.product_order_$->{0..1}
#根据用户id分库
spring.shardingsphere.sharding.tables.product_order.database-strategy.standard.sharding-column=user_id
spring.shardingsphere.sharding.tables.product_order.database-strategy.standard.precise-algorithm-class-name=com.lixiang.strategy.CustomDBPreciseShardingAlgorithm
#根据订单id分表
spring.shardingsphere.sharding.tables.product_order.table-strategy.standard.sharding-column=id
spring.shardingsphere.sharding.tables.product_order.table-strategy.standard.precise-algorithm-class-name=com.lixiang.strategy.CustomTablePreciseShardingAlgorithm
测试代码
@Test
public void testSaveProductOrder(){
Random random = new Random();
for(int i=0;i<10;i++){
ProductOrderDO productOrder = new ProductOrderDO();
productOrder.setCreateTime(new Date());
productOrder.setNickname("李祥:i="+i);
productOrder.setOutTradeNo(UUID.randomUUID().toString().substring(0,32));
productOrder.setPayAmount(100.00);
productOrder.setState("PAY");
productOrder.setUserId(Long.valueOf(random.nextInt(50)));
productOrderMapper.insert(productOrder);
}
}
RangeShardingAlgorithm范围分片
代码示例
public class CustomRangeShardingAlgorithm implements RangeShardingAlgorithm<Long> {
/**
* @param dataSourceNames 数据源集合
* 分库时为所有的库的集合
* 分表时为所有的表的集合
* @param rangeShardingValue 范围分片对象
* logicTableName:逻辑表名
* columnName:分片键名
* valueRange:范围对象,包括lower和upper
* @return
*/
@Override
public Collection<String> doSharding(Collection<String> dataSourceNames, RangeShardingValue<Long> rangeShardingValue) {
Set<String> result = new LinkedHashSet<>();
//between 起始值
Long lower = rangeShardingValue.getValueRange().lowerEndpoint();
//between 结束值
Long upper = rangeShardingValue.getValueRange().upperEndpoint();
//循环范围计算分库逻辑
for (long i = lower; i <= upper; i++) {
for (String dataSourceName : dataSourceNames) {
if(dataSourceName.endsWith(i % dataSourceNames.size() +"")){
result.add(dataSourceName);
}
}
}
return result;
}
}
配置文件
spring.shardingsphere.sharding.tables.product_order.key-generator.column=id
spring.shardingsphere.sharding.tables.product_order.key-generator.type=SNOWFLAKE
spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds$->{0..1}.product_order_$->{0..1}
#根据用户id分库
spring.shardingsphere.sharding.tables.product_order.database-strategy.standard.sharding-column=user_id
spring.shardingsphere.sharding.tables.product_order.database-strategy.standard.precise-algorithm-class-name=com.lixiang.strategy.CustomPreciseShardingAlgorithm
#根据订单id分表
spring.shardingsphere.sharding.tables.product_order.table-strategy.standard.sharding-column=id
spring.shardingsphere.sharding.tables.product_order.table-strategy.standard.precise-algorithm-class-name=com.lixiang.strategy.CustomPreciseShardingAlgorithm
#根据订单id进行范围分表
spring.shardingsphere.sharding.tables.product_order.table-strategy.standard..range-algorithm-class-name=com.lixiang.strategy.CustomRangeShardingAlgorithm
测试代码
@Test
public void testBetweenSelect(){
productOrderMapper.selectList(new QueryWrapper<ProductOrderDO>().between("id",1L,2L));
}
复合分片算法ComplexShardingStrategy
提供对SQL语句中的=,in和between and的分片操作,支持【多分片健】
由于多分片键之间的关系复杂,Sharding-JDBC并未做过多的封装
而是直接将分片键值组合以及分片操作符交于算法接口,全部由应用开发者实现,提供最大的灵活度
代码案例
public class CustomComplexKeysShardingAlgorithm implements ComplexKeysShardingAlgorithm<Long> {
/**
*
* @param dataSourceNames 数据源集合
* @param complexKeysShardingValue 分片属性
* logicTableName:逻辑表名
* columnNameAndShardingValuesMap:多分片键集合
@Override
public Collection<String> doSharding(Collection<String> dataSourceNames, ComplexKeysShardingValue<Long> complexKeysShardingValue) {
Collection<Long> orderIdValues = this.getShardingValue(complexKeysShardingValue,"id");
Collection<Long> userIdValues = this.getShardingValue(complexKeysShardingValue,"user_id");
List<String> shardingSuffix = new ArrayList<>();
//对两个分片键取模的方式
for (Long userId : userIdValues) {
for (Long orderId : orderIdValues) {
//拼接的效果,0_0,0_1,1_0,1_1,1去匹配product_order_0_0,product_order_0_1,product_order_1_0,product_order_1_1
String suffix = userId % 2 + "_" + orderId % 2;
for (String databaseName : dataSourceNames) {
if (databaseName.endsWith(suffix)) {
shardingSuffix.add(databaseName);
}
}
}
}
return null;
}
/**
* shardingValues:分片属性
* logicTableName:逻辑表
* columnNameAndShardingValuesMap 存储多个分片健 包括key-value
* value:分片value,66和99
* @param shardingValues
* @param key
* @return
*/
private Collection<Long> getShardingValue(ComplexKeysShardingValue<Long> shardingValues, String key) {
Collection<Long> valueSet = new ArrayList<>();
Map<String, Collection<Long>> columnNameAndShardingValuesMap = shardingValues.getColumnNameAndShardingValuesMap();
if(columnNameAndShardingValuesMap.containsKey(key)){
valueSet.addAll(columnNameAndShardingValuesMap.get(key));
}
return valueSet;
}
}
配置文件,记得注释其他策略,否则报错 Only allowed 0 or 1 sharding strategy configuration
#复合分片算法,order_id,user_id 同时作为分片健
spring.shardingsphere.sharding.tables.product_order.table-strategy.complex.sharding-columns=user_id,id
spring.shardingsphere.sharding.tables.product_order.table-strategy.complex.algorithm-class-name=com.lixiang.strategy.CustomComplexKeysShardingAlgorithm
测试代码
@Test
public void testComSelect(){
productOrderMapper.selectList(new QueryWrapper<ProductOrderDO>().eq("id",66L).eq("user_id",99L));
}
Hint分片策略HintShardingStrategy
代码案例
//分库分表共用一个策略
public class CustomHintShardingAlgorithm implements HintShardingAlgorithm<Long> {
/**
*
* @param dataSourceNames 数据源集合
* 在分库时值为所有分片库的集合 databaseNames
* 分表时为对应分片库中所有分片表的集合 tablesNames
* @param hintShardingValue 分片属性
* logicTableName:逻辑表
* columnName:分片健(字段),hint策略下为空,""
* value:不在从sql中解析分片的值,而是从
* hintManager.addDatabaseShardingValue("product_order",3L)和
* hintManager.addTableShardingValue("product_order", 8L)中拿值指定数据库,指定表
* @return
*/
@Override
public Collection<String> doSharding(Collection<String> dataSourceNames, HintShardingValue<Long> hintShardingValue) {
Collection<String> result = new ArrayList<>();
for (String dataSourceName : dataSourceNames) {
for (Long value : hintShardingValue.getValues()) {
if(dataSourceName.endsWith(String.valueOf(value % dataSourceNames.size()))){
result.add(dataSourceName);
}
}
}
return result;
}
}
配置文件
spring.application.name=sharding-jdbc
server.port=8080
spring.shardingsphere.props.sql.show=true
# 配置数据源
spring.shardingsphere.datasource.names=ds0,ds1
# 配置ds0库
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.159.101:3306/shop_order_0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456
# 配置ds1库
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.159.101:3306/shop_order_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=123456
spring.shardingsphere.sharding.tables.product_order.key-generator.column=id
spring.shardingsphere.sharding.tables.product_order.key-generator.type=SNOWFLAKE
spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds$->{0..1}.product_order_$->{0..1}
#Hint 分片算法
spring.shardingsphere.sharding.tables.product_order.table-strategy.hint.algorithm-class-name=com.lixiang.strategy.CustomHintShardingAlgorithm
spring.shardingsphere.sharding.tables.product_order.database-strategy.hint.algorithm-class-name=com.lixiang.strategy.CustomHintShardingAlgorithm
测试代码
/**
* HintManger可以配合AOP切面
*/
@Test
public void testHint(){
//清除掉历史的规则
HintManager.clear();
//Hint分片策略必须使用HintManager工具类
HintManager hintManager = HintManager.getInstance();
//设置库的分片健,value用于表分片取模
hintManager.addDatabaseShardingValue("product_order",3L);
//设置表的分片健,value用于表分片健
hintManager.addTableShardingValue("product_order",8L);
//如果在读写分离数据库中,Hint可以强制读主库(主从复制存在一定的延迟)
//hintManager.setMasterRouteOnly();
//对应的value只做查询,不做sql解析
productOrderMapper.selectList(new QueryWrapper<ProductOrderDO>().eq("id", 66L));
}
product_order_$->{user_id % 8}表示订单表根据user_id模8,而分成8张表,表名称为`prouduct_order_0`到`prouduct_order_7
问题一:执行SQL排序、翻页、函数计算问题
分库后,数据分布再不同的节点上, 跨节点多库进行查询时,会出现limit分页、order by排序等问题。
而且当排序字段非分片字段时,更加复杂了,要在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序(也会带来更多的CPU/IO资源损耗)。
解决方式:
问题二:数据库全局主键重复问题
问题三:分库分表技术选型问题
问题:分库操作带来的分布式事务问题
常见分布式事务解决方案
分布式事务框架
问题:容量规划,分库分表后二次扩容问题
解决方式
方式一:利用主从同步
方式二:停服务