ShardingJDBC
1. 概述
Apache ShardingSphere 是一个开源分布式数据库生态项目,旨在在最大限度地利用原始数据库的生存计算能力,进一步提供全球扩展和叠加计算能力。其核心采用数据库协议和可插拔架构 SQL 提供数据分片、访问路由、数据安全等多种增强功能的方法。
轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直接连接数据库,以 jar 以包的形式提供服务,不需要额外的部署和依赖,可以理解为增强版 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。
适用于任何基础 JDBC 的 ORM 框架,如:JPA,Hibernate, Mybatis,Spring JDBC Template 或直接使用 JDBC。
如:DBCP,C3P0,BoneCP,Druid,HikariCP 等。 支持任意实现 JDBC 目前支持标准化数据库 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 标准数据库。
- 功能列表
- 数据分片
- 分库 & 分表
- 读写分离
- 定制分片策略
- 无集中分布式主键
- 分布式事务
- 标准化事务接口
- XA 强一致事务
- 柔性事务
- 数据库治理
- 分布式治理
- 弹性伸缩
- 可视化链路跟踪
- 数据加密
SQL 解析
分为词法分析和语法分析。 先通过词法解析器将 SQL 分成不可分割的单词。然后使用语法分析器对其进行处理 SQL 理解并最终提取分析上下文。 分析上下文包括表、选择、排序、分组、聚合函数、分页信息、查询条件和可能需要修改的占位符标记。
查询优化
分片条件的合并和优化,如 OR 等。
SQL 路由
根据分析匹配用户配置的分片策略,生成路由路径。目前支持分片路由和广播路由。
SQL 改写
将 SQL 改写为可以在真实数据库中正确执行的句子。SQL 改写分为正确性改写和优化改写。
SQL 执行
多线程执行器异步执行。
结果归并
通过统一的方式,将多个执行结果集合并 JDBC 接口输出。结果合并包括流式合并、内存合并和装饰模式的附加合并。
2. MySQL安装
http://repo.mysql.com/yum/mysql-5.7-community/el/7/x86_64/
rpm -ivh http://repo.mysql.com/yum/mysql-5.7-community/el/7/x86_64/mysql57-community-release-el7-10.noarch.yum rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022 yum -y install mysql-community-server systemctl start mysqld # 启动MySQL systemctl status mysqlld # 检查启动状态 systemctl stop mysqld # 停止MySQL systemctl enable mysqld # 开机自启动 grep "password" /var/log/mysqld.log # 使用此命令查看默认密码 [root@summer1245 ~]# mysql -uroot -pQ5oZnaK.Sdjj 登录数据库 # 默认密码规则必须携带大小写字母和特殊符号,字符长度大于8,否则会报错。 # 因此,在设置相对简单的密码时,首先需要修改set global validate_password_policy和_length参数值。 set global validate_password_policy=0; set global validate_password_length=1; set password for root@localhost = password('root'); # 到目前为止,可视化客户端还不能连接,需要我们授权: grant all on *.* to root@'%' identified by 数据库密码; flush privileges; # 现在可以用了Navicat连接数据库 记得阿里云修改
3. 主从复制
主从复制,允许未来来自一个MySQL复制到一个或多个数据库服务器(主)MySQL(从)数据库服务器。
复制过程是异步的,可以配置复制数据库或某个表,而不需要永久连接主服务器的更新。
复制主从需要保证一个前提:主服务器必须打开二进制日志
大体流程:
- 从服务器上开始 I/O thread,将用户名和密码连接到主服务器,请求读取二进制日志,然后读取二进制日志写到本地的一个Realy log(中继日志)里面。
- 从服务器上面同时开启一个 SQL thread 定时检查 Realy log(这个文件也是二进制的),如果发现有更新立即把更新的内容在本机的数据库上面执行一遍。每个从服务器都会收到主服务器二进制日志的全部内容的副本。
- 从服务器设备负责决定应该执行二进制日志中的哪些语句。 除非指定,否则主二进制日志中的所有事件都在从上执行,如果需要,可以将从配置为仅处理一些特定数据库或表的事件。
# 在主服务器修改以下配置
vim /etc/my.cnf
## 同一局域网内注意要唯一
server-id=100
## 开启二进制日志功能,可以随便取(关键)
log-bin=mysql-bin
## 复制过滤:不需要备份的数据库,不输出(mysql库一般不同步)
binlog-ignore-db=mysql
## 为每个session 分配的内存,在事务过程中用来存储二进制日志的缓存
binlog_cache_size=1M
## 主从复制的格式(mixed,statement,row,默认格式是statement)
binlog_format=mixed
# 修改后重启
# 在从服务器修改以下配置
> vim /etc/my.cnf
[mysqld]
## 设置server_id,注意要唯一
server-id=102
## 开启二进制日志功能,以备Slave作为其它Slave的Master时使用
log-bin=mysql-slave-bin
## relay_log配置中继日志
relay_log=edu-mysql-relay-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
在主服务器执行
# 授予slave服务器可以同步master服务
grant replication slave, replication client on *.* to 'root'@'slave服务的ip' identified by 'slave服务器的密码';
flush privileges;
# 查看MySQL现在有哪些用户及对应的IP权限(可以不执行,只是一个查看)
select user,host from mysql.user;
查询master服务的binlog文件名和位置
show master status;
slave进行关联master节点
change master to master_host='master服务器ip', master_user='root', master_password='master数据库密码', master_port=3306, master_log_file='mysql-bin.000001',master_log_pos=626;
slave查看主从同步状态
# 启动主从复制
start slave;
# 查看主从同步状态
show slave status\G;
# 停止复制
stop slave;
- Slave_IO_Running:Connecting
# 检查以下内容
# 网络不通
# 检查ip,端口
# 密码不对
# 检查是否创建用于同步的用户和用户密码是否正确
# pos不对
# Position不对
- Slave_SQL_Running: No
造成这类问题的原因一般是在主从复制的时候,基于创建表,然后又去删除和操作了数据表或者表。
# 解决方案1
# 先stop slave,然后执行了一下提示的语句,再
stop slave;
set global sql_slave_skip_counter=1;
start slave;
show slave status\G;
# 解决方案2
# master节点执行,获取日志文件和position
show master status;
# slave节点重新绑定
stop slave;
CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=626;
start slave;
4. 代码具体实现
创建一个SpringBoot项目
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-core-common</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.22</version>
</dependency>
配置文件
server:
port: 8085
spring:
main:
allow-bean-definition-overriding: true
shardingsphere:
# 参数配置,显示sql
props:
sql:
show: true
# 配置数据源
datasource:
# 给每个数据源取别名,下面的ds1,ds2,ds3任意取名字
names: ds1,ds2,ds3
# 给master-ds1每个数据源配置数据库连接信息
ds1:
# 配置druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://ip: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://ip:3306/test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
username: root
password: root
maxPoolSize: 100
minPoolSize: 5
# 配置ds3-slave
ds3:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://ip:3306/test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
username: root
password: root
maxPoolSize: 100
minPoolSize: 5
# 配置默认数据源ds1
sharding:
# 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错。
default-data-source-name: ds1
# 配置数据源的读写分离,但是数据库一定要做主从复制
masterslave:
# 配置主从名称,可以任意取名字
name: ms
# 配置主库master,负责数据的写入
master-data-source-name: ds1
# 配置从库slave节点
slave-data-source-names: ds2,ds3
# 配置slave节点的负载均衡均衡策略,采用轮询机制 随机的话是random
load-balance-algorithm-type: round_robin
# 整合mybatis的配置XXXXX
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.hua.pojo
如果不做上面的shardingjdbc配置,那么shardingjdbc会采用随机的方式进行选择数据源。如果不配置default-data-source-name,那么就会把三个节点都当做从slave节点,那么新增,修改和删除会出错。
@Repository
public interface UserMapper {
int addUser(User user);
List<User> getUserList();
}
主函数记得扫描包
@SpringBootApplication
@MapperScan("com.hua.dao")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hua.dao.UserMapper">
<insert id="addUser" parameterType="user">
insert into user(id,name,age)
values(#{id},#{name},#{age})
</insert>
<select id="getUserList" resultType="user">
select * from user
</select>
</mapper>
userService
public interface UserService {
int addUser(User user);
List<User> getUserList();
}
UserServiceImpl
@Service
public class UserServiceImpl implements UserService{
@Autowired
UserMapper userMapper;
@Override
public int addUser(User user) {
return userMapper.addUser(user);
}
@Override
public List<User> getUserList() {
return userMapper.getUserList();
}
}
UserController
@RestController
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/add")
public String addUser(){
User user = new User();
user.setId(1);
user.setName("小明");
user.setAge(15);
int i = userService.addUser(user);
if (i>0){
return "添加成功";
}else{
return "添加失败";
}
}
@RequestMapping("/getUserlist")
public List<User> getUserList(){
return userService.getUserList();
}
}
SQL: insert into user(name,age) values(?,?) ::: DataSources: ds1
# 执行插入的时候是在主节点
# 执行查询的时候是在从节点 两个从节点轮流读
数据库中需要有user表
读写分离建立在主从复制的基础上
5. 分库分表
- 高并发的情况下,会造成IO读写频繁,自然就造成读写缓慢,甚至宕机。一般单库不要超过2k并发。
- 数据量大的问题,由于MySQL的索引使用的B+Tree,数据量很大的话,会导致索引树特别大,造成查询缓慢。innodb的最大存储限制在64TB.
目的:解决高并发,数据量大的问题。将一个表拆分为N个表,让每个表的数据量控制在一定范围内,保证性能。一个表的数据量不要超过500W。
- 水平拆分:统一将一个表的数据拆分到不同库不同表,可以根据时间、地区或某个业务维度。拆分后的表结构一致。
- 垂直拆分:将有很多字段的表拆分为多个表或者多个库,拆分后的表结构不一致,可以根据业务维度进行拆分,如订单表可以拆分为订单、订单支持、订单地址等;也可以根据冷热程度拆分,20%的热点字段拆到一个表,80%冷门字段拆到另一个表。
总之就是垂直拆分就是按照业务来拆,水平拆分就是对表进行拆分,表的垂直拆分就是表的字段过多,字段使用的频率不一,此时可以拆为两个一对一关系的表
水平拆分数据库或者数据表的相同数据结构表的总称,例用户表拆分为两个user0和user1,逻辑表名就是user
spring:
shardingsphere:
sharding:
tables:
# user 逻辑表名
user:
tables:
# user 逻辑表名
user:
# 数据节点:多数据源$->{0..N}.逻辑表名$->{0..N} 相同表
actual-data-nodes: ds$->{
0..2}.user$->{
0..1}
# 数据节点:多数据源$->{0..N}.逻辑表名$->{0..N} 不同表
actual-data-nodes: ds0.user$->{
0..1},ds1.user$->{
2..4}
# 指定单数据源的配置方式
actual-data-nodes: ds0.user$->{
0..4}
# 全部手动指定
actual-data-nodes: ds0.user0,ds1.user0,ds0.user1,ds1.user1,
https://www.kuangstudy.com/zl/sharding#1369532584442216449
数据源分片:
- 数据源分片(分片键、分片算法)
- 表分片(分片键、分片算法)
- none
对应NoneShardingStragey 不分片策略 sql会发给所有的节点
- inline(重点)
对应InlineShardingStragey 使用Groovy表达式。提供对sql语句中的=和in的支持,只支持单分片键,例如user$(userid%5) 表示user表通过字段userid模5进行分表,分为user0–user4
server:
port: 8085
spring:
main:
allow-bean-definition-overriding: true
shardingsphere:
props:
sql:
show: true
datasource:
names: ds0,ds1
ds0:
# 配置druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://ip:3306/test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
username: root
password: root
maxPoolSize: 100
minPoolSize: 5
# 配置ds2-slave
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://ip:3306/test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
username: root
password: root
maxPoolSize: 100
minPoolSize: 5
sharding:
default-data-source-name: ds0
tables:
user:
actual-data-nodes: ds$->{
0..1}.user$->{
0..1}
database-strategy:
inline:
sharding-column: age # 分片字段(分片键)
algorithm-expression: ds$->{
age%2} # 分片算法表达式
# 拆分表策略,也就是什么样子的数据放入放到哪个数据表中。
table-strategy:
inline:
sharding-column: age # 分片字段(分片键)
algorithm-expression: user$->{
age%2} # 分片算法表达式
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.hua.pojo
@Autowired
UserService userService;
@Test
void contextLoads() {
User user = new User();
user.setName("小明");
user.setAge(15); // 15%2=1 所以此条数据就会放到 1库1表中
userService.addUser(user);
}
server: port: 8085 spring: main: allow-bean-definition-overriding: true shardingsphere: # 参数配置,显示sql props: sql: show: true # 配置数据源 datasource: # 给每个数据源取别名,下面的ds1,ds1任意取名字 names: ds0,ds1 # 给master-ds1每个数据源配置数据库连接信息 ds0: # 配置druid数据源 type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://ip:3306/ksd-sharding-db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT 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://ip:3306/ksd-sharding-db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT username: root password: root maxPoolSize: 100 minPoolSize: 5 # 配置默认数据源ds0 sharding: default-data-source-name: ds0 # 配置分表的规则 tables: # user 逻辑表名 user: # 数据节点:数据源$->{0..N}.逻辑表名$->{0..N} actual-data-nodes: ds$->{ 0..1}.user$->{ 0..1} # 拆分库策略,也就是什么样子的数据放入放到哪个数据库中。 database-strategy: inline: sharding-column: sex # 分片字段(分片键) algorithm-expression: ds$- 标签:
selet传感器b08系列