ShardingSphere 分库分表
什么是 ShardingSphere
Apache ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(布局中)这 3 款互相独立,却又可能混合部署配合应用的产品组成。它们均提供标准化的数据分片、分布式事务和数据库治理性能,可实用于如 Java 同构、异构语言、云原生等各种多样化的利用场景。
- 一套开源的分布式数据库中间件解决方案。
- 有三个产品:JDBC、Proxy、Sidecar。
什么是分库分表
当咱们应用读写拆散、索引、缓存后,数据库的压力还是很大的时候,这就须要应用到数据库拆分了。
数据库拆分简略来说,就是指通过某种特定的条件,依照某个维度,将咱们寄存在同一个数据库中的数据扩散寄存到多个数据库(主机)下面以达到扩散单库(主机)负载的成果。
分库分表之垂直拆分
专库专用。一个数据库由很多表的形成,每个表对应着不同的业务,垂直切分是指 依照业务将表进行分类,散布到不同的数据库下面,这样也就将数据或者说压力分担到不同的库下面。如下图:
长处:
- 拆分后业务清晰,拆分规定明确。
- 零碎之间整合或扩大容易。
- 数据保护简略。
毛病:
- 局部业务表无奈 join,只能通过接口方式解决,进步了零碎复杂度。
- 受每种业务不同的限度存在单库性能瓶颈,不易数据扩大跟性能进步。
- 事务处理简单。
分库分表之程度切分
垂直拆分后遇到单机瓶颈,能够应用程度拆分。绝对于垂直拆分的区别是:垂直拆分是把不同的表拆到不同的数据库中,而程度拆分是把同一个表拆到不同的数据库中。
绝对于垂直拆分,程度拆分不是将表的数据做分类,而是依照某个字段的某种规定来扩散到多个库之中,每个表中蕴含一部分数据。简略来说,咱们能够将数据的程度切分了解为是依照数据行的切分,就是将表中的某些行切分到一个数据库,而另外的某些行又切分到其余的数据库中,次要有分表,分库两种模式。如下图:
长处:
- 不存在单库大数据,高并发的性能瓶颈。
- 对利用通明,利用端革新较少。
- 依照正当拆分规定拆分,join 操作根本防止跨库。
- 进步了零碎的稳定性跟负载能力。
毛病:
- 拆分规定难以形象。
- 分片事务一致性难以解决。
- 数据屡次扩大难度跟保护量极大。
- 跨库 join 性能较差。
什么是 ShardingSphere-JDBC
定位为轻量级 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 规范的数据库。
须要留神的是,分库分表并不是由 ShardingSphere-JDBC 来做,它是用来负责操作曾经分完之后的 CRUD 操作。
Sharding-JDBC 分表实操
环境应用:Springboot 2.2.11 + MybatisPlus + ShardingSphere-JDBC 4.0.0-RC1 + Druid 连接池
具体 Maven 依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</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-RC1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
依照程度分表来创立数据库
- 创立数据库 course_db
- 创立表 course_1、course_2
- 约定规定:如果增加的课程 id 为偶数增加到 course_1 中,奇数增加到 course_2 中。
SQL 如下:
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 ,
status 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 ,
status varchar(10) not null
) engine = InnoDB;
配置对应实体类以及 Mapper
/**
* @author 又坏又迷人
* 公众号: Java 菜鸟程序员
* @date 2020/11/19
* @Description: Course 实体类
*/
@Data
public class Course {
private Long cid;
private String cname;
private Long userId;
private String status;
}
mapper:
/**
* @author 又坏又迷人
* 公众号: Java 菜鸟程序员
* @date 2020/11/19
* @Description: mapper
*/
@Repository
@MapperScan("com.jack.shardingspherejdbc.mapper")
public interface CourseMapper extends BaseMapper<Course> {}
启动类配置 MapperScan
@SpringBootApplication
@MapperScan("com.jack.shardingspherejdbc.mapper")
public class ShardingsphereJdbcDemoApplication {public static void main(String[] args) {SpringApplication.run(ShardingsphereJdbcDemoApplication.class, args);
}
}
配置 Sharding-JDBC 分片策略
application.properties 内容:
# sharding-jdbc 程度分表策略
# 配置数据源,给数据源起别名
spring.shardingsphere.datasource.names=m1
# 一个实体类对应两张表,笼罩
spring.main.allow-bean-definition-overriding=true
# 配置数据源的具体内容,蕴含连接池,驱动,地址,用户名,明码
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://localhost:3306/course_db?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=123456
# 指定 course 表散布的状况,配置表在哪个数据库里,表的名称都是什么 m1.course_1,m1.course_2
spring.shardingsphere.sharding.tables.course.actual-data-nodes=m1.course_$->{1..2}
# 指定 course 表外面主键 cid 的生成策略 SNOWFLAKE
spring.shardingsphere.sharding.tables.course.key-generator.column=cid
spring.shardingsphere.sharding.tables.course.key-generator.type=SNOWFLAKE
# 配置分表策略 约定 cid 值偶数增加到 course_1 表,如果 cid 是奇数增加到 course_2 表
spring.shardingsphere.sharding.tables.course.table-strategy.inline.sharding-column=cid
spring.shardingsphere.sharding.tables.course.table-strategy.inline.algorithm-expression=course_$->{cid % 2 + 1}
# 关上 sql 输入日志
spring.shardingsphere.props.sql.show=true
测试代码运行
@RunWith(SpringRunner.class)
@SpringBootTest
class ShardingsphereJdbcDemoApplicationTests {
@Autowired
private CourseMapper courseMapper;
// 增加课程
@Test
public void addCourse() {Course course = new Course();
//cid 由咱们设置的策略,雪花算法进行生成
course.setCname("Java");
course.setUserId(100L);
course.setStatus("Normal");
courseMapper.insert(course);
}
}
运行后果
咱们查问一下看看:
@Test
public void findCourse() {QueryWrapper<Course> wrapper = new QueryWrapper<>();
wrapper.eq("cid", 536248443081850881L);
courseMapper.selectOne(wrapper);
}
能够看到查问的表也是正确的。
Sharding-JDBC 实现程度分库
需要:
- 创立两个数据库,edu_db_1、edu_db_2。
- 每个库中蕴含:course_1、course_2。
- 数据库规定:userid 为偶数增加到 edu_db_1 库,奇数增加到 edu_db_2。
- 表规定:如果增加的 cid 为偶数增加到 course_1 中,奇数增加到 course_2 中。
创立数据库和表构造
create database edu_db_1;
create database edu_db_2;
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
);
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
);
配置分片策略
application.properties 内容:
# sharding-jdbc 程度分库分表策略
# 配置数据源,给数据源起别名
# 程度分库须要配置多个数据库
spring.shardingsphere.datasource.names=m1,m2
# 一个实体类对应两张表,笼罩
spring.main.allow-bean-definition-overriding=true
# 配置第一个数据源的具体内容,蕴含连接池,驱动,地址,用户名,明码
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://localhost:3306/edu_db_1?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=123456
# 配置第二个数据源的具体内容,蕴含连接池,驱动,地址,用户名,明码
spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m2.url=jdbc:mysql://localhost:3306/edu_db_2?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m2.username=root
spring.shardingsphere.datasource.m2.password=123456
# 指定数据库散布的状况和数据表散布的状况
# m1 m2 course_1 course_2
spring.shardingsphere.sharding.tables.course.actual-data-nodes=m$->{1..2}.course_$->{1..2}
# 指定 course 表外面主键 cid 的生成策略 SNOWFLAKE
spring.shardingsphere.sharding.tables.course.key-generator.column=cid
spring.shardingsphere.sharding.tables.course.key-generator.type=SNOWFLAKE
# 指定分库策略 约定 user_id 值偶数增加到 m1 库,如果 user_id 是奇数增加到 m2 库
# 默认写法(所有的表的 user_id)#spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=user_id
#spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=m$->{user_id % 2 + 1}
# 指定只有 course 表的 user_id
spring.shardingsphere.sharding.tables.course.database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.course.database-strategy.inline.algorithm-expression=m$->{user_id % 2 + 1}
# 指定分表策略 约定 cid 值偶数增加到 course_1 表,如果 cid 是奇数增加到 course_2 表
spring.shardingsphere.sharding.tables.course.table-strategy.inline.sharding-column=cid
spring.shardingsphere.sharding.tables.course.table-strategy.inline.algorithm-expression=course_$->{cid % 2 + 1}
# 关上 sql 输入日志
spring.shardingsphere.props.sql.show=true
测试代码运行
@Test
public void addCourse() {Course course = new Course();
//cid 由咱们设置的策略,雪花算法进行生成
course.setCname("python");
// 分库依据 user_id
course.setUserId(100L);
course.setStatus("Normal");
courseMapper.insert(course);
course.setCname("c++");
course.setUserId(111L);
courseMapper.insert(course);
}
对应的咱们 python 的 userId 为偶数所以增加到 edu_db_1 库中,而 c++ 是奇数所以增加到 edu_db_2 库中。
运行后果
看下对应的数据库数据,也是没有问题的。
Sharding-JDBC 实现垂直分库
需要:
咱们再额定创立一个 user_db 数据库。当咱们查问用户信息就去 user_db,课程信息就去 edu_db_1、edu_db_2。
创立数据库和表构造
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
);
配置对应实体类和 Mapper
实体类:
/**
* @author 又坏又迷人
* 公众号: Java 菜鸟程序员
* @date 2020/11/20
* @Description:t_user 实体类
*/
@Data
@TableName("t_user")
public class User {
private Long userId;
private String username;
private String status;
}
mapper:
/**
* @author 又坏又迷人
* 公众号: Java 菜鸟程序员
* @date 2020/11/20
* @Description: UserMapper
*/
@Repository
public interface UserMapper extends BaseMapper<User> {}
配置分片策略
application.properties 内容:
# sharding-jdbc 程度分库分表策略
# 配置数据源,给数据源起别名
# 程度分库须要配置多个数据库
# m0 为用户数据库
spring.shardingsphere.datasource.names=m1,m2,m0
# 一个实体类对应两张表,笼罩
spring.main.allow-bean-definition-overriding=true
# 配置第一个数据源的具体内容,蕴含连接池,驱动,地址,用户名,明码
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://localhost:3306/edu_db_1?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=123456
# 配置第二个数据源的具体内容,蕴含连接池,驱动,地址,用户名,明码
spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m2.url=jdbc:mysql://localhost:3306/edu_db_2?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m2.username=root
spring.shardingsphere.datasource.m2.password=123456
# 配置 user 数据源的具体内容,蕴含连接池,驱动,地址,用户名,明码
spring.shardingsphere.datasource.m0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m0.url=jdbc:mysql://localhost:3306/user_db?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m0.username=root
spring.shardingsphere.datasource.m0.password=123456
# 配置 user_db 数据库外面 t_user 专库专表
spring.shardingsphere.sharding.tables.t_user.actual-data-nodes=m0.t_user
# 配置主键的生成策略
spring.shardingsphere.sharding.tables.t_user.key-generator.column=user_id
spring.shardingsphere.sharding.tables.t_user.key-generator.type=SNOWFLAKE
# 指定分表策略
spring.shardingsphere.sharding.tables.t_user.table-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.t_user.table-strategy.inline.algorithm-expression=t_user
# 指定数据库散布的状况和数据表散布的状况
# m1 m2 course_1 course_2
spring.shardingsphere.sharding.tables.course.actual-data-nodes=m$->{1..2}.course_$->{1..2}
# 指定 course 表外面主键 cid 的生成策略 SNOWFLAKE
spring.shardingsphere.sharding.tables.course.key-generator.column=cid
spring.shardingsphere.sharding.tables.course.key-generator.type=SNOWFLAKE
# 指定分库策略 约定 user_id 值偶数增加到 m1 库,如果 user_id 是奇数增加到 m2 库
# 默认写法(所有的表的 user_id)#spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=user_id
#spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=m$->{user_id % 2 + 1}
# 指定只有 course 表的 user_id
spring.shardingsphere.sharding.tables.course.database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.course.database-strategy.inline.algorithm-expression=m$->{user_id % 2 + 1}
# 指定分表策略 约定 cid 值偶数增加到 course_1 表,如果 cid 是奇数增加到 course_2 表
spring.shardingsphere.sharding.tables.course.table-strategy.inline.sharding-column=cid
spring.shardingsphere.sharding.tables.course.table-strategy.inline.algorithm-expression=course_$->{cid % 2 + 1}
# 关上 sql 输入日志
spring.shardingsphere.props.sql.show=true
测试代码运行
@Autowired
private UserMapper userMapper;
@Test
public void addUser(){User user = new User();
user.setUsername("Jack");
user.setStatus("Normal");
userMapper.insert(user);
}
@Test
public void findUser() {QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("user_id", 536472243283165185L);
userMapper.selectOne(wrapper);
}
增加办法运行后果
查询方法运行后果
Sharding-JDBC 公共表
概念
- 存储固定数据的表,表数据很少发生变化,查问时常常要进行关联。
- 在每个数据库中都创立出雷同构造公共表。
- 操作公共表时,同时操作增加了公共表的数据库中的公共表,增加记录时,同时增加,删除时,同时删除。
在多个数据库中创立公共表
# use user_db;
# use edu_db_1;
use edu_db_2;
create table t_dict(`dict_id` bigint(20) primary key,
`status` varchar(100) not null,
`value` varchar(100) not null
);
配置公共表的实体类和 mapper
实体类:
/**
* @author 又坏又迷人
* 公众号: Java 菜鸟程序员
* @date 2020/11/20
* @Description:Dict 实体类
*/
@Data
@TableName("t_dict")
public class Dict {
private Long dictId;
private String status;
private String value;
}
mapper:
/**
* @author 又坏又迷人
* 公众号: Java 菜鸟程序员
* @date 2020/11/20
* @Description: DictMapper
*/
@Repository
public interface DictMapper extends BaseMapper<Dict> {}
配置分片策略
application.properties:
# sharding-jdbc 程度分库分表策略
# 配置数据源,给数据源起别名
# 程度分库须要配置多个数据库
# m0 为用户数据库
spring.shardingsphere.datasource.names=m1,m2,m0
# 一个实体类对应两张表,笼罩
spring.main.allow-bean-definition-overriding=true
# 配置第一个数据源的具体内容,蕴含连接池,驱动,地址,用户名,明码
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://localhost:3306/edu_db_1?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=123456
# 配置第二个数据源的具体内容,蕴含连接池,驱动,地址,用户名,明码
spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m2.url=jdbc:mysql://localhost:3306/edu_db_2?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m2.username=root
spring.shardingsphere.datasource.m2.password=123456
# 配置 user 数据源的具体内容,蕴含连接池,驱动,地址,用户名,明码
spring.shardingsphere.datasource.m0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m0.url=jdbc:mysql://localhost:3306/user_db?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m0.username=root
spring.shardingsphere.datasource.m0.password=123456
# 配置 user_db 数据库外面 t_user 专库专表
spring.shardingsphere.sharding.tables.t_user.actual-data-nodes=m0.t_user
# 配置主键的生成策略
spring.shardingsphere.sharding.tables.t_user.key-generator.column=user_id
spring.shardingsphere.sharding.tables.t_user.key-generator.type=SNOWFLAKE
# 指定分表策略
spring.shardingsphere.sharding.tables.t_user.table-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.t_user.table-strategy.inline.algorithm-expression=t_user
# 指定数据库散布的状况和数据表散布的状况
# m1 m2 course_1 course_2
spring.shardingsphere.sharding.tables.course.actual-data-nodes=m$->{1..2}.course_$->{1..2}
# 指定 course 表外面主键 cid 的生成策略 SNOWFLAKE
spring.shardingsphere.sharding.tables.course.key-generator.column=cid
spring.shardingsphere.sharding.tables.course.key-generator.type=SNOWFLAKE
# 指定分库策略 约定 user_id 值偶数增加到 m1 库,如果 user_id 是奇数增加到 m2 库
# 默认写法(所有的表的 user_id)#spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=user_id
#spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=m$->{user_id % 2 + 1}
# 指定只有 course 表的 user_id
spring.shardingsphere.sharding.tables.course.database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.course.database-strategy.inline.algorithm-expression=m$->{user_id % 2 + 1}
# 指定分表策略 约定 cid 值偶数增加到 course_1 表,如果 cid 是奇数增加到 course_2 表
spring.shardingsphere.sharding.tables.course.table-strategy.inline.sharding-column=cid
spring.shardingsphere.sharding.tables.course.table-strategy.inline.algorithm-expression=course_$->{cid % 2 + 1}
# 公共表配置
spring.shardingsphere.sharding.broadcast-tables=t_dict
# 配置主键的生成策略
spring.shardingsphere.sharding.tables.t_dict.key-generator.column=dict_id
spring.shardingsphere.sharding.tables.t_dict.key-generator.type=SNOWFLAKE
# 关上 sql 输入日志
spring.shardingsphere.props.sql.show=true
测试代码运行
@Autowired
private DictMapper dictMapper;
@Test
public void addDict() {Dict dict = new Dict();
dict.setStatus("Normal");
dict.setValue("启用");
dictMapper.insert(dict);
}
@Test
public void deleteDict() {QueryWrapper<Dict> wrapper = new QueryWrapper<>();
wrapper.eq("dict_id", 536486065947541505L);
dictMapper.delete(wrapper);
}
增加办法运行后果
删除办法运行后果
什么是读写拆散
理解读写拆散前,咱们先理解下什么是主从复制。
主从复制,是用来建设一个和主数据库齐全一样的数据库环境,称为 从数据库,主数据库个别是准实时的业务数据库。一台服务器充当主服务器,而另外一台服务器充当从服务器。
主从复制原理
主库将变更写入 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 环境
我应用的是两台 Centos7 虚拟机,主服务器 IP 为:192.168.3.107,从服务器 IP:192.168.3.108。
MySQL 环境为:8.0.15。
这里不讲如何搭建 MySQL 环境了。
首先咱们进入 主服务器 输出以下命令:
vim /etc/my.cnf
在 [mysqld] 节点下退出:
# 设置主 mysql 的 id
server-id = 1
#启用二进制日志
log-bin=mysql-bin
#设置 logbin 格局
binlog_format = STATEMENT
也能够退出 binlog-do-db 来指定同步的数据库,或者应用 binlog-ignore-db 来疏忽同步的数据库,如果不写则同步所有数据库!
而后咱们进入 从服务器 输出以下命令:
vim /etc/my.cnf
在 [mysqld] 节点下退出:
# 设置从 mysql 的 id
server-id = 2
#启用中继日志
relay-log = mysql-relay
最初咱们应用上面命令在主和从都执行,重启 MySQL 服务器。
/etc/init.d/mysqld restart
以上结束之后咱们登录 主服务器 的 MySQL。
mysql -u root -p
进入 MySQL 后执行以下命令:
# 创立用于主从复制的账号 db_sync,明码 db_sync
create user 'db_sync'@'%' identified with mysql_native_password by 'db_sync';
#受权
grant replication slave on *.* to 'db_sync'@'%';
#刷新权限
FLUSH PRIVILEGES;
而后咱们执行以下命令,记得 file
和position
的值!
show master status;
以上结束之后咱们登录 从服务器 的 MySQL。
mysql -u root -p;
进入 MySQL 后执行以下命令:
STOP SLAVE;
接着咱们输出命令来连贯主服务器:
# 批改从库指向到主库
# master_host 主 ip 地址
# master_port 主 mysql 裸露的端口
# master_user 主 mysql 的用户名
# master_password 主 mysql 的明码
# master_log_file 填写方才查看到的 file
# master_log_pos 填写方才查看到的 position
CHANGE MASTER TO
master_host = '192.168.3.107',
master_port = 3306,
master_user = 'db_sync',
master_password = 'db_sync',
master_log_file = 'mysql-bin.000006',
master_log_pos = 863;
而后启动咱们的 slave:
START SLAVE;
最初肯定要查看一下是否胜利!
show slave status \G;
Slave_IO_Runing 和 Slave_SQL_Runing 字段值都为 Yes,示意同步配置胜利。
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
);
配置读写拆散策略
application.properties:
# 配置数据源,给数据源起别名
# m0 为用户数据库
spring.shardingsphere.datasource.names=m0,s0
# 一个实体类对应两张表,笼罩
spring.main.allow-bean-definition-overriding=true
#user_db 主服务器
spring.shardingsphere.datasource.m0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m0.url=jdbc:mysql://192.168.3.107:3306/user_db?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m0.username=root
spring.shardingsphere.datasource.m0.password=123456
#user_db 从服务器
spring.shardingsphere.datasource.s0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.s0.url=jdbc:mysql://192.168.3.108:3306/user_db?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.s0.username=root
spring.shardingsphere.datasource.s0.password=123456
# 主库从库逻辑数据源定义 ds0 为 user_db
spring.shardingsphere.sharding.master-slave-rules.ds0.master-data-source-name=m0
spring.shardingsphere.sharding.master-slave-rules.ds0.slave-data-source-names=s0
# 配置 user_db 数据库外面 t_user 专库专表
#spring.shardingsphere.sharding.tables.t_user.actual-data-nodes=m0.t_user
# t_user 分表策略,固定调配至 ds0 的 t_user 实在表
spring.shardingsphere.sharding.tables.t_user.actual-data-nodes=ds0.t_user
# 配置主键的生成策略
spring.shardingsphere.sharding.tables.t_user.key-generator.column=user_id
spring.shardingsphere.sharding.tables.t_user.key-generator.type=SNOWFLAKE
# 指定分表策略
spring.shardingsphere.sharding.tables.t_user.table-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.t_user.table-strategy.inline.algorithm-expression=t_user
# 关上 sql 输入日志
spring.shardingsphere.props.sql.show=true
测试代码运行
@Autowired
private UserMapper userMapper;
@Test
public void addUser(){User user = new User();
user.setUsername("Jack");
user.setStatus("Normal");
userMapper.insert(user);
}
@Test
public void findUser() {QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("user_id", 536553906142969857L);
userMapper.selectOne(wrapper);
}
增加办法运行后果
m0 就是咱们配置的主库。
能够看到增加是没问题的。而后咱们看一下从库里有没有数据。
查询方法运行后果
能够看到后果也是 OK 的!
什么是 ShardingSphere-Proxy
定位为透明化的数据库代理端,提供封装了数据库二进制协定的服务端版本,用于实现对异构语言的反对。目前提供 MySQL 和 PostgreSQL 版本,它能够应用任何兼容 MySQL/PostgreSQL 协定的拜访客户端 (如:MySQL Command Client, MySQL Workbench, Navicat 等) 操作数据,对 DBA 更加敌对。
- 向应用程序齐全通明,可间接当做 MySQL/PostgreSQL 应用。
- 实用于任何兼容 MySQL/PostgreSQL 协定的的客户端。
简略了解为:之前咱们要配置多个数据源,而当初咱们应用 ShardingSphere-Proxy 之后,咱们相当于只操作一个库一个表,而多库多表操作被封装在了 ShardingSphere-Proxy 外面。是一个透明化的代理端。
下载 ShardingSphere-Proxy
下载地址:
https://archive.apache.org/di…
下载完进行解压
Sharding-Proxy 配置(分表)
进入到 conf 中关上server.yaml
。
将此局部正文关上即可。
而后咱们关上 config-sharding.yaml
文件进行分库分表的配置
依据提醒,如果应用 mysql,须要把 mysql 的驱动 jar 包放到 lib 目录下。拷贝即可。
而后我在主服务器创立了一个数据库
create database test_db;
关上如下正文填写对应参数:
schemaName: sharding_db
dataSources:
ds_0:
url: jdbc:mysql://192.168.3.107:3306/test_db?serverTimezone=UTC&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
shardingRule:
tables:
t_order:
actualDataNodes: ds_${0}.t_order_${0..1}
tableStrategy:
inline:
shardingColumn: order_id
algorithmExpression: t_order_${order_id % 2}
keyGenerator:
type: SNOWFLAKE
column: order_id
bindingTables:
- t_order
defaultDatabaseStrategy:
inline:
shardingColumn: user_id
algorithmExpression: ds_${0}
defaultTableStrategy:
none:
而后咱们保留进入 bin 目录启动./start.sh
。
启动胜利后咱们进入 logs 目录查看 stdout.log 日志文件。如下图即启动胜利!
而后咱们进入端口为 3307 的 mysql,ShardingSphere-Proxy 默认端口为:3307
mysql -uroot -proot -h127.0.0.1 -P3307
新建一张表插入条数据。
use sharding_db;
create table if not exists ds_0.t_order(`order_id` bigint primary key,`user_id` int not null,`status` varchar(50));
insert into t_order(`order_id`,`user_id`,`status`)values(11,1,'jack');
依照 order_id 进行调配,因为是奇数所以被分到了 t_order_1 表里。
Sharding-Proxy 配置(分库)
咱们在 主库 创立数据库:
create database test_1;
咱们在 从库 创立数据库:
create database test_2;
咱们还是关上 config-sharding.yaml
进行如下配置:
schemaName: sharding_db
dataSources:
ds_0:
url: jdbc:mysql://192.168.3.107:3306/test_1?serverTimezone=UTC&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
ds_1:
url: jdbc:mysql://192.168.3.108:3306/test_2?serverTimezone=UTC&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
shardingRule:
tables:
t_order:
actualDataNodes: ds_${0..1}.t_order_${1..2}
tableStrategy:
inline:
shardingColumn: order_id
algorithmExpression: t_order_${order_id % 2 + 1}
keyGenerator:
type: SNOWFLAKE
column: order_id
bindingTables:
- t_order
defaultDatabaseStrategy:
inline:
shardingColumn: user_id
algorithmExpression: ds_${user_id % 2}
defaultTableStrategy:
none:
之后进入 bin 目录下重启一下 Proxy。
./stop.sh
./start.sh
进入 mysql
mysql -uroot -proot -h127.0.0.1 -P3307
创立表增加数据
use sharding_db;
create table if not exists ds_0.t_order(`order_id` bigint primary key,`user_id` int not null,`status` varchar(50));
insert into t_order(`order_id`,`user_id`,`status`)values(11,1,'jack');
能够看到后果曾经插入到了对应的库中表中。
配置 Sharding-Proxy 读写拆散
咱们还是应用之前的一主一从搭配主从复制,在主和从上创立数据库:
create database master_slave_user;
批改 config-master_slave.yaml
文件(此文件为读写拆散的配置)
schemaName: master_slave_db
dataSources:
master_ds:
url: jdbc:mysql://192.168.3.107:3306/master_slave_user?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
slave_ds_0:
url: jdbc:mysql://192.168.3.108:3306/master_slave_user?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
masterSlaveRule:
name: ms_ds
masterDataSourceName: master_ds
slaveDataSourceNames:
- slave_ds_0
# - slave_ds_1
之后进入 bin 目录下重启一下 Proxy。
./stop.sh
./start.sh
进入 mysql
mysql -uroot -proot -h127.0.0.1 -P3307
创立表增加数据
use master_slave_db;
create table if not exists master_slave_user.t_order(`order_id` bigint primary key,`user_id` int not null,`status` varchar(50));
insert into t_order(`order_id`,`user_id`,`status`)values(11,1,'Jack');
能够看到下图:主库和从库都曾经存在数据了。
读取操作就不再演示了,读取的是从库数据。