I just threw together a quick script to report status on a MySQL replication ring. While replication rings have been the only real multi-master MySQL solution for replication (with the ability for nodes to go down without majorly breaking things) until recently, I have read that MariaDB (still not MySQL) now allows a slave to have multiple masters, meaning many replication topologies are now possible (star, mesh, etc). This script could easily be adapted for those circumstances too.
This script will report all the variables from “SHOW MASTER STATUS” and “SHOW SLAVE STATUS” from all servers in your replication ring, in a unified table. It also includes a “Pretty Status” row that lets you quickly see how things look. The possibilities for this row are:
Bad state: ...
This shows if the Slave_IO_State is not “Waiting for master to send event”
Cannot determine master’s real position
This shows if the Position variable on the master could not be read
On old master file
This shows if the slave’s “Master_Log_File” variable does not equal the master’s “File” variable
Bytes behind: xxx
This shows if none of the above errors occurred. It subtracts the master’s “Position” from the slave’s “Read_Master_Log_Pos”. This should generally be at or around 0. A negative value essentially means 0 (this should only happen between the last and first server).
The “Seconds_Behind_Master” variable can also be useful for determining the replication ring’s current replication status.
The code is below the example. The entire source file can also be found here. The 3 variables that need to be configured are at the top of the file. It assumes that all servers are accessible via the single given username and password.
Example:
Master
Server Name
EXAMPLE1.MYDOMAIN.COM
EXAMPLE2
File
mysql-bin.000003
mysql-bin.000011
Position
25249746
3215834
Binlog_Do_DB
example_data,devexample_data
example_data,devexample_data
Binlog_Ignore_DB
Slave
Pretty Status
Bytes behind: 0
Bytes behind: 0
Slave_IO_State
Waiting for master to send event
Waiting for master to send event
Master_Host
EXAMPLE2
EXAMPLE1.MYDOMAIN.COM
Master_User
example_slave
example_slave
Master_Port
3306
3306
Connect_Retry
60
60
Master_Log_File
mysql-bin.000011
mysql-bin.000003
Read_Master_Log_Pos
3215834
25249746
Relay_Log_File
www-relay-bin.070901
www-relay-bin.071683
Relay_Log_Pos
252
252
Relay_Master_Log_File
mysql-bin.000011
mysql-bin.000003
Slave_IO_Running
Yes
Yes
Slave_SQL_Running
Yes
Yes
Replicate_Do_DB
example_data,devexample_data
example_data,devexample_data
Replicate_Ignore_DB
Replicate_Do_Table
Replicate_Ignore_Table
Replicate_Wild_Do_Table
Replicate_Wild_Ignore_Table
Last_Errno
0
0
Last_Error
Skip_Counter
0
0
Exec_Master_Log_Pos
3215834
25249746
Relay_Log_Space
552
552
Until_Condition
None
None
Until_Log_File
Until_Log_Pos
0
0
Master_SSL_Allowed
No
No
Master_SSL_CA_File
Master_SSL_CA_Path
Master_SSL_Cert
Master_SSL_Cipher
Master_SSL_Key
Seconds_Behind_Master
0
0
Master_SSL_Verify_Server_Cert
No
No
Last_IO_Errno
0
0
Last_IO_Error
Last_SQL_Errno
0
0
Last_SQL_Error
Replicate_Ignore_Server_Ids
Not given
Master_Server_Id
2
Not given
Code:
<?//Configurations$Servers=Array('SERVER1.YOURDOMAIN.COM', 'SERVER2.YOURDOMAIN.COM'); //List of host names to access mysql servers on. This must be in the order of the replication ring.$SlaveUserName='SLAVE_RING_USERNAME'; //This assumes all servers are accessible via this username with the same password$SlavePassword='SLAVE_RING_PASSWORD';//Get the info for each server$ServersInfo=Array(); //SERVER_NAME=>Array('Master'=>Array(Col1=>Val1, ...), 'Slave'=>Array(Col1=>Val1, ...)$ColsNames=Array('Master'=>Array('Server Name'=>0), 'Slave'=>Array('Pretty Status'=>0)); //The column names for the 2 (master and slave) queries. Custom column names are also added here$CustomFieldNames=array_merge($ColsNames['Master'], $ColsNames['Slave']); //Store the custom column names so they are not HTML escaped laterforeach($Serversas$ServerName){//Connect to the server$Link=@newmysqli($ServerName, $SlaveUserName, $SlavePassword);if($Link->connect_error)die(EHTML("Connection error to $ServerName server: $Link->connect_error"));//Get the replication status info from the server$MyServerInfo=$ServersInfo[$ServerName]=Array('Master'=>$Link->Query('SHOW MASTER STATUS')->fetch_array(MYSQLI_ASSOC),'Slave'=>$Link->Query('SHOW SLAVE STATUS')->fetch_array(MYSQLI_ASSOC) );mysqli_close($Link); //Close the connection//Gather the column namesforeach($ColsNamesas$ColType=>&$ColNames)foreach($MyServerInfo[$ColType] as$ColName=>$Dummy)$ColNames[$ColName]=0;}unset($ColNames);//Gather the pretty statusesforeach($Serversas$Index=>$ServerName){//Determine the pretty status$SlaveInfo=$ServersInfo[$ServerName]['Slave'];$MasterInfo=$ServersInfo[$Servers[($Index+1)%count($Servers)]]['Master'];if($SlaveInfo['Slave_IO_State']!='Waiting for master to send event')$PrettyStatus='Bad state: '.EHTML($SlaveInfo['Slave_IO_State']);elseif(!isset($MasterInfo['Position']))$PrettyStatus='Cannot determine master’s real position';elseif($SlaveInfo['Master_Log_File']!=$MasterInfo['File'])$PrettyStatus='On old master file';else$PrettyStatus='Bytes behind: '.($MasterInfo['Position']-$SlaveInfo['Read_Master_Log_Pos']);//Add the server name and pretty status to the output columns$ServersInfo[$ServerName]['Master']['Server Name']='<div class=ServerName>'.EHTML($ServerName).'</div>';$ServersInfo[$ServerName]['Slave']['Pretty Status']='<div class=PrettyStatus>'.EHTML($PrettyStatus).'</div>';}//Output the documentfunctionEHTML($S) { returnhtmlspecialchars($S, ENT_QUOTES, 'UTF-8'); } //Escape HTML?>
<!DOCTYPE html>
<html>
<head>
<title>Replication Status</title>
<metacharset="UTF-8">
<style>table { border-collapse:collapse; }tabletr>* { border:1pxsolidblack; padding:3px; }th { text-align:left; font-weight:bold; }.ReplicationDirectionType { font-weight:bold; text-align:center; color:blue; }.ServerName { font-weight:bold; text-align:center; color:red; }.PrettyStatus { font-weight:bold; color:red; }.NotGiven { font-weight:bold; } </style>
</head>
<body><table>
<?//Output the final tableforeach($ColsNamesas$Type=>$ColNames) //Process by direction type (Master/Slave) then columns{print'<tr><td colspan='.(count($Servers)+1).' class=ReplicationDirectionType>'.$Type.'</td></tr>'; //Replication direction (Master/Server) type title columnforeach($ColNamesas$ColName=>$Dummy) //Process each column name individually {print'<tr><th>'.EHTML($ColName).'</th>'; //Column name$IsHTMLColumn=isset($CustomFieldNames[$ColName]); //Do not escape HTML on custom fieldsforeach($ServersInfoas$ServerInfo) //Output the column for each serverif($IsHTMLColumn) //Do not escape HTML on custom fieldsprint'<td>'.$ServerInfo[$Type][$ColName].'</td>';else//If not a custom field, output the escaped HTML of the value. If the column does not exist for this server (different mysql versions), output "Not given"print'<td>'.(isset($ServerInfo[$Type][$ColName]) ? EHTML($ServerInfo[$Type][$ColName]) : '<div class=NotGiven>Not given</div>').'</td>';print'</tr>'; }}?>
</table></body>
</html>
One final note. When having this script run, you might need to make sure none of the listed server IPs evaluates to localhost (127.x.x.x), as MySQL may instead then use the local socket pipe, which may not work with users who only have REPLICATION permissions and a wildcard host.