ShardingSphere分库分表

第一章 ShardingSphere

1、ShardingSphere简介

Apache ShardingSphere 是一套开源的分布式数据库解决方案组成的生态圈。

组成部分: JDBCProxySidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。

功能特性:它们均提供标准化的数据水平扩展分布式事务分布式治理等功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。

官方网站:https://shardingsphere.apache.org/index_zh.html

文档地址:https://shardingsphere.apache.org/document/legacy/4.x/document/cn/overview/

本教程主要介绍:Sharding-JDBC和Sharding-Proxy

2、Sharding-JDBC简介

image-20201213161858364

  • 磁盘IO的开销问题,单库情况下无法承载过多的访问
  • 所有的用户数据都存放在同一个表中,mysql单表可以存储 2^32条,但查询的时候效率比较低,单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表
  • 业务的边界不清晰,专库专用

虽然我们对应用系统做了大量的微服务处理,提升了系统在应用体系上的吞吐量,但是随着业务的越来越多,我们的数据库势必有不堪重负的一天,那么我们是怎么处理数据层的问题呢?这里就需要采用分库分表的策略来处理:

Sharding-jdbc是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。

Sharding-JDBC的核心功能为数据分片读写分离,通过Sharding-JDBC,应用可以透明的使用jdbc访问已经分库分表、读写分离的多个数据源,而不用关心数据源的数量以及数据如何分布。

第二章 分库分表方式

分库分表包括分库和分表两个部分,在生产中通常包括:垂直分库、水平分库、垂直分表、水平分表四种方式。

1、垂直分表

名词解释:将一个表按照字段分成多表,每个表存储其中一部分字段

image-20201216212743505

它带来的提升是:

  • 为了避免IO争抢并减少锁表的几率,查看详情的用户与商品信息浏览互不影响

  • 充分发挥热门数据的操作效率,商品信息的操作的高效率不会被商品描述的低效率所拖累。

  • 改变了数据库的表结构,对数据存储的行数没有影响

垂直拆分原则:

  1. 把不常用的字段单独放在一张表;
  2. 把text,blob等大字段拆分出来放在附表中;
  3. 经常组合查询的列放在一张表中;

2、垂直分库

名词解释:垂直分库是指按照业务将表进行分类,分布到不同的数据库上面,每个库可以放在不同的服务器上,它的核心理念是专库专用。

通过垂直分表性能得到了一定程度的提升,但是还没有达到要求,并且磁盘空间也快不够了,因为数据还是始终限制在一台服务器,库内垂直分表只解决了单一表数据量过大的问题,但没有将表分布到不同的服务器上,因此每个表还是竞争同一个物理机的CPU、内存、网络IO、磁盘。

垂直分库通过将表按业务分类,然后分布在不同数据库,并且可以将这些数据库部署在不同服务器上,从而达到多个服务器共同分摊压力的效果,它的核心理念 是专库专用。

image-20210713162722109

它带来的提升是:

  • 解决业务层面的耦合,业务清晰
  • 能对不同业务的数据进行分级管理、维护、监控、扩展等
  • 高并发场景下,垂直分库一定程度的提升IO、数据库连接数、降低单机硬件资源的瓶颈

垂直分库通过将表按业务分类,然后分布在不同数据库,并且可以将这些数据库部署在不同服务器上,从而达到多 个服务器共同分摊压力的效果,但是依然没有解决单表数据量过大的问题。

3、水平分库

名词解释:是把同一个表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上。

经过垂直分库后,数据库性能问题得到一定程度的解决,但是随着业务量的增长,单库存储数据已经超出预估。

以电商网站为例:有8w店铺,每个店铺平均150个不同规格的商品,再算上增长,那商品数量得往1500w+上预估,并且商品库属于访问非常频繁的资源,单台服务器已经无法支撑。此时该如何优化?

再次分库?但是从业务角度分析,目前情况已经无法再次垂直分库。

水平分库是把不同表拆到不同数据库中,它是对数据行的拆分,不影响表结构。

image-20210713163106122

思考:如何存储和访问数据库呢?

