您的位置:新葡亰496net > 网络数据库 > 新葡亰496netmyrocks复制中断问题排查,剖析复制线

新葡亰496netmyrocks复制中断问题排查,剖析复制线

发布时间:2019-11-15 13:01编辑:网络数据库浏览(99)

    背景

    # rocksdb engine 写逻辑

    金钱观复制线程

    新葡亰496net 1

    复制线程图

    • master
      • Dump_thread
    • slave
      • IO_Thread
      • SQL_Thread
    • 纠葛的标题
      • MySQL Replication是主库把日记推给Slave,仍旧Slave从主库把日志拉复原的?
      • 能够如此清楚,延迟时间过长,比如用备份恢复生机时就是拉取,不奇怪主从未有延迟或推迟非常的低时是推
      • 主干结构的瓶颈是何许?
      • sql_thread是单线程重放,master是八线程写入,轻便招致瓶颈

          mysql可以帮衬三种不一样的储存引擎,innodb由于其急迅的读写质量,何况帮助理工科程师作天性,使得它成为mysql存储引擎的代名词,使用十三分广阔。随着SSD渐渐普遍,硬件存款和储蓄费用更加高,面向写优化的rocksdb引擎渐渐流行起来,大家也是看中了rocksdb引擎在写放大和空中放大的优势,将其引进到mysql种类。二种引擎的构造B-Tree(innodb引擎)和LSM-Tree(rocksdb引擎)很好地造成互补,我们得以依赖业务项目来筛选极其的积攒。平日mysql暗中认可是mysql innodb引擎,而mysql rocksdb引擎称之为myrocks。后天要钻探的便是myrocks复制中断难题,案例来源于真实的事情场景。

    ## 施行路线

    GTID原理

    • GTID
      • 事务独一编号,全剧唯生机勃勃(二个复制Group里)
      • 并且和东西记录到binlog中,用来标志事务
      • binlog中多叁个:Gtid_log_event
    • 构成
      • UUID:sequence_number
      • sequence_number是MySQL内部的贰个作业编号,二个MySQL不会再次的顺序号(保证服务器内唯生龙活虎)
      • 每一种MySQL有三个大局唯意气风发的UUID:$datadir/auto.cnf中积攒
      • 新葡亰496net,GTID复制中现身断点,推测是应用:master_auto_position=0
    • master_auto_position=1
      • MySQL可以记录推行过的事情ID,能够透过show master status -> gtid_executed查看
      • MySQL5.6依赖于binlog和gtid_purge;MySQL5.7依赖于mysql.gtid_executed表
      • slave上记下了采用和进行的gtid,能够透过show slave status:
        • Retrieve_gtid_set
        • Execute_gtid_set
      • slave连接Master时,会把gtid_executed中的gtid发给Master,Master会Skip过executed_gtid_set,把还没实施过的GTID事务发送给slave
      • BUG:slave_net_timeout 参数设置过小,造成MySQL Master Dump thread加多,主库会认为本人多了从库,其实是屡次的从库连接又断开形成的
    • MySQL5.6从非GTID到GTID切换,需求重启
    • MySQL5.7支撑在线启用GTID
      • 不用重启mysqld
      • 新葡亰496netmyrocks复制中断问题排查,剖析复制线程。在启用GTID进程,能够对外提供服务
      • 无需退换复制结构
      • 纵然急需GTID,又不想太多停机时间:进级到MySQL5.7在线启用就OK
    • MySQL5.7GTID运营进程
      • gtid_mode
    gitd_mode 解释
    OFF 不产生GTID,Slave只接收不带GTID的事务
    OFF_PERMISSIVE 不产生GTID,Slave接收不带GTID的事务也接收带GTID的事务
    ON_PERMISSIVE 产生GTID,Slave接收不带GTID的事务也接收带GTID的事务
    ON 产生GTID,Slave只接收带GTID的事务
    • 5.7GTID的切换流程
    gitd_mode 执行对象
    set global gtid_mode='OFF_PERMISSIVE'; 在Group中每个MySQL上执行
    set global gtid_mode='ON_PERMISSIVE'; 在Group中每个MySQL上执行,为了安全尽量现在从库上执行
    确认每个Group中binlog非GTID的执行完毕
    set global gtid_mode='ON'; 在Group中每个MySQL上执行
    • MySQL5.7GTID会蕴藏到表里
      • 支撑slave不用开启binlog(log_slave_status),提出依然开启
    CREATE TABLE `gtid_executed` (
      `source_uuid` char(36) NOT NULL COMMENT 'uuid of the source where the transaction was originally executed.',
      `interval_start` bigint(20) NOT NULL COMMENT 'First number of interval.',
      `interval_end` bigint(20) NOT NULL COMMENT 'Last number of interval.',
      PRIMARY KEY (`source_uuid`,`interval_start`)
    
    • 什么记录gtid

      • 借使开启binlog,在binlog日志文件切换时,将最近的gtid插入到gtid_executed表中
        • insert into mysql.gtid_executed(UUID,1000,2000);
      • 倘使未有开启binlog,各个工作提交前,会实践一个insert操作
        • Begin
        • 业务操作
        • insert into mysql.gtid_executed(UUID,1000,2000); #隐式MySQL内部加多
        • commit
    • gtid_executed表压缩

      • 支配压缩频率
        • mysql>set global gtid_executed_compression_period=N;(N事务个数,暗许是1000)
            ![](https://upload-images.jianshu.io/upload_images/9520647-e8d8de8fe3db46c7.png)
    
            压缩前后对比
    
    • enforce-gtid-consistency

      • OFF 不检查实验是或不是有GTID不知的言语/事务
      • WA宝马X5N 当开掘不支持语句/事务,再次来到警示,并在日记中著录警报信息
      • ON 当开掘说话/事务不扶植GTID时,再次来到错误
    • TIPS

      • 在线上从非GTID到GTID切换进程中,能够先设置成:WA福睿斯N
    • GTID Limit

      • 不能够采纳 create table ... select ...
      • 业务中更新非事务表
        • begin:update no_trx_tabe set col1=xxx where xxx;update trx_tb set col1=xxx where ....;commit;
      • 事情中开创/删除一时表
        • begin:update trx_tb set xxx;create temporary table ...;commit;
      • sql_slave_skip_counter 不支持
      • 假设在人生观复制到GTID复制切换时,主从卡住了,能够用start slave sql_thread试一下看能还是不能够过去,假设过不去只可以动用enforce-gtid-consistency=off先让主从能跑通
    • GTID跳过错误

      • stop slave sql_thread;
      • set gtid_next='uuid:N';
      • begin;commit;
      • set gtid_next='automatic';
      • start slave sql_thread;

    标题现象

    DB::Put(key, value)是一个写操作简便包装, 最后都会卷入三个WriteBatch对象,调用rocksdb::DBImpl::WriteImpl来成功写。

    半一块复制

     新葡亰496net 2

    也足以手工构造叁个WriteBatch,作为一个批量事务操作,纳入多个key/value操作,三次提交。

    MySQL5.7无数据错过的半协助进行理并答复制

    新葡亰496net 3

    半协同复制

    • 半一齐复制里面主从会不会存在延迟?
      • 会存在延迟,半联合只可以有限支持写入到relay log,也正是IO_thread同步的局地,sql_thread重播有望会现出延迟
    • 在Master选拔到slave的ACK应答后才Commit事务(5.6上是Master在Commit后,才等待Slave应答)
      • 由此在作业复制到slave此前,并发的业务看不到当前的业务的数码(5.6那多少难题)
      • 当Master故障时,全数已交给的政工都会复制到slave上
      • 缺省级银行使无数据错失的答问等待机制,客商还可以采用5.6的应答待机制
      • mysql>set rpl_semi_sync_master_wait_point={AFTER_SYNC|AFTER_COMMIT}

    复制进度中,现身了1756荒谬,依照错误日志和debug确认是slave-sql线程在立异slave_worker_info表时出错招致,饭馆如下:

    ```cpp

    抓好半协同

    • mysql>set rpl_semi_sync_master_wait_point=AFTER_SYNC;
      [图形上传失败...(image-e3c6a6-1513426634210)]
    #7  0x0000000000a30104 in Rpl_info_table::do_flush_info (this=0x2b9999c9de00, force=<optimized out>) at /home/admin/95_20161208115448428_15352432_code/rpm_workspace/sql/rpl_info_table.cc:171
    #8  0x0000000000a299b1 in flush_info (force=true, this=<optimized out>) at /home/admin/95_20161208115448428_15352432_code/rpm_workspace/sql/rpl_info_handler.h:92
    #9  Slave_worker::flush_info (this=0x2b9999f80000, force=<optimized out>) at /home/admin/95_20161208115448428_15352432_code/rpm_workspace/sql/rpl_rli_pdb.cc:318
    #10 0x0000000000a29d95 in Slave_worker::commit_positions (this=this@entry=0x2b9999f80000, ev=ev@entry=0x2b9999c9ab00, ptr_g=ptr_g@entry=0x2b9999daca00, force=<optimized out>)
        at /home/admin/95_20161208115448428_15352432_code/rpm_workspace/sql/rpl_rli_pdb.cc:582
    #11 0x00000000009d61f0 in Xid_log_event::do_apply_event_worker (this=0x2b9999c9ab00, w=0x2b9999f80000) at /home/admin/95_20161208115448428_15352432_code/rpm_workspace/sql/log_event.cc:74
    

    #0  rocksdb::MemTable::Add (this=0x1619800, s=1, type=rocksdb::kTypeValue, key=..., value=..., allow_concurrent=false, post_process_info=0x0) at db/memtable.cc:425

    半同步

    • mysql>set rpl_semi_sync_master_wait_point=AFTER_COMMIT;
    ![](https://upload-images.jianshu.io/upload_images/9520647-d094f02d43c86bb7.jpg)
    
    半同步
    

    这边差不离介绍下复制相关的位点表,在并行复制方式下,加入复制的重要有八个角色,slave_io线程担任将主库的binlog拉取到本地;slave_sql线程读取binlog并基于早晚的国有国法分发给各样slave_worker;slave_worker线程回放主库的操作,到达与主库同步的指标。slave_io线程和slave_sql线程分别唯有一个,而worker线程可以有1个或多个,能够依照参数slave_parallel_workers设置。如果将slave_parallel_workers设置为0,则意味着关闭并行复制,slave_sql线程承当重放的工作。位点表首要有3张,包涵slave_worker_info,slave_relay_log_info和slave_master_info表。slave_io线程更新slave_master_info表,更新拉取的风靡岗位;slave_sql线程更新slave_relay_log_info表,更新同步位点;而slave_worker线程更新slave_worker_info,每种worker线程在该表中都对应一条记下,每种worker各自更新自身的位点。

    #1  0x00000000005c25c2 in rocksdb::MemTableInserter::PutCF (this=0x7fffffffa8b0, column_family_id=0, key=..., value=...) at db/write_batch.cc:869

    更加快的半生龙活虎并复制

    • MySQL5.6
    ![](https://upload-images.jianshu.io/upload_images/9520647-3a3164607962b8c1.png)
    
    mysql5.6半同步复制
    
    -   mysql5.6半同步复制两个事务中间有三个流程要走,所以两个事务之间存在时间间隔
    
    • MySQL5.7
      • 创制单独的对答采纳线程
      • 成为双工情势:发送和采纳互不影响
        ![](https://upload-images.jianshu.io/upload_images/9520647-f1c47ffd00a38f79.png)
    
        mysql5.7半同步复制
    

    Slave_worker的做事流程如下:

    #2  0x00000000005bdfe7 in rocksdb::WriteBatch::Iterate (this=0x7fffffffb0c0, handler=0x7fffffffa8b0) at db/write_batch.cc:381

    5.7半同步

    • Master接收到N个Slave应答后,才Commit事务
    • 客商能够设置应答的Slave数量
    mysql>set global rpl_semi_sync_master_wait_for_slave_count=2;
    
    • 专程提示:
      • 小于5.7版本,在从库关闭后生可畏段时间后,刚运营时,注意先用异步复制,复制追上后,在用半一齐复制
      • master:
        • set global rpl_semi_sync_master_enabled=ON|OFF;
      • slave:
        • set global rpl_semi_sync_slave_enabled=ON|OFF;
    • 增进半联合mysqld creash recovery
      • 环顾最终一个binlog提取在那之中Xid
      • 举目四望InnoDB维持状态在Prepare的事体链表,和Binlog中的Xid相比,假若Binlog中设有,则交给,不然回滚事务
      • 付给到InnoDB中居于Prepare,并且写入Binlog的,就能够从崩溃中平复专门的学业
      • 三种现象
        • redo中有Xid,filename,position 执行Commit
        • redo中有prepare_mutex_lock,Xid
          • 在last binlog中找到相应的事务 施行Commit
          • 反之rollback
        • redo只有职业本身,未有Xid,prepare_mutex_lock 执行rollback

    1) 读取event,调用do_apply_event进行重放;
    2) 境遇xid event(commit/rollback event),表示事情甘休,调用commit_positions更新位点音讯;
    3) 调用do_commit提交事务。

    #3  0x00000000005bfa41 in rocksdb::WriteBatchInternal::InsertInto (writers=..., sequence=1, memtables=0x1616100, flush_scheduler=0x1608808, ignore_missing_column_families=false, log_number=0, db=0x1608000,

    并行复制

    从大家抓的酒店来看,是worker线程在施行试行第2步出错,未来我们收获了启幕的信息,更新位点表失利直接引致了错误。

    concurrent_memtable_writes=false) at db/write_batch.cc:1206

    出现复制

    • 在MySQL5.6的产出复制是基于库等第
    • 精气神儿上急需:
      • 让在Master上能并发施行的作业,在Slave上也应际而生实施
      • 在Binlog中记录事务并发试行的有关新闻
      • Slave上根据以上那个音信,让那么些事情在Slave上五个线程中并发实践
    • MySQL5.7的面世复制是基于事务等级(Logical Clock)
    • 在Master上并发COMMIT事务更加多,slave上并发品质越好
    • 微调参数
      • binlog_group_commit_sync_delay #group_commit等待时间,提出20微妙
      • binlog_group_commit_sync_no_delay_count #group_commit等待时间内达到多少个事情,比如这么些参数是四贰十个,当等待时间内已经有伍11个专门的事业在等候,就径直交给,不在等待了,建议安装20
      • slave_preserve_commit_order=ON|OFF
        • 开启binlog
        • Logical_clock有作用

    标题一定与剖析

    #4  0x00000000004c85ae in rocksdb::DBImpl::WriteImpl (this=0x1608000, write_options=..., my_batch=0x7fffffffb0c0, callback=0x0, log_used=0x0, log_ref=0, disable_memtable=false) at db/db_impl.cc:4953

    依赖锁的并行复制

    • 专门的学业能还是无法现身是锁来支配的.假若有锁冲突,则一个作业要等待另二个业务施行完结
    • 怎么样判别并行事务没有锁冲突
      • 假诺七个业务(操作)能够并行实践,那三个事情没有锁的冲突
      • 当事情初始实行Commit语句时,它已经获得了有着的锁
      • 当事情走入到prepare阶段时,它已经获取了全体的锁
    • 并发新闻以逻辑的光阴方式写入gtid_log_event中,共记录八个逻辑时间
      • sequence_number
        • 当职业写入binlog时,事务和睦的逻辑时间,依据作业记录binlog的各种依次增加
      • commit_parent
        • 当工作进行prepare阶段时,已经交由的专业的最大逻辑时间
    • 事务在Slave上执行
    动作 事务 依赖关系
    insert commit T1 (1,0)
    update commit T2 (2,0)
    delete commit T3 (3,1)
    • commit_parent在此之前实行的事务全体交到之后,才起来施行
    • T1和T2同事起始进行,T1 Commit后,T3开首荐行

    接下去,大家要拜访最后是什么样招致了翻新位点表战败?依据最后的错误码,大家调节和测验时设置了多少断点,最后收获了之类饭店

    #5  0x00000000004c6b72 in rocksdb::DBImpl::Write (this=0x1608000, write_options=..., my_batch=0x7fffffffb0c0) at db/db_impl.cc:4585

    启用并行复制

    • 启用GTID!!!
    mysql>stop slave sql_thread;
    mysql>set global slave_parallel_workers=4|8|max_cpu_core/2;  #最多可以设成cpu核数,不过不建议,一般4个或8个就够了
    mysql>set global slave_parallel_type='LOGICAL_CLOCK'; #OR DATABASE
    mysql>start slave sql_thread;
    
    • MySQL 5.7并行复制的思辨轻便易懂,总的来讲: 叁个组提交的作业都以足以相互回放,因为这一个业务皆是步入到事情的prepare阶段,则证实事情之间平昔不别的冲突(不然就一点都不大概付出卡塔尔。
    • DATABASE:私下认可值,基于库的并行复制情势
    • LOGICAL_CLOCK:基于组提交的并行复制格局
    • 最棒配置binlog group commit
      • binlog_group_commit_sync_delay
      • binlog_group_commit_sync_no_delay_count
    #0  myrocks::Rdb_transaction::set_status_error (this=0x2b99b6c5b400, thd=0x2b99b6c51000, s=..., kd=std::shared_ptr (count 18, weak 0) 0x2b997fbd7910,
        tbl_def=0x2b9981bc95b0) at /home/admin/95_20161208115448428_15352432_code/rpm_workspace/storage/rocksdb/ha_rocksdb.cc:1460
    #1  0x0000000000a6da28 in myrocks::ha_rocksdb::check_and_lock_unique_pk (this=this@entry=0x2b997fbb3010, key_id=key_id@entry=0, row_info=...,
        found=found@entry=0x2b9a58ca77ef, pk_changed=pk_changed@entry=0x2b9a58ca782f)
        at /home/admin/95_20161208115448428_15352432_code/rpm_workspace/storage/rocksdb/ha_rocksdb.cc:7092
    #2  0x0000000000a6e8a8 in myrocks::ha_rocksdb::check_uniqueness_and_lock (this=this@entry=0x2b997fbb3010, row_info=..., pk_changed=pk_changed@entry=0x2b9a58ca782f)
        at /home/admin/95_20161208115448428_15352432_code/rpm_workspace/storage/rocksdb/ha_rocksdb.cc:7250
    #3  0x0000000000a7386a in myrocks::ha_rocksdb::update_write_row (this=this@entry=0x2b997fbb3010, old_data=old_data@entry=0x0
    

    #6  0x00000000004ccd99 in rocksdb::DB::Put (this=0x1608000, opt=..., column_family=0x15d6500, key=..., value=...) at db/db_impl.cc:5803

    延期复制

    • 让从库和主库保持一直时间的推移
    • 应用情况
      • 选拔延迟复制做误操作恢复生机
      • 动用延迟复制做总计解析情况管理
    mysql>stop slave sql_thread;
    mysql>change master to master_delay=N;  #N单位秒
    mysql>start slave sql_thread;
    
    • 从库在某叁个复制地点停住
      • start slave中有个参数为until能够兑现那几个职能
      • start slave sql_thread until master_log_file='xxx',master_log_pos=xxx;
      • 还是能用GTID情势,{SQL_BEFORE_GTIDS|SQL_AFTER_GTIDS}=gtid_set

    经过代码剖析拿到了之类音信:

    #7  0x00000000004c69f0 in rocksdb::DBImpl::Put (this=0x1608000, o=..., column_family=0x15d6500, key=..., val=...) at db/db_impl.cc:4560

    多源复制

    • Slave 能够同不常间从四个Master复制
    • 各种DB中的名字不可能长久以来,不然有望复制出错
      [图表上传退步...(image-6f90e6-1513426634210)]
    • 场景
      • 汇聚备份
      • 数据深入分析聚合
      • 分片数据聚合
        ![](https://upload-images.jianshu.io/upload_images/9520647-7b244ef4563f90fa.png)
    
    • 四个channels(channels包括:recevier thread ,relay logs ,applier threads)每种channel能独立的运转和结束
    • 通过P_S表进行情状监察和控制:
    • 下列表中增添了channel_name字段,不一样的Channel的信息在分裂的行中展现
      • replication_applier_status_by_coordinator
      • replication_applier_status_by_worker
      • replication_connection_status

    履新位点表之所以战败是因为更新记录时意识早就存在了一条记下,那条记下的sequnceNumber比当前快速照相获取的sequnceNumber大,所以报错。这里差非常少介绍下sequenceNumber,sequenceNumber是大局依次增加的,内部存款和储蓄为二个7字节的整数,罗克sdb信任sequenceNumber完毕MVCC。每条记下有贰个唯意气风发的sequenceNumber,rocksdb利用sequenceNumber实行可以预知性判别。事务在提交时,获取当前最大的sequenceNumber,并依据前后相继顺序为作业中的每条记下分配八个sequenceNumber,然后写入memtable。同二个key的七个例外的sequenceNumber记录依照逆序存放,即sequenceNumber最大的key放在最前边,最小的放在最前面。表第11中学key-n,key表示key值,n表示key对应sequenceNumber,倘使key1<key2<key3<key4,则存款和储蓄顺序如下:

    ```

    group replication

    新葡亰496net 4

    group replication

    • Group Replication的庐山真面目目是:
      • 二进制日志
      • 基于Row格式 GTID
      • 二个通讯框架 事务排序调整(atomic message delivery & total ordering of message)
      • 增进半一同 & 并行复制
    • Group Replication是四个趋势
    • 备份糟糕化解
      • mysqldump不扶助风流罗曼蒂克致性备份(5.7.18后扶助)
      • xtrabackup备份,产生品质损失比较严重
    • 新的节点参加进度对原本集群质量有震慑

    Key1-100

    Key1-50

    Key2-120

    Key2-80

    Key3-70

    Key4-150

    ValueA

    ValueB

    ValueC

    ValueD

    ValueE

    ValueF

    ## WriteBatch

    5.7复制别的地点的压实

    • 动态更动filter
      • 不供给重启MySQL
      • 支撑具有的Filter选项
      • 支撑各类字符集
    mysql>stop slave sql_thread;
    mysql>CHANGE REPLICATION FILTER REPLICATE_DO_DB= (db1,db2);
    mysql>start slave sql_thread;
    
    • 切换主时不用停sql_tread
    • 切换master时,只需终止io_thread,无需结束sql_thread
    mysql>stop slave io_thread;
    mysql>change master to master_host='master-2',...;
    mysql>start slave io_thread;
    
    • Replication的消息增添到了Performance Schema中
      • 透过SQL举行监察
      • 逻辑上无关的音信被放在区别的表中
    • 铺排消息表
      • replication_connection_configuration
      • replication_applier_configuration
    • 意况音信表
      • replication_connection_status
      • replication_applier_status
      • replication_applier_status_by_coordinator
      • replication_applier_status_by_worker

    打开读取时,会采用sequenceNumber构造建设三个快速照相,读取快照发生前的已经存在的记录,系统未来的改良与本快速照相非亲非故。假诺快照的sequenceNumber是150,推行get(key2)时,会找到(key2-120,ValueC);而只要快速照相sequenceNumber是100,试行get(key2)时,则会找到(key2-80,ValueD)。

    二个WriteBatch就是三个政工,里面会有不菲条操作记录,可以调用WriteBatch.Put/Delete...等操作参预操作(Key/Value)

    回到难点本人,看看与难点生死相依的翻新流程:

    WriteBatch.rep_ 是贰个binary buffer, 用于存款和储蓄batch中有着操作的笔录,格式如下:

    1.尝试对峙异key加锁,若无现身更新,上锁成功 //TryLock
    2.用到当前最大的sequenceNumberA生成快照
    3.反省快速照相生成后,key是还是不是被涂改 // ValidateSnapshot
      1)再度赢得最大sequenceNumberB,构造key进行检索 // GetLatestSequenceForKey
      2)查找是不是存在同样的key的笔录
    4.若雷同key的记录存在,且key的sequnceNumber大于sequenceNumberA,则以为有写冲突,报错。

    WriteBatch.content_flags_ 标志batch中含有的操作类型会集。

    作者们相遇的谬误刚巧就是凌驾了写冲突报错,那么以往主题素材来了,明明这么些key已经上锁了,并且获得了最新的sequencNumberA,为何照旧会读到肖似的key,且相应的seqeunceNumber比sequencNumberA大?结合此前解析的slave_worker_info表,大家能够作出以下测度

    field | length  | description |

    强悍猜度:

    ---------:| :----- |:-----

    1. 行级锁并发未有调控好,招致三个线程同时更新
    2. 少数路线下,快照的sequenceNumber相比较旧,不是新型
    3. slave_worker线程并发未有决定好,八个worker同一时间更新一条记下

    kHeader | FixInt64 | 类别号,单调依次增加, Batch sequence的初阶值

    小心求证:

    Count | FixInt32 | 操作记录个数

    现象看起来是如此的不创造,全体的只要认为都以一触就破,不过事实如此,独有通过越多的音信来辅佐我们特别认清。结合代码,大家对于地方疑心的多少个点,在连带路径下展开埋点验证。埋点主要为了拿走以下音讯:

    Type | FixInt8 Var32Int | 操作类型 column_family(if != 0)

    1. 切实哪个worker出错了,出错的是哪些key,sequence是不怎么?
    2. worker与key的映射关系
    3. 这么些key在失误前被何人更新过?
    4. 各个业务包括的笔录个数是有一些?

    Key | Var32String | key binary buffer

    赢得日志如下:

    Value | Var32String | value binary buffer

    Resource busy: seq:38818368818, key_seq:38818368817, index number is 0-0-1-2, pk is 0-0-0-10, thread is 46983376549632
    
    映射关系:
    worker id is 10, thread is 46983367104256  //写memtable线程
    worker id is 9, thread is 46983376549632   //出错线程
    worker id is 11, thread is 46983359772416  //事务提交线程
    
    写memtable线程:
    2016-12-19 16:42:06 53743 [ERROR] LibRocksDB:In Memtable, index_num is 0-0-1-2, pk is 0-0-0-10, seq is 38818368818, thread is 46983367104256
    
    2016-12-19 16:42:06 53743 [ERROR] LibRocksDB:In Memtable, index_num is 0-0-1-2, pk is 0-0-0-4, seq is 38818368665, thread is 46983367104256
    
    2016-12-19 16:42:06 53743 [ERROR] LibRocksDB:In Memtable, index_num is 0-0-1-2, pk is 0-0-0-3, seq is 38818368675, thread is 46983367104256
    
    提交线程:
    type is commit, write_start, thread is 46983359772416,   seq is 38818368817
    type is commit, write_end, thread is 46983359772416,   seq is 38818368818, count is 1
    

    Type/Key/Value各类记录重复一条, kHeader/Count batch对象共用

    报错的直接原因正是 已存在同样key的sequenceNumber 38818368818大于快速照相的squenceNumber 38818368817。那个冲突的key由worker 10写入memtable,不过由worker 11付给,而不是失误的线程。总计下来, 我们开掘几个奇异的风貌:

    ## rocksdb::DBImpl::WriteImpl

        1. 留存多个线程写二个key的景色,譬喻worker10曾经写过key为10,4,和3的笔录
        2. 对于出错的sequenceNumber(38818368818)的key,为啥会被worker11提交
        3. 日志中开采SequenceNumber不三回九转,存在跳跃的气象

    - 新建八个WriteThread::Writer对象,关联到传播的batch object.

    前多个难点相当轻便让我们陷入误区,存在多少个worker写同叁个key的情事,而实际上那多少个难题都以缘于group-commit机制,此外线程只怕会代替你付出,招致您会见到同叁个worker写分歧key的风貌。这里的group-commit包涵七个等级次序,server层group-commit和rocksdb引擎的group-commit。

    - 调用write_thread_.JoinBatchGroup(&w);

      新葡亰496net 5

    ### Group Commit

    从图中能够看看,在server层group-commit机制下,流入到rocksdb引擎层commit接口的皆以串行的,既然是串行的,为何sequenceNumber会存在跳跃呢?当时作者想开了binlog_order_commits参数,从前为了加强品质,关闭了该参数,也正是在付出的尾声一品级,三个专门的职业并发在引擎层提交,会不会与并发写memtable有关,因为在头里复现的长河中,大家开掘关闭并发写memtable特性(rocksdb_allow_concurrent_memtable_write=0),难点不会复现。

    为了巩固commit质量,存款和储蓄引擎会将众八线程的并发write合併到一个group,批量写日记,write memory table,然后一遍性commit.

    但令人大失所望的是,在并发写memtable情况下,就算展开了binlog_ordered_commit(server层串行commit),事务串行在引擎层提交仍旧会师世相仿的难题。

    JoinBatchGroup调用LinkOne(w, &linked_as_leader);将当前write_thread_中的writer连接成多个链表,个中write_thread_.newest_writer_是链表的头,是流行参与的follower,而首先个步入链表的也正是现阶段group的leader(link_older=nullptr).

           到那边就像陷入了末路,引擎层永久独有叁个事务进来,为什么开启并发写memtable会影响精确性呢?因为二个事情没有办法并发。不知怎么着时候使得意气风开采,prepare阶段和commit阶段现身。因为rocksdb最后提交接口WriteImpl是prepare和commit公用的,通过传播的参数来分化。prepare阶段写wal日志,commit阶段写memtable和commit日志。那我们就非同平常来看prepare和commit并发走入WriteImpl时SequenceNumber相关的代码,果然开掘了难点。这里本人民代表大会约介绍下展开并发写memtable选项时,事务的交给逻辑。

    follower(newest_writer) --*link_older*--> follower --*link_older*--> follower --*link_older*--> **leader** ----> nullptr

       1.各类事情都对应叁个write-batch
       2.先是个步入WriteImpl函数的线程为leader,别的线程为follower
       3.leader和follower依照前后相继顺序串成多个链表
       4.对此并发写memtable的动静,leader依照各种事情write-batch的count,总括种种专业的start-sequenceNumber。
       5.leader写完wal日志后,follower依照各自start-sequenceNumber,并发写memtable
       6.全数业务都写完memtable后,leader更新全局的sequenceNumber。 

    大器晚成经当前的Writer成为了Leader,那么重回做多余的付出逻辑,假诺当前生龙活虎度有了Leader,必要等待Write.state成为STATE_GROUP_LEADER | STATE_PARALLEL_FOLLOWER | STATE_COMPLETED

    主题材料首要发生在第四个步骤,总括start-sequenceNumber时,忽视了prepare事务的剖断,招致在prepare事务与commit事务成为三个group时,commit事务的sequence现身跳跃,而全局的sequenceNumber只总括了commit事务,最后招致了写入memtable的sequenceNumber比全局sequenceNumber大的情景,从而发生了接二连三的大错特错。上面列举三个荒谬的事例,倘使slave_worker1和slave_worker2分别试行到位务trx1和trx2操作,更新位点后最早作业提交,trx1处于prepare阶段,trx2处于commit阶段,trx1和trx2组成三个commit-group, trx1是leader,trx2是follower,current_sequence 是101。

    - As Leader

    trx1: prepare phase, batch count is 3
    put(user_key1,v1);
    put(user_key2,v2);
    put(user_key3,v3);
    
    trx2: commit phase, batch count is 2
    put(user_keyA,v1);
    put(user_keyB,v2);
    

    - 检查是还是不是须要Flush, 假诺必要,寻觅装有column_family中最大的MemTable的CF,调用SwitchMemtable,冻结当前active memtable, 调用SchedulePendingFlush调节刷盘。

    trx1是leader,因此trx2的start sequence 是101 3=104,写入memtable中的user_keyA的sequence是104,user_keyB的sequence是105。Current sequence推动到103。那么些group结束后,对于新业务trx3, 倘若Current sequnce为已经推进到120(全局任何事情提交都会推进sequence),trx3更新user_keyA,就能够发觉已经存在(user_keyA, 104),也正是我们蒙受的荒谬;而除此以外后生可畏种状态,若是current Sequence未有推向,仍是103,则会时有爆发更新错失,因为查不到(user_keyA,104)那条记下。那刚刚表达了,为什么大家在协同进度中,会发生错失更新的标题。

    - 取当前的versions_的LastSequence。 起先有所DBImpl::mutex

    while (w != pg->last_writer) {
    // Writers that won't write don't get sequence allotment
    if (!w->CallbackFailed()) {
    sequence  = WriteBatchInternal::Count(w->batch); # // BUG HERE: not check w-> ShouldWriteToMemtable, sequence out of bound.
    }
    
    w = w->link_newer;
    w->sequence = sequence;
    w->parallel_group = pg;
    SetState(w, STATE_PARALLEL_FOLLOWER);
    }
    

    - 调用write_thread_.EnterAsBatchGroupLeader,这几个函数分明当前付出的批次应该包涵哪些数据

    小插曲

    - 总括当前能够批量交给的最大尺寸max_size; 如果leader.size<128KB max_size=leader.size 128KB,如果>128KB,max_size=1MB.

          到此处,我们早已答复了前边的全部疑难,难点也最后一定。但相对没悟出,改善代码提交后,复制难题仍旧存在,笔者咋舌是或不是不止三个bug。于是延续查,看了下日志,已经不是事先的slave_worker_info表出错了,而是一张业务表。后来才察觉是因为替换mysqld后隔开等第没有设置,重启实例后,隔开分离品级变为Read-Repeatable等级导致。这里大约说下宝马X5昂Cora隔绝下,并行复制下,招致上述失实的来头。首先鲜明一点,LX570XC60隔开分离是在作业的首个语句获取快速照相,以往工作中存有语句都使用那些快速照相,而RC隔绝品级则是业务的每一个语句会单独赢得快照。在并行复制方式下,即使这么黄金年代种情景:

    - 调用CreateMissingNewerLinks(newest_writer),将全方位链表的反向链接建构起来(link_newer),成了贰个双向链表。

    时间轴

    Trx1

    Trx2

    1

    Begin

    Begin

    2

    Update t1 set v=? where k=1

     

    3

     

    Update t2 set v=? where k=1

    4

     

    commit

    5

    Update t2 set v=? where k=1

     

    - 从leader开首反向遍历,一贯到newest_writer,  累加每种batch的size,一向到max_size,当先之后截断,同一时等候检查查种种writer的sync/no_slowdown/disableWAL是或不是肖似,不相近的地点也最初截断,用last_writer标志链表的完工地方,作为函数输出参数再次来到。w->callback->AllowWriteBatching(),也足以设置不想被batch的writer. 并且把切合条件的writer batch都push到write_group的vector中。

    SportageRubicon隔绝品级下,trx1会在首先个update语句获取快速照相,更新t2表时,依然选拔以前的快速照相,而在这里时期,t2表的k=1对应的笔录也许被改善,以致记录的sequenceNumber大于trx1快速照相的sequenceNumber,进而导致立异t2失败。而大器晚成旦是RC等第,trx1实行更新t2表时则会另行得到快速照相,不会存在难点。

    - 检查是还是不是足以parallel提交,条件有多少个:1. memtable协理,2.allow_concurrent_memtable_write设置,3.write_group 有多个batch, 4. batch中没有merge操作。

    标题解决

    - 鲜明当前付出的group的current_sequence=last_sequence 1(作为开首sequence), 并且将sequence先举行占位,一遍性**为write_group中每种batch的各类操作记录都分配叁个sequence**. (注意writer.ShouldWriteToMemtable标志为false的不计入sequence)

          管理sequenceNumber逻辑不许确主要会导致多个难点,备库错过更新和备库复制中断。定位到标题原因,况且对负有疑问都有合理性的解释后,改正就比较简单了,在思索start-sequenceNumber函数LaunchParallelFollowers中,加多prepare事务的论断就可以,随后还亟需编写制定测量试验用例牢固复现,并打开回归测量检验才算是最后修复这几个补丁。咱们将难点反馈给官方,非常的慢得到了合法的认可和复苏。

    - 将write_group中的各种batch的数码都append到三个新的WriteBatch对象merged_batch中(tmp_batch_),假设group中独有叁个batch, 那么就用这一个batch,没需要拷贝数据了。

    总结

    - 设置新的merged_batch对象的sequence为current_sequence(起始sequence)

          整个逐个审查进程可能相比曲折,因为这些bug涉及到现身,并且是特定参数组合的产出才会出标题,所以对于这种复杂的气象,通过创混入假的如与日志埋点能逐步拿到部分结论和依靠,最后寻踪觅源获取与难题有关的音信,才最后化解难点。大家在测验注明中经过中连连开掘众多看似与预期不符的日志,也多亏那个日记让大家把全路工艺流程弄透顶,离消除难题特别近,一言以蔽之不用放过其余四个质疑,因为要坚信日志不会骗人,而代码逻辑大概因为你不经意了好几分支,招致会有错误的测度。

    - 写WAL,用的数额正是merged_batch中的rep_ (假使是多少个batch,那么tmp_batch_可以清理了)

     

    - 假使不容许现身:串行实践write memtable,调用WriteBatchInternal::InsertInto将write_group中兼有数据串行写入到memtable.

    - 并行实施(concurrent_memtable_writes)

    - WriteThread::ParallelGroup 建构叁个相互影响写memtable group,pg.leader/last_writer分别指向链表的头和尾。

    - write_thread_.LaunchParallelFollowers: 设置链表中各样writer.sequence为事先分配好的sequence,(注意每一个batch分配本身的风姿洒脱段,调用InsertInto的时候再每种Key设置自个儿的sequence),设置writer.state为STATE_PARALLEL_FOLLOWER

    - 作为Leader的writer batch开始写memtable

    - 调用write_thread_.CompleteParallelWorker(&w)判别是否最终叁个完毕write memtable的线程

    - 平常景况下(要是early_exit_allowed为false),唯有leader会最终做扫尾工作,因而纵然leader不是最后三个写完memtable的线程,也会等待writer.state == STATE_COMPLETED 才会脱离, 并重临true,表示需求做最后的付出专门的工作(update versions_.LastSequence),leader的STATE_COMPLETEDe是由最终贰个脱离的follower线程设置的。

    - follower线程如若不是最后八个完了工作的线程,那么会直接等到writer.state == STATE_COMPLETED退出。

    - follower线程倘诺是最终一个到位职业的线程,那么会先把group.leader.state设置为STATE_COMPLETED,然后等待自个儿成为STATE_COMPLETED,退出。

    - 如果 CompleteParallelWorker返回了true(leader等到了STATE_COMPLETED也许自个儿就是最后三个),做提交动作更新全局的sequence: versions_->SetLastSequence(last_sequence);

    - 调用write_thread_.ExitAsBatchGroupLeader

    - 注意最近的newest_writer_或是早已加了众多新的write batch进来了,在上三遍commit的长河中,新步向的write batch还有或许会一恋慕write_thread_的链表上挂,可是此次交付的扫尾的点在EnterAsBatchGroupLeader时候鲜明的,因此退出的时候会将此番提交链还剩余的writer链,重新成立好反向链接,设置紧接着的writer为新的Leader,早先调用JoinBatchGroup等待的线程,又足以继续试行下一群业务。

    - 遍历write_group中兼有的writer, 设置有着writer的state为STATE_COMPLETED,这样还在调用CompleteParallelWorker的follower线程就能够退出。

    - As follower

    - JoinBatchGroup之后若无成为Leader,这就等着Leader线程在LaunchParallelFollowers的时候把温馨安装为follower状态,大器晚成旦设置完,就步入follower写memtable逻辑,最终判别CompleteParallelWorker是不是能够脱离,平日是伺机全数的batch都干完活将来退出。

    - 如果allow_concurrent_memtable_write未有展开, follower线程会一贯等候Leader干完全数的业务,最后调用ExitAsBatchGroupLeader设置情形为STATE_COMPLETED后平素退出。

    ### writer链表在group commit进度中的变化

    ![link_list](rocksdb_writer_link_list.png)

    ### writer对象景况变迁图

    ![state_flow](rocksdb_writer_state_flow.png)

    本文由新葡亰496net发布于网络数据库,转载请注明出处:新葡亰496netmyrocks复制中断问题排查,剖析复制线

    关键词: