ShardingSphere

Posted by 余腾 on 2019-07-20
Estimated Reading Time 17 Minutes
Words 3.6k In Total
Viewed Times

一、ShardingSphere

ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 Sharding-JDBCSharding-Proxy 和 Sharding-Sidecar(计划中)这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。

ShardingSphere 定位为关系型数据库中间件,旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。 它与NoSQL和NewSQL是并存而非互斥的关系。NoSQL和NewSQL作为新技术探索的前沿,放眼未来,拥抱变化,是非常值得推荐的。反之,也可以用另一种思路看待问题,放眼未来,关注不变的东西,进而抓住事物本质。

QX{VLO3VPW$_J~)XW`71{39.png

1.1、Sharding-JDBC

Sharding-JDBC是ShardingSphere的第一个产品,也是ShardingSphere的前身。定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。

  • 适用于任何基于Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
  • 基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
  • 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer和PostgreSQL。
DDJVTK73ZVCKNPY2L9{L}5M.png

1.2、Sharding-Proxy

定位为透明化的数据库代理端,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持。 目前先提供MySQL版本,它可以使用任何兼容MySQL协议的访问客户端(如:MySQL Command Client, MySQL Workbench等)操作数据,对DBA更加友好。

  • 向应用程序完全透明,可直接当做MySQL使用。
  • 适用于任何兼容MySQL协议的客户端。
1@C}9~]]JMDO}@}L5[~7F`4.png

1.3、Sharding-Sidecar(TBD)

Sharding-JDBC Sharding-Proxy Sharding-Sidecar
数据库 任意 MySQL MySQL
连接消耗数
异构语言 仅Java 任意 任意
性能 损耗低 损耗略高 损耗低
无中心化
静态入口

数据分片

  • 分库 & 分表
  • 读写分离
  • 分片策略定制化
  • 无中心化分布式主键

分布式事务

  • 标准化事务接口
  • XA强一致事务
  • 柔性事务

数据库治理

  • 配置动态化
  • 编排 & 治理
  • 数据脱敏
  • 可视化链路追踪
  • 弹性伸缩(规划中)

二、分库分表

2.1、水平分库

概念:以字段为依据,按照一定策略(hash、range等),将一个库中的数据拆分到多个库中。

结果:

  • 每个库的结构都一样;
  • 每个库的数据都不一样,没有交集;
  • 所有库的并集是全量数据;

场景:系统绝对并发量上来了,分表难以根本上解决问题,并且还没有明显的业务归属来垂直分库。

分析:库多了,io和cpu的压力自然可以成倍缓解。


2.2、水平分表

概念:以字段为依据,按照一定策略(hash、range等),将一个表中的数据拆分到多个表中。

结果:

  • 每个表的结构都一样;
  • 每个表的数据都不一样,没有交集;
  • 所有表的并集是全量数据;

场景:系统绝对并发量并没有上来,只是单表的数据量太多,影响了SQL效率,加重了CPU负担,以至于成为瓶颈。

分析:表的数据量少了,单次SQL执行效率高,自然减轻了CPU的负担。

水平:以某个字段按照一定的规律(取模)将一个表的数据分到多个库中,将表中不同的数据行按照一定规律分布到不同的数据库
表中(这些表保存在同一个数据库中) , 这样来降低单表数据量,优化查询性能。

1、每个(表)的结构都一样
2、每个(表)的数据都不一样
3、每个(表)的并集是全量数据
4、可以处理数据热点的问题
5、带来扩容的问题麻烦
6、分片规则抽取出来
7、跨库查询、分布式事务、分布式id


2.3、垂直分库

概念:以表为依据,按照业务归属不同,将不同的表拆分到不同的库中。

将一个属性比较多、一行数据大的表把不同属性拆分不同表中降低单库(表)大小的目的来提高性能。

结果:

  • 每个库的结构都不一样;
  • 每个库的数据至少一列一样;
  • 所有库的并集是全量数据;
  • 拆分业务非常的清晰;
  • 数据库维护简单;

场景:系统绝对并发量上来了,并且可以抽象出单独的业务模块。

分析:到这一步,基本上就可以服务化了。例如,随着业务的发展一些公用的配置表、字典表等越来越多,这时可以将这些表拆到单独的库中,甚至可以服务化。再有,随着业务的发展孵化出了一套业务模式,这时可以将相关的表拆到单独的库中,甚至可以服务化。

缺点:
1、如果单表的数据量大读写压力也大;
2、跨库查询、分布式事务、分布式id;
3、受某种业务来决定会被限制双十一订单量大;


2.4、垂直分表

概念:以字段为依据,按照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中。

结果:

  • 每个表的结构都不一样;
  • 每个表的数据也不一样,一般来说,每个表的字段至少有一列交集,一般是主键,用于关联数据;
  • 所有表的并集是全量数据;

