记一次Mysql8主从复制和读写分离实验的踩坑之旅

数据库主从复制与读写分离

简介

随着系统使用,数据量增多,关系型数据库的瓶颈开始凸显,即使进行了建立索引、优化sql,也还是存在性能下降,查询大表等待时间过长等问题。

基于此,进行对数据库集群部署、主从复制、读写分离,从而实现数据热备,数据库故障主备切换;提升查询效率、用户体验。

主从复制

选择任意一种即可

基于二进制日志文件偏移量(Binary Log File Position)

配置
主库my.conf或my.ini
server-id=1            # 数据库中的id号,主从mysql数据库不能相同
log-bin=mysql-bin     # 允许slave同步自己的二进制文件
binlog-format=ROW #选择row模式
binlog-ignore-db=mysql #不同步mysql系统数据库 多个多写
从库my.conf或my.ini
server-id=2            # 数据库中的id号,主从mysql数据库不能相同
log-bin=mysql-bin     # 允许slave同步自己的二进制文件
binlog-format=ROW #选择row模式
binlog-ignore-db=mysql #不同步mysql系统数据库 多个多写

注意:可能是个例,mysql 8.02发现从库配置需要放在【default-character-set=utf8】 配置上测才能生效。

binlog-format参数
binlog-format包含三种类型分别为Statement,MiXED,以及ROW。
**ROW Level**
记录的方式是行,即如果批量修改数据,记录的不是批量修改的SQL语句事件,而是每条记录被更改的SQL语句,因此,ROW模式的binlog日志文件会变得很“重”。

优点:row level的binlog日志内容会非常清楚的记录下每一行数据被修改的细节。而且不会出现某些特定情况下存储过程或function,以及trigger的调用和触发器无法被正确复制的问题。

缺点:row level下,所有执行的语句当记录到日志中的时候,都以每行记录的修改来记录,这样可能会产生大量的日志内容,产生的binlog日志量是惊人的。批量修改几百万条数据,那么记录几百万行。

**Statement level(默认)**

记录每一条修改数据的SQL语句(批量修改时,记录的不是单条SQL语句,而是批量修改的SQL语句事件)。
优点:statement模式记录的更改的SQ语句事件,并非每条更改记录,所以大大减少了binlog日志量,节约磁盘IO,提高性能。
缺点:statement level下对一些特殊功能的复制效果不是很好,比如:函数、存储过程的复制。由于row level是基于每一行的变化来记录的,所以不会出现类似问题。

**Mixed**
实际上就是前两种模式的结合。在Mixed模式下,MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式,也就是在Statement和Row之间选择一种。
更多参数参见官方文档。
**企业场景如何选择binlog的模式**
1、 如果生产中使用MySQL的特殊功能相对少(存储过程、触发器、函数)。选择默认的语句模式,Statement Level。
2、 如果生产中使用MySQL的特殊功能较多的,可以选择Mixed模式。
3、 如果生产中使用MySQL的特殊功能较多,又希望数据最大化一致,此时最好Row level模式;但是要注意,该模式的binlog非常“沉重”。
操作
主库操作

查看MASTER_LOG_FILE 与 MASTER_LOG_POS

show master status;
从库操作
     CHANGE MASTER TO
  MASTER_HOST='192.168.1.2',
MASTER_USER='root',
MASTER_port=3307,
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='binlog.000026',
    MASTER_LOG_POS=891;  
START SLAVE;//开启从库复制
SHOW SLAVE STATUS;
STOP slave;//关闭从库复制

参数: MASTER_HOST: 主库物理地址 MASTER_USER:用户名 MASTER_port:主库端口 MASTER_PASSWORD:密码 MASTER_LOG_POS:从库复制位 MASTER_LOG_FILE: 从库起始复制文件(主库最新日志文件)

半同步复制开启

点位复制默认为异步复制。并不关注slave是否成功接受数据,如果脱机会导致slave数据丢失;有必要可以开启半从不复制,即确认一台slave同步成功,才继续操作,但会影响性能。

基于 GTID(Global Transaction Identifiers)

Master:
show variables like '%uuid%';  
+---------------+--------------------------------------+
| Variable_name | Value                               |
+---------------+--------------------------------------+
| server_uuid   | 2a09ee6e-645d-11e7-a96c-000c2953a1cb |
+---------------+--------------------------------------+
Slave
show variables like '%uuid%';
+---------------+--------------------------------------+
| Variable_name | Value                               |
+---------------+--------------------------------------+
| server_uuid   | 8ce853fc-6f8a-11e7-8940-000c29e3f5ab |
+---------------+--------------------------------------+

参数讲解
Retrieved_Gtid_Set : 从库已经接收到主库的事务编号
Executed_Gtid_Set : 从库自身已经执行的事务编号

Master Slave my.inf修改
#开启全局事务ID,GTID能够保证让一个从服务器到其他的从服务器那里实现数据复制而且能够实现数据整合的
gtid_mode = on
#开启gtid,必须主从全开
enforce_gtid_consistency = 1

change master to
master_host='master1',   #这边master1是master1的主机名,也可以是IP或者域名
master_user='master2',   #这边master2是用户名而已
master_password='Root@123',
master_auto_position=1;
start slave;
show slave STATUS;

GTID (Global Transaction Identifiers)是对于一个已提交事务的编号,事务的唯一编号,并且是一个全局唯一的编号。GTID 和事务会记录到 binlog 中,用来标识事务。GTID 是用来替代以前 传统的复制方法,MySQL-5.6.2 开始支持 GTID,在 MySQL-5.6.10 后完善。有了 GTID,一个事务在集群中就不再孤单,在每一个节点中,都存在具有相同标识符的兄弟们和它作伴,可以避免同一个事务,在同一个节点中出现多次的情况。GTID 的出现,最直接的效果就是,每一个事务在集群中具有了唯一性的意义,这在运维方面具有更大的意义,因为使用 GTID 后再也不需要为了不断地找点而烦恼了,给 DBA 带来了很大的便利性。

GTID的优点: 1.根据GTID可以知道事务最初是在哪个实例上提交的 2.GTID的存在方便了Replication的Failover

GTID的缺点: 1.GTID 模式实例和非GTID模式实例是不能进行复制的,要求非常严格,要么都是GTID,要么都不是 2.gtid_mode 是只读的,要改变状态必须1)关闭实例、2)修改配置文件、3) 重启实例

读写分离

读写分离使用了阿里的中间件Mycat实现,由于Mycat1主要用于支持Mysql5,需要对源码进行修改进行重新打包,参见[Mycat1注意]。

官方:http://mycat.org.cn/

团队项目:https://github.com/MyCATApache

源码地址:https://github.com/MyCATApache/Mycat-Server

安装部署Mycat

在源码地址下载源码,按照[Mycat1注意]修改后打包生成Mycat-server-1.6.7.6-release-20210128213924-win.tar.gz,解压缩后执行启动脚本mycat.bat即可。

Mycat1配置

简单配置读写分离只需要修改schema.xml 和server.xml 即可。

server.xml用于配置Mycat用户权限。schema.xml用于配置物理数据库,是否读写分离、主备切换、心跳检测等。

如下xml配置了两个关系:

数据库代理(mycat-water)-----→ dataNode(dn_water)--------→ dataHost(dh_conf)--------→ 物理层数据库(water-dev)----→ 物理地址 读配置 写配置

DB_proxy(mycat-water)-----→ dataNode(dn_water)--------→ dataHost(dh_conf)--------→ database(water-dev)----→ 物理地址 读配置 写配置

DB_proxy(mycat)-----→ dataNode(dn_water)--------→ dataHost(dh_conf)--------→ database(xxl_job)----→ 物理地址 读配置 写配置

其中root对 mycat拥有读写权限,test对mycat-water有读写权限。

schema.xml

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="mycat" checkSQLschema="true" sqlMaxLimit="100" dataNode="dn_callcenteradmin"> </schema>
<schema name="mycat-water" checkSQLschema="true" sqlMaxLimit="100" dataNode="dn_water"> </schema>
<dataNode name="dn_water" dataHost="dh_conf" database="water-dev" />
<dataNode name="dn_callcenteradmin" dataHost="dh_conf" database="xxl_job" />
<dataHost name="dh_conf" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>show slave status</heartbeat>
<writeHost host="ip3307" url="localhost:3307" user="root" password="123456">
<readHost host="ip3308" url="localhost:3308" user="root" password="123456" />
</writeHost>
<!--<writeHost host="hostM2" url="jdbc:mysql://localhost:330?useSSL=false&amp;serverTimezone=UTC&amp;characterEncoding=utf8" user="root" password="123456" /> 高可用,hostM1宕机了, hostM2顶上 -->
</dataHost>
</mycat:schema>

server.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://io.mycat/">
<system>
<property name="nonePasswordLogin">0</property> <!-- 0为需要密码登陆、1为不需要密码登陆 ,默认为0,设置为1则需要指定默认账户-->
<property name="ignoreUnknownCommand">0</property><!-- 0遇上没有实现的报文(Unknown command:),就会报错、1为忽略该报文,返回ok报文。
在某些mysql客户端存在客户端已经登录的时候还会继续发送登录报文,mycat会报错,该设置可以绕过这个错误-->
<property name="useHandshakeV10">1</property>
   <property name="removeGraveAccent">1</property>
