文章目录
- 二 ShardingSphere JDBC 基本使用
- 2.1 ShardingSphere JDBC 水平分表
- 2.1.1 案例入门
- 2.1.2 解读配置文件
- 2.1.3 其他测试
- 2.1.4 延伸问题
- 2.2 ShardingSphere JDBC 水平分库
- 2.2.1 案例入门
- 2.2.2 解读配置文件
- 2.1.3 其他测试
- 2.3 ShardingSphere JDBC 广播表
- 2.3.1 基本案例入门
- 2.3.2 扩展
- 2.4 ShardingSphere JDBC 读写分离
- 2.4.1 Mysql主从复制环境搭建
- 2.4.1.1 安装Docker,Docker-Compose
- 2.4.1.2 新建文件
- 2.4.1.3 启动测试
- 2.4.1.4 其他
- 2.4.2 准备工作
- 2.4.3 入门案例
- 2.4.4 会产生的问题?
- 2.4.4.1 可能从库会出现读延迟,导致查询不到数据?
二 ShardingSphere JDBC 基本使用
2.1 ShardingSphere JDBC 水平分表
2.1.1 案例入门
- sql
DROP TABLE IF EXISTS t_order_1;
CREATE TABLE t_order_1(
order_id varchar(255) NOT NULL COMMENT '订单编号' ,
order_time DATETIME NOT NULL COMMENT '订单时间' ,
order_money DECIMAL(24,6) NOT NULL COMMENT '订单金额' ,
user_id INT NOT NULL COMMENT '订单下单人' ,
PRIMARY KEY (order_id)
) COMMENT = '订单表';
DROP TABLE IF EXISTS t_order_2;
CREATE TABLE t_order_2(
order_id INT NOT NULL COMMENT '订单编号' ,
order_time DATETIME NOT NULL COMMENT '订单时间' ,
order_money DECIMAL(24,6) NOT NULL COMMENT '订单金额' ,
user_id INT NOT NULL COMMENT '订单下单人' ,
PRIMARY KEY (order_id)
) COMMENT = '订单表';
DROP TABLE IF EXISTS t_dict;
CREATE TABLE t_dict(
dict_id INT(11) NOT NULL AUTO_INCREMENT COMMENT '字典id' ,
dict_fid INT(11) COMMENT '所属字典;上级id;0:为字典类型,其余为该字典类型下的值' ,
dict_label VARCHAR(255) COMMENT '字典名称;字典名称' ,
dict_value VARCHAR(255) COMMENT '字典值' ,
dict_code VARCHAR(255) COMMENT '字典唯一编码' ,
create_time DATETIME COMMENT '创建时间' ,
remark VARCHAR(255) COMMENT '备注' ,
is_delete VARCHAR(1) NOT NULL DEFAULT 1 COMMENT '是否删除;0:已删除;1:未删除' ,
PRIMARY KEY (dict_id)
) COMMENT = '字典信息表';
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.shu</groupId>
<artifactId>v1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Sharding-Jdbc-V4.0-Demo</name>
<description>Sharding-Jdbc-V4.0-Demo</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- mybatis依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<!--swagger2依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 实体类
package com.shu.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* @description:
* @author: shu
* @createDate: 2022/11/24 9:57
* @version: 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dict {
/** 字典id */
@ApiModelProperty(name = "字典id",notes = "")
private Integer dictId ;
/** 所属字典;上级id;0:为字典类型,其余为该字典类型下的值 */
@ApiModelProperty(name = "所属字典",notes = "上级id;0:为字典类型,其余为该字典类型下的值")
private Integer dictFid ;
/** 字典名称;字典名称 */
@ApiModelProperty(name = "字典名称",notes = "字典名称")
private String dictLabel ;
/** 字典值 */
@ApiModelProperty(name = "字典值",notes = "")
private String dictValue ;
/** 字典唯一编码 */
@ApiModelProperty(name = "字典唯一编码",notes = "")
private String dictCode ;
/** 创建时间 */
@ApiModelProperty(name = "创建时间",notes = "")
@JsonFormat(locale="zh", timezone="GMT+8", pattern="yyyy-MM-dd HH:mm:ss")
private Date createTime ;
/** 备注 */
@ApiModelProperty(name = "备注",notes = "")
private String remark ;
/** 是否删除;0:已删除;1:未删除 */
@ApiModelProperty(name = "是否删除",notes = "0:已删除;1:未删除")
private String isDelete ;
}
package com.shu.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* @description: 订单实体类
* @author: shu
* @createDate: 2022/8/4 15:13
* @version: 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
/** 订单编号 */
@ApiModelProperty(name = "订单编号",notes = "")
private String orderId ;
/** 订单时间 */
@ApiModelProperty(name = "订单时间",notes = "")
@JsonFormat(locale="zh", timezone="GMT+8", pattern="yyyy-MM-dd HH:mm:ss")
private Date orderTime ;
/** 订单金额 */
@ApiModelProperty(name = "订单金额",notes = "")
private Double orderMoney ;
/** 订单下单人 */
@ApiModelProperty(name = "订单下单人",notes = "")
private Integer userId ;
}
- 增删改查的编写就省略了
- 配置文件(请注意这4.0的配置文件,5.0版本还是有很大差别的)
# 开启大驼峰命名法
mybatis:
configuration:
map-underscore-to-camel-case: true
# 分库分表配置
spring:
main:
allow-bean-definition-overriding: true
sharding-sphere:
datasource:
# 数据源名称
names: d1,m3
# 数据源d1配置
d1.type: com.alibaba.druid.pool.DruidDataSource
d1.url: jdbc:mysql://localhost:3306/order_db_1?useUnicode=true&characterEncoding=utf8
d1.username: root
d1.password: 123456
d1.driverClassName: com.mysql.cj.jdbc.Driver
# 其他数据源配置
m3.type: com.alibaba.druid.pool.DruidDataSource
m3.url: jdbc:mysql://localhost:3306/auth?useUnicode=true&characterEncoding=utf8
m3.username: root
m3.password: 123456
m3.driverClassName: com.mysql.cj.jdbc.Driver
# 简单分片规则配置
sharding:
tables:
# 订单表
t_order.actual_data_nodes: d1.t_order_$->{1..2}
# 订单主键
t_order.key-generator.column: order_id
# 订单主键生成策略,主键生成策略为雪花算法
t_order.key-generator.type: Snowflake
# 定义分片键
t_order.table-strategy.inline.sharding-column: user_id
# 定义分片策略
t_order.table-strategy.inline.algorithm-expression: t_order_$->{user_id % 2 + 1}
#### 展示sql运行日志##############
props:
sql:
show: true
- 测试
2.1.2 解读配置文件
- 数据源配置
spring:
main:
allow-bean-definition-overriding: true
sharding-sphere:
datasource:
# 数据源名称
names: d1,m3
# 数据源d1配置
d1.type: #数据库连接池类型
d1.url: #数据库url
d1.username: #账号
d1.password: # 密码
d1.driverClassName:
# 其他数据源配置
m3.type:
m3.url:
m3.username:
m3.password:
m3.driverClassName:
- 实际数据节点
sharding:
tables:
# 订单表
【需要进行分片的表名】.actual_data_nodes: 实际是数据节点
- 主键生成策越
sharding:
tables:
# 订单主键
【需要进行分片的表名】.key-generator.column: 主键字段
# 订单主键生成策略,主键生成策略为雪花算法,UUID
【需要进行分片的表名】.key-generator.type: Snowflake
- 分片键策越
sharding:
tables:
# 定义分片键
【需要进行分片的表名】.table-strategy.inline.sharding-column: 分片键字段
# 定义分片策略
【需要进行分片的表名】.table-strategy.inline.algorithm-expression: 行表达式
2.1.3 其他测试
插入测试:是否生成唯一主键?
注意:Mybatis不用写主键插入
查询测试:是否会查询全部数据?
单个数据查询呢?
更新数据呢?
我们可以看到,上面是查询都走了两张表,全表路由造成性能浪费,携带上分片键查询。
2.1.4 延伸问题
主要的分片算法?
参考文章:Sharding-JDBC 实战(史上最全)_40岁资深老架构师尼恩的博客-CSDN博客_sharding-jdbc
- range 分片
一种是按照 range 来分,就是每个片,一段连续的数据,这个一般是按比如时间范围/数据范围来的,但是这种一般较少用,因为很容易发生数据倾斜,大量的流量都打在最新的数据上了。
- ID取模分片
此种分片规则将数据分成n份(通常dn节点也为n),从而将数据均匀的分布于各个表中,或者各节点上。
- hash 哈希分片
使用hash 算法,获取key的哈希结果,再按照规则进行分片,这样可以保证数据被打散,同时保证数据分布的比较均匀
当我们的sql语句中存在mysql 自带的函数是否能解析?
踩坑日记?
2.2 ShardingSphere JDBC 水平分库
2.2.1 案例入门
结构与上面一样,主要是配置文件的不同
- 配置文件
# 开启大驼峰命名法
mybatis:
configuration:
map-underscore-to-camel-case: true
# 分库分表配置
spring:
main:
allow-bean-definition-overriding: true
sharding-sphere:
datasource:
# 数据源名称
names: d1,d2,d3
# 数据源d1配置
d1.type: com.alibaba.druid.pool.DruidDataSource
d1.url: jdbc:mysql://localhost:3306/order_db_1?useUnicode=true&characterEncoding=utf8
d1.username: root
d1.password: 123456
d1.driverClassName: com.mysql.cj.jdbc.Driver
# 数据源d2配置
d2.type: com.alibaba.druid.pool.DruidDataSource
d2.url: jdbc:mysql://localhost:3306/order_db_2?useUnicode=true&characterEncoding=utf8
d2.username: root
d2.password: 123456
d2.driverClassName: com.mysql.cj.jdbc.Driver
# 数据源d3配置
m3.type: com.alibaba.druid.pool.DruidDataSource
m3.url: jdbc:mysql://localhost:3306/order_db_3?useUnicode=true&characterEncoding=utf8
m3.username: root
m3.password: 123456
m3.driverClassName: com.mysql.cj.jdbc.Driver
# 简单分片规则配置
sharding:
tables:
# 订单表
t_order.actual_data_nodes: d$->{1..3}.t_order
# 订单主键
t_order.key-generator.column: order_id
# 订单主键生成策略,主键生成策略为UUID
t_order.key-generator.type: UUID
# 定义分片键
t_order.database-strategy.inline.sharding-column: user_id
# 定义分片策略
t_order.database-strategy.inline.algorithm-expression: d$->{user_id % 3 + 1}
#### 展示sql运行日志##############
props:
sql:
show: true
- 测试
2.2.2 解读配置文件
- 数据源配置
spring:
main:
allow-bean-definition-overriding: true
sharding-sphere:
datasource:
# 数据源名称
names: d1,d2
# 数据源d1配置
d1.type: #数据库连接池类型
d1.url: #数据库url
d1.username: #账号
d1.password: # 密码
d1.driverClassName:
# 其他数据源配置
d2.type:
d2.url:
d2.username:
d2.password:
d2.driverClassName:
与水平分表不一样的是,这里我们配置了多个数据源,且数据源表结构是一样
- 实际数据节点
sharding:
tables:
# 订单表
【需要进行分片的表名】.actual_data_nodes: 实际是数据库节点,不是表节点
- 主键生成策越
sharding:
tables:
# 订单主键
【需要进行分片的表名】.key-generator.column: 主键字段
# 订单主键生成策略,主键生成策略为雪花算法,UUID
【需要进行分片的表名】.key-generator.type: Snowflake
- 分片键策越
sharding:
tables:
# 定义分片键
【需要进行分片的表名】.table-strategy.inline.sharding-column: 分片键字段
# 定义分片策略
【需要进行分片的表名】.table-strategy.inline.algorithm-expression: 数据库行表达式
2.1.3 其他测试
插入测试?
查询测试?
更新测试?
2.3 ShardingSphere JDBC 广播表
2.3.1 基本案例入门
- 广播表也叫全局表,也就是它会存在于多个库中冗余,避免跨库查询问题。
- 比如省份、字典等一些基础数据,为了避免分库分表后关联表查询这些基础数据存在跨库问题,所以可以把这些数据同步给每一个数据库节点,这个就叫广播表。
结构与前面一样,主要是配置
- 测试
插入测试
查询:会随机选择一个数据源进行查询
更新
删除
2.3.2 扩展
如果我只想查询走我指定的库,应该怎么办?
- 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 自定义注解
package com.shu.aspect;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @description: 自定义注解走某个库
* @author: shu
* @createDate: 2022/11/22 13:32
* @version: 1.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface DBMaster {
}
、
package com.shu.aspect;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.api.hint.HintManager;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Objects;
/**
* @description:
* @author: shu
* @createDate: 2022/11/22 13:32
* @version: 1.0
*/
@Slf4j
@Component
@Aspect
public class DBMasterHandler {
@Around("execution(* com.shu.mapper.*.*(..))")
public Object master(ProceedingJoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
Object ret = null;
log.info(joinPoint.toShortString());
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
DBMaster dBMaster = method.getAnnotation(DBMaster.class);
HintManager hintManager = null;
try {
if (Objects.nonNull(dBMaster)) {
HintManager.clear();
hintManager = HintManager.getInstance();
hintManager.setDatabaseShardingValue("d1");
}
ret = joinPoint.proceed(args);
}catch (Exception ex){
log.error("exception error",ex);
}catch (Throwable ex2){
log.error("Throwable",ex2);
}finally {
if (Objects.nonNull(dBMaster) && Objects.nonNull(hintManager)) {
hintManager.close();
}
}
return ret;
}
}
- 自定义分片算法
package com.shu.aspect;
import org.apache.shardingsphere.api.sharding.hint.HintShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.hint.HintShardingValue;
import java.util.Collection;
import java.util.Collections;
/**
* @description:
* @author: shu
* @createDate: 2022/11/22 14:12
* @version: 1.0
*/
public class HintShardingKeyAlgorithm implements HintShardingAlgorithm<Integer> {
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, HintShardingValue<Integer> shardingValue) {
String key = (String) shardingValue.getValues().toArray()[0];
if(availableTargetNames.contains(key)){
return Collections.singletonList(key);
}
throw new UnsupportedOperationException("route "+ key +" is not supported ,please check your config");
}
}
- 配置
####################默认路由算法############
spring.shardingsphere.sharding.default-database-strategy.hint.algorithm-class-name=com.shu.aspect.HintShardingKeyAlgorithm
- 在查询mapper上加上注解
- 测试
可以看到我们每次查询走了144
思考?
当我们更新字典表是,突然其中一台数据库挂了,新增数据库字典正在运行,你会发现,其中有数据库,更新成功,有的则失败,当我们再次查询时会出现数据不一致的问题?怎么解决?
2.4 ShardingSphere JDBC 读写分离
2.4.1 Mysql主从复制环境搭建
2.4.1.1 安装Docker,Docker-Compose
- 安装Docker
#1.卸载旧版本
yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
#2.需要的安装包
yum install -y yum-utils
#3.设置镜像的仓库
yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
#默认是从国外的,不推荐
#推荐使用国内的
yum-config-manager \
--add-repo \
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
#更新yum软件包索引
yum makecache fast
#4.安装docker相关的 docker-ce 社区版 而ee是企业版
yum install docker-ce docker-ce-cli containerd.io
#5、启动docker
#root用户
systemctl start docker
#非root用户
sudo systemctl start docker
#6. 使用docker version查看是否按照成功
docker version
- 安装Docker-Compose
sudo curl -L "https://get.daocloud.io/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
如有需要,修改上面 1.24.1 为指定版本号即可
- 安装完后执行
sudo chmod +x /usr/local/bin/docker-compose
- 验证
docker-compose version
2.4.1.2 新建文件
- docker-compose.yml:编写文件如下
version: "2"
services:
mysql-master:
image: mysql:5.7
ports:
- 3340:3306
volumes:
- ./conf-master:/etc/mysql/conf.d
- ./data-master:/var/lib/mysql
- ./conf-master:/etc/mysql/mysql.conf.d
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: db
MYSQL_USER: user
MYSQL_PASSWORD: user123
restart: always
container_name: mysql-master
mysql-slave:
image: mysql:5.7
ports:
- 3341:3306
volumes:
- ./conf-slave:/etc/mysql/conf.d
- ./data-slave:/var/lib/mysql
- ./conf-slave:/etc/mysql/mysql.conf.d
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: db
MYSQL_USER: user
MYSQL_PASSWORD: user123
restart: always
container_name: mysql-slave
- 先建conf-master,conf-slave,mysql运行配置文件夹
编写数据库配置文件
conf-master 配置文件my.cnf
[mysqld]
server-id=101
log-bin=mysql-bin
binlog_format=MIXED
max_binlog_size = 124M
expire_logs_days = 3
binlog_ignore_db = mysql,performance_schema,information_schema,sys
conf-slave 配置文件my.cnf
[mysqld]
server_id = 1002
log_bin = mysql-bin
binlog_format=MIXED
max_binlog_size = 124M
expire_logs_days = 3
replicate-ignore-db=mysql,performance_schema,information_schema,sy
relay_log_recovery = 1
log_slave_updates = 1
- 其他文件夹
data-master(mysql数据卷) logs-master(mysql日志文件)
data-slave(mysql数据卷) logs-slave(mysql日志文件)
2.4.1.3 启动测试
主库配置
# Docker-compose编译
[root@ecs-341636 MysqlMs]# docker-compose up -d
Creating network "mysqlms_default" with the default driver
Creating mysql-slave ... done
Creating mysql-master ... done
# 进入容器
[root@ecs-341636 MysqlMs]# docker exec -it mysql-master bash
# 连接
bash-4.2# mysql -uroot -p123456
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.40-log MySQL Community Server (GPL)
Copyright (c) 2000, 2022, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
# 查看配置信息
mysql> show variables like '%server_id%';
+----------------+-------+
| Variable_name | Value |
+----------------+-------+
| server_id | 101 |
| server_id_bits | 32 |
+----------------+-------+
2 rows in set (0.01 sec)
# 查看主节点状态
mysql> show master status;
+------------------+----------+--------------+-------------------------------------------------------------------------------------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+-------------------------------------------------------------------------------------------------+-------------------+
| mysql-bin.000003 | 154 | | mysql,performance_schema,information_schema,sys,mysql,performance_schema,information_schema,sys | |
+------------------+----------+--------------+-------------------------------------------------------------------------------------------------+-------------------+
1 row in set (0.00 sec)
# 授权
mysql> grant replication slave,replication client on *.* to 'slave'@'%' identified by "123456";
Query OK, 0 rows affected, 1 warning (0.01 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.01 sec)
从库配置
[root@ecs-341636 MysqlMs]# docker exec -it mysql-slave bash
bash-4.2# mysql -uroot -p123456
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.7.40-log MySQL Community Server (GPL)
Copyright (c) 2000, 2022, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show variables like '%server_id%';
+----------------+-------+
| Variable_name | Value |
+----------------+-------+
| server_id | 1002 |
| server_id_bits | 32 |
+----------------+-------+
2 rows in set (0.00 sec)
mysql> change master to
-> master_host='127.0.0.1', // 注意阿里云上需要用外网地址
-> master_user='slave',
-> master_password='123456',
-> master_port=3340,
-> master_log_file='mysql-bin.000001',
-> master_log_pos= 0,
-> master_connect_retry=30;
#启动slave
mysql> start slave;
# 查看状态
mysql> show slave status;
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.112.3
Master_User: slave
Master_Port: 3306
Connect_Retry: 30
Master_Log_File: mysql-bin.000004
Read_Master_Log_Pos: 617
Relay_Log_File: 7fee2f1fd5d2-relay-bin.000002
Relay_Log_Pos: 783
Relay_Master_Log_File: mysql-bin.000004
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 617
Relay_Log_Space: 997
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 1
Master_UUID: 8f6e9f5a-61f4-11eb-ac84-0242c0a86002
Master_Info_File: /var/lib/mysql/master.info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 0
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
1 row in set (0.01 sec)
- master_host :Master的地址,指的是容器的独立ip,可以通过docker inspect --format=‘{{.NetworkSettings.IPAddress}}’ 容器名称|容器id查询容器的ip
- master_port:Master的端口号,指的是容器的端口号
- master_user:用于数据同步的用户
- master_password:用于同步的用户的密码
- master_log_file:指定 Slave 从哪个日志文件开始复制数据,即上文中提到的 File 字段的值
- master_log_pos:从哪个 Position 开始读,即上文中提到的 Position 字段的值
- master_connect_retry:如果连接失败,重试的时间间隔,单位是秒,默认是60秒
上面看到,有两个Yes,说明已经成功了
Relay_Master_Log_File: mysql-bin.000004
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
2.4.1.4 其他
- 从库设置权限
SHOW VARIABLES LIKE '%read_only%'; #查看只读状态
SET GLOBAL super_read_only=1; #super权限的用户只读状态 1.只读 0:可写
SET GLOBAL read_only=1; #普通权限用户读状态 1.只读 0:可写
- 服务器命令
stop slave;
start slave;
show slave status;
2.4.2 准备工作
- 我们在主数据库上创建数据文件
DROP TABLE IF EXISTS t_order_1;
CREATE TABLE t_order_1(
order_id INT NOT NULL COMMENT '订单编号' ,
order_time DATETIME NOT NULL COMMENT '订单时间' ,
order_money DECIMAL(24,6) NOT NULL COMMENT '订单金额' ,
user_id INT NOT NULL COMMENT '订单下单人' ,
PRIMARY KEY (order_id)
) COMMENT = '订单表';
DROP TABLE IF EXISTS t_order_2;
CREATE TABLE t_order_2(
order_id INT NOT NULL COMMENT '订单编号' ,
order_time DATETIME NOT NULL COMMENT '订单时间' ,
order_money DECIMAL(24,6) NOT NULL COMMENT '订单金额' ,
user_id INT NOT NULL COMMENT '订单下单人' ,
PRIMARY KEY (order_id)
) COMMENT = '订单表';
DROP TABLE IF EXISTS t_dict;
CREATE TABLE t_dict(
dict_id INT(11) NOT NULL AUTO_INCREMENT COMMENT '字典id' ,
dict_fid INT(11) COMMENT '所属字典;上级id;0:为字典类型,其余为该字典类型下的值' ,
dict_label VARCHAR(255) COMMENT '字典名称;字典名称' ,
dict_value VARCHAR(255) COMMENT '字典值' ,
dict_code VARCHAR(255) COMMENT '字典唯一编码' ,
create_time DATETIME COMMENT '创建时间' ,
remark VARCHAR(255) COMMENT '备注' ,
is_delete VARCHAR(1) NOT NULL DEFAULT 1 COMMENT '是否删除;0:已删除;1:未删除' ,
PRIMARY KEY (dict_id)
) COMMENT = '字典信息表';
我们不需要从库建表,他会自动同步表结构与数据库结构
2.4.3 入门案例
结构与前面一样,关键是配置文件,使用Sharding-JDBC配置读写分离,优点在于数据源完全有Sharding托管,写操作自动执行master库,读操作自动执行slave库。不需要程序员在程序中关注这个实现了。
- 配置文件
# 开启大驼峰命名法
mybatis:
configuration:
map-underscore-to-camel-case: true
# 分库分表配置
spring:
main:
allow-bean-definition-overriding: true
sharding-sphere:
datasource:
# 数据源名称
names: d1,d2
# 数据源d1配置
d1.type: com.alibaba.druid.pool.DruidDataSource
d1.url: jdbc:mysql://ip:3340/db?useUnicode=true&characterEncoding=utf8
d1.username: root
d1.password: 123456
d1.driverClassName: com.mysql.cj.jdbc.Driver
# 数据源d2配置
d2.type: com.alibaba.druid.pool.DruidDataSource
d2.url: jdbc:mysql://ip:3341/db?useUnicode=true&characterEncoding=utf8
d2.username: root
d2.password: 123456
d2.driverClassName: com.mysql.cj.jdbc.Driver
# 简单分片规则配置
sharding:
# 绑定广播表
broadcast-tables: t_dict
# 读写分离配置
masters-lave:
name: dataSource
master-data-source-name: d1 # 只能一个
slave-data-source-names: d2 # 可以多个
load-balance-algorithm-type: round_robin # 负载均衡算法,可选值:ROUND_ROBIN(轮询),RANDOM(随机)
#### 展示sql运行日志##############
props:
sql:
show: true
- 测试
插入操作?
查询操作?
更新操作?
我们测试可以发现,当我们对数据库进行写操作时,他会走主库,查询操作时他会走从库
2.4.4 会产生的问题?
2.4.4.1 可能从库会出现读延迟,导致查询不到数据?
强制走主库,但是违背了主从复制的初衷
(1)业务层面妥协,是否操作完之后马上要进行读取
(2)对于操作完马上要读出来的,且业务上不能妥协的,我们可以对于这类的读取直接走主库,当然Sharding-JDBC也是考虑到这个问题的存在,所以给我们提供了一个功能,可以让用户在使用的时候指定要不要走主库进行读取。在读取前使用下面的方式进行设置就可以了
HintManager.clear();
hintManager = HintManager.getInstance();
// 强制路由主库
hintManager.setMasterRouteOnly();
延迟查询,比如主库更新一条数据以后,查询从库数据的时候后端接口可以线sleep一段时间,比如绝大多数主从同步都可以在1s内完成,那就可以sleep一秒钟的时间。或者也可以延迟查询逻辑放在前端,比如订单支付完成后,可以前端延迟一秒再发起Ajax请求。
参考官网:
https://shardingsphere.apache.org/document/current/cn/features/sharding/limitation/
参考文章:
https://blog.csdn.net/crazymakercircle/article/details/123420859
https://blog.csdn.net/weixin_36586120/article/details/118018414
https://tech.meituan.com/2017/04/21/mt-leaf.html
https://blog.csdn.net/sinat_32873711/article/details/128029810