场景:系统绝对并发量并没有上来,表的记录并不多,但是字段多,并且热点数据和非热点数据在一起,单行数据所需的存储空间较大。以至于数据库缓存的数据行减少,查询时会去读磁盘数据产生大量的随机读IO,产生IO瓶颈。

分析:可以用列表页和详情页来帮助理解。垂直分表的拆分原则是将热点数据(可能会冗余经常一起查询的数据)放在一起作为主表,非热点数据放在一起作为扩展表。这样更多的热点数据就能被缓存下来,进而减少了随机读IO。拆了之后,要想获得全部数据就需要关联两个表来取数据。但记住,千万别用join,因为join不仅会增加CPU负担并且会讲两个表耦合在一起(必须在一个数据库实例上)。关联数据,应该在业务Service层做文章,分别获取主表和扩展表数据然后用关联字段关联得到全部数据。

总结

垂直拆分库,水平拆分表

垂直拆分 👉 表的结构

水平拆分 👉 表的内容

一般来说 ,在系统设计阶段就应该根据业务耦合松紧来确定垂直分库,垂直分表方案,在数据量及访问压力不是特别大的情况,首先考虑缓存、读写分离、索引技术等方案。若数据量极大,且持续增长,再考虑水平分库水平分表方案。


三、测试 分库分表

springboot+shardingjdbc+JPA

com.shardingjdbc.sharding