<property name="useSqlStat">0</property>  <!-- 1为开启实时统计、0为关闭 -->
<property name="useGlobleTableCheck">0</property>  <!-- 1为开启全加班一致性检测、0为关闭 -->
<property name="sqlExecuteTimeout">300</property>  <!-- SQL 执行超时 单位:秒-->
<property name="sequenceHandlerType">1</property>
<!--<property name="sequnceHandlerPattern">(?:(\s*next\s+value\s+for\s*MYCATSEQ_(\w+))(,|\)|\s)*)+</property>
INSERT INTO `travelrecord` (`id`,user_id) VALUES ('next value for MYCATSEQ_GLOBAL',"xxx");
-->
<!--必须带有MYCATSEQ_或者 mycatseq_进入序列匹配流程 注意MYCATSEQ_有空格的情况-->
<property name="sequnceHandlerPattern">(?:(\s*next\s+value\s+for\s*MYCATSEQ_(\w+))(,|\)|\s)*)+</property>
<property name="subqueryRelationshipCheck">false</property> <!-- 子查询中存在关联查询的情况下,检查关联字段中是否有分片字段 .默认 false -->
<property name="sequenceHanlderClass">io.mycat.route.sequence.handler.HttpIncrSequenceHandler</property>
     <!-- <property name="useCompression">1</property>--> <!--1为开启mysql压缩协议-->
       <!-- <property name="fakeMySQLVersion">5.6.20</property>--> <!--设置模拟的MySQL版本号-->
<!-- <property name="processorBufferChunk">40960</property> -->
<!--
<property name="processors">1</property>
<property name="processorExecutor">32</property>
-->
       <!--默认为type 0: DirectByteBufferPool | type 1 ByteBufferArena | type 2 NettyBufferPool -->
<property name="processorBufferPoolType">0</property>
<!--默认是65535 64K 用于sql解析时最大文本长度 -->
<!--<property name="maxStringLiteralLength">65535</property>-->
<!--<property name="sequenceHandlerType">0</property>-->
<!--<property name="backSocketNoDelay">1</property>-->
<!--<property name="frontSocketNoDelay">1</property>-->
<!--<property name="processorExecutor">16</property>-->
<!--
<property name="serverPort">8066</property> <property name="managerPort">9066</property>
<property name="idleTimeout">300000</property> <property name="bindIp">0.0.0.0</property>
<property name="dataNodeIdleCheckPeriod">300000</property> 5 * 60 * 1000L; //连接空闲检查
<property name="frontWriteQueueSize">4096</property> <property name="processors">32</property> -->
<!--分布式事务开关,0为不过滤分布式事务,1为过滤分布式事务(如果分布式事务内只涉及全局表,则不过滤),2为不过滤分布式事务,但是记录分布式事务日志-->
<property name="handleDistributedTransactions">0</property>

<!--
off heap for merge/order/group/limit     1开启   0关闭
-->
<property name="useOffHeapForMerge">0</property>

<!-- 单位为m -->
       <property name="memoryPageSize">64k</property>

<!-- 单位为k -->
<property name="spillsFileBufferSize">1k</property>
<property name="useStreamOutput">0</property>
<!-- 单位为m -->
<property name="systemReserveMemorySize">384m</property>
<!--是否采用zookeeper协调切换 -->
<property name="useZKSwitch">false</property>
<!-- XA Recovery Log日志路径 -->
<!--<property name="XARecoveryLogBaseDir">./</property>-->
<!-- XA Recovery Log日志名称 -->
<!--<property name="XARecoveryLogBaseName">tmlog</property>-->
<!--如果为 true的话 严格遵守隔离级别,不会在仅仅只有select语句的时候在事务中切换连接-->
<property name="strictTxIsolation">false</property>
<!--如果为0的话,涉及多个DataNode的catlet任务不会跨线程执行-->
<property name="parallExecute">0</property>
</system>
<!-- 全局SQL防火墙设置 -->
<!--白名单可以使用通配符%或着*-->
<!--例如<host host="127.0.0.*" user="root"/>-->
<!--例如<host host="127.0.*" user="root"/>-->
<!--例如<host host="127.*" user="root"/>-->
<!--例如<host host="1*7.*" user="root"/>-->
<!--这些配置情况下对于127.0.0.1都能以root账户登录-->
<!--
<firewall>
  <whitehost>
     <host host="1*7.0.0.*" user="root"/>
  </whitehost>
      <blacklist check="false">
      </blacklist>
</firewall>
-->
<user name="test" defaultAccount="true">
<property name="password">test</property>
<property name="schemas">mycat-water</property>
<property name="defaultSchema">mycat-water</property>
<!--No MyCAT Database selected 错误前会尝试使用该schema作为schema,不设置则为null,报错 -->
<!-- 表级 DML 权限设置 -->
<!-- <privileges check="false">
<schema name="TESTDB" dml="0110" >
<table name="tb01" dml="0000"></table>
<table name="tb02" dml="1111"></table>
</schema>
</privileges> -->
</user>
<user name="root" defaultAccount="true">
<property name="password">test</property>
<property name="schemas">mycat</property>
<property name="defaultSchema">mycat</property>
<!--No MyCAT Database selected 错误前会尝试使用该schema作为schema,不设置则为null,报错 -->

