所属系列:
上:https://zhuanlan.zhihu.com/p/70594302
中:https://zhuanlan.zhihu.com/p/70594699
下:https://zhuanlan.zhihu.com/p/70595011
无法shutdown问题
前面提到MyRocks在使用mysqladmin执行shutdwon的时候,持续的时间很长,最终因为超时mysqladmin退出,mysqld没有顺利shutdown,观察mysqld.log,在shutdwon的过程中,有下图日志显示:
线上都是rocksdb表,shutdown的时候显示存在活跃innodb 事务,这也是很奇怪。根据上图中显示的信息加上代码调试,锁定在 logs_empty_and_mark_files_at_shutdown(),该函数是innodb关闭的主要处理函数。根据上图日志的输出,在下图代码位置出问题。如果total_trx大于0 打印一条上图中的输出,然后继续循环。trx_sys_any_active_transaction() 该函数检查 trx_sys->mysql_trx_list 链表是否还有trx存在,存在的话也就是total_trx大于0的情况,shutdown就无法完成。
线下利用sysbench_xa复现,实验发现没有xa事务的情况下可以顺利shutdown,存在xa事务的情况下shutdown就无法完成,这说明MyRocks在处理 xa 事务的时候可能存在bug。
之前的文章已经介绍过了,MySQL5.7版本中,xa 事务拆分成了两部分,xa prepare和xa commit有单独的gtid,这样导致了xa 在slave上回放时需要考虑xa commit与xa prepare分离的情况,因为xa prepare后事务处于prepared状态,如果接下来不是xa commit,那事务的状态就错乱了。MySQL现在的做法是在xa start的时候所有存储引擎都执行replace_native_transaction_in_thd这个接口,xa prepare的时候只有实际参与的引擎才调用该接口。如果新增一个事务引擎,比如rocksdb,这样的逻辑会不会存在问题呢?
咱就以一个rocksdb表的xa事务为例,看看执行一个xa事务会不会出问题,xa事务的语法如下:
xa start xid;
xa end xid;
xa prepare xid;
xa commit xid;
(其实还有xa commit xid one phase,这个在下一节介绍。)
xa start xid的在函数 Sql_cmd_xa_start::execute中处理:
trans_xa_start 开始一个xa事务,同时在server层的transaction_cache中插入thd->get_transaction()。函数执行成功返回false,进入thd->rpl_detach_engine_ha_data函数,在这个函数里面就要执行所有引擎的detach的接口了。
new_trx_arg: NULL
ptr_trx_arg:
deattach之后,trx=NULL, xa start 之后,执行SQL语句,rocksdb存储引擎会向server层注册。xa end xid操作逻辑简单,只是把xa的状态由active改成idle。xa prepare 函数由Sql_cmd_xa_prepare::execute处理,
trans_xa_prepare 函数内部会做binlog prepare和存储引擎 prepare,更改xa状态为XA_PREPARED,函数返回值false。applier_reset_xa_trans 做两件事情(1)server层transaction_cache移除当前thd->get_transaction()移除掉(2)reattach 存储引擎的trx。
考虑正常的情况,就是业务表都是rocksdb,事务包括普通事务和xa事务场景。因为现在只有rocksdb表,并且开启了binlog,那注册的引擎只有binlog和rocksdb,Ha_trx_info 保存注册的存储引擎,binlog引擎没有实现replace_native_transaction_in_thd接口,所以只有rocksdb reattach之前deattach的trx。上一篇提到start slave的时候worker线程会打开slave_worker_info,由于innodb trx是可复用的,所以没有释放,在执行rocksdb分布式事务xa start时,会将打开slave_worker_info这个innodb系统表时deattach掉,但是在xa prepare的时候,因为没有innodb事务参与,所以该没有reattach,导致该innodb trx_t在 trx_sys->mysql_trx_list 中不能被回收,shutdown的处理逻辑上文也介绍了,如果trx_sys->mysql_trx_list链表上还存在trx则无法shutdwon。因为我们设置了8个worker线程,所以提示有8个trx_t对象遗留。
stop slave导致crash问题
延迟从库线上还会偶尔出现stop slave后slave crash,这种crash是概率性的,线下使用sysbench_xa并没有复现出来。结合代码分析和线上的relay-log解析,在执行xa commit xid one phase后接着执行stop slave,mysqld会crash。xa commit xid one phase后slave worker会更新位点信息,打开系统表slave_worker_info 更新记录,问题就出在这个过程中。更新位点的入口函数是commit_positions,函数调用栈:
do_flush_info函数先调用open_table:
before_open函数会重置thd->lex相关的信息,thd->lex->m_sql_cmd=NULL, thd->lex->sql_command=SQLCOM_END,这样执行完之后stop slave是没问题的。上一节介绍过为了解决操作复制相关的系统表而出现的内存泄漏,我们引入了xa-prepare-memleak.patch,在该patch的执行逻辑中,执行完更新系统表之后会把 thd->lex->sql_command 又设为SQLCOM_XA_COMMIT,而m_sql_cmd还是NULL,这样stop slave后就会crash。
stop slave crash的代码调用栈:
do_binlog_xa_commit_rollback函数crash的代码位置是get_xa_opt:
上面介绍了m_sql_cmd已经是NULL了,所以段错误。
解决方法
http://mysql.taobao.org/monthly/2017/11/06/阿里这篇文章也有相关类似的介绍,以上问题的原因可以归结为由于MySQL Server层目前只考虑了innodb事务引擎,当添加rocksdb事务引擎时,原有的代码逻辑就存在问题了。上面介绍过了xa start是所有存储引擎参与了dettach,xa prepare时也应该所有存储引擎参与reattach。
按照这个思路修改reattach_engine_ha_data_to_thd,
在rpl_reattach_engine_ha_data 中会调用plugin_foreach,所有存储引擎都有调用replace_native_transaction_in_thd。这种解决方式在有binlog+rocksdb情况下是没问题的,binlog+rocksdb+innodb的情况下,每个SE的replace方法会被调用两次,为此,又进一步在函数中增加判断,确保不会出现重复调用。
总结
本文主要介绍了MyRocks在云音乐业务场景上碰到的与xa事务有关的问题以及目前的解决方法,MySQL对多事务引擎的支持还存在缺陷,还需要继续跟进社区加上内核代码深度分析探讨出一种更完美的解决方法。
参考资料
- http://mysql.taobao.org/monthly/2017/11/06/
- https://jin-yang.github.io/post/mysql-shutdown.html
- https://zhuanlan.zhihu.com/p/38382205
- http://mysql.taobao.org/monthly/2015/08/09/
- http://mysql.taobao.org/monthly/2017/09/05/