3.1、SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
CREATE DATABASE database0;
USE database0;
DROP TABLE IF EXISTS `goods_0`;
CREATE TABLE `goods_0`
(
`goods_id` bigint(20) NOT NULL,
`goods_name` varchar(100) COLLATE utf8_bin NOT NULL,
`goods_type` bigint(20) DEFAULT NULL,
PRIMARY KEY (`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE =utf8_bin;
DROP TABLE IF EXISTS `goods_1`;
CREATE TABLE `goods_1`
(
`goods_id` bigint(20) NOT NULL,
`goods_name` varchar(100) COLLATE utf8_bin NOT NULL,
`goods_type` bigint(20) DEFAULT NULL,
PRIMARY KEY (`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE =utf8_bin;

CREATE DATABASE database0_slave0;
USE database0_slave0;
DROP TABLE IF EXISTS `goods_0`;
CREATE TABLE `goods_0` (
`goods_id` bigint(20) NOT NULL,
`goods_name` varchar(100) collate utf8_bin NOT NULL,
`goods_type` bigint(20) default NULL,
PRIMARY KEY (`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
DROP TABLE IF EXISTS `goods_1`;
CREATE TABLE `goods_1` (
`goods_id` bigint(20) NOT NULL,
`goods_name` varchar(100) collate utf8_bin NOT NULL,
`goods_type` bigint(20) default NULL,
PRIMARY KEY (`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin

CREATE DATABASE database0_slave1;
USE database0_slave1;
DROP TABLE IF EXISTS `goods_0`;
CREATE TABLE `goods_0` (
`goods_id` bigint(20) NOT NULL,
`goods_name` varchar(100) collate utf8_bin NOT NULL,
`goods_type` bigint(20) default NULL,
PRIMARY KEY (`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
DROP TABLE IF EXISTS `goods_1`;
CREATE TABLE `goods_1` (
`goods_id` bigint(20) NOT NULL,
`goods_name` varchar(100) collate utf8_bin NOT NULL,
`goods_type` bigint(20) default NULL,
PRIMARY KEY (`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin

CREATE DATABASE database1;
USE database1;
DROP TABLE IF EXISTS `goods_0`;
CREATE TABLE `goods_0`
(
`goods_id` bigint(20) NOT NULL,
`goods_name` varchar(100) COLLATE utf8_bin NOT NULL,
`goods_type` bigint(20) DEFAULT NULL,
PRIMARY KEY (`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE =utf8_bin;
DROP TABLE IF EXISTS `goods_1`;
CREATE TABLE `goods_1`
(
`goods_id` bigint(20) NOT NULL,
`goods_name` varchar(100) COLLATE utf8_bin NOT NULL,
`goods_type` bigint(20) DEFAULT NULL,
PRIMARY KEY (`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE =utf8_bin;

CREATE DATABASE database2;
USE database2;
DROP TABLE IF EXISTS `goods_0`;
CREATE TABLE `goods_0`
(
`goods_id` bigint(20) NOT NULL,
`goods_name` varchar(100) COLLATE utf8_bin NOT NULL,
`goods_type` bigint(20) DEFAULT NULL,
PRIMARY KEY (`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE =utf8_bin;
DROP TABLE IF EXISTS `goods_1`;
CREATE TABLE `goods_1`
(
`goods_id` bigint(20) NOT NULL,
`goods_name` varchar(100) COLLATE utf8_bin NOT NULL,
`goods_type` bigint(20) DEFAULT NULL,
PRIMARY KEY (`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE =utf8_bin;

3.2、mould

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "goods")
@Data
public class Goods {
@Id
private Long goodsId;
private String goodsName;
private Long goodsType;
}

3.3、Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.shardingjdbc.sharding.controller;

import com.shardingjdbc.sharding.entity.Goods;
import com.shardingjdbc.sharding.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("api/" + "goods")
public class GoodsController {

@Autowired
GoodsService goodsService;

@PostMapping
public String saveGoods() {
goodsService.saveGoods();
return "success";
}

@GetMapping
public List<Goods> listGoods() {
return goodsService.listAllGoods();
}

@DeleteMapping
public String deleteAllGoods() {
goodsService.deleteAllGoods();
return "delete success";
}

@GetMapping("/betweenGoodsId")
public List<Goods> queryBetweenGoodsIds() {
// 单行表达式不支持 范围查询 Inline strategy cannot support range sharding
return goodsService.listAllByGoodsIdBetween(2L, 2L);
}

@GetMapping("/inGoodsId")
public List<Goods> queryInGoodsIds() {
List<Long> goodsIds = new ArrayList<>();
goodsIds.add(3L);
goodsIds.add(6L);
return goodsService.listAllByGoodsIdIn(goodsIds);
}
}

3.4、Service/ServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.shardingjdbc.sharding.service;

import com.shardingjdbc.sharding.entity.Goods;
import java.util.List;

public interface GoodsService {

void saveGoods();

List<Goods> listAllGoods();

void deleteAllGoods();

List<Goods> listAllByGoodsIdBetween(long start, long end);

List<Goods> listAllByGoodsIdIn(List<Long> goodsIds);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.shardingjdbc.sharding.service.ServiceImpl;

import com.shardingjdbc.sharding.entity.Goods;
import com.shardingjdbc.sharding.repository.GoodsRepository;
import com.shardingjdbc.sharding.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Service
public class GoodsServiceImpl implements GoodsService {

@Autowired
private GoodsRepository goodsRepository;

@Override
@Transactional
public void saveGoods() {
Goods goods = new Goods();
for (int i = 1; i <= 40; i++) {
goods.setGoodsId((long) i);
goods.setGoodsName("shangpin" + i);
goods.setGoodsType((long) (i + 1));
goodsRepository.save(goods);
}
}

@Override
public List<Goods> listAllGoods() {
return goodsRepository.findAll();
}

@Override
@Transactional
public void deleteAllGoods() {
goodsRepository.deleteAll();
}

@Override
public List<Goods> listAllByGoodsIdBetween(long start, long end) {
return goodsRepository.findAllByGoodsIdBetween(start, end);
}

@Override
public List<Goods> listAllByGoodsIdIn(List<Long> goodsIds) {
return goodsRepository.findAllByGoodsIdIn(goodsIds);
}
}

3.5、Repository

1
2
3
4
5
6
7
8
9
10
11
12
package com.shardingjdbc.sharding.repository;

import com.shardingjdbc.sharding.entity.Goods;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;

public interface GoodsRepository extends JpaRepository<Goods, Long> {

List<Goods> findAllByGoodsIdBetween(Long goodsId1, Long goodsId2);

List<Goods> findAllByGoodsIdIn(List<Long> goodsIds);
}

3.6、启动类

1
2
3
4
5
6
7
8
9
10
11
12
package com.shardingjdbc.sharding;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})
public class ShardingApplication {
public static void main(String[] args) {
SpringApplication.run(ShardingApplication.class, args);
}
}

3.7、yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#Jpa配置
spring:
application: shardingsphere
jpa:
database: mysql
show-sql: true
hibernate:
ddl-auto: none

#分库分表以及数据源配置
sharding:
jdbc:
datasource:
names: master0,master0slave0,master0slave1,master1,master2

master0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/database0?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root
master0slave0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/database0_slave0?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root
master0slave1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/database0_slave1?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root

master1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/database1?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root

master2:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/database2?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root

config:
sharding:
default-data-source-name: master0
default-database-strategy:
inline:
sharding-column: goods_id
algorithm-expression: master$->{goods_id % 3}
tables:
goods:
actual-data-nodes: master$->{0..2}.goods_$->{0..1}
table-strategy:
inline:
sharding-column: goods_type
algorithm-expression: goods_$->{goods_type % 2}
key-generator:
column: goods_id
type: SNOWFLAKE
# master-slave-rules:
# ds0:
# master-data-source-name: master0
# slave-data-source-names: master0slave0,master0slave1
# load-balance-algorithm-type: round_robin

3.8、pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.shardingJdbc</groupId>
<artifactId>sharding</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sharding</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

感谢阅读


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !