实现水平分表
同一个数据库,多个表,把数据分到多个表里面
环境:spring boot 2.3.2.RELEASE
create database course_db;
use course_db;
create table course_1 (
cid bigint(20) primary key ,
cname varchar(50) not null,
user_id bigint(20) not null ,
cstatus varchar(10) not null
) engine = InnoDB;
create table course_2 (
cid bigint(20) primary key ,
cname varchar(50) not null,
user_id bigint(20) not null ,
cstatus varchar(10) not null
) engine = InnoDB;
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.20</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>3.0.5</version>
</dependency>
</dependencies>
实体类与mapper
public class Course {
private Long cid;
private String cname;
private Long userId;
private String cstatus;
}
@Repository
@Mapper
public interface CourseMapper extends BaseMapper<Course> {
}
水平分表配置
spring:
datasource:
url: jdbc:mysql://192.168.56.10:3306/course_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
#一个实体类可以对应两张表,覆盖
main:
allow-bean-definition-overriding: true
#配置数据源,给数据源起个别名
shardingsphere:
datasource:
names: m1
## 配置数据源的具体内容,包含连接池,驱动,地址,用户名,密码
m1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.56.10:3306/course_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: root
# 指定course表分布的情况,配置表在哪个数据库里,表的名称都是什么 m1.course_1,m1.course_2
sharding:
tables:
course:
actual-data-nodes: m1.course_$->{1..2}
# 指定 course 表里面主键 cid 的生成策略 SNOWFLAKE
key-generator:
column: cid
type: SNOWFLAKE
# 配置分表策略 约定 cid 值偶数添加到 course_1 表,如果 cid 是奇数添加到 course_2 表
table-strategy:
inline:
sharding-column: cid
algorithm-expression: course_$->{cid % 2 + 1}
props:
sql:
show: true
server:
port: 9090
测试
@Test
void contextLoads() {
for (int i = 0; i < 10; i++) {
Course course = new Course();
//cid由我们设置的策略,雪花算法进行生成
course.setCname("Java"+i);
course.setUserId(100L);
course.setCstatus("Normal"+i);
courseMapper.insert(course);
}
}
@Test
void contextLoads1() throws JsonProcessingException {
List<Course> courseList= courseMapper.selectList(new QueryWrapper<>());
System.out.println(objectMapper.writeValueAsString(courseList));
}
@Test
void contextLoads1() throws JsonProcessingException {
QueryWrapper queryWrapper=new QueryWrapper<>();
queryWrapper.orderByDesc("cid");
List<Course> courseList= courseMapper.selectList(queryWrapper);
System.out.println(objectMapper.writeValueAsString(courseList));
}
@Test
void contextLoads1() throws JsonProcessingException {
QueryWrapper queryWrapper=new QueryWrapper<>();
// queryWrapper.orderByDesc("cid");
queryWrapper.eq("cid",959068439920508928L);
List<Course> courseList= courseMapper.selectList(queryWrapper);
System.out.println(objectMapper.writeValueAsString(courseList));
}
实现水平分库
把一个表的数据拆分放到不同数据库
环境:两台服务器
//192.168.56.10
create database edu_db_1;
use edu_db_1;
create table course_1 (
`cid` bigint(20) primary key,
`cname` varchar(50) not null,
`user_id` bigint(20) not null,
`status` varchar(10) not null
);
create table course_2 (
`cid` bigint(20) primary key,
`cname` varchar(50) not null,
`user_id` bigint(20) not null,
`status` varchar(10) not null
);
//192.168.56.11
create database edu_db_2;
use edu_db_2;
create table course_1 (
`cid` bigint(20) primary key,
`cname` varchar(50) not null,
`user_id` bigint(20) not null,
`status` varchar(10) not null
);
create table course_2 (
`cid` bigint(20) primary key,
`cname` varchar(50) not null,
`user_id` bigint(20) not null,
`status` varchar(10) not null
);
spring:
main:
allow-bean-definition-overriding: true
shardingsphere:
#数据源别名
datasource:
names: m1,m2
#多个数据源配置 不同服务器的数据库
m1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.56.10:3306/edu_db_1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: root
m2:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.56.11:3306/edu_db_2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: root
# 指定数据库分布的情况和数据表分布的情况
# m1 m2 course_1 course_2
sharding:
tables:
course:
actual-data-nodes: m$->{1..2}.course_$->{1..2}
# cid 生成策略 雪花算法 或 UUID
key-generator:
type: SNOWFLAKE
column: cid
# 指定分库策略 约定 user_id 值偶数添加到 m1 库,如果 user_id 是奇数添加到 m2 库
# 分表是table-strategy
database-strategy:
inline:
sharding-column: user_id
algorithm-expression: m$->{user_id % 2 + 1}
# 指定分表策略 约定 cid 值偶数添加到 course_1 表,如果 cid 是奇数添加到 course_2 表
# 进入到库后的分表策略
table-strategy:
inline:
sharding-column: cid
algorithm-expression: course_$->{cid % 2 + 1}
props:
sql:
show: true
server:
port: 9091
@Test
public void addCourse() {
for (int i = 0; i < 100; i++) {
Course course = new Course();
//cid由我们设置的策略,雪花算法进行生成
course.setCname("python" + i);
//分库根据user_id
course.setUserId((long) (Math.random()*10+1));
course.setStatus("Normal" + i);
courseMapper.insert(course);
}
}
实现垂直分库
将表进行分类,分别部署在不同的数据库上面,每个库放到不同的服务器上,它的核心理念就是专库专用
create database user_db;
use user_db;
create table t_user(
`user_id` bigint(20) primary key,
`username` varchar(100) not null,
`status` varchar(50) not null
);
spring:
main:
allow-bean-definition-overriding: true
shardingsphere:
#数据源别名
datasource:
names: m1,m2,m3
#多个数据源配置 不同服务器的数据库
#其他的略
m3:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.56.10:3306/user_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: root
# 指定数据库分布的情况和数据表分布的情况
sharding:
tables:
t_user:
actual-data-nodes: m3.t_user
key-generator:
column: user_id
type: SNOWFLAKE
# 既然只有一张表,这个配置不太需要
table-strategy:
inline:
sharding-column: user_id
algorithm-expression: t_user
props:
sql:
show: true
server:
port: 9091
@Test
public void addUser(){
User user = new User();
user.setUsername("Jack1");
user.setStatus("Normal1");
userMapper.insert(user);
}
@Test
public void findUser() throws JsonProcessingException {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("user_id", 959126595728048129L);
System.out.println(objectMapper.writeValueAsString(userMapper.selectOne(wrapper)));
}
默认数据源
如果表没有进行配置进行配置的话会直接走默认数据源
spring.shardingsphere.sharding.default-data-source-name=m4
spring:
main:
allow-bean-definition-overriding: true
shardingsphere:
#数据源别名
datasource:
names: m1,m2,m3,m4
#多个数据源配置 不同服务器的数据库
m4:
url: jdbc:mysql://192.168.56.10:3306/mall-pms?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
# 指定数据库分布的情况和数据表分布的情况
# m1 m2 course_1 course_2
sharding:
#默认数据源
default-data-source-name: m4
公共表
- 存储固定数据的表,表数据很少发生变化,查询时经常要进行关联。
- 在每个数据库中都创建出相同结构公共表。
- 操作公共表时,同时操作添加了公共表的数据库中的公共表,添加记录时,同时添加,删除时,同时删除。
# 配置公共表
spring.shardingsphere.sharding.broadcast-tables=t_dict
spring.shardingsphere.sharding.tables.t_udict.key-generator.column=dict_id
spring.shardingsphere.sharding.tables.t_udict.key-generator.type=SNOWFLAKE
所以数据源的t_dict都会操作数据,但是主要事务问题
事务
读写分离
主从复制,是用来建立一个和主数据库完全一样的数据库环境,称为从数据库,主数据库一般是准实时的业务数据库。一台服务器充当主服务器,而另外一台服务器充当从服务器。
主库将变更写入 binlog 日志,然后从库连接到主库之后,从库有一个 IO 线程,将主库的 binlog 日志拷贝到自己本地,写入一个 relay 中继日志(relay log)中。接着从库中有一个 SQL 线程会从中继日志读取 binlog,然后执行 binlog 日志中的内容,也就是在自己本地再次执行一遍 SQL 语句,从而使从服务器和主服务器的数据保持一致。
从库会生成两个线程,一个 I/O 线程,一个 SQL 线程; I/O 线程会去请求主库的 binlog,并将得到的 binlog 写到本地的 relay-log(中继日志)文件中; 主库会生成一个 log dump 线程, 用来给从库 I/O 线程传 binlog; SQL 线程,会读取 relay log 文件中的日志,并解析成 sql 语句逐一执行。
从库同步主库数据的过程是串行化的,也就是说主库上并行的操作,在从库上会串行执行。由于从库从主库拷贝日志以及串行执行 SQL 的特点,在高并发场景下,从库的数据是有延时的。在实际运用中,时常会出现这样的情况,主库的数据已经有了,可从库还是读取不到,可能要过几十毫秒,甚至几百毫秒才能读取到。
- 半同步复制:解决主库数据丢失问题。也叫 semi-sync 复制,指的就是主库写入 binlog 日志之后,就会强制将数据立即同步到从库,从库将日志写入自己本地的 relay log 之后,接着会返回一个 ack 给主库,主库接收到至少一个从库的 ack 之后才会认为写操作完成了。
- 并行复制:解决从库复制延迟的问题。指的是从库开启多个线程,并行读取 relay log 中不同库的日志,然后并行存放不同库的日志,这是库级别的并行。
主从同步延迟问题
MySQL 可以通过 MySQL 命令 show slave status 获知当前是否主从同步正常工作。
另外一个重要指标就是 Seconds_Behind_Master,根据输出的 Seconds_Behind_Master 参数的值来判断:
- NULL,表示 io_thread 或是 sql_thread 有任何一个发生故障。
- 0,表示主从复制良好。
- 正值,表示主从已经出现延时,数字越大表示从库延迟越严重。
导致主从同步延迟情况
- 主库的从库太多,导致复制延迟。
- 从库硬件比主库差,导致复制延迟。
- 慢 SQL 语句过多。
- 主从复制的设计问题,例如主从复制单线程,如果主库写并发太大,来不及传送到从库,就会导致延迟。Mysql5.7之后可以支持多线程复制。设置参数slave_parallel_workers>0和slave_parallel_type=’LOGICAL_CLOCK’。
- 网络延迟。
主从同步解决方案
- 使用 PXC 架构
- 避免一些无用的 IO 消耗,可以上 SSD。
- IO 调度要选择 deadline 模式。
- 适当调整 buffer pool 的大小。
- 避免让数据库进行各种大量运算,数据库只是用来存储数据的,让应用端多分担些压力,或者可以通过缓存来完成。
mysql主从配置
两个数据库
进入到容器
#然后vim my.cnf配置主服务的配置
[mysqld]
## 同一局域网内注意要唯一
server-id=100
## 开启二进制日志功能,可以随便取(关键)
log-bin=mysql-bin
#重启主服务
[Err] 1055 - Expression #1 of ORDER BY clause is not in GROUP BY clause 解决办法 MySQL
select version(),@@sql_mode;
SET sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));
#新建一个用户专门用来同步master
CREATE USER 'backup'@'%' IDENTIFIED BY '123456';
#给backup用户分配备份的权限
GRANT REPLICATION SLAVE ON *.* to 'backup'@'%' identified by '123456';
#主库配置完成。查看主库状态:
show master status;
记住查询结果,后面会用。
File: mysql-bin.000001
Position: 1083
进入到容器
#然后vim my.cnf配置主服务的配置
#设置从mysql的id
server-id = 2
#启用中继日志
relay-log = mysql-relay
#忽略的数据库
replicate-ignore-db=mysql,infomational
#重启主服务
change master to master_host='192.168.56.11',
master_port=3306,
master_user='backup',
master_password='123456',
master_log_file='929a6b255828-bin.000006',
master_log_pos=155;
start slave;
stop slave;
start slave报错
> 1872 - Slave failed to initialize relay log info structure from the repository
mysql>stop slave;
mysql>reset slave;
测试:主库的操作会同步到从库
Sharding-jdbc实现读写分离
Sharding-JDBC 实现读写分离则是根据sql 语句语义分析,当 sql 语句有 insert、update、delete 时,Sharding-JDBC 就把这次操作在主数据库上执行;当 sql 语句有 select 时,就会把这次操作在从数据库上执行,从而实现读写分离过程。
但 Sharding-JDBC 并不会做数据同步,数据同步是配置 MySQL 后由 MySQL 自己完成的
#主库和从库上都建库建表 其实在主库建就好,从库也会同步操作
create database user_db;
use user_db;
create table t_user(
`user_id` bigint(20) primary key,
`username` varchar(100) not null,
`status` varchar(50) not null
);
spring:
# 一个实体类对应两张表,覆盖
main:
allow-bean-definition-overriding: true
shardingsphere:
# m1为用户数据库
datasource:
names: m1,m2
#user_db 主服务器
m1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.56.11:3306/user_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: root
#user_db 从服务器
m2:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.56.11:3307/user_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: root
sharding:
# 主库从库逻辑数据源定义 ds0 为 user_db
master-slave-rules:
ds0:
master-data-source-name: m1
slave-data-source-names: m2
# 配置user_db数据库里面t_user 专库专表
#spring.shardingsphere.sharding.tables.t_user.actual-data-nodes=m0.t_user
# t_user 分表策略,固定分配至 ds0 的 t_user 真实表
tables:
t_user:
key-generator:
type: SNOWFLAKE
column: user_id
table-strategy:
inline:
sharding-column: user_id
algorithm-expression: t_user
props:
sql:
show: true