它带来的提升是:

  • 解决了单库单表大数据,高并发的性能瓶颈。
  • 提高了系统的稳定性及可用性

当一个应用难以再细粒度的垂直切分,或切分后数据量行数巨大,存在单库读写、存储性能瓶颈,这时候就需要进行水平分库了,经过水平切分的优化,往往能解决单库存储量及性能瓶颈。但由于同一个表被分配在不同的数据库,需要额外进行数据操作的路由工作,因此大大提升了系统复杂度。

4、水平分表

水平分表是在同一个数据库内,把同一个表的数据按一定规则拆到多个表中。

库内的水平分表,解决了单一表数据量过大的问题,分出来的小表中只包含一部分数据,从而使得单个表的数据量变小,提高检索性能。

image-20201218153359933

思考:如何存储和访问数据库中的表呢?

它带来的提升是:

  • 优化单一表数据量过大而产生的性能问题
  • 避免IO争抢并减少锁表的几率

库内的水平分表,解决了单一表数据量过大的问题,分出来的小表中只包含一部分数据,从而使得单个表的数据量变小,提高检索性能。

5、分库分表带来的问题

分库分表能有效的缓解了单机和单库带来的性能瓶颈和压力,突破网络IO、硬件资源、连接数的瓶颈,同时也带来了一些问题。

  • 事务一致性问题
  • 跨节点关联查询
  • 跨节点分页、排序函数
  • 主键避重
  • 公共表

6、小结

分库分表方式:垂直分表、垂直分库、水平分库和水平分表

垂直分表:可以把一个宽表的字段按访问频次、是否是大字段的原则拆分为多个表,这样既能使业务清晰,还能提升部分性能。拆分后,尽量从业务角度避免联查,否则性能方面将得不偿失。

垂直分库:可以把多个表按业务耦合松紧归类,分别存放在不同的库,这些库可以分布在不同服务器,从而使访问压力被多服务器负载,大大提升性能,同时能提高整体架构的业务清晰度,不同的业务库可根据自身情况定制优化方案。但是它需要解决跨库带来的所有复杂问题。

水平分库:可以把一个表的数据(按数据行)分到多个不同的库,每个库只有这个表的部分数据,这些库可以分布在不同服务器,从而使访问压力被多服务器负载,大大提升性能。它不仅需要解决跨库带来的所有复杂问题,还要解决数据路由的问题(数据路由问题后边介绍)。

水平分表:可以把一个表的数据(按数据行)分到多个同一个数据库的多张表中,每个表只有这个表的部分数据,这样做能小幅提升性能,它仅仅作为水平分库的一个补充优化。

最佳实践:

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

第三章 sharding-jdbc架构

1、名词解释

参考官网-核心概念

  • 逻辑表(LogicTable):进行水平拆分的时候同一类型(逻辑、数据结构相同)的表的总称。例:用户数据根据主键尾数拆分为2张表,分别是tab_user_0到tab_user_1,他们的逻辑表名为tab_user。

  • 真实表(ActualTable):在分片的数据库中真实存在的物理表。即上个示例中的tab_user_0到tab_user_1。

  • 数据节点(DataNode):数据分片的最小单元。由数据源名称和数据表组成,例:spring-boot_0.tab_user_0,spring-boot_0.tab_user_1,spring-boot_1.tab_user_0,spring-boot_1.tab_user_1。

  • 动态表(DynamicTable):逻辑表和物理表不一定需要在配置规则中静态配置。如,按照日期分片的场景,物理表的名称随着时间的推移会产生变化。

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

  • 绑定表(BindingTable):指分片规则一致的主表和子表。例如:t_order表和t_order_item表,均按照`order_no分片,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。举例说明,如果SQL为:

    1
    SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

    ==让order的数据落库位置,与order_item落库的位置在同一个数据节点==

    在不配置绑定表关系时,假设分片键order_id将数值10路由至第0片,将数值11路由至第1片,那么路由后的SQL应该为4条,它们呈现为笛卡尔积:

    1
    2
    3
    4
    5
    6
    7
    SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

    SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

    SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

    SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

    在配置绑定表关系后,路由的SQL应该为2条:

    1
    2
    3
    SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

    SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

    其中t_order在FROM的最左侧,ShardingSphere将会以它作为整个绑定表的主表。 所有路由计算将会只使用主表的策略,那么t_order_item表的分片计算将会使用t_order`的条件。故绑定表之间的分区键要完全相同。

  • 分片键(ShardingColumn):分片字段用于将数据库(表)水平拆分的字段,支持单字段及多字段分片。例如上例中的order_id。

2、Sharding-JDBC执行原理

img

参考官网-内部剖析

shardingSphere的3个产品的数据分片主要流程是完全一致的。 核心由SQL解析 => 执行器优化 => SQL路由 => SQL改写 => SQL执行 => 结果归并的流程组成。

例如现在有一条查询语句:

1
select * from t_user where id=10

进行了分库分表操作,2个库ds0,ds1,采用的分片键为id,逻辑表为t_user,真实表为t_user_0、t_user_1两张表,分库、分表算法为均为取余(%2)。

  • sql解析:通过解析sql语句提取分片键列与值进行分片,例如比较符 =、in 、between and,及查询的表等。

  • 执行器优化:合并和优化分片条件,如OR等。

  • sql路由:找到sql需要去哪个库、哪个表执行语句,上例sql根据采用的策略可以得到将在ds0库,t_user_0表执行语句。

  • sql改写:根据解析结果,及采用的分片逻辑改写sql,SQL改写分为正确性改写和优化改写。

    上例经过sql改写后,真实语句为:

1
2
3
4
select * from ds0.t_user_0 where id=10==>ds0.t_user_0 2S
select * from ds0.t_user_1 where id=10==>ds0.t_user_1 2S
select * from ds1.t_user_0 where id=10==>ds1.t_user_0 2S
select * from ds1.t_user_1 where id=10==>ds1.t_user_1 2S
  • sql执行:通过多线程执行器异步执行。
  • 结果归并:将多个执行结果集归并以便于通过统一的JDBC接口输出。结果归并包括流式归并、内存归并和使用装饰者模式的追加归并这几种方式。

第四章 sharding-jdbc实战

那我们怎么使用这个插件呢?来我们看下我们项目中集成的framework-sharding-jdbc的模块:

1
2
|——restkeeper-framework 	核心组件模块,集成:mybatis-plus、seata、jwt、redis等等
|————framework-sharding-jdbc关于sharding-jdbc基础集成

framework-sharding-jdbc 模块中我们需要导入对sharding-jdbc的支持:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependencies>
<!-- 使用XA事务时,需要引入此模块 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-xa-core</artifactId>
</dependency>

<!-- 使用BASE事务时,需要引入此模块 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-base-seata-at</artifactId>
</dependency>

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

1、sharding-jdbc-集成并处理分布式事务

官网参考:https://shardingsphere.apache.org/document/legacy/4.x/document/cn/features/transaction

1.1、XA事务使用sharding-jdbc

model-shop-applet 项目为例

我们使用sharding-jdbc的时候只需要依赖: framework-sharding-jdbc 模块

image-20210713202416892

1、排除druid-spring-boot-starter和sharding-transaction-base-seata-at的jar

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
<dependency>
<groupId>com.itheima.restkeeper</groupId>
<artifactId>model-security-service</artifactId>
<exclusions>
<exclusion>
<artifactId>druid-spring-boot-starter</artifactId>
<groupId>com.alibaba</groupId>
</exclusion>
</exclusions>
</dependency>
<!--sharding-JDBC 必须使用原生的druid,排除druid-spring-boot-starter-->
<dependency>
<groupId>com.itheima.restkeeper</groupId>
<artifactId>model-shop-service</artifactId>
<exclusions>
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--使用XA事务,则必须移除seata-at依赖-->
<dependency>
<groupId>com.itheima.restkeeper</groupId>
<artifactId>framework-sharding-jdbc</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-base-seata-at</artifactId>
</exclusion>
</exclusions>
</dependency>

