zoukankan      html  css  js  c++  java
  • 28 读写分离有哪里要注意的地方

    28 读写分离有哪里要注意的地方

     在一主多从的架构中,读写分离,以及怎么处理主备延时导致的读写分离的问题。

    读写分离的主要目标是分担主库压力,上图的结构是客户端主动做负载均衡,在这种模式下一般会把数据库的连接信息放在客户端的连接层,也就是由客户端来选择后端数据库进行查询。

    还有一种架构,在mysql和客户端之间加入一层中间件层proxy,客户端只连接proxy,由proxy根据请求类型和上下文请求决定路由的分发。

    我们看一下客户端直连和带proxy的读写分离架构,各有那些特点

    1 客户端直连方案,因为少了一层proxy转发,所以查询性能稍微好一点,并且整体架构简单,排查问题更方便,但这种方案,由于要了解后端部署细节,所以在出现主备切换、库迁移等操作的时候,客户端都会感知,并且需要调整数据库连接信息。

    你可能会觉得这样客户端比较麻烦,信息大量冗余,架构很丑,其实也未必,一般这样的架构,一定会伴随一个负责管理后端的组件,比如zookeeper,尽量让业务端只专注于业务逻辑开发

    2 proxy的架构,对客户端比较友好,客户端不需要关注背后细节,连接维护、后端信息维护等工作,都是有proxy完成。但这样的话,对后端维护团队的要求会更高,而且,proxy也需要有高可用架构,因此,带proxy架构的整体相对比较复杂。

    但是,不论使用哪种架构,都会碰到:由于主从可能存在延时,客户端执行完一个更新事务后立马发起查询,如果查询选择的是从库的话,就很有可能读的是事务更新之前的状态。

    这种”在从库上会读到系统的一个过期状态”的现象,称为”过期读”。

    处理过期读的方案

    1 强制走主库方案

    2 sleep方案

    3 判断主备无延迟方案

    4 配合semi-sync方案

    5 等主库位点方案

    6 gtid方案

    强制走主库方案

    将查询请求做分类。通常情况下,可以将查询分为两类

    1 对于必须要拿到最新结果的请求,强制将其发到主库上,比如,一个交易平台,卖家发布商品以后,马上要返回主页面,看商品是否发布成功,那么,这个请求需要拿到最新的结果,就必须走主库。

    2 对于可以读到旧数据的请求,才将其发到从库,在这个交易平台上,买家来逛商铺页面,就算晚几秒看到最新发布的商品,也是可以接受的。那么,这类请求就可以走从库。

    这个方法有点取巧的意思,用的还是比较多,这个方案的最大的问题在于,有时候会碰到”所有查询都不能是过期读”的需求,比如一些金融类的业务,这样的话,要放弃读写分离,所有读写压力都在主库,等同于放弃了扩展性。

    Sleep方案

    主库更新后,读从库先sleep一下,具体方案就是,类似于执行一条select sleep(1)的命令。

    这个方案的假设是,大多数情况下主备延迟在1秒之内,做一个sleep可以有大概率拿到新数据。这个方案的第一感觉应该是不靠谱,直接在发起查询的时候先执行一条sleep的语句,用户体验不友好。

    从严格意义上来说,这个方案存在的问题就是不精确:

    1 如果这个查询请求本来0.5秒就可以在从库返回拿到正确结果,也会等到1

    2 如果延迟超过1秒,还是会出现过期读。

    判断主备无延迟方案

    要判断备库无延迟,通常有三种做法

    show slave status结果里的seconds_behind_master参数的值,可以用来衡量主备延迟时间的长短。

    每次从库执行查询请求前,先判断seconds_behind_master是否已经等于0,如果还不等于0,那就必须等到这个参数变0在从库执行查询。seconds_behind_master的单位是秒,如果觉得精度不够,还可以采用对比位点和gtid的方法来确保主备无延迟

    (system@127.0.0.1:3306) [(none)]> show slave statusG;
    *************************** 1. row ***************************
                   Slave_IO_State: Waiting for master to send event
                      Master_Host: 192.168.19.145
                      Master_User: repl
                      Master_Port: 3306
                    Connect_Retry: 60
                  Master_Log_File: mysql-bin.000024
              Read_Master_Log_Pos: 239249859
                   Relay_Log_File: relaylog.000006
                    Relay_Log_Pos: 239250022
            Relay_Master_Log_File: mysql-bin.000024
                 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: 239249859
                  Relay_Log_Space: 239250351
                  Until_Condition: None

    第二种对比位点确保主备无延迟

    Master_Log_FileRead_Master_Log_Pos表示的是读到的主库的最新文件和位点

    Relay_Master_Log_FileExec_Master_Log_Pos表示的是备库执行的最新文件和位点

    如果Master_Log_FileRelay_Master_Log_FileRead_Master_Log_PosExec_Master_Log_Pos的这两组值完全相同,就表示接收到的日志已经同步完成。

    第三种方法对比gtid集合确保主备不延迟

    Auto_Position=1 表示这对主备关系使用了gtid协议

    Retrieved_Gtid_Set,是备库收到的所有日志的gtid的集合

    Executed_Gtid_Set,是备库所有已经执行完成的gtid的集合

    如果这两个集合相同,也表示备库接收到的日志都已经全部同步完成。

    可见,对比位点和对比gtid这两种方法,都比判断seconds_behind_master要准确

    在回顾一下,一个事务的binlog在主备库之间的状态

    1 主库执行完成,写入binlog,并反馈给客户端

    2 binlog被主库发送到备库,备库收到

    3 在备库上执行binlog完成。

    我们上面判断主备无延迟的逻辑,是”备库接收到的日志都执行完成了”。但是,从binlog中间状态的分析中,有一部分日志,处于客户端已经接收到提交确认,而备库还没有收到日志的状态。

    这时候,主库上执行完成了三个事务,trx1,trx2trx3

    1 trx1trx2已经传到从库,并且已经执行完成

    2 trx3在主库执行完成,并且已经恢复给客户端,但是还没有传到从库。

    如果这时候在从库B上执行查询请求,按照上面的逻辑,从库认为已经没有同步延时,但还是查不到trx3的事务。

    配合semi-sync

    要解决这个问题,就要引入半同步复制,semi-sync replication

    semi-sync的设计:

    1 事务提交的时候,主库把binlog发给从库

    2 从库收到binlog后,发回给主库一个ack,表示收到了

    3 主库收到这个ack后,才能给客户端返回”事务完成”的确认。

    也就是说,如果启用了semi-sync,就表示所有给客户端发送过确认的事务,都确保备库已经收到了这个日志。

    其实,判断同步位点的方案还有一个潜在的问题,即:如果在业务更新的高峰期,主库的位点或者gtid集合更新很快,那么上面的两个位点等值判断就会一直不成立,很可能出现从库无法响应查询请求的情况。

    等主库位点方案

    select master_pos_wait(file, pos[, timeout]);

    这条命令的逻辑:

    1 在从库执行

    2 参数filepos指的是主库上的文件名和位置

    3 timeout可选,设置为正整数N表示这个函数最多等待N

    这个命令正常返回的结果是一个正整数M,表示从命令开始执行,到应用完filepos表示的binlog位置,执行了多少个事务

    当然,除了正常返回一个正整数M外,还返回一些其他结果

    1 如果执行期间,备库同步线程发生异常,则返回null

    2 如果等待超过N秒,就返回-1

    3 如果刚开始执行的时候,就发现已经执行过这个位置了,则返回0

    1 trx1事务更新完成后,马上执行show master status得到当前主库执行的fileposition

    2 选定一个从库执行查询语句

    3 在从库上执行select master_pos_wait(file,position,1)

    4 如果返回值>=0的正整数,则在这个从库执行查询语句

    5 否则,到主库执行查询语句

    假设,这条select查询最多在从库上等待1秒,那么,如果1秒内master_pos_wait返回一个大于等于0的整数,就确保了从库上执行的这个查询结果一定包含了trx1的数据。

    步骤5到主库执行查询语句,是这类方案常用的退化机制,因为从库的延时不可控,不能无限等待,所以如果等待超时,就应该去主库查询。

    GTID方案

    数据库开启gtidmysql提供了一个类似的命令

     select wait_for_executed_gtid_set(gtid_set, 1);

    1 等待,直到这个库执行的事务中包含传入的gtid_set,返回0

    2 超时返回1

    在前面等待位点的方案中,我们执行完事务后,需要执行show master status,而mysql 5.6开始,允许在执行完更新类事务后,把这个事务的gtid返回给客户端,这样等待gtid的方案就可以减少一次查询。

    这时,等gtid的执行流程就变成了:

    1 trx1事务更新完成,从返回包直接获取这个事务的gtid,记为gtid1

    2 选定一个从库执行查询语句

    3 在从库上执行select wait_for_executed_gtid_set(gtid1, 1);

    4 如果返回值0,则在这个从库执行查询

    5 否则,到主库执行查询语句

    在上面的第一步中,trx1事务更新完成后,从返回包直接获取这个事务的gtid,问题是,怎么能让mysql在执行事务后,返回包中带上gtid

    你只需要将参赛session_track_gtids位置为OWN_GTID,然后通过api结构mysql_session_track_get_first从返回包解析出gtid的值即可

    其实,mysql并没有提供这类接口的sql用户,是提供给程序的API(https://dev.mysql.com/doc/refman/5.7/en/c-api-functions.html)

    比如,为了让客户端在事务提交后,返回的gtid能够在客户端显示出来,对mysql客户端代码做点修改

    看到语句执行完成之后,显示出gtid的值

     

    上次问题

    gtid模式下,如果从一个新的从库接上主库,但是需要的binlog已经丢失,需要怎么做?

    1 如果业务允许主从不一致的情况,那么可以在主库上先执行show global variables like ‘gtid_purged’,得到主库已经删除的gtid集合,假设是gtid_purged1,然后在从库上执行reset master,再执行set global gtid_purged=’gtid_purged1’;最后执行start slave,就会从主库现存的binlog开始同步,binlog缺少的那一部分,数据在从库上就可能会有丢失,造成主从不一致

    2 如果需要主从数据一致,最好还是通过重新搭建主从来做

    3 如果有其他的从库保留的全量的binlog,可以把新的从库先连接到这个保存了全量的binlog的从库,追上日志,有需要在接回主库

    4 如果binlog有备份的话,可以现在从库上应用缺少的binlog,然后在执行start slave

    Mysql是怎么快速定位binlog里面的某一个gtid位置?

    binlog文件头部的previous_gtids可以解决这个问题,每个文件都有一个这个值

    sql_slave_skip_counter跳过的是一个event,由于mysql总部能执行一半的事务,所以既然跳过了一个event,就会跳到这个事务的末尾,因此,set global sql_slave_skip_countre=1;start slave是可以跳过整个事务的。

  • 相关阅读:
    uni-app实现下拉效果
    求点到已知直线的距离和点到直线的垂点坐标
    Mybatis 自定义SqlSessionFactoryBean扫描通配符typeAliasesPackage
    前端string类型的日期 -后端实体类属性为Date
    如何更新npm为最新版本
    简述ThreadPoolExecutor的运行机制
    list在遍历过程中的add/remove
    Linux下安装mysql
    Linux下安装activeMQ并设置开机启动
    solr集群环境搭建
  • 原文地址:https://www.cnblogs.com/yhq1314/p/10711808.html
Copyright © 2011-2022 走看看