<!-- 表级 DML 权限设置 -->
<!--
<privileges check="false">
<schema name="TESTDB" dml="0110" >
<table name="tb01" dml="0000"></table>
<table name="tb02" dml="1111"></table>
</schema>
</privileges>
-->
</user>
</mycat:server>

Mycat1常用参数:

  • 负载均衡类型,目前的取值有 3 种:

  1. balance="0", 不开启读写分离机制,所有读操作都发送到当前可用的 writeHost 上。

  2. balance="1",全部的 readHost 与 stand by writeHost 参与 select 语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2,并且 M1 与 M2 互为主备),正常情况下,M2,S1,S2 都参与 select 语句的负载均衡。

  3. balance="2",所有读操作都随机的在 writeHost、readhost 上分发。

  4. balance="3",所有读请求随机的分发到 wiriterHost 对应的 readhost 执行,writerHost 不负担读压力,注意 balance=3 只在 1.4 及其以后版本有,1.3 没有。

  • 负载均衡类型,目前的取值有 3 种:

  1. writeType="0", 所有写操作发送到配置的第一个 writeHost,第一个挂了切到还生存的第二个writeHost,重新启动后已切换后的为准,切换记录在配置文件中:dnindex.properties 。

  2. writeType="1",所有写操作都随机的发送到配置的 writeHost,1.5 以后废弃不推荐。

  • switchType 属性: -1 表示不自动切换。 1 默认值,自动切换。 2 基于 MySQL 主从同步的状态决定是否切换。

更多参数参见[官方文档]或[资料]中的权威指南。

压测

注:由于是单机测试,一主两从台数据库,io使用不具备和生产环境一样的随机性,请求耗时无法和生产,建议建立开发环境,统计接口耗时

Mysql : 8+

Mycat :1.6

Jmeter:5.4

样本:10000条数据 1000ramp-up压测

1.1 内存使用

mycat

image-20210129235129256

Mysql

mycat 性能测试报告

image-20210130001031661

Mysql性能测试报告

压测结果:

Mycat在高吞吐量下会占用最高400,最低100的堆内存空间。

Mysql在数据量少的情况下,数据库的执行效率略高于Mycat,但不明显(可能单机测试不存在网络问题的原因)。

未来

升级

Mycat2 Mysql-proxy 未来可能被作为升级方案。

Mycat2已经被开源给apache团队。新版本Mycat2专为Mysql8设计,但还未完善。

Mysql-proxy 是Mysql官方团队的中间件,用于请求转发,目前暂未发布正式版本。

分区分片分表

当单表的数据量达到1000W或100G左右,由于查询维度较多,即使添加从库、优化索引,做很多操作时性能仍下降严重。此时就要考虑对其进行切分了,切分的目的就在于减少数据库的负担,缩短查询时间,这时可以考虑分区、分表、分库。

如基于分表:就是把一张表按一定的规则分解成N个具有独立存储空间的实体表。系统读写时需要根据定义好的规则得到对应的字表名,然后操作它。可以进行

分年表、月表、日表,按照日期生成表名进行查询。

Mycat1注意

  1. mycat1.6是不兼容后端使用JDBC 8连接驱动连接建议降为JDBC 5..版本,若一定要使用JDBC 8驱动必须在源码做个性化配置

  2. 当链接到macat时mycat会直接将sql语句解析并发送到对应数据库而在查询表语句中mycat1.6解析的sql携带了query_cache_size而mysql8是已经删除了该参数但mycat却直接将该参数放到转发sql转发到数据库中导致数据库sql异常。

  3. 解决方式

    源码解决

    进入mycat源码的 io.mycat.server.handler.SelectHandler.java进行修改

//   c.execute(stmt, ServerParse.SELECT);
      SelectVariables.execute(c,stmt);

JAR包替换

删除mycat下lib中的mycat/lib/mysql-connector-java-5.1.35.jar驱动,换位/mysql-connector-java-8.0.18.jar(此方法windows下不生效)。

参考https://blog.csdn.net/Until_U/article/details/105614625

资料

Mycat1权威指南

https://www.yuque.com/ccazhw/tuacvk/vttsnv

Mycat2权威指南

https://www.yuque.com/books/share/6606b3b6-3365-4187-94c4-e51116894695/e9d8be124824ed0b6cbd4a7e9e70f1b9

 

posted @ 2021-02-02 13:41  不忘初心V  阅读(541)  评论(0)    收藏  举报