MySQL-主从同步与搭建

发布时间 2023-08-24 10:46:47作者: 王寄鱼

零、主从配置使用场景

1、防止单点故障,做数据备份,从服务器作为主服务器的实时数据备份,遇到故障时可切换到从库

2、实现读写分离,提高可用性,主库执行写操作,从库配置多个执行读操作

3、性能大大提高,根据不同的从库,可以根据项目的模块与业务进行拆分访问

4、防止某些事务锁表时,无法执行读操作

...

一、主从配置原理

image-20191104214516600

mysql主从配置的流程大体如图:

1)master会将变动记录到二进制日志里面;

2)master有一个I/O线程将二进制日志发送到slave;

3)slave有一个I/O线程把master发送的二进制写入到relay日志里面;

4)slave有一个SQL线程,按照relay日志处理slave的数据;

二、操作步骤

(一)配置文件

具体执行命令如下

#把docker中的文件挂载出来,创建文件夹conf.d,data

mkdir /home/mysql1
mkdir /home/mysql1/conf.d
mkdir /home/mysql1/data/
touch /home/mysql1/my.cnf

mkdir /home/mysql2
mkdir /home/mysql2/conf.d
mkdir /home/mysql2/data/
touch /home/mysql2/my.cnf

主库的my.cnf文件,必须具备的参数

[mysqld]
#同一局域网内注意要唯一
server-id=100  
# 开启二进制日志功能,名字可以随便取
log-bin=mysql-bin
#需要同步的二进制数据库名;
binlog-do-db=test
#不同步的二进制数据库名,如果不设置可以将其注释掉;
binlog-ignore-db=information_schema
binlog-ignore-db=mysql

从库my.cnf文件

[mysqld]
# 设置server_id,注意要唯一
server-id=101  
# 开启二进制日志功能,若Slave作为其它Slave的Master时使用
log-bin=mysql-slave-bin   
# relay_log配置中继日志
relay_log=mysql-relay-bin

(二)主从配置

1、启动两个mysql

# 主库33306
docker run  -di -v /Users/yangxiaobo/Desktop/mysql1/data/:/var/lib/mysql -v /Users/yangxiaobo/Desktop/mysql1/conf.d:/etc/mysql/conf.d -v /Users/yangxiaobo/Desktop/mysql1/my.cnf:/etc/mysql/my.cnf -p 33306:3306 --name mysql-master -e MYSQL_ROOT_PASSWORD=123456 dockerhub.datagrand.com/idps/mysql:latest

          
# 从库33307
docker run  -di -v /Users/yangxiaobo/Desktop/mysql2/data/:/var/lib/mysql -v /Users/yangxiaobo/Desktop/mysql2/conf.d:/etc/mysql/conf.d -v /Users/yangxiaobo/Desktop/mysql2/my.cnf:/etc/mysql/my.cnf -p 33307:3306 --name mysql-slave -e MYSQL_ROOT_PASSWORD=123456 dockerhub.datagrand.com/idps/mysql:latest

2、远程连接入主库和从库

#连接主库
mysql -h 127.0.0.1 -P 33306 -u root -p123456
#在主库创建用户并授权
#创建用户
create user 'repl'@'%' identified by '123';
#授权用户
grant replication slave,replication client on *.* to 'repl'@'%' ;
#刷新权限
flush privileges;
#查看主服务器状态(显示如下图)
show master status; 

image-20191105085514111

#连接从库
mysql -h 127.0.0.1 -P 33307 -u root -p123456
#配置详解
/*
change master to 
master_host='MySQL主服务器IP地址', 
master_user='之前在MySQL主服务器上面创建的用户名', 
master_password='之前创建的密码', 
master_log_file='MySQL主服务器状态中的二进制文件名', 
master_log_pos='MySQL主服务器状态中的position值';
*/
#命令如下
change master to master_host='100.100.20.191',master_port=33306,master_user='repl',master_password='123',master_log_file='mysql-bin.000003',master_log_pos=759;
#启用从库
start slave;
#查看从库状态
show slave status\G;

Slave_IO_Running与Slave_SQL_Running都为Yes表示成功

(三)测试主从同步

在主库上新建表格与数据

#在主库上创建数据库test
create database test;
use test;
create table datagrand (id int,salary int);
insert datagrand (id,salary) values(1,1),(2,2);

#在从库上查询
show database;
use test;
show tables;
select * from datagrand;

#查看主从库之间的连接
show processlist \G

三、主从搭建形式

1、一主一从

较为常见的方式

2、主主复制

互相作为主从,可以就可以互相作为备份库

3、一主多从

当读压力较大的场景时可使用

4、多主一从

当写压力较大的场景时可使用

5、联级复制(mysql5.7开始支持)

image-20221025181428659

四、其他配置参数

(一)主库配置文件

[mysqld]
#同一局域网内注意要唯一
server-id=100  
# 开启二进制日志功能,名字可以随便取
log-bin=mysql-bin
#需要同步的二进制数据库名;
binlog-do-db=test
#不同步的二进制数据库名,如果不设置可以将其注释掉;
binlog-ignore-db=information_schema
binlog-ignore-db=mysql

# 表示binlog的写入频率STATEMENT,ROW,MIXED
binlog-format= MIXED
# innodb提交事务时,控制relo log写入磁盘的频率,可设置:0,1,2,控制的是事务
innodb_flush_log_at_trx_commit=1
# 表示binlog的写入频率,默认为0,开启该参数之后,mysql每n次提交事务前会将二进制日志同步到磁盘上,保证在服务器崩溃时不会丢失事件。
sync_binlog=1
# 主库只写
write_only=1

binlog-format详述:

binlog-format=(STATEMENT,ROW,MIXED)
表示binlog以什么方式写入日志
1、STATEMENT
每一条会修改数据的sql语句会记录到binlog中。优点是并不需要记录每一条sql语句和每一行的数据变化,减少了binlog日志量,节约IO,提高性能。缺点是在某些情况下会导致master-slave中的数据不一致(如last_insert_id())
2、ROW
不记录每条sql语句的上下文信息,仅需记录哪条数据被修改了,修改成什么样了,几乎没有行复制模式无法处理的场景。缺点是会产生大量的日志,尤其是alter table的时候会让日志暴涨。
3、MIXED
以上两种模式的混合使用,一般的复制使用STATEMENT模式保存binlog,对于STATEMENT模式无法复制的操作使用ROW模式保存binlog,MySQL会根据执行的SQL语句选择日志保存方式。


关于ROW与STATEMENT比较:
比如有下面两个sql语句
insert into t2 (col1,col2,sum_col3) select col1,col2,sum(col3) from t15.1 group by col1,col2;


update t1 set col1 = 0;

(二)从库配置文件

[mysqld]
# 设置server_id,注意要唯一
server-id=101  
# 开启二进制日志功能,若Slave作为其它Slave的Master时使用
log-bin=mysql-slave-bin   
# relay_log配置中继日志
relay_log=mysql-relay-bin
​
# (0:关闭,1:开启)允许链式复制,即从库做为其他机子的主库
log-slave-updates=1
# 从库执行binlog中的sql出错时候,默认会停止复制,改参数可跳过错误继续执行后面的操作
slave_skip_errors=all
# slave_skip_errors=1062,1053  #可直接填写mysql错误码
# 从库只读
read_only=1

五、主从同步延时问题

(一)延时原因

1、主库执行写入操作,从库上会根据主库的binlog,生成一个relay log执行对应的写入操作,在执行这些relay log时,重放数据,对于这些DML和DDL的IO操作来说是随机io。当主库写入量多的情况下,就会产生延迟。

2、数据重放过程中遇到锁等待

(二)缓解方案

1、若某些数据实时性要求极高,可以读取主库数据
2、对于从库若不需要非常高的数据安全,从库配置可以考虑innodb_flush_log_at_trx_commit=0与sync_binlog=0
3、从库开启并行复制

六、sqlachemy读写分离

import time
import random
import pymysql

pymysql.install_as_MySQLdb()
from flask import Flask
from flask_sqlalchemy import SQLAlchemy, SignallingSession, get_state
from sqlalchemy import orm

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:123456@127.0.0.1:33306/test'  # 设置数据库连接地址
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False  # 是否追踪数据库变化(触发某些钩子函数), 开启后效率会变
app.config['SQLALCHEMY_ECHO'] = True  # 开启后, 控制台会打印底层执行的SQL语句

app.config['SQLALCHEMY_BINDS'] = {  # get_engine的bind参数为该配置的键
    'master': 'mysql://root:123456@127.0.0.1:33306/test',
    'slave1': 'mysql://root:123456@127.0.0.1:33307/test'
}


class RoutingSession(SignallingSession):
    def get_bind(self, mapper=None, clause=None):
        """每次数据库操作(增删改查及事务操作)都会调用该方法, 来获取对应的数据库引擎(访问的数据库)"""
        state = get_state(self.app)
        if mapper is not None:
            try:
                # SA >= 1.3
                persist_selectable = mapper.persist_selectable
            except AttributeError:
                # SA < 1.3
                persist_selectable = mapper.mapped_table
            # 如果项目中指明了特定数据库,就获取到bind_key指明的数据库,进行数据库绑定
            info = getattr(persist_selectable, 'info', {})
            bind_key = info.get('bind_key')
            if bind_key is not None:
                return state.db.get_engine(self.app, bind=bind_key)

                # 使用默认的主数据库
                # SQLALCHEMY_DATABASE_URI 返回数据库引擎
                # return SessionBase.get_bind(self, mapper, clause)

        from sqlalchemy.sql.dml import UpdateBase
        # 写操作 或者 更新 删除操作 - 访问主库
        if self._flushing or isinstance(clause, UpdateBase):
            print("写更新删除 访问主库")
            # 返回主库的数据库引擎
            return state.db.get_engine(self.app, bind="master")
        else:
            # 读操作--访问从库,目前是直接随机选取,若需要负载需进一步优化
            slave_key = random.choice(["slave1"])
            print("访问从库:{}".format(slave_key))
            # 返回从库的数据库引擎
            return state.db.get_engine(self.app, bind=slave_key)


class RoutingSQLAlchemy(SQLAlchemy):
    def create_session(self, options):
        return orm.sessionmaker(class_=RoutingSession, db=self, **options)


# 初始化组件(建立数据库连接)
db = RoutingSQLAlchemy(app)


# ORM  类->表  类属性->字段  对象->记录
class User(db.Model):
    __tablename__ = "t_user"  # 设置表名, 默认为类名的小写
    id = db.Column(db.Integer, primary_key=True)  # 主键
    name = db.Column(db.String(20), unique=True, nullable=False)  # 设置唯一&非空约束
    age = db.Column(db.Integer, default=10, index=True)  # 设置默认值约束&建立索引


@app.route('/')
def index():
    # 增加数据  进行检验是否读写分离
    # 1.创建模型对象
    write_data()
    time.sleep(1)
    print('-' * 30)
    # 2.查询数据
    read_all_data()
    return "index"


def write_data():
    user1 = User(name=5, age=5)
    db.session.add(user1)
    db.session.commit()


def read_all_data():
    print(User.query.all())


if __name__ == '__main__':
    db.drop_all()  # 删除所有继承自db.Model的表
    db.create_all()  # 创建所有继承自db.Model的表
    app.run(debug=True)