2、然后在model-shop-applet的application.yml中添加sharding-jdbc配置即可==【如果使用nacos配置中心,需要在配置中心中添加配置: model-shop-applet-prod.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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#服务配置
server:
#端口
port: 7079
#服务编码
tomcat:
uri-encoding: UTF-8
#spring相关配置
spring:
main:
allow-bean-definition-overriding: true
redis:
redisson:
config: classpath:singleServerConfig.yaml
#redis配置信息
host: 192.168.200.129
port: 6379
password: pass
jta:
atomikos:
properties:
log-base-dir: logs/model-shop-applet-atomikos
log-base-name: model-shop-applet
#数据源配置
shardingsphere:
datasource:
names: ds0,ds1
ds0:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.200.129:3306/restkeeper-shop-0?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
username: root
password: pass
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.200.151:3306/restkeeper-shop-1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
username: root
password: root
#分库分表
sharding:
tables:
tab_order: #数据库中要分表的逻辑名
actualDataNodes: ds${0..1}.tab_order_${0..1}
databaseStrategy:
inline:
shardingColumn: sharding_id
algorithmExpression: ds${sharding_id % 2}
tableStrategy:
inline:
shardingColumn: order_no
algorithmExpression: tab_order_${order_no % 2}
keyGenerator:
type: SNOWFLAKE
column: id
tab_order_item: #数据库中要分表的逻辑名
actualDataNodes: ds${0..1}.tab_order_item_${0..1}
databaseStrategy:
inline:
shardingColumn: sharding_id
algorithmExpression: ds${sharding_id % 2}
tableStrategy:
inline:
shardingColumn: product_order_no
algorithmExpression: tab_order_item_${product_order_no % 2}
keyGenerator:
type: SNOWFLAKE
column: id
binding-tables:
- tab_order
- tab_order_item
broadcast-tables:
- tab_brand
- tab_category
- tab_dish
- tab_dish_flavor
- tab_printer
- tab_printer_dish
- tab_store
- tab_table
- tab_table_area
- undo_log
default-key-generator:
type: SNOWFLAKE
column: id
props:
sql.show: true
#mybatis配置
mybatis-plus:
# MyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名
type-aliases-package: com.itheima.springcloud.pojo
# 该配置请和 typeAliasesPackage 一起使用,如果配置了该属性,则仅仅会扫描路径下以该类作为父类的域对象 。
type-aliases-super-type: com.itheima.restkeeper.basic.BasicPojo
configuration:
# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 驼峰下划线转换
map-underscore-to-camel-case: true
use-generated-keys: true
default-statement-timeout: 60
default-fetch-size: 100
#忽略商户号表
ignore-enterprise-tables:
- tab_brand
- tab_category
- tab_dish
- tab_dish_flavor
- tab_order
- tab_order_item
- tab_printer
- tab_printer_dish
- tab_store
- tab_table
- tab_table_area
- tab_role
- tab_order_role
- undo_log
#忽略门店号表
ignore-store-tables:
- tab_brand
- tab_category
- tab_dish
- tab_dish_flavor
- tab_order
- tab_order_item
- tab_printer
- tab_printer_dish
- tab_store
- tab_table
- tab_table_area
- tab_role
- tab_order_role
- undo_log
logging:
config: classpath:logback.xml
dubbo:
application:
version: 1.0.0
logger: slf4j
scan:
base-packages: com.itheima.restkeeper
registry:
address: spring-cloud://192.168.200.129
protocol:
#指定二进制协议
name: dubbo
port: 27079
threads: 200
accesslog: logs/model-shop-applet-01.log

3、在 resources 文件夹下添加jta.properties

1
2
com.atomikos.icatch.output_dir=D:/atomikos/model-shop-applet
com.atomikos.icatch.log_base_dir=D:/atomikos/model-shop-applet

4、指定事务分类刚性事务@ShardingTransactionType(TransactionType.XA)

image-20210714004113897

5、启动服务测试,观察数据库数据添加情况,和控制台日志打印情况:

image-20210830172521349

保存到数据库记录:

image-20210830201213373

1.2、seata-at使用sharding-jdbc

model-shop-producer 项目为例

我们使用sharding-jdbc的时候只需要依赖: framework-sharding-jdbc 模块

image-20210714004420941

1、排除 druid-spring-boot-starter 和sharding-transaction-xa-core的jar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependency>
<groupId>com.itheima.restkeeper</groupId>
<artifactId>model-shop-service</artifactId>
<exclusions>
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>com.itheima.restkeeper</groupId>
<artifactId>framework-sharding-jdbc</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-xa-core</artifactId>
</exclusion>
</exclusions>
</dependency>

2、然后在model-shop-producer的application.yml中添加sharding-jdcb配置即可==【如果使用nacos配置中心,需要在配置中心中添加配置】==:

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#服务配置
server:
#端口
port: 7077
#服务编码
tomcat:
uri-encoding: UTF-8
#spring相关配置
spring:
#应用配置
application:
#应用名称
name: model-shop-producer
main:
allow-bean-definition-overriding: true
redis:
redisson:
config: classpath:singleServerConfig.yaml
#redis配置信息
host: 192.168.200.129
port: 6379
password: pass
cloud:
alibaba:
seata:
tx-service-group: project_tx_group
jta:
atomikos:
properties:
log-base-dir: logs/model-shop-producer-atomikos
log-base-name: model-shop-producer
#数据源配置
shardingsphere:
datasource:
names: ds0,ds1
ds0:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.200.129:3306/restkeeper-shop-0?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
username: root
password: pass
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.200.151:3306/restkeeper-shop-1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
username: root
password: root
#分库分表
sharding:
tables:
tab_order: #数据库中要分表的逻辑名
actualDataNodes: ds${0..1}.tab_order_${0..1}
databaseStrategy:
inline:
shardingColumn: sharding_id
algorithmExpression: ds${sharding_id % 2}
tableStrategy:
inline:
shardingColumn: order_no
algorithmExpression: tab_order_${order_no % 2}
keyGenerator:
type: SNOWFLAKE
column: id
tab_order_item: #数据库中要分表的逻辑名
actualDataNodes: ds${0..1}.tab_order_item_${0..1}
databaseStrategy:
inline:
shardingColumn: sharding_id
algorithmExpression: ds${sharding_id % 2}
tableStrategy:
inline:
shardingColumn: product_order_no
algorithmExpression: tab_order_item_${product_order_no % 2}
keyGenerator:
type: SNOWFLAKE
column: id
binding-tables:
- tab_order
- tab_order_item
broadcast-tables:
- tab_brand
- tab_category
- tab_dish
- tab_dish_flavor
- tab_printer
- tab_printer_dish
- tab_store
- tab_table
- tab_table_area
- undo_log
default-key-generator:
type: SNOWFLAKE
column: id
props:
sql.show: true
#mybatis配置
mybatis-plus:
# MyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名
type-aliases-package: com.itheima.springcloud.pojo
# 该配置请和 typeAliasesPackage 一起使用,如果配置了该属性,则仅仅会扫描路径下以该类作为父类的域对象 。
type-aliases-super-type: com.itheima.restkeeper.basic.BasicPojo
configuration:
# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 驼峰下划线转换
map-underscore-to-camel-case: true
use-generated-keys: true
default-statement-timeout: 60
default-fetch-size: 100
#忽略商户号表
ignore-enterprise-tables:
- tab_dish_flavor
- tab_order_item
- undo_log
#忽略门店号表
ignore-store-tables:
- tab_dish_flavor
- tab_brand
- tab_order_item
- tab_store
- undo_log
seata:
tx-service-group: project_tx_group
enabled: true
application-id: ${spring.application.name}
# **注意:此处必须设置为 false,将数据源的代理权限交给sharding-jdbc管理
enable-auto-data-source-proxy: false
service:
#这里的名字与file.conf中vgroup_project_tx_group = "default"相同
vgroup-mapping:
project_tx_group: default
#这里的名字与file.conf中default.grouplist = "192.168.200.129:8091"相同
grouplist:
default: 192.168.200.129:9200
config:
type: nacos
nacos:
group: SEATA_GROUP
server-addr: 192.168.200.129:8848
username: nacos
password: nacos
registry:
type: nacos
nacos:
group: SEATA_GROUP
server-addr: 192.168.200.129:8848
username: nacos
password: nacos
dubbo:
#dubbo应用服务定义
application:
#版本
version: 1.0.0
#日志
logger: slf4j
scan:
#扫描路径
base-packages: com.itheima.restkeeper
registry:
#注册中心
address: spring-cloud://192.168.200.129
#服务协议定义
protocol:
#服务协议名称
name: dubbo
#协议端口
port: 27077
#线程数
threads: 200
#dubbo调用日志
accesslog: logs/model-shop-producer-01.log

