官网:http://shardingsphere.apache.org/index_zh.html
官网概述:https://shardingsphere.apache.org/document/current/cn/overview/
Apache ShardingSphere 是一套开源的分布式数据库解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的数据水平扩展、分布式事务和分布式治理等功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。
shardingjdbc定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。
定位为透明化的数据库代理端,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持。
ShardingSphere-JDBC | ShardingSphere-Proxy | ShardingSphere-Sidecar | |
---|---|---|---|
数据库 | 任意 | MySQL/PostgreSQL | MySQL/PostgreSQL |
连接消耗数 | 高 | 低 | 高 |
异构语言 | 仅 Java | 任意 | 任意 |
性能 | 损耗低 | 损耗略高 | 损耗低 |
无中心化 | 是 | 否 | 是 |
静态入口 | 无 | 有 | 无 |
ShardingSphere-JDBC 采用无中心化架构,适用于 Java 开发的高性能的轻量级 OLTP(连接事务处理) 应用;ShardingSphere-Proxy 提供静态入口以及异构语言的支持,适用于 OLAP(连接数据分析) 应用以及对分片数据库进行管理和运维的场景。
Apache ShardingSphere 是多接入端共同组成的生态圈。 通过混合使用 ShardingSphere-JDBC 和 ShardingSphere-Proxy,并采用同一注册中心统一配置分片策略,能够灵活的搭建适用于各种场景的应用系统,使得架构师更加自由地调整适合与当前业务的最佳系统架构。
数据分片
分布式事务
数据库治理
ShardingSphere 的 3 个产品的数据分片主要流程是完全一致的。 核心由 SQL 解析 => 执行器优化 => SQL 路由 => SQL 改写 => SQL 执行 => 结果归并的流程组成。
SQL 解析
分为词法解析和语法解析。 先通过词法解析器将 SQL 拆分为一个个不可再分的单词。再使用语法解析器对 SQL 进行理解,并最终提炼出解析上下文。 解析上下文包括表、选择项、排序项、分组项、聚合函数、分页信息、查询条件以及可能需要修改的占位符的标记。
执行器优化
合并和优化分片条件,如 OR 等。
SQL 路由
根据解析上下文匹配用户配置的分片策略,并生成路由路径。目前支持分片路由和广播路由。
SQL 改写
将 SQL 改写为在真实数据库中可以正确执行的语句。SQL 改写分为正确性改写和优化改写。
SQL 执行
通过多线程执行器异步执行。
结果归并
将多个执行结果集归并以便于通过统一的 JDBC 接口输出。结果归并包括流式归并、内存归并和使用装饰者模式的追加归并这几种方式。
主从复制(也称 AB 复制)允许将来自一个MySQL数据库服务器(主服务器)的数据复制到一个或多个MySQL数据库服务器(从服务器)。其中复制是异步的 从站不需要永久连接以接收来自主站的更新。
优点
对于Mysql环境,本次系统使用了Centos8,Mysql版本为8.0
主服务器上面的任何修改都会通过自己的 I/O tread(I/O 线程)保存在二进制日志 Binary log 里面。
注:作为主服务器角色的数据库服务器必须开启二进制日志
/etc/my.cnf
(master节点执行)> vim /etc/my.cnf
[mysqld]
## 同一局域网内注意要唯一
server-id=100
## 开启二进制日志功能,可以随便取(关键)
log-bin=mysql-bin
## 复制过滤:不需要备份的数据库,不输出(mysql库一般不同步)
binlog-ignore-db=mysql
## 为每个session 分配的内存,在事务过程中用来存储二进制日志的缓存
binlog_cache_size=1M
## 主从复制的格式(mixed,statement,row,默认格式是statement)
binlog_format=mixed
/etc/my.cnf
(slave节点执行)> vim /etc/my.cnf
[mysqld]
## 设置server_id,注意要唯一
server-id=102
## 开启二进制日志功能,以备Slave作为其它Slave的Master时使用
log-bin=mysql-slave-bin
##复制过滤:不需要备份的数据库,不输出(mysql库一般不同步)
binlog-ignore-db=mysql
## 如果需要同步函数或者存储过程
log_bin_trust_function_creators=true
## 为每个session 分配的内存,在事务过程中用来存储二进制日志的缓存
binlog_cache_size=1M
## 主从复制的格式(mixed,statement,row,默认格式是statement)
binlog_format=mixed
## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。
## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致
slave_skip_errors=1062
# 在Master上执行
mysql -uroot -p(master的密码)
# 授予slave服务器可以同步master服务
CREATE user 'root'@'从机ip' IDENTIFIED WITH mysql_native_password by '从机数据库密码';
grant replication slave on *.* to 'root'@'从机ip';
# 刷新权限
flush privileges;
# 查看MySQL现在有哪些用户及对应的IP权限(可以不执行,只是一个查看)
select user,host from mysql.user;
show master status;
mysql -uroot -p(slave的密码)
#这里要保证master_log_file和master_log_pos和主机一致
change master to master_host='主机ip', master_user='root', master_password='主机数据库密码', master_port=3306, master_log_file='mysql-bin.000006',master_log_pos=1490;
#开始复制
start slave;
#查看slave状态
show slave status\G;
#停止复制
stop slave;
在主从复制操作的时候,不要基于去创建数据库或者相关操作,然后又去删除。若这样可能需要重新绑定主机位置
发生该问题一般是想远程连接root权限的数据库,这需要修改修改mysql权限表 。
##进入root权限的数据库后查看当前权限
use mysql;
select host,user from user;
##这里%代表全ip开放,正式环境可以选择ip
update user set host='%' where user='root';
##最后刷新一下权限即可
flush privileges;
使用start slave
开启主从复制过程后,如果SlaveIORunning一直是Connecting,则说明主从复制一直处于连接状态,这种情况一般是下面几种原因造成的,我们可以根据 Last_IO_Error提示予以排除。
stop slave;
set global sql_slave_skip_counter=1;
start slave;
show slave status\G ;
造成这类问题的原因一般是在主从复制的时候,基于创建表,然后又去删除和操作了数据表或者表。
show master status;
stop slave;
CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000006', MASTER_LOG_POS=1254;
start slave;
<properties>
<java.version>1.8java.version>
<sharding-sphere.version>4.0.0-RC1sharding-sphere.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.2.0version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.apache.shardingspheregroupId>
<artifactId>sharding-jdbc-spring-boot-starterartifactId>
<version>${sharding-sphere.version}version>
dependency>
<dependency>
<groupId>org.apache.shardingspheregroupId>
<artifactId>sharding-core-commonartifactId>
<version>${sharding-sphere.version}version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.22version>
dependency>
dependencies>
server:
port: 8085
spring:
main:
allow-bean-definition-overriding: true
shardingsphere:
# 参数配置,显示sql
props:
sql:
show: true
# 配置数据源
datasource:
# 给每个数据源取别名,下面的ds0,ds1,ds2任意取名字
names: ds0,ds1,ds2
# 给master-ds0每个数据源配置数据库连接信息
ds0:
# 配置druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://云服务器ip1:3306/test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
username: root
password: root
maxPoolSize: 100
minPoolSize: 5
# 配置ds1-slave
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://云服务器ip2:3306/test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
username: root
password: root
maxPoolSize: 100
minPoolSize: 5
# 配置ds2-slave
ds2:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://云服务器ip3:3306/test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
username: root
password: root
maxPoolSize: 100
minPoolSize: 5
# 配置默认数据源ds0
sharding:
# 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错。
default-data-source-name: ds0
# 配置数据源的读写分离,但是数据库一定要做主从复制
masterslave:
# 配置主从名称,可以任意取名字
name: ms
# 配置主库master,负责数据的写入
master-data-source-name: ds0
# 配置从库slave节点
slave-data-source-names: ds1,ds2
# 配置slave节点的负载均衡均衡策略,采用轮询机制
load-balance-algorithm-type: round_robin
# 整合mybatis的配置XXXXX
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.shawn.sharingjdbc.entity
entity
@Data
public class User {
// 主键
private long id;
// 昵称
private String nickname;
// 密码
private String password;
// 性别
private Integer sex;
//年龄
private Integer age;
// 生日
private Date birthday;
}
mapper
@Mapper
public interface UserMapper {
/**
* @description 保存用户
* @params [user]
*/
@Insert("insert into shawn_order_db(nickname,password,age,sex,birthday) values(#{nickname},#{password},#{age},#{sex},#{birthday})")
void addUser(User user);
/**
* @description 保存用户
* @params [user]
*/
@Select("select * from shawn_order_db")
List<User> findUsers();
}
controller
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserMapper userMapper;
@GetMapping("/save")
public String insert() throws ParseException {
User user = new User();
user.setNickname("shawn1"+ new Random().nextInt());
user.setPassword("1234567");
user.setSex(1);
user.setAge(22);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
user.setBirthday(simpleDateFormat.parse("1999-02-22"));
userMapper.addUser(user);
return "success";
}
@GetMapping("/listuser")
public List<User> listuser() {
return userMapper.findUsers();
}
}
acceptor.size: # accept连接的线程数量,默认为cpu核数2倍
executor.size: #工作线程数量最大,默认值: 无限制
max.connections.size.per.query: # 每个查询可以打开的最大连接数量,默认为1
check.table.metadata.enabled: #是否在启动时检查分表元数据一致性,默认值: false
proxy.frontend.flush.threshold: # proxy的服务时候,对于单个大查询,每多少个网络包返回一次
proxy.transaction.type: # 默认LOCAL,proxy的事务模型 允许LOCAL,XA,BASE三个值,LOCAL无分布式事务,XA则是采用atomikos实现的分布式事务 BASE目前尚未实现
proxy.opentracing.enabled: # 是否启用opentracing
proxy.backend.use.nio: # 是否采用netty的NIO机制连接后端数据库,默认False ,使用epoll机制
proxy.backend.max.connections: # 使用NIO而非epoll的话,proxy后台连接每个netty客户端允许的最大连接数量(注意不是数据库连接限制) 默认为8
proxy.backend.connection.timeout.seconds: #使用nio而非epoll的话,proxy后台连接的超时时间,默认60s
官网参考:https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-jdbc/usage/sharding/
分库分表目的:解决高并发,和数据量大的问题。
1、高并发情况下,会造成IO读写频繁,自然就会造成读写缓慢,甚至是宕机。一般单库不要超过2k并发,一个表数据建议不要超过500W。
2、数据量大的问题。主要由于底层索引实现导致,MySQL的索引实现为B+TREE,数据量其他,会导致索引树十分庞大,造成查询缓慢。第二,innodb的最大存储限制64TB。
分库分表又分为水平拆分和垂直拆分
**水平拆分:**统一个表的数据拆到不同的库不同的表中。可以根据时间、地区、或某个业务键维度,也可以通过hash进行拆分,最后通过路由访问到具体的数据。拆分后的每个表结构保持一致。
**垂直拆分:**就是把一个有很多字段的表给拆分成多个表,或者是多个库上去。每个库表的结构都不一样,每个库表都包含部分字段。一般来说,可以根据业务维度进行拆分,如订单表可以拆分为订单、订单支持、订单地址、订单商品、订单扩展等表;也可以,根据数据冷热程度拆分,20%的热点字段拆到一个表,80%的冷字段拆到另外一个表(拆分两个表建立1:1关系)。
对于数据同步,有全量数据同步(主从复制)和增量数据同步(Canal)两种
数据库复制了shawn_order_db
表变成shawn_order_db0
和shawn_order_db1
,对于yml配置文件
。配置成功后分库分表、单库分表等操作可以自定义实现。
server:
port: 8085
spring:
main:
allow-bean-definition-overriding: true
shardingsphere:
# 参数配置,显示sql
props:
sql:
show: true
# 配置数据源
datasource:
# 给每个数据源取别名,下面的ds1,ds2,ds3任意取名字
names: ds0,ds1,ds2
# 给master-ds0每个数据源配置数据库连接信息
ds0:
# 配置druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://服务器ip1:3306/test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
username: root
password: root
maxPoolSize: 100
minPoolSize: 5
# 配置ds1-slave
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://服务器ip2:3306/test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
username: root
password: root
maxPoolSize: 100
minPoolSize: 5
# 配置ds2-slave
ds2:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://服务器ip3:3306/test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
username: root
password: root
maxPoolSize: 100
minPoolSize: 5
sharding:
# 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错。
default-data-source-name: ds0
# 配置分表的规则
tables:
# shawn_order_db 逻辑表名
shawn_order_db:
# 数据节点:数据源$->{0..N}.逻辑表名$->{0..N}
actual-data-nodes: ds$->{0..1}.shawn_order_db$->{0..1}
# 根据需求自己修改
# actual-data-nodes: ds0.shawn_order_db$->{0..1}
# 拆分库策略,也就是什么样子的数据放入放到哪个数据库中。
database-strategy:
# 一般用id类数字
inline:
sharding-column: sex # 分片字段(分片键)
algorithm-expression: ds$->{sex % 2} # 分片算法表达式
# 拆分表策略,也就是什么样子的数据放入放到哪个数据表中。
table-strategy:
inline:
sharding-column: age # 分片字段(分片键)
algorithm-expression: shawn_order_db$->{age % 2} # 分片算法表达式
ShardingSphere提供灵活的配置分布式主键生成策略方式。在分片规则配置模块克配置每个表的主键生成策略。默认使用雪花算法。(snowflake)生成64bit的长整型数据。支持两种方式配置
切记:如果用雪花算法,数据库主键列不能自增长。数据类型是:bigint(20),在java类中使用long类型,不要用String类型;
如果用UUID,数据库用varchar,Java类用String,推荐用雪花,性能好
mapper修改
//这样能返回id值,直接控制台输出可以看见,这是Mybatis注解
@Insert("insert into shawn_order_db(nickname,password,age,sex,birthday) values(#{nickname},#{password},#{age},#{sex},#{birthday})")
@Options(useGeneratedKeys = true,keyColumn = "id",keyProperty = "id")
void addUser(User user);
yaml配置文件修改
#在配置文件相应位置添加
spring:
shardingsphere:
sharding:
tables:
# shawn_order_db 逻辑表名
shawn_order_db:
key-generator:
# 主键的列明,
column: id
type: SNOWFLAKE
按照年月分库分表,比如shawn_user_order_202101、shawn_user_order_202102,这样可以方便按照月份划分表,在运行前需要在数据库先创建好表。
下面配置仅供参考
策略类
public class YearMonthShardingAlgorithm implements PreciseShardingAlgorithm<String> {
private static final String SPLITTER = "_";
@Override
public String doSharding(Collection availableTargetNames, PreciseShardingValue shardingValue) {
String tbName = shardingValue.getLogicTableName() + "_" + shardingValue.getValue();
System.out.println("Sharding input:" + shardingValue.getValue() + ", output:{}" + tbName);
return tbName;
}
}
配置类,数据源和之前一样
spring:
shardingsphere:
# 配置默认数据源ds0
sharding:
# 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错。
default-data-source-name: ds0
# 配置分表的规则
tables:
shawn_order_db:
key-generator:
column: id
type: SNOWFLAKE
# 数据节点:数据源$->{0..N}.逻辑表名$->{0..N}
actual-data-nodes: ds$->{0..1}.shawn_order_db$->{0..1}
# 拆分库策略,也就是什么样子的数据放入放到哪个数据库中。
database-strategy:
standard:
shardingColumn: birthday
preciseAlgorithmClassName: com.shawnn.shardingjdbc.algorithm.BirthdayAlgorithm
table-strategy:
inline:
sharding-column: age # 分片字段(分片键)
algorithm-expression: shawn_order_db$->{age % 2} # 分片算法表达式
#第二个表
order_user_db:
# 数据节点:数据源$->{0..N}.逻辑表名$->{0..N}
actual-data-nodes: ds0.order_user_db$->{0..1}
key-generator:
column: orderid
type: SNOWFLAKE
# 拆分库策略,也就是什么样子的数据放入放到哪个数据库中。
table-strategy:
inline:
sharding-column: orderid # 分片字段(分片键)
algorithm-expression: order_user_db$->{orderid % 2} # 分片算法表达式
#第三个表
shawn_user_order:
# 数据节点:数据源$->{0..N}.逻辑表名$->{0..N}
actual-data-nodes: ds0.shawn_user_order_$->{2021..2022}${(1..3).collect{t ->t.toString().padLeft(2,'0')} }
key-generator:
column: orderid
type: SNOWFLAKE
# 拆分库策略,也就是什么样子的数据放入放到哪个数据库中。
table-strategy:
# inline:
# shardingColumn: yearmonth
# algorithmExpression: shawn_user_order_$->{yearmonth}
standard:
shardingColumn: yearmonth
preciseAlgorithmClassName: com.shawn.shardingjdbc.algorithm.YearMonthShardingAlgorithm
# 整合mybatis的配置XXXXX
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.shawn.shardingjdbc.entity
数据库事务需要满足ACID(原子性、一致性、隔离性、持久性)四个特性。
两阶段提交
XA协议最早的分布式事务模型是由X/Open国际联盟提出的X/Open Distributed Transaction Processing(DTP)模型,简称XA协议。
基于XA协议实现的分布式事务对业务侵入很小。 它最大的优势就是对使用方透明,用户可以像使用本地事务一样使用基于XA协议的分布式事务。 XA协议能够严格保障事务ACID特性。严格保障事务ACID特性是一把双刃剑。 事务执行在过程中需要将所需资源全部锁定,它更加适用于执行时间确定的短事务。 对于长事务来说,整个事务进行期间对数据的独占,将导致对热点数据依赖的业务系统并发性能衰退明显。 因此,在高并发的性能至上场景中,基于XA协议的分布式事务并不是最佳选择。
柔性事务
如果将实现了ACID的事务要素的事务称为刚性事务的话,那么基于BASE事务要素的事务则称为柔性事务。 BASE是基本可用、柔性状态和最终一致性这三个要素的缩写。
基本可用(Basically Available)保证分布式事务参与方不一定同时在线。柔性状态(Soft state)则允许系统状态更新有一定的延时,这个延时对客户来说不一定能够察觉。而最终一致性(Eventually consistent)通常是通过消息传递的方式保证系统的最终一致性。
在ACID事务中对隔离性的要求很高,在事务执行过程中,必须将所有的资源锁定。 柔性事务的理念则是通过业务逻辑将互斥锁操作从资源层面上移至业务层面。通过放宽对强一致性要求,来换取系统吞吐量的提升。
添加依赖
<dependency>
<groupId>io.shardingspheregroupId>
<artifactId>sharding-transaction-spring-boot-starterartifactId>
<version>3.1.0version>
dependency>
在service层添加注解
@Service
public class UserOrderService {
@Autowired
private UserMapper userMapper;
@Autowired
private OrderMapper orderMapper;
//加上依赖即可
@ShardingTransactionType(TransactionType.XA)
@Transactional(rollbackFor = Exception.class)
public int saveUserOrder(User user, Order order) {
userMapper.addUser(user);
order.setUserid(user.getId());
orderMapper.addOrder(order);
//int a = 1/0; //测试回滚,统一提交的话,将这行注释掉就行
return 1;
}
}
基础规范
列设计规范
索引规范
SQL规范
表的垂直拆分
垂直拆分:业务模块拆分、商品库,用户库,订单库
水平拆分:对表进行水平拆分(也就是我们说的:分表)
表进行垂直拆分:表的字段过多,字段使用的频率不一。(可以拆分两个表建立1:1关系)
原则:
如何平滑添加字段
场景:在开发时,有时需要给表加字段,在大数据量且分表的情况下,怎么样平滑添加。建议125
1:直接alter table add column,数据量大时不建议,(会产生写锁)
alter table ksd_user add column api_pay_no varchar(32) not null comment '用户扩展订单号'
alter table ksd_user add column api_pay_no varchar(32) not null unique comment '用户扩展订单号'
2:提前预留字段(不优雅:造成空间浪费,预留多少很难控制,拓展性差)
3:新增一张表,(增加字段),迁移原表数据,在重新命名新表作为原表。
4:放入extinfo(无法使用索引)
5: 提前设计,使用key/value方法存储,新增字段时 ,直接加一个key就好了(优雅)
学习参考
https://www.bilibili.com/video/BV1ei4y1K7dn?p=1
https://shardingsphere.apache.org/