主题
Mysql
MySQL 的读写分离
读写分离,简单地说是把对数据库的读和写操作分开,以对应不同的数据库服务器。主数据库提供写操作,从数据库提供读操作,这样能有效地减轻单台数据库的压力。
MySQL 主从复制原理
主库将变更写入 binlog 日志,然后从库连接到主库之后,从库有一个 IO 线程,将主库的 binlog 日志拷贝到自己本地,写入一个 relay 中继日志中。接着从库中有一个 SQL 线程会从中继日志读取 binlog,然后执行 binlog 日志中的内容,也就是在自己本地再次执行一遍 SQL,这样就可以保证自己跟主库的数据是一样的。
这里有一个非常重要的一点,就是从库同步主库数据的过程是串行化的,也就是说主库上并行的操作,在从库上会串行执行。所以这就是一个非常重要的点了,由于从库从主库拷贝日志以及串行执行 SQL 的特点,在高并发场景下,从库的数据一定会比主库慢一些,是有延时的。所以经常出现,刚写入主库的数据可能是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。
而且这里还有另外一个问题,就是如果主库突然宕机,然后恰好数据还没同步到从库,那么有些数据可能在从库上是没有的,有些数据可能就丢失了。
所以 MySQL 实际上在这一块有两个机制,一个是半同步复制,用来解决主库数据丢失问题;一个是并行复制,用来解决主从同步延时问题。
这个所谓半同步复制,也叫 semi-sync
复制,指的就是主库写入 binlog 日志之后,就会将强制此时立即将数据同步到从库,从库将日志写入自己本地的 relay log 之后,接着会返回一个 ack 给主库,主库接收到至少一个从库的 ack 之后才会认为写操作完成了。
所谓并行复制,指的是从库开启多个线程,并行读取 relay log 中不同库的日志,然后并行重放不同库的日志,这是库级别的并行。
安装 MySQL
技巧:获取默认配置文件
小技巧
: 运行一个临时容器并复制默认配置文件, 然后删除这个临时镜像, 这样就可以获取到默认的配置文件
sh
# 启动 MySQL 8.0 容器并复制配置文件到本地
mkdir -p /data/mysql80/config
mkdir -p /data/mysql80/data
docker run --name temp-mysql80 -d -e MYSQL_ROOT_PASSWORD=pwdxxxxx mysql:8.0
docker cp temp-mysql80:/etc/mysql/. /data/mysql80/config
docker cp temp-mysql80:/var/lib/mysql/. /data/mysql80/data
docker stop temp-mysql80 && docker rm temp-mysql80
上面步骤将 MySQL 8.0 容器的配置文件放置在宿主机上的 /data/mysql80/config 目录,并将数据文件放置在 /data/mysql80/data 目录中。
(1)单机版
现在正式安装MySQL 8.0
sh
docker run --name mysql80 \
--restart=always \
-v /data/mysql80/config:/etc/mysql \
-v /data/mysql80/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=pwdxxxxx \
-d mysql:8.0
-e MYSQL_ROOT_PASSWORD=pwdxxxxx:设置 MySQL 的 root 用户密码为 pwdxxxxx。
这时调整/data/mysql80/config/my.cnf配置就会同步, 编辑好只要重启就行。而且即使 MySQL 容器停止或删除后, 数据库文件仍然存在于宿主机的指定目录中, 保证数据的安全性和持久性。
(2)一主二从读写分离版
处理docker容器跨主机通信问题
建立dockerNet1、dockerNet2、dockerNet3网络
sh
docker network create --driver bridge --subnet 192.168.1.0/24 --gateway 192.168.1.1 dockerNet1
docker network create --driver bridge --subnet 192.168.2.0/24 --gateway 192.168.2.1 dockerNet1
docker network create --driver bridge --subnet 192.168.3.0/24 --gateway 192.168.3.1 dockerNet1
分别在三台宿主机上配置路由表及iptables
在主机1上,添加路由以到达其他网段:
sh
route add -net 192.168.2.0 netmask 255.255.255.0 gw 192.168.220.111
route add -net 192.168.3.0 netmask 255.255.255.0 gw 192.168.220.112
iptables -t nat -I PREROUTING -s 192.168.1.0/24 -d 192.168.2.0/24 -j DNAT --to 192.168.1.1
iptables -t nat -I PREROUTING -s 192.168.1.0/24 -d 192.168.3.0/24 -j DNAT --to 192.168.1.1
在主机2上,添加路由以到达其他网段:
sh
route add -net 192.168.1.0 netmask 255.255.255.0 gw 192.168.220.110
route add -net 192.168.3.0 netmask 255.255.255.0 gw 192.168.220.112
iptables -t nat -I PREROUTING -s 192.168.2.0/24 -d 192.168.1.0/24 -j DNAT --to 192.168.2.1
iptables -t nat -I PREROUTING -s 192.168.2.0/24 -d 192.168.3.0/24 -j DNAT --to 192.168.2.1
在主机3上,添加路由以到达其他网段:
sh
route add -net 192.168.1.0 netmask 255.255.255.0 gw 192.168.220.110
route add -net 192.168.2.0 netmask 255.255.255.0 gw 192.168.220.111
iptables -t nat -I PREROUTING -s 192.168.3.0/24 -d 192.168.1.0/24 -j DNAT --to 192.168.3.1
iptables -t nat -I PREROUTING -s 192.168.3.0/24 -d 192.168.2.0/24 -j DNAT --to 192.168.3.1
启动主服务器(master/主机1)
sh
docker run --name mysql80-master --net dockerNet1\
--restart=always \
-v /data/mysql80/config:/etc/mysql \
-v /data/mysql80/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=pwdxxxxx \
-e MYSQL_REPLICATION_USER=repl_user \
-e MYSQL_REPLICATION_PASSWORD=repl_pwd \
-e MYSQL_DATABASE=exampledb \
-d mysql:8.0
配置主服务器
进入主服务器容器并配置主服务器:
sh
docker exec -it mysql-master bash
mysql -u root -p
在MySQL命令行中执行以下命令
sh
CREATE USER 'repl_user'@'%' IDENTIFIED BY 'repl_pwd';
GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'%';
FLUSH PRIVILEGES;
SHOW MASTER STATUS;
记下File和Position的值。
启动从服务器(slave1/主机2)
sh
docker run --name mysql80-slave1 --net dockerNet2 \
--restart=always \
-v /data/mysql80/config:/etc/mysql \
-v /data/mysql80/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=pwdxxxxx \
-e MYSQL_REPLICATION_USER=repl_user \
-e MYSQL_REPLICATION_PASSWORD=repl_pwd \
-e MYSQL_DATABASE=exampledb \
-d mysql:8.0
启动从服务器(slave2/主机3)
sh
docker run --name mysql80-slave2 --net dockerNet3 \
--restart=always \
-v /data/mysql80/config:/etc/mysql \
-v /data/mysql80/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=pwdxxxxx \
-e MYSQL_REPLICATION_USER=repl_user \
-e MYSQL_REPLICATION_PASSWORD=repl_pwd \
-e MYSQL_DATABASE=exampledb \
-d mysql:8.0
进入从服务器mysql容器并配置它们:
sh
CHANGE MASTER TO
MASTER_HOST='192.168.1.1',
MASTER_USER='repl_user',
MASTER_PASSWORD='repl_pwd',
MASTER_LOG_FILE='mysql-bin.000001主服务器的File',
MASTER_LOG_POS=主服务器的Position;
START SLAVE;
java多数据源管理
java使用 dynamic-datasource-spring-boot-starter
实现多数据源管理
yml
--- # 数据源配置
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
dynamic:
primary: master
strict: true
datasource:
master:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.1.1:3306/exampledb?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: root
password: pwdxxxxx
slave1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.2.2:3306/exampledb?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: root
password: pwdxxxxx
slave2:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.3.2:3306/exampledb?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: root
password: pwdxxxxx
hikari:
max-pool-size: 20
min-idle: 10
connection-timeout: 30000
validation-timeout: 5000
idle-timeout: 600000
max-lifetime: 1800000
connection-test-query: SELECT 1
keepalive-time: 30000