3、添加jta.properties

1
2
com.atomikos.icatch.output_dir=D:/atomikos/model-shop-producer
com.atomikos.icatch.log_base_dir=D:/atomikos/model-shop-producer

4、在resources路径下添加seata.conf

1
2
3
4
client {
application.id = model-shop-producer
transaction.service.group = project_tx_group
}

5、指定事务分类(柔性事务):@ShardingTransactionType(TransactionType.BASE)

image-20210714005053598

6、测试下单结算成功后,执行XXl-JOB任务查询订单结算状态出现以下bug:

image-20210830222749513

问题分析:

  • Sharding-JDBC中分片键一旦设置成功,是不允许被修改的。

解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
@GlobalTransactional
public Boolean synchTradingState(Long orderNo, String tradingState) {
OrderVo orderVo = orderService.findOrderByOrderNo(orderNo);
if (EmptyUtil.isNullOrEmpty(orderVo)) {
throw new ProjectException(ShoppingCartEnum.UPDATE_ORDER_FAIL);
}
// 注意:不能将 sharding_id 的分片键进行修改,否则就会报错
orderVo = OrderVo.builder()
.id(orderVo.getId())
.orderState(tradingState).build();
return orderService.saveOrUpdate(BeanConv.toBean(orderVo,Order.class));
}

2、分库键回填机制

在BasicPojo中添加分片字段shardingId

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
package com.itheima.springcloud.basic;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.itheima.springcloud.utils.ToString;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.springframework.format.annotation.DateTimeFormat;

import java.util.Date;

/**
* @Description:基础类
*/
@AllArgsConstructor
public class BasicPojo extends ToString {

public BasicPojo() {
}

public BasicPojo(Long id) {
this.id = id;
}

/**
* @Description 主键
*/
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long id;

/**
* @Description 数据源分片Id
*/
@TableField(fill = FieldFill.INSERT)
protected Long shardingId;

@TableField(fill = FieldFill.INSERT)//INSERT代表只在插入时填充
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")//set
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")//get
protected Date createdTime;

@TableField(fill = FieldFill.INSERT_UPDATE)// INSERT_UPDATE 首次插入、其次更新时填充(或修改)
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")//set
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")//get
protected Date updatedTime;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public Date getCreatedTime() {
return createdTime;
}

public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}

public Date getUpdatedTime() {
return updatedTime;
}

public void setUpdatedTime(Date updatedTime) {
this.updatedTime = updatedTime;
}

public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}

MyBatisMetaObjectHandler中添加shardingId回填

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
package com.itheima.springcloud.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.itheima.springcloud.utils.SnowflakeIdWorker;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Date;

/**
* @Description:自动填充
*/
@Slf4j
public class MyBatisMetaObjectHandler implements MetaObjectHandler {

@Autowired
SnowflakeIdWorker snowflakeIdWorker;

@Override
public void insertFill(MetaObject metaObject) {
log.info("开始插入填充.....");
Object shardingId = getFieldValByName("shardingId", metaObject);
if (metaObject.hasSetter("shardingId")&&shardingId==null) {
this.strictInsertFill(metaObject, "shardingId", Long.class,snowflakeIdWorker.nextId() );
}
if (metaObject.hasSetter("createdTime")) {
this.strictInsertFill(metaObject, "createdTime", Date.class, new Date());
}
if (metaObject.hasSetter("updatedTime")) {
this.strictInsertFill(metaObject, "updatedTime", Date.class, new Date());
}
}

@Override
public void updateFill(MetaObject metaObject) {
log.info("开始更新填充.....");
if (metaObject.hasSetter("updatedTime")) {
this.strictUpdateFill(metaObject, "updatedTime", Date.class, new Date());
}
}
}

第五章 sharding-proxy实战

1、Sharding-Proxy简介

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

  • 向应用程序完全透明,可直接当做MySQL/PostgreSQL使用。
  • 适用于任何兼容MySQL/PostgreSQL协议的的客户端。

Sharding-Proxy Architecture

JDBC和Proxy对比:

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

Sharding-Proxy的优势在于对异构语言的支持,以及为DBA提供可操作入口。

2、sharding-proxy快速入门

1、下载sharding-proxy 执行的软件包

下载连接地址:https://archive.apache.org/dist/shardingsphere/

2、编辑配置文件:server.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
authentication:
users:
root:
password: root
sharding:
password: sharding
authorizedSchemas: sharding_db

props:
max.connections.size.per.query: 1
acceptor.size: 16 # The default value is available processors count * 2.
executor.size: 16 # Infinite by default.
proxy.frontend.flush.threshold: 128 # The default value is 128.
# LOCAL: Proxy will run with LOCAL transaction.
# XA: Proxy will run with XA transaction.
# BASE: Proxy will run with B.A.S.E transaction.
proxy.transaction.type: LOCAL
proxy.opentracing.enabled: false
proxy.hint.enabled: false
query.with.cipher.column: true
sql.show: true
allow.range.query.with.inline.sharding: false

3、编辑配置文件:config-sharding.yaml

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
schemaName: sharding_db

dataSources:
ds_0:
url: jdbc:mysql://192.168.200.129:3306/restkeeper-shop-0?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
username: root
password: pass
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
ds_1:
url: jdbc:mysql://192.168.200.151:3306/restkeeper-shop-1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
username: root
password: root
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50

shardingRule:
tables:
tb_order:
actualDataNodes: ds_${0..1}.tb_order_${0..1}
tableStrategy:
inline:
shardingColumn: order_no
algorithmExpression: tb_order_${order_no % 2}
keyGenerator:
type: SNOWFLAKE
column: id
tb_order_item:
actualDataNodes: ds_${0..1}.tb_order_item_${0..1}
tableStrategy:
inline:
shardingColumn: order_no
algorithmExpression: tb_order_item_${order_no % 2}
keyGenerator:
type: SNOWFLAKE
column: id
bindingTables:
- tb_order,tb_order_item

defaultDatabaseStrategy:
inline:
shardingColumn: sharding_id
algorithmExpression: ds_${sharding_id % 2}
defaultTableStrategy:
none:

4、添加mysql驱动包到lib目录

image-20210831001232354

5、启动服务

  • 使用默认配置项
1
${sharding-proxy}\bin\start.sh
  • 配置端口
1
${sharding-proxy}\bin\start.sh ${port}

6、使用cmd黑窗口连接测试建表

1
mysql  -P3307 -h127.0.0.1 -p
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
mysql> show databases;
+-------------+
| Database |
+-------------+
| sharding_db |
+-------------+
1 row in set (0.00 sec)

mysql> use sharding_db
Database changed
mysql>
mysql>
mysql>
mysql> show tables;
Empty set (0.01 sec)

mysql>
mysql>
mysql> CREATE TABLE `tb_order` (
-> `id` bigint(18) NOT NULL COMMENT '',
-> `order_no` bigint(18) NOT NULL COMMENT '',
-> `sharding_id` bigint(18) NOT NULL COMMENT '',
-> `store_id` bigint(18) NOT NULL COMMENT 'id',
-> PRIMARY KEY (`id`)
-> ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Query OK, 0 rows affected (0.16 sec)

mysql>

建表sql语句:

1
2
3
4
5
6
7
CREATE TABLE `tb_order` (
`id` bigint(18) NOT NULL COMMENT '',
`order_no` bigint(18) NOT NULL COMMENT '',
`sharding_id` bigint(18) NOT NULL COMMENT '',
`store_id` bigint(18) NOT NULL COMMENT 'id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

客户端查看效果:

image-20210831001705637

3、SpringBoot集成sharding-proxy

1、搭建测试工程引入pom依赖

2、导入基础工程

3、测试代码