MXS-839: Detect multi-master topologies with mysqlmon
The mysqlmon now supports proper detection of multi-master topologies by building a directed graph out of the monitored server. If cycles are found from this graph, they are assigned a master group ID. All servers with a positive master group ID will receive the Master status unless they have `@@read_only` enabled. This new functionality can be enabled with the 'multimaster' boolean parameter.
This commit is contained in:
@ -97,6 +97,32 @@ Enable support for MySQL 5.1 replication monitoring. This is needed if a MySQL s
|
|||||||
mysql51_replication=true
|
mysql51_replication=true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `multimaster`
|
||||||
|
|
||||||
|
Detect multi-master replication topologies. This feature is disabled by default.
|
||||||
|
|
||||||
|
When enabled, the multi-master detection looks for the root master servers in
|
||||||
|
the replication clusters. These masters can be found by detecting cycles in the
|
||||||
|
graph created by the servers. When a cycle is detected, it is assigned a master
|
||||||
|
group ID. Every master in a master group will receive the Master status. The
|
||||||
|
special group ID 0 is assigned to all servers which are not a part of a
|
||||||
|
multi-master replication cycle.
|
||||||
|
|
||||||
|
If one or more masters in a group has the `@@read_only` system variable set to
|
||||||
|
`ON`, those servers will receive the Slave status even though they are in the
|
||||||
|
multi-master group. Slave servers with `@@read_only` disabled will never receive
|
||||||
|
the master status.
|
||||||
|
|
||||||
|
By setting the servers into read-only mode, the user can control which
|
||||||
|
server receive the master status. To do this:
|
||||||
|
|
||||||
|
- Enable `@@read_only` on all servers (preferrably through the configuration file)
|
||||||
|
- Manually disable `@@read_only` on the server which should be the master
|
||||||
|
|
||||||
|
This functionality is similar to the [Multi-Master Monitor](MM-Monitor.md)
|
||||||
|
functionality. The only difference is that the MySQL monitor will also detect
|
||||||
|
traditional Master-Slave topologies.
|
||||||
|
|
||||||
## Example 1 - Monitor script
|
## Example 1 - Monitor script
|
||||||
|
|
||||||
Here is an example shell script which sends an email to an admin when a server goes down.
|
Here is an example shell script which sends an email to an admin when a server goes down.
|
||||||
|
@ -184,6 +184,7 @@ static char *monitor_params[] =
|
|||||||
"available_when_donor",
|
"available_when_donor",
|
||||||
"disable_master_role_setting",
|
"disable_master_role_setting",
|
||||||
"use_priority",
|
"use_priority",
|
||||||
|
"multimaster",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -63,6 +63,7 @@ typedef struct
|
|||||||
int replicationHeartbeat; /**< Monitor flag for MySQL replication heartbeat */
|
int replicationHeartbeat; /**< Monitor flag for MySQL replication heartbeat */
|
||||||
bool detectStaleMaster; /**< Monitor flag for MySQL replication Stale Master detection */
|
bool detectStaleMaster; /**< Monitor flag for MySQL replication Stale Master detection */
|
||||||
bool detectStaleSlave; /**< Monitor flag for MySQL replication Stale Master detection */
|
bool detectStaleSlave; /**< Monitor flag for MySQL replication Stale Master detection */
|
||||||
|
bool multimaster; /**< Detect and handle multi-master topologies */
|
||||||
int disableMasterFailback; /**< Monitor flag for Galera Cluster Master failback */
|
int disableMasterFailback; /**< Monitor flag for Galera Cluster Master failback */
|
||||||
int availableWhenDonor; /**< Monitor flag for Galera Cluster Donor availability */
|
int availableWhenDonor; /**< Monitor flag for Galera Cluster Donor availability */
|
||||||
int disableMasterRoleSetting; /**< Monitor flag to disable setting master role */
|
int disableMasterRoleSetting; /**< Monitor flag to disable setting master role */
|
||||||
|
@ -74,7 +74,7 @@ extern char *strcasestr(const char *haystack, const char *needle);
|
|||||||
|
|
||||||
static void monitorMain(void *);
|
static void monitorMain(void *);
|
||||||
|
|
||||||
static char *version_str = "V1.4.0";
|
static char *version_str = "V1.5.0";
|
||||||
|
|
||||||
/* @see function load_module in load_utils.c for explanation of the following
|
/* @see function load_module in load_utils.c for explanation of the following
|
||||||
* lint directives.
|
* lint directives.
|
||||||
@ -156,6 +156,8 @@ typedef struct mysql_server_info
|
|||||||
{
|
{
|
||||||
int server_id; /**< Value of @@server_id */
|
int server_id; /**< Value of @@server_id */
|
||||||
int master_id; /**< Master server id from SHOW SLAVE STATUS*/
|
int master_id; /**< Master server id from SHOW SLAVE STATUS*/
|
||||||
|
int group; /**< Multi-master group where this server
|
||||||
|
belongs, 0 for servers not in groups */
|
||||||
bool read_only; /**< Value of @@read_only */
|
bool read_only; /**< Value of @@read_only */
|
||||||
bool slave_configured; /**< Whether SHOW SLAVE STATUS returned rows */
|
bool slave_configured; /**< Whether SHOW SLAVE STATUS returned rows */
|
||||||
bool slave_io; /**< If Slave IO thread is running */
|
bool slave_io; /**< If Slave IO thread is running */
|
||||||
@ -272,6 +274,7 @@ startMonitor(MONITOR *monitor, const CONFIG_PARAMETER* params)
|
|||||||
handle->detectStaleSlave = true;
|
handle->detectStaleSlave = true;
|
||||||
handle->master = NULL;
|
handle->master = NULL;
|
||||||
handle->script = NULL;
|
handle->script = NULL;
|
||||||
|
handle->multimaster = false;
|
||||||
handle->mysql51_replication = false;
|
handle->mysql51_replication = false;
|
||||||
memset(handle->events, false, sizeof(handle->events));
|
memset(handle->events, false, sizeof(handle->events));
|
||||||
spinlock_init(&handle->lock);
|
spinlock_init(&handle->lock);
|
||||||
@ -291,6 +294,10 @@ startMonitor(MONITOR *monitor, const CONFIG_PARAMETER* params)
|
|||||||
{
|
{
|
||||||
handle->replicationHeartbeat = config_truth_value(params->value);
|
handle->replicationHeartbeat = config_truth_value(params->value);
|
||||||
}
|
}
|
||||||
|
else if (!strcmp(params->name, "multimaster"))
|
||||||
|
{
|
||||||
|
handle->multimaster = config_truth_value(params->value);
|
||||||
|
}
|
||||||
else if (!strcmp(params->name, "script"))
|
else if (!strcmp(params->name, "script"))
|
||||||
{
|
{
|
||||||
if (externcmd_can_execute(params->value))
|
if (externcmd_can_execute(params->value))
|
||||||
@ -419,6 +426,12 @@ static void diagnostics(DCB *dcb, const MONITOR *mon)
|
|||||||
dcb_printf(dcb, "Master ID: %d\n", serv_info->master_id);
|
dcb_printf(dcb, "Master ID: %d\n", serv_info->master_id);
|
||||||
dcb_printf(dcb, "Master binlog file: %s\n", serv_info->binlog_name);
|
dcb_printf(dcb, "Master binlog file: %s\n", serv_info->binlog_name);
|
||||||
dcb_printf(dcb, "Master binlog position: %lu\n", serv_info->binlog_pos);
|
dcb_printf(dcb, "Master binlog position: %lu\n", serv_info->binlog_pos);
|
||||||
|
|
||||||
|
if (handle->multimaster)
|
||||||
|
{
|
||||||
|
dcb_printf(dcb, "Master group: %d\n", serv_info->group);
|
||||||
|
}
|
||||||
|
|
||||||
dcb_printf(dcb, "\n");
|
dcb_printf(dcb, "\n");
|
||||||
db = db->next;
|
db = db->next;
|
||||||
}
|
}
|
||||||
@ -978,6 +991,212 @@ monitorDatabase(MONITOR *mon, MONITOR_SERVERS *database)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A node in a graph
|
||||||
|
*/
|
||||||
|
struct graph_node
|
||||||
|
{
|
||||||
|
int index;
|
||||||
|
int lowest_index;
|
||||||
|
int cycle;
|
||||||
|
bool active;
|
||||||
|
struct graph_node *parent;
|
||||||
|
MYSQL_SERVER_INFO *info;
|
||||||
|
MONITOR_SERVERS *db;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Visit a node in the graph
|
||||||
|
*
|
||||||
|
* This function is the main function used to determine whether the node is a
|
||||||
|
* part of a cycle. It is an implementation of the Tarjan's strongly connected
|
||||||
|
* component algorithm. All one node cycles are ignored since normal
|
||||||
|
* master-slave monitoring handles that.
|
||||||
|
*
|
||||||
|
* Tarjan's strongly connected component algorithm:
|
||||||
|
*
|
||||||
|
* https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
|
||||||
|
*/
|
||||||
|
static void visit_node(struct graph_node *node, struct graph_node **stack,
|
||||||
|
int *stacksize, int *index, int *cycle)
|
||||||
|
{
|
||||||
|
/** Assign an index to this node */
|
||||||
|
node->lowest_index = node->index = *index;
|
||||||
|
node->active = true;
|
||||||
|
*index += 1;
|
||||||
|
|
||||||
|
stack[*stacksize] = node;
|
||||||
|
*stacksize += 1;
|
||||||
|
|
||||||
|
if (node->parent == NULL)
|
||||||
|
{
|
||||||
|
/** This node does not connect to another node, it can't be a part of a cycle */
|
||||||
|
node->lowest_index = -1;
|
||||||
|
}
|
||||||
|
else if (node->parent->index == 0)
|
||||||
|
{
|
||||||
|
/** Node has not been visited */
|
||||||
|
visit_node(node->parent, stack, stacksize, index, cycle);
|
||||||
|
|
||||||
|
if (node->parent->lowest_index < node->lowest_index)
|
||||||
|
{
|
||||||
|
/** The parent connects to a node with a lower index, this node
|
||||||
|
could be a part of a cycle. */
|
||||||
|
node->lowest_index = node->parent->lowest_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (node->parent->active)
|
||||||
|
{
|
||||||
|
/** This node could be a root node of the cycle */
|
||||||
|
if (node->parent->index < node->lowest_index)
|
||||||
|
{
|
||||||
|
/** Root node found */
|
||||||
|
node->lowest_index = node->parent->index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/** Node connects to an already connected cycle, it can't be a part of it */
|
||||||
|
node->lowest_index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node->active && node->parent && node->lowest_index > 0)
|
||||||
|
{
|
||||||
|
if (node->lowest_index == node->index &&
|
||||||
|
node->lowest_index == node->parent->lowest_index)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Found a multi-node cycle from the graph. The cycle is formed from the
|
||||||
|
* nodes with a lowest_index value equal to the lowest_index value of the
|
||||||
|
* current node. Rest of the nodes on the stack are not part of a cycle
|
||||||
|
* and can be discarded.
|
||||||
|
*/
|
||||||
|
|
||||||
|
*cycle += 1;
|
||||||
|
|
||||||
|
while (*stacksize > 0)
|
||||||
|
{
|
||||||
|
struct graph_node *top = stack[(*stacksize) - 1];
|
||||||
|
top->active = false;
|
||||||
|
|
||||||
|
if (top->lowest_index == node->lowest_index)
|
||||||
|
{
|
||||||
|
top->cycle = *cycle;
|
||||||
|
}
|
||||||
|
*stacksize -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/** Pop invalid nodes off the stack */
|
||||||
|
node->active = false;
|
||||||
|
*stacksize -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Find the strongly connected components in the replication tree graph
|
||||||
|
*
|
||||||
|
* Each replication cluster is a directed graph made out of replication
|
||||||
|
* trees. If this graph has strongly connected components (more generally
|
||||||
|
* cycles), it is considered a multi-master cluster due to the fact that there
|
||||||
|
* are multiple nodes where the data can originate.
|
||||||
|
*
|
||||||
|
* Detecting the cycles in the graph allows this monitor to better understand
|
||||||
|
* the relationships between the nodes. All nodes that are a part of a cycle can
|
||||||
|
* be labeled as master nodes. This information will later be used to choose the
|
||||||
|
* right master where the writes should go.
|
||||||
|
*
|
||||||
|
* This function also populates the MYSQL_SERVER_INFO structures group
|
||||||
|
* member. Nodes in a group get a positive group ID where the nodes not in a
|
||||||
|
* group get a group ID of 0.
|
||||||
|
*/
|
||||||
|
void find_graph_cycles(MYSQL_MONITOR *handle, MONITOR_SERVERS *database, int nservers)
|
||||||
|
{
|
||||||
|
struct graph_node graph[nservers];
|
||||||
|
struct graph_node *stack[nservers];
|
||||||
|
int nodes = 0;
|
||||||
|
|
||||||
|
for (MONITOR_SERVERS *db = database; db; db = db->next)
|
||||||
|
{
|
||||||
|
graph[nodes].info = hashtable_fetch(handle->server_info, db->server->unique_name);
|
||||||
|
graph[nodes].db = db;
|
||||||
|
ss_dassert(graph[nodes].info);
|
||||||
|
graph[nodes].index = graph[nodes].lowest_index = 0;
|
||||||
|
graph[nodes].cycle = 0;
|
||||||
|
graph[nodes].active = false;
|
||||||
|
graph[nodes].parent = NULL;
|
||||||
|
nodes++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Build the graph */
|
||||||
|
for (int i = 0; i < nservers; i++)
|
||||||
|
{
|
||||||
|
if (graph[i].info->master_id > 0)
|
||||||
|
{
|
||||||
|
/** Found a connected node */
|
||||||
|
for (int k = 0; k < nservers; k++)
|
||||||
|
{
|
||||||
|
if (graph[k].info->server_id == graph[i].info->master_id)
|
||||||
|
{
|
||||||
|
graph[i].parent = &graph[k];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = 1;
|
||||||
|
int cycle = 0;
|
||||||
|
int stacksize = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < nservers; i++)
|
||||||
|
{
|
||||||
|
if (graph[i].index == 0)
|
||||||
|
{
|
||||||
|
/** Index is 0, this node has not yet been visited */
|
||||||
|
visit_node(&graph[i], stack, &stacksize, &index, &cycle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < nservers; i++)
|
||||||
|
{
|
||||||
|
graph[i].info->group = graph[i].cycle;
|
||||||
|
|
||||||
|
if (graph[i].cycle > 0)
|
||||||
|
{
|
||||||
|
/** We have at least one cycle in the graph */
|
||||||
|
if (graph[i].info->read_only)
|
||||||
|
{
|
||||||
|
monitor_set_pending_status(graph[i].db, SERVER_SLAVE);
|
||||||
|
monitor_clear_pending_status(graph[i].db, SERVER_MASTER);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
monitor_set_pending_status(graph[i].db, SERVER_MASTER);
|
||||||
|
monitor_clear_pending_status(graph[i].db, SERVER_SLAVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (handle->detectStaleMaster && cycle == 0 && graph[i].cycle == 0 &&
|
||||||
|
graph[i].db->server->status & SERVER_MASTER)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Stale master detection is handled here for multi-master mode.
|
||||||
|
*
|
||||||
|
* If we know that no cycles were found from the graph and that a
|
||||||
|
* server once had the master status, replication has broken
|
||||||
|
* down. These masters are assigned the stale master status allowing
|
||||||
|
* them to be used as masters even if they lose their slaves. A
|
||||||
|
* slave in this case can be either a normal slave or another
|
||||||
|
* master.
|
||||||
|
*/
|
||||||
|
monitor_set_pending_status(graph[i].db, SERVER_MASTER | SERVER_STALE_STATUS);
|
||||||
|
monitor_clear_pending_status(graph[i].db, SERVER_SLAVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The entry point for the monitoring module thread
|
* The entry point for the monitoring module thread
|
||||||
*
|
*
|
||||||
@ -1151,6 +1370,14 @@ monitorMain(void *arg)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (handle->multimaster)
|
||||||
|
{
|
||||||
|
/** Find all the master server cycles in the cluster graph. If
|
||||||
|
multiple masters are found, the servers with the read_only
|
||||||
|
variable set to ON will be assigned the slave status. */
|
||||||
|
find_graph_cycles(handle, mon->databases, num_servers);
|
||||||
|
}
|
||||||
|
|
||||||
/* Update server status from monitor pending status on that server*/
|
/* Update server status from monitor pending status on that server*/
|
||||||
|
|
||||||
ptr = mon->databases;
|
ptr = mon->databases;
|
||||||
@ -1158,8 +1385,11 @@ monitorMain(void *arg)
|
|||||||
{
|
{
|
||||||
if (!SERVER_IN_MAINT(ptr->server))
|
if (!SERVER_IN_MAINT(ptr->server))
|
||||||
{
|
{
|
||||||
/* If "detect_stale_master" option is On, let's use the previous master */
|
/** If "detect_stale_master" option is On, let's use the previous master.
|
||||||
if (detect_stale_master && root_master &&
|
*
|
||||||
|
* Multi-master mode detects the stale masters in find_graph_cycles().
|
||||||
|
*/
|
||||||
|
if (detect_stale_master && root_master && !handle->multimaster &&
|
||||||
(strcmp(ptr->server->name, root_master->server->name) == 0 &&
|
(strcmp(ptr->server->name, root_master->server->name) == 0 &&
|
||||||
ptr->server->port == root_master->server->port) &&
|
ptr->server->port == root_master->server->port) &&
|
||||||
(ptr->server->status & SERVER_MASTER) &&
|
(ptr->server->status & SERVER_MASTER) &&
|
||||||
|
Reference in New Issue
Block a user