
When log messages are written with both address and port information, IPv6 addresses can cause confusion if the normal address:port formatting is used. The RFC 3986 suggests that all IPv6 addresses are expressed as a bracket enclosed address optionally followed by the port that is separate from the address by a colon. In practice, the "all interfaces" address and port number 3306 can be written in IPv4 numbers-and-dots notation as 0.0.0.0:3306 and in IPv6 notation as [::]:3306. Using the latter format in log messages keeps the output consistent with all types of addresses. The details of the standard can be found at the following addresses: https://www.ietf.org/rfc/rfc3986.txt https://www.rfc-editor.org/std/std66.txt
561 lines
17 KiB
C
561 lines
17 KiB
C
/*
|
|
* Copyright (c) 2016 MariaDB Corporation Ab
|
|
*
|
|
* Use of this software is governed by the Business Source License included
|
|
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
|
|
*
|
|
* Change Date: 2019-07-01
|
|
*
|
|
* On the date above, in accordance with the Business Source License, use
|
|
* of this software will be governed by version 2 or later of the General
|
|
* Public License.
|
|
*/
|
|
|
|
#include "readwritesplit.h"
|
|
|
|
#include <stdio.h>
|
|
#include <strings.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
|
|
#include <maxscale/router.h>
|
|
#include "rwsplit_internal.h"
|
|
/**
|
|
* @file rwsplit_select_backends.c The functions that implement back end
|
|
* selection for the read write split router. All of these functions are
|
|
* internal to that router and not intended to be called from elsewhere.
|
|
*
|
|
* @verbatim
|
|
* Revision History
|
|
*
|
|
* Date Who Description
|
|
* 08/08/2016 Martin Brampton Initial implementation
|
|
*
|
|
* @endverbatim
|
|
*/
|
|
|
|
static bool connect_server(backend_ref_t *bref, MXS_SESSION *session, bool execute_history);
|
|
|
|
static void log_server_connections(select_criteria_t select_criteria,
|
|
backend_ref_t *backend_ref, int router_nservers);
|
|
|
|
static SERVER_REF *get_root_master(backend_ref_t *servers, int router_nservers);
|
|
|
|
static int bref_cmp_global_conn(const void *bref1, const void *bref2);
|
|
|
|
static int bref_cmp_router_conn(const void *bref1, const void *bref2);
|
|
|
|
static int bref_cmp_behind_master(const void *bref1, const void *bref2);
|
|
|
|
static int bref_cmp_current_load(const void *bref1, const void *bref2);
|
|
|
|
/**
|
|
* The order of functions _must_ match with the order the select criteria are
|
|
* listed in select_criteria_t definition in readwritesplit.h
|
|
*/
|
|
int (*criteria_cmpfun[LAST_CRITERIA])(const void *, const void *) =
|
|
{
|
|
NULL,
|
|
bref_cmp_global_conn,
|
|
bref_cmp_router_conn,
|
|
bref_cmp_behind_master,
|
|
bref_cmp_current_load
|
|
};
|
|
|
|
/**
|
|
* @brief Check whether it's possible to connect to this server
|
|
*
|
|
* @param bref Backend reference
|
|
* @return True if a connection to this server can be attempted
|
|
*/
|
|
static bool bref_valid_for_connect(const backend_ref_t *bref)
|
|
{
|
|
return !BREF_HAS_FAILED(bref) && SERVER_IS_RUNNING(bref->ref->server);
|
|
}
|
|
|
|
/**
|
|
* Check whether it's possible to use this server as a slave
|
|
*
|
|
* @param bref Backend reference
|
|
* @param master_host The master server
|
|
* @return True if this server is a valid slave candidate
|
|
*/
|
|
static bool bref_valid_for_slave(const backend_ref_t *bref, const SERVER *master_host)
|
|
{
|
|
SERVER *server = bref->ref->server;
|
|
|
|
return (SERVER_IS_SLAVE(server) || SERVER_IS_RELAY_SERVER(server)) &&
|
|
(master_host == NULL || (server != master_host));
|
|
}
|
|
|
|
/**
|
|
* @brief Find the best slave candidate
|
|
*
|
|
* This function iterates through @c bref and tries to find the best backend
|
|
* reference that is not in use. @c cmpfun will be called to compare the backends.
|
|
*
|
|
* @param bref Backend reference
|
|
* @param n Size of @c bref
|
|
* @param master The master server
|
|
* @param cmpfun qsort() compatible comparison function
|
|
* @return The best slave backend reference or NULL if no candidates could be found
|
|
*/
|
|
backend_ref_t* get_slave_candidate(backend_ref_t *bref, int n, const SERVER *master,
|
|
int (*cmpfun)(const void *, const void *))
|
|
{
|
|
backend_ref_t *candidate = NULL;
|
|
|
|
for (int i = 0; i < n; i++)
|
|
{
|
|
if (!BREF_IS_IN_USE(&bref[i]) &&
|
|
bref_valid_for_connect(&bref[i]) &&
|
|
bref_valid_for_slave(&bref[i], master))
|
|
{
|
|
if (candidate)
|
|
{
|
|
if (cmpfun(candidate, &bref[i]) > 0)
|
|
{
|
|
candidate = &bref[i];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
candidate = &bref[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
return candidate;
|
|
}
|
|
|
|
/**
|
|
* @brief Search suitable backend servers from those of router instance
|
|
*
|
|
* It is assumed that there is only one master among servers of a router instance.
|
|
* As a result, the first master found is chosen. There will possibly be more
|
|
* backend references than connected backends because only those in correct state
|
|
* are connected to.
|
|
*
|
|
* @param p_master_ref Pointer to location where master's backend reference is to be stored
|
|
* @param backend_ref Pointer to backend server reference object array
|
|
* @param router_nservers Number of backend server pointers pointed to by @p backend_ref
|
|
* @param max_nslaves Upper limit for the number of slaves
|
|
* @param max_slave_rlag Maximum allowed replication lag for any slave
|
|
* @param select_criteria Slave selection criteria
|
|
* @param session Client session
|
|
* @param router Router instance
|
|
* @return true, if at least one master and one slave was found.
|
|
*/
|
|
bool select_connect_backend_servers(backend_ref_t **p_master_ref,
|
|
backend_ref_t *backend_ref,
|
|
int router_nservers, int max_nslaves,
|
|
int max_slave_rlag,
|
|
select_criteria_t select_criteria,
|
|
MXS_SESSION *session,
|
|
ROUTER_INSTANCE *router,
|
|
bool active_session)
|
|
{
|
|
if (p_master_ref == NULL || backend_ref == NULL)
|
|
{
|
|
MXS_ERROR("Master reference (%p) or backend reference (%p) is NULL.",
|
|
p_master_ref, backend_ref);
|
|
ss_dassert(false);
|
|
return false;
|
|
}
|
|
|
|
/* get the root Master */
|
|
SERVER_REF *master_backend = get_root_master(backend_ref, router_nservers);
|
|
SERVER *master_host = master_backend ? master_backend->server : NULL;
|
|
|
|
if (router->rwsplit_config.master_failure_mode == RW_FAIL_INSTANTLY &&
|
|
(master_host == NULL || SERVER_IS_DOWN(master_host)))
|
|
{
|
|
MXS_ERROR("Couldn't find suitable Master from %d candidates.", router_nservers);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* New session:
|
|
*
|
|
* Connect to both master and slaves
|
|
*
|
|
* Existing session:
|
|
*
|
|
* Master is already connected or we don't have a master. The function was
|
|
* called because new slaves must be selected to replace failed ones.
|
|
*/
|
|
bool master_connected = active_session || *p_master_ref != NULL;
|
|
|
|
/** Check slave selection criteria and set compare function */
|
|
int (*p)(const void *, const void *) = criteria_cmpfun[select_criteria];
|
|
ss_dassert(p);
|
|
|
|
SERVER *old_master = *p_master_ref ? (*p_master_ref)->ref->server : NULL;
|
|
|
|
if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
|
|
{
|
|
log_server_connections(select_criteria, backend_ref, router_nservers);
|
|
}
|
|
|
|
int slaves_found = 0;
|
|
int slaves_connected = 0;
|
|
const int min_nslaves = 0; /*< not configurable at the time */
|
|
bool succp = false;
|
|
|
|
if (!master_connected)
|
|
{
|
|
/** Find a master server */
|
|
for (int i = 0; i < router_nservers; i++)
|
|
{
|
|
SERVER *serv = backend_ref[i].ref->server;
|
|
|
|
if (bref_valid_for_connect(&backend_ref[i]) &&
|
|
master_host && serv == master_host)
|
|
{
|
|
if (connect_server(&backend_ref[i], session, false))
|
|
{
|
|
*p_master_ref = &backend_ref[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Calculate how many connections we already have */
|
|
for (int i = 0; i < router_nservers; i++)
|
|
{
|
|
if (bref_valid_for_connect(&backend_ref[i]) &&
|
|
bref_valid_for_slave(&backend_ref[i], master_host))
|
|
{
|
|
slaves_found += 1;
|
|
|
|
if (BREF_IS_IN_USE(&backend_ref[i]))
|
|
{
|
|
slaves_connected += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
ss_dassert(slaves_connected < max_nslaves || max_nslaves == 0);
|
|
|
|
backend_ref_t *bref = get_slave_candidate(backend_ref, router_nservers, master_host, p);
|
|
|
|
/** Connect to all possible slaves */
|
|
while (bref && slaves_connected < max_nslaves)
|
|
{
|
|
if (connect_server(bref, session, true))
|
|
{
|
|
slaves_connected += 1;
|
|
}
|
|
else
|
|
{
|
|
/** Failed to connect, mark server as failed */
|
|
bref_set_state(bref, BREF_FATAL_FAILURE);
|
|
}
|
|
|
|
bref = get_slave_candidate(backend_ref, router_nservers, master_host, p);
|
|
}
|
|
|
|
/**
|
|
* Successful cases
|
|
*/
|
|
if (slaves_connected >= min_nslaves && slaves_connected <= max_nslaves)
|
|
{
|
|
succp = true;
|
|
|
|
if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
|
|
{
|
|
if (slaves_connected < max_nslaves)
|
|
{
|
|
MXS_INFO("Couldn't connect to maximum number of "
|
|
"slaves. Connected successfully to %d slaves "
|
|
"of %d of them.", slaves_connected, slaves_found);
|
|
}
|
|
|
|
for (int i = 0; i < router_nservers; i++)
|
|
{
|
|
if (BREF_IS_IN_USE((&backend_ref[i])))
|
|
{
|
|
MXS_INFO("Selected %s in \t[%s]:%d",
|
|
STRSRVSTATUS(backend_ref[i].ref->server),
|
|
backend_ref[i].ref->server->name,
|
|
backend_ref[i].ref->server->port);
|
|
}
|
|
} /* for */
|
|
}
|
|
}
|
|
/** Failure cases */
|
|
else
|
|
{
|
|
MXS_ERROR("Couldn't establish required amount of slave connections for "
|
|
"router session. Would need between %d and %d slaves but only have %d.",
|
|
min_nslaves, max_nslaves, slaves_connected);
|
|
|
|
/** Clean up connections */
|
|
for (int i = 0; i < router_nservers; i++)
|
|
{
|
|
if (BREF_IS_IN_USE((&backend_ref[i])))
|
|
{
|
|
ss_dassert(backend_ref[i].ref->connections > 0);
|
|
|
|
close_failed_bref(&backend_ref[i], true);
|
|
|
|
/** Decrease backend's connection counter. */
|
|
atomic_add(&backend_ref[i].ref->connections, -1);
|
|
RW_CHK_DCB(&backend_ref[i], backend_ref[i].bref_dcb);
|
|
dcb_close(backend_ref[i].bref_dcb);
|
|
RW_CLOSE_BREF(&backend_ref[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return succp;
|
|
}
|
|
|
|
/** Compare number of connections from this router in backend servers */
|
|
static int bref_cmp_router_conn(const void *bref1, const void *bref2)
|
|
{
|
|
SERVER_REF *b1 = ((backend_ref_t *)bref1)->ref;
|
|
SERVER_REF *b2 = ((backend_ref_t *)bref2)->ref;
|
|
|
|
if (b1->weight == 0 && b2->weight == 0)
|
|
{
|
|
return b1->connections - b2->connections;
|
|
}
|
|
else if (b1->weight == 0)
|
|
{
|
|
return 1;
|
|
}
|
|
else if (b2->weight == 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
return ((1000 + 1000 * b1->connections) / b1->weight) -
|
|
((1000 + 1000 * b2->connections) / b2->weight);
|
|
}
|
|
|
|
/** Compare number of global connections in backend servers */
|
|
static int bref_cmp_global_conn(const void *bref1, const void *bref2)
|
|
{
|
|
SERVER_REF *b1 = ((backend_ref_t *)bref1)->ref;
|
|
SERVER_REF *b2 = ((backend_ref_t *)bref2)->ref;
|
|
|
|
if (b1->weight == 0 && b2->weight == 0)
|
|
{
|
|
return b1->server->stats.n_current -
|
|
b2->server->stats.n_current;
|
|
}
|
|
else if (b1->weight == 0)
|
|
{
|
|
return 1;
|
|
}
|
|
else if (b2->weight == 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
return ((1000 + 1000 * b1->server->stats.n_current) / b1->weight) -
|
|
((1000 + 1000 * b2->server->stats.n_current) / b2->weight);
|
|
}
|
|
|
|
/** Compare replication lag between backend servers */
|
|
static int bref_cmp_behind_master(const void *bref1, const void *bref2)
|
|
{
|
|
SERVER_REF *b1 = ((backend_ref_t *)bref1)->ref;
|
|
SERVER_REF *b2 = ((backend_ref_t *)bref2)->ref;
|
|
|
|
if (b1->weight == 0 && b2->weight == 0)
|
|
{
|
|
return b1->server->rlag -
|
|
b2->server->rlag;
|
|
}
|
|
else if (b1->weight == 0)
|
|
{
|
|
return 1;
|
|
}
|
|
else if (b2->weight == 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
return ((1000 + 1000 * b1->server->rlag) / b1->weight) -
|
|
((1000 + 1000 * b2->server->rlag) / b2->weight);
|
|
}
|
|
|
|
/** Compare number of current operations in backend servers */
|
|
static int bref_cmp_current_load(const void *bref1, const void *bref2)
|
|
{
|
|
SERVER_REF *b1 = ((backend_ref_t *)bref1)->ref;
|
|
SERVER_REF *b2 = ((backend_ref_t *)bref2)->ref;
|
|
|
|
if (b1->weight == 0 && b2->weight == 0)
|
|
{
|
|
return b1->server->stats.n_current_ops - b2->server->stats.n_current_ops;
|
|
}
|
|
else if (b1->weight == 0)
|
|
{
|
|
return 1;
|
|
}
|
|
else if (b2->weight == 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
return ((1000 + 1000 * b1->server->stats.n_current_ops) / b1->weight) -
|
|
((1000 + 1000 * b2->server->stats.n_current_ops) / b2->weight);
|
|
}
|
|
|
|
/**
|
|
* @brief Connect a server
|
|
*
|
|
* Connects to a server, adds callbacks to the created DCB and updates
|
|
* router statistics. If @p execute_history is true, the session command
|
|
* history will be executed on this server.
|
|
*
|
|
* @param b Router's backend structure for the server
|
|
* @param session Client's session object
|
|
* @param execute_history Execute session command history
|
|
* @return True if successful, false if an error occurred
|
|
*/
|
|
static bool connect_server(backend_ref_t *bref, MXS_SESSION *session, bool execute_history)
|
|
{
|
|
SERVER *serv = bref->ref->server;
|
|
bool rval = false;
|
|
|
|
bref->bref_dcb = dcb_connect(serv, session, serv->protocol);
|
|
|
|
if (bref->bref_dcb != NULL)
|
|
{
|
|
bref_clear_state(bref, BREF_CLOSED);
|
|
bref->closed_at = 0;
|
|
|
|
if (!execute_history || execute_sescmd_history(bref))
|
|
{
|
|
/** Add a callback for unresponsive server */
|
|
dcb_add_callback(bref->bref_dcb, DCB_REASON_NOT_RESPONDING,
|
|
&router_handle_state_switch, (void *) bref);
|
|
bref->bref_state = 0;
|
|
bref_set_state(bref, BREF_IN_USE);
|
|
atomic_add(&bref->ref->connections, 1);
|
|
rval = true;
|
|
}
|
|
else
|
|
{
|
|
MXS_ERROR("Failed to execute session command in %s ([%s]:%d). See earlier "
|
|
"errors for more details.",
|
|
bref->ref->server->unique_name,
|
|
bref->ref->server->name,
|
|
bref->ref->server->port);
|
|
RW_CHK_DCB(bref, bref->bref_dcb);
|
|
dcb_close(bref->bref_dcb);
|
|
RW_CLOSE_BREF(bref);
|
|
bref->bref_dcb = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MXS_ERROR("Unable to establish connection with server [%s]:%d",
|
|
serv->name, serv->port);
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
/**
|
|
* @brief Log server connections
|
|
*
|
|
* @param select_criteria Slave selection criteria
|
|
* @param backend_ref Backend reference array
|
|
* @param router_nservers Number of backends in @p backend_ref
|
|
*/
|
|
static void log_server_connections(select_criteria_t select_criteria,
|
|
backend_ref_t *backend_ref, int router_nservers)
|
|
{
|
|
if (select_criteria == LEAST_GLOBAL_CONNECTIONS ||
|
|
select_criteria == LEAST_ROUTER_CONNECTIONS ||
|
|
select_criteria == LEAST_BEHIND_MASTER ||
|
|
select_criteria == LEAST_CURRENT_OPERATIONS)
|
|
{
|
|
MXS_INFO("Servers and %s connection counts:",
|
|
select_criteria == LEAST_GLOBAL_CONNECTIONS ? "all MaxScale"
|
|
: "router");
|
|
|
|
for (int i = 0; i < router_nservers; i++)
|
|
{
|
|
SERVER_REF *b = backend_ref[i].ref;
|
|
|
|
switch (select_criteria)
|
|
{
|
|
case LEAST_GLOBAL_CONNECTIONS:
|
|
MXS_INFO("MaxScale connections : %d in \t[%s]:%d %s",
|
|
b->server->stats.n_current, b->server->name,
|
|
b->server->port, STRSRVSTATUS(b->server));
|
|
break;
|
|
|
|
case LEAST_ROUTER_CONNECTIONS:
|
|
MXS_INFO("RWSplit connections : %d in \t[%s]:%d %s",
|
|
b->connections, b->server->name,
|
|
b->server->port, STRSRVSTATUS(b->server));
|
|
break;
|
|
|
|
case LEAST_CURRENT_OPERATIONS:
|
|
MXS_INFO("current operations : %d in \t[%s]:%d %s",
|
|
b->server->stats.n_current_ops,
|
|
b->server->name, b->server->port,
|
|
STRSRVSTATUS(b->server));
|
|
break;
|
|
|
|
case LEAST_BEHIND_MASTER:
|
|
MXS_INFO("replication lag : %d in \t[%s]:%d %s",
|
|
b->server->rlag, b->server->name,
|
|
b->server->port, STRSRVSTATUS(b->server));
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/********************************
|
|
* This routine returns the root master server from MySQL replication tree
|
|
* Get the root Master rule:
|
|
*
|
|
* find server with the lowest replication depth level
|
|
* and the SERVER_MASTER bitval
|
|
* Servers are checked even if they are in 'maintenance'
|
|
*
|
|
* @param servers The list of servers
|
|
* @param router_nservers The number of servers
|
|
* @return The Master found
|
|
*
|
|
*/
|
|
static SERVER_REF *get_root_master(backend_ref_t *servers, int router_nservers)
|
|
{
|
|
int i = 0;
|
|
SERVER_REF *master_host = NULL;
|
|
|
|
for (i = 0; i < router_nservers; i++)
|
|
{
|
|
if (servers[i].ref == NULL)
|
|
{
|
|
/** This should not happen */
|
|
ss_dassert(false);
|
|
continue;
|
|
}
|
|
|
|
SERVER_REF *b = servers[i].ref;
|
|
|
|
if (SERVER_IS_MASTER(b->server))
|
|
{
|
|
if (master_host == NULL ||
|
|
(b->server->depth < master_host->server->depth))
|
|
{
|
|
master_host = b;
|
|
}
|
|
}
|
|
}
|
|
return master_host;
|
|
}
|