Seconds_Behind_Master 能正确反映主从延时吗?

时间:2021-01-29 16:23:45

在mysql的备库的监控中有一项很重要的指标:Seconds_Behind_Master,这个值是怎么得到的呢?下面从5.1.58的代码中分析一下:

    mysql的replication中有2个比较重要的class:Master_info(rpl_mi.h), Relay_log_info(rpl_rli.h),他们分别对应于master,info文件和slave.info文件;很显然,Master_info是io_thread需要的,Relay_log_info是sql_thread需要的。Master_info中有一个变量 clock_diff_with_master,这个值记录着mysql的主库和备库的时间差,可以理解为主备的主机时间差。clock_diff_with_master变量的定义如下:

Cpp代码  Seconds_Behind_Master 能正确反映主从延时吗?
  1. /* 
  2.    The difference in seconds between the clock of the master and the clock of 
  3.    the slave (second - first). It must be signed as it may be <0 or >0. 
  4.    clock_diff_with_master is computed when the I/O thread starts; for this the 
  5.    I/O thread does a SELECT UNIX_TIMESTAMP() on the master. 
  6.    "how late the slave is compared to the master" is computed like this: 
  7.    clock_of_slave - last_timestamp_executed_by_SQL_thread - clock_diff_with_master 
  8.  
  9. */  
  10. long clock_diff_with_master;  

     这个变量的注释直接提到了Seconds_Behind_Master的计算方法:clock_of_slave - last_timestamp_executed_by_SQL_thread - clock_diff_with_master。clock_of_slave是slave的当前时间--执行show slave status的当前时间。

     先看一下clock_diff_with_master的计算:(slave.cc)。执行”start slave;“/“start slave io_thread;”后,会执行start_slave_threads来启动io thread,io thread启动后首先做的就是获取主库的mysql版本和主库的当前时间(mysql_real_query(mysql, STRING_WITH_LEN("SELECT UNIX_TIMESTAMP()"))),获取到主库的当前时间后,用备库的当前时间减去主库的时间,得到clock_diff_with_master。

Cpp代码  Seconds_Behind_Master 能正确反映主从延时吗?
  1. int start_slave_threads(bool need_slave_mutex, bool wait_for_start,  
  2.                         Master_info* mi, const char* master_info_fname,  
  3.                         const char* slave_info_fname, int thread_mask)  
  4. {  
  5. ...  
  6.   if (thread_mask & SLAVE_IO)  
  7.     error=start_slave_thread(handle_slave_io,lock_io,lock_cond_io,  
  8.                              cond_io,  
  9.                              &mi->slave_running, &mi->slave_run_id,  
  10.                              mi, 1); //high priority, to read the most possible  
  11. ...  
  12. }  
  13. ...  
  14. /** 
  15.   Slave IO thread entry point. 
  16.  
  17.   @param arg Pointer to Master_info struct that holds information for 
  18.   the IO thread. 
  19.  
  20.   @return Always 0. 
  21. */  
  22. pthread_handler_t handle_slave_io(void *arg)  
  23. {  
  24. ...  
  25.   thd_proc_info(thd, "Checking master version");  
  26.   ret= get_master_version_and_clock(mysql, mi);  
  27. ...  
  28. }  
  29. ...  
  30. static int get_master_version_and_clock(MYSQL* mysql, Master_info* mi)  
  31. {  
  32. ...  
  33.   master_res= NULL;  
  34.   if (!mysql_real_query(mysql, STRING_WITH_LEN("SELECT UNIX_TIMESTAMP()")) &&  
  35.       (master_res= mysql_store_result(mysql)) &&  
  36.       (master_row= mysql_fetch_row(master_res)))  
  37.   {  
  38.     mi->clock_diff_with_master=  
  39.       (long) (time((time_t*) 0) - strtoul(master_row[0], 0, 10));  
  40.   }  
  41.   else if (is_network_error(mysql_errno(mysql)))  
  42.   {  
  43.     mi->report(WARNING_LEVEL, mysql_errno(mysql),  
  44.                "Get master clock failed with error: %s", mysql_error(mysql));  
  45.     goto network_err;  
  46.   }  
  47.   else   
  48.   {  
  49.     mi->clock_diff_with_master= 0; /* The "most sensible" value */  
  50.     sql_print_warning("\"SELECT UNIX_TIMESTAMP()\" failed on master, "  
  51.                       "do not trust column Seconds_Behind_Master of SHOW "  
  52.                       "SLAVE STATUS. Error: %s (%d)",  
  53.                       mysql_error(mysql), mysql_errno(mysql));  
  54.   }  
  55. ...  
  56. }  

     last_timestamp_executed_by_SQL_thread则是sql slave thread最近一次执行的event的时间戳(rpl.rli.cc中的last_event_start_time变量)。而这个变量的更新则是在stmt_done()函数中完成的(rpl.rli.cc),相关定义如下:

Cpp代码  Seconds_Behind_Master 能正确反映主从延时吗?
  1. /* 
  2.   Used by row-based replication to detect that it should not stop at 
  3.   this event, but give it a chance to send more events. The time 
  4.   where the last event inside a group started is stored here. If the 
  5.   variable is zero, we are not in a group (but may be in a 
  6.   transaction). 
  7.  */  
  8. time_t last_event_start_time;  
  9.   
  10. /** 
  11.   Helper function to do after statement completion. 
  12.  
  13.   This function is called from an event to complete the group by 
  14.   either stepping the group position, if the "statement" is not 
  15.   inside a transaction; or increase the event position, if the 
  16.   "statement" is inside a transaction. 
  17.  
  18.   @param event_log_pos 
  19.   Master log position of the event. The position is recorded in the 
  20.   relay log info and used to produce information for <code>SHOW 
  21.   SLAVE STATUS</code>. 
  22.  
  23.   @param event_creation_time 
  24.   Timestamp for the creation of the event on the master side. The 
  25.   time stamp is recorded in the relay log info and used to compute 
  26.   the <code>Seconds_behind_master</code> field. 
  27. */  
  28. void stmt_done(my_off_t event_log_pos,  
  29.                time_t event_creation_time);  

     具体的逻辑则是,sql thread启动后,读取relaylog(netxt_event()),apply & update pos(apply_event_and_update_pos()),update_pos的时候判断是否执行到了事务的结束位置,如果执行到了,则调用stmt_done(),stmt_done()会将last_master_timestamp更新为最近一次event的创建时间(event_creation_time)。因此如果在主备基本无延迟的时候,主库执行了一个大事务,你会发现备库延迟突然很大,然后又没了,"延迟跳跃"。

Cpp代码  Seconds_Behind_Master 能正确反映主从延时吗?
  1. /** 
  2.   Slave SQL thread entry point. 
  3.  
  4.   @param arg Pointer to Relay_log_info object that holds information 
  5.   for the SQL thread. 
  6.  
  7.   @return Always 0. 
  8. */  
  9. pthread_handler_t handle_slave_sql(void *arg)  
  10. {  
  11. ...  
  12.     if (exec_relay_log_event(thd,rli))  
  13. ...  
  14. }  
  15. ...  
  16. static int exec_relay_log_event(THD* thd, Relay_log_info* rli)  
  17. {  
  18. ...  
  19.   Log_event * ev = next_event(rli);  
  20. ...  
  21.     exec_res= apply_event_and_update_pos(ev, thd, rli);  
  22. ...  
  23. }  
  24. ...  
  25. int apply_event_and_update_pos(Log_event* ev, THD* thd, Relay_log_info* rli)  
  26. {  
  27. ...  
  28.     exec_res= ev->apply_event(rli);  
  29. ...  
  30.     int error= ev->update_pos(rli);  
  31. ...  
  32. }  
  33. ....log_event.cc  
  34. int  
  35. Rows_log_event::do_update_pos(Relay_log_info *rli)  
  36. {  
  37. ...  
  38.   if (get_flags(STMT_END_F))  
  39.   {  
  40.     /* 
  41.       Indicate that a statement is finished. 
  42.       Step the group log position if we are not in a transaction, 
  43.       otherwise increase the event log position. 
  44.     */  
  45.     rli->stmt_done(log_pos, when);  
  46.    }  
  47. ...  
  48. }  
  49. ...rpl_rli.cc  
  50. void Relay_log_info::stmt_done(my_off_t event_master_log_pos,  
  51.                                   time_t event_creation_time)  
  52. {  
  53. ...  
  54.   if ((sql_thd->options & OPTION_BEGIN) && opt_using_transactions)  
  55.     inc_event_relay_log_pos();  
  56.   else  
  57.   {  
  58.     inc_group_relay_log_pos(event_master_log_pos);  
  59.     flush_relay_log_info(this);  
  60.     /* 
  61.       Note that Rotate_log_event::do_apply_event() does not call this 
  62.       function, so there is no chance that a fake rotate event resets 
  63.       last_master_timestamp.  Note that we update without mutex 
  64.       (probably ok - except in some very rare cases, only consequence 
  65.       is that value may take some time to display in 
  66.       Seconds_Behind_Master - not critical). 
  67.     */  
  68. #ifndef DBUG_OFF  
  69.     if (!(event_creation_time == 0 && debug_not_change_ts_if_art_event > 0))  
  70. #else  
  71.       if (event_creation_time != 0)  
  72. #endif  
  73.         last_master_timestamp= event_creation_time;  
  74.   }  
  75. }  

    另外一种可能会出现延迟跳跃的情况是:主库的网络有问题,io thread拉binlog的速度很慢,备库实际有很大的延迟,而sql thread执行的时候永远是hot log,这个时候会出现,备库一直有很大的延迟,突然一下延迟没了(=0),接下去延迟又变得很大。这种情况的原因为:sql thread读取relay log是在next_event(slave.cc)中完成的,而next_event中会判断当前的读取的relay log是否是“热的”--正在被io thread使用,如果是"热的",并且读取到当前relay log的最后一个event 或io thread没有再更新relay log的时候,会将last_master_time_stamp置为0,并new一个 Rotate_log_event。而如果在这个时候执行"show slave status",延迟就是0了。这个地方需要补充一点的是“show slave status”和前面提到的逻辑,唯一有争用的锁是&rli->data_lock,但是next_event在恢复last_master_time_stamp前就释放了这个锁:

Cpp代码  Seconds_Behind_Master 能正确反映主从延时吗?
  1. ...slave.cc...  
  2. static Log_event* next_event(Relay_log_info* rli)  
  3. {  
  4. ...  
  5.   safe_mutex_assert_owner(&rli->data_lock);  
  6. ...  
  7.     if (!cur_log->error) /* EOF */  
  8.     {  
  9.       /* 
  10.         On a hot log, EOF means that there are no more updates to 
  11.         process and we must block until I/O thread adds some and 
  12.         signals us to continue 
  13.       */  
  14.       if (hot_log)  
  15.       {  
  16.         time_t save_timestamp= rli->last_master_timestamp;  
  17.         rli->last_master_timestamp= 0;  
  18. ...  
  19.         /* 
  20.           We can, and should release data_lock while we are waiting for 
  21.           update. If we do not, show slave status will block 
  22.         */  
  23.         pthread_mutex_unlock(&rli->data_lock);  
  24. ...  
  25.         rli->last_master_timestamp= save_timestamp;  
  26. ...  
  27.        }  
  28. ...  
  29.      }  
  30. ...  
  31. }  
  32. ....  
  33. /** 
  34.   Execute a SHOW SLAVE STATUS statement. 
  35.  
  36.   @param thd Pointer to THD object for the client thread executing the 
  37.   statement. 
  38.  
  39.   @param mi Pointer to Master_info object for the IO thread. 
  40.  
  41.   @retval FALSE success 
  42.   @retval TRUE failure 
  43. */  
  44. bool show_master_info(THD* thd, Master_info* mi)  
  45. {  
  46. ...  
  47.     pthread_mutex_lock(&mi->run_lock);  
  48.     protocol->store(mi->io_thd ? mi->io_thd->proc_info : "", &my_charset_bin);  
  49.     pthread_mutex_unlock(&mi->run_lock);  
  50.   
  51.     pthread_mutex_lock(&mi->data_lock);  
  52.     pthread_mutex_lock(&mi->rli.data_lock);  
  53.     pthread_mutex_lock(&mi->err_lock);  
  54.     pthread_mutex_lock(&mi->rli.err_lock);  
  55. ...  
  56.     /* 
  57.       Seconds_Behind_Master: if SQL thread is running and I/O thread is 
  58.       connected, we can compute it otherwise show NULL (i.e. unknown). 
  59.     */  
  60.     if ((mi->slave_running == MYSQL_SLAVE_RUN_CONNECT) &&  
  61.         mi->rli.slave_running)  
  62.     {  
  63.       long time_diff= ((long)(time(0) - mi->rli.last_master_timestamp)  
  64.                        - mi->clock_diff_with_master);  
  65.       /* 
  66.         Apparently on some systems time_diff can be <0. Here are possible 
  67.         reasons related to MySQL: 
  68.         - the master is itself a slave of another master whose time is ahead. 
  69.         - somebody used an explicit SET TIMESTAMP on the master. 
  70.         Possible reason related to granularity-to-second of time functions 
  71.         (nothing to do with MySQL), which can explain a value of -1: 
  72.         assume the master's and slave's time are perfectly synchronized, and 
  73.         that at slave's connection time, when the master's timestamp is read, 
  74.         it is at the very end of second 1, and (a very short time later) when 
  75.         the slave's timestamp is read it is at the very beginning of second 
  76.         2. Then the recorded value for master is 1 and the recorded value for 
  77.         slave is 2. At SHOW SLAVE STATUS time, assume that the difference 
  78.         between timestamp of slave and rli->last_master_timestamp is 0 
  79.         (i.e. they are in the same second), then we get 0-(2-1)=-1 as a result. 
  80.         This confuses users, so we don't go below 0: hence the max(). 
  81.  
  82.         last_master_timestamp == 0 (an "impossible" timestamp 1970) is a 
  83.         special marker to say "consider we have caught up". 
  84.       */  
  85.       protocol->store((longlong)(mi->rli.last_master_timestamp ?  
  86.                                  max(0, time_diff) : 0));  
  87.     }  
  88.     else  
  89.     {  
  90.       protocol->store_null();  
  91.     }  
  92. ...  
  93. }  

     到这里Seconds_Behind_Master的计算就结束了。总结起来就是Seconds_Behind_Master的计算有3部分组成,备库当前时间,sql thread最近一次执行的event的时间,主备系统的时间差。备库出现延迟跳跃,可能是大事务引起,也可能是网络问题。(ps:这2种情况,都被我遇到过:))

 

链接:

http://www.mysqlperformanceblog.com/2013/09/16/possible-reasons-when-mysql-replication-lag-is-flapping-between-0-and-xxxxx/