The servers with a zero weight would be always used over ones that have a weight. This means that the behavior was inverted and caused the mxs2054_hybrid_cluster test to fail in 2.3. Also fixed a typo in the deprecation message.
		
			
				
	
	
		
			459 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			459 lines
		
	
	
		
			14 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: 2022-01-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.hh"
 | 
						|
 | 
						|
#include <stdio.h>
 | 
						|
#include <strings.h>
 | 
						|
#include <string.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <stdint.h>
 | 
						|
#include <sstream>
 | 
						|
#include <functional>
 | 
						|
#include <random>
 | 
						|
#include <iostream>
 | 
						|
 | 
						|
#include <maxbase/stopwatch.hh>
 | 
						|
#include <maxscale/router.h>
 | 
						|
 | 
						|
using namespace maxscale;
 | 
						|
 | 
						|
// TODO, there should be a utility with the most common used random cases.
 | 
						|
// FYI: rand() is about twice as fast as the below toss.
 | 
						|
static std::mt19937 random_engine;
 | 
						|
static std::uniform_real_distribution<> zero_to_one(0.0, 1.0);
 | 
						|
double toss()
 | 
						|
{
 | 
						|
    return zero_to_one(random_engine);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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.
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * Check whether it's possible to use this server as a slave
 | 
						|
 *
 | 
						|
 * @param server The slave candidate
 | 
						|
 * @param master The master server or NULL if no master is available
 | 
						|
 *
 | 
						|
 * @return True if this server is a valid slave candidate
 | 
						|
 */
 | 
						|
static bool valid_for_slave(const SRWBackend& backend, const SRWBackend& master)
 | 
						|
{
 | 
						|
    return (backend->is_slave() || backend->is_relay())
 | 
						|
           && (!master || backend != master);
 | 
						|
}
 | 
						|
 | 
						|
SRWBackendVector::iterator best_score(SRWBackendVector& sBackends,
 | 
						|
                                      std::function<double(SERVER_REF* server)> server_score)
 | 
						|
{
 | 
						|
    double min {std::numeric_limits<double>::max()};
 | 
						|
    auto best = sBackends.end();
 | 
						|
    for (auto ite = sBackends.begin(); ite != sBackends.end(); ++ite)
 | 
						|
    {
 | 
						|
        double score = server_score((***ite).backend());
 | 
						|
        if (min > score)
 | 
						|
        {
 | 
						|
            min = score;
 | 
						|
            best = ite;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return best;
 | 
						|
}
 | 
						|
 | 
						|
/** Compare number of connections from this router in backend servers */
 | 
						|
SRWBackendVector::iterator backend_cmp_router_conn(SRWBackendVector& sBackends)
 | 
						|
{
 | 
						|
    static auto server_score = [](SERVER_REF* server) {
 | 
						|
            return server->server_weight ? (server->connections + 1) / server->server_weight :
 | 
						|
                   std::numeric_limits<double>::max();
 | 
						|
        };
 | 
						|
 | 
						|
    return best_score(sBackends, server_score);
 | 
						|
}
 | 
						|
 | 
						|
/** Compare number of global connections in backend servers */
 | 
						|
SRWBackendVector::iterator backend_cmp_global_conn(SRWBackendVector& sBackends)
 | 
						|
{
 | 
						|
    static auto server_score = [](SERVER_REF* server) {
 | 
						|
            return server->server_weight ? (server->server->stats.n_current + 1) / server->server_weight :
 | 
						|
                   std::numeric_limits<double>::max();
 | 
						|
        };
 | 
						|
 | 
						|
    return best_score(sBackends, server_score);
 | 
						|
}
 | 
						|
 | 
						|
/** Compare replication lag between backend servers */
 | 
						|
SRWBackendVector::iterator backend_cmp_behind_master(SRWBackendVector& sBackends)
 | 
						|
{
 | 
						|
    static auto server_score = [](SERVER_REF* server) {
 | 
						|
            return server->server_weight ? server->server->rlag / server->server_weight :
 | 
						|
                   std::numeric_limits<double>::max();
 | 
						|
        };
 | 
						|
 | 
						|
    return best_score(sBackends, server_score);
 | 
						|
}
 | 
						|
 | 
						|
/** Compare number of current operations in backend servers */
 | 
						|
SRWBackendVector::iterator backend_cmp_current_load(SRWBackendVector& sBackends)
 | 
						|
{
 | 
						|
    static auto server_score = [](SERVER_REF* server) {
 | 
						|
            return server->server_weight ? (server->server->stats.n_current_ops + 1) / server->server_weight :
 | 
						|
                   std::numeric_limits<double>::max();
 | 
						|
        };
 | 
						|
 | 
						|
    return best_score(sBackends, server_score);
 | 
						|
}
 | 
						|
 | 
						|
SRWBackendVector::iterator backend_cmp_response_time(SRWBackendVector& sBackends)
 | 
						|
{
 | 
						|
    const int SZ = sBackends.size();
 | 
						|
    double slot[SZ];
 | 
						|
 | 
						|
    // fill slots with inverses of averages
 | 
						|
    double pre_total {0};
 | 
						|
    for (int i = 0; i < SZ; ++i)
 | 
						|
    {
 | 
						|
        SERVER_REF* server = (**sBackends[i]).backend();
 | 
						|
        double ave = server_response_time_average(server->server);
 | 
						|
 | 
						|
        if (ave == 0)
 | 
						|
        {
 | 
						|
            constexpr double very_quick = 1.0 / 10000000;   // arbitrary very short duration (0.1 us)
 | 
						|
            slot[i] = 1 / very_quick;                       // will be used and updated (almost) immediately.
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            slot[i] = 1 / ave;
 | 
						|
        }
 | 
						|
        slot[i] = slot[i] * slot[i] * slot[i];      // favor faster servers even more
 | 
						|
        pre_total += slot[i];
 | 
						|
    }
 | 
						|
 | 
						|
    // make the slowest server(s) at least a good fraction of the total to guarantee
 | 
						|
    // some amount of sampling, should the slow server become faster.
 | 
						|
    double total = 0;
 | 
						|
    constexpr int divisor = 197;    // ~0.5%, not exact because SZ>1
 | 
						|
    for (int i = 0; i < SZ; ++i)
 | 
						|
    {
 | 
						|
        slot[i] = std::max(slot[i], pre_total / divisor);
 | 
						|
        total += slot[i];
 | 
						|
    }
 | 
						|
 | 
						|
    // turn slots into a roulette wheel, where sum of slots is 1.0
 | 
						|
    for (int i = 0; i < SZ; ++i)
 | 
						|
    {
 | 
						|
        slot[i] = slot[i] / total;
 | 
						|
    }
 | 
						|
 | 
						|
    // Find the winner, role the ball:
 | 
						|
    double ball = toss();
 | 
						|
    double slot_walk {0};
 | 
						|
    int winner {0};
 | 
						|
 | 
						|
    for (; winner < SZ; ++winner)
 | 
						|
    {
 | 
						|
        slot_walk += slot[winner];
 | 
						|
        if (ball < slot_walk)
 | 
						|
        {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return sBackends.begin() + winner;
 | 
						|
}
 | 
						|
 | 
						|
BackendSelectFunction get_backend_select_function(select_criteria_t sc)
 | 
						|
{
 | 
						|
    switch (sc)
 | 
						|
    {
 | 
						|
    case LEAST_GLOBAL_CONNECTIONS:
 | 
						|
        return backend_cmp_global_conn;
 | 
						|
 | 
						|
    case LEAST_ROUTER_CONNECTIONS:
 | 
						|
        return backend_cmp_router_conn;
 | 
						|
 | 
						|
    case LEAST_BEHIND_MASTER:
 | 
						|
        return backend_cmp_behind_master;
 | 
						|
 | 
						|
    case LEAST_CURRENT_OPERATIONS:
 | 
						|
        return backend_cmp_current_load;
 | 
						|
 | 
						|
    case ADAPTIVE_ROUTING:
 | 
						|
        return backend_cmp_response_time;
 | 
						|
    }
 | 
						|
 | 
						|
    assert(false && "incorrect use of select_criteria_t");
 | 
						|
    return backend_cmp_current_load;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief Find the best slave candidate for routing reads.
 | 
						|
 *
 | 
						|
 * @param backends All backends
 | 
						|
 * @param select   Server selection function
 | 
						|
 * @param masters_accepts_reads
 | 
						|
 *
 | 
						|
 * @return iterator to the best slave or backends.end() if none found
 | 
						|
 */
 | 
						|
SRWBackendVector::iterator find_best_backend(SRWBackendVector& backends,
 | 
						|
                                             BackendSelectFunction select,
 | 
						|
                                             bool masters_accepts_reads)
 | 
						|
{
 | 
						|
    // Group backends by priority. The set of highest priority backends will then compete.
 | 
						|
    std::map<int, SRWBackendVector> priority_map;
 | 
						|
    int best_priority {INT_MAX};    // low numbers are high priority
 | 
						|
 | 
						|
    for (auto& psBackend : backends)
 | 
						|
    {
 | 
						|
        auto& backend = **psBackend;
 | 
						|
        bool is_busy = backend.in_use() && backend.has_session_commands();
 | 
						|
        bool acts_slave = backend.is_slave() || (backend.is_master() && masters_accepts_reads);
 | 
						|
 | 
						|
        int priority;
 | 
						|
        if (acts_slave)
 | 
						|
        {
 | 
						|
            if (!is_busy)
 | 
						|
            {
 | 
						|
                priority = 1;   // highest priority, idle servers
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                priority = 13;      // lowest priority, busy servers
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            priority = 2;   // idle masters with masters_accept_reads==false
 | 
						|
        }
 | 
						|
 | 
						|
        priority_map[priority].push_back(psBackend);
 | 
						|
        best_priority = std::min(best_priority, priority);
 | 
						|
    }
 | 
						|
 | 
						|
    auto best = select(priority_map[best_priority]);
 | 
						|
 | 
						|
    return std::find(backends.begin(), backends.end(), *best);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief Log server connections
 | 
						|
 *
 | 
						|
 * @param criteria Slave selection criteria
 | 
						|
 * @param rses     Router client session
 | 
						|
 */
 | 
						|
static void log_server_connections(select_criteria_t criteria, const SRWBackendList& backends)
 | 
						|
{
 | 
						|
    MXS_INFO("Servers and %s connection counts:",
 | 
						|
             criteria == LEAST_GLOBAL_CONNECTIONS ? "all MaxScale" : "router");
 | 
						|
 | 
						|
    for (SRWBackendList::const_iterator it = backends.begin(); it != backends.end(); it++)
 | 
						|
    {
 | 
						|
        SERVER_REF* b = (*it)->backend();
 | 
						|
 | 
						|
        switch (criteria)
 | 
						|
        {
 | 
						|
        case LEAST_GLOBAL_CONNECTIONS:
 | 
						|
            MXS_INFO("MaxScale connections : %d in \t[%s]:%d %s",
 | 
						|
                     b->server->stats.n_current,
 | 
						|
                     b->server->address,
 | 
						|
                     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->address,
 | 
						|
                     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->address,
 | 
						|
                     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->address,
 | 
						|
                     b->server->port,
 | 
						|
                     STRSRVSTATUS(b->server));
 | 
						|
            break;
 | 
						|
 | 
						|
        case ADAPTIVE_ROUTING:
 | 
						|
            {
 | 
						|
                maxbase::Duration response_ave(server_response_time_average(b->server));
 | 
						|
                std::ostringstream os;
 | 
						|
                os << response_ave;
 | 
						|
                MXS_INFO("adaptive avg. select time: %s from \t[%s]:%d %s",
 | 
						|
                         os.str().c_str(),
 | 
						|
                         b->server->address,
 | 
						|
                         b->server->port,
 | 
						|
                         STRSRVSTATUS(b->server));
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
        default:
 | 
						|
            mxb_assert(!true);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
SRWBackend get_root_master(const SRWBackendList& backends)
 | 
						|
{
 | 
						|
    SRWBackend master;
 | 
						|
    for (auto candidate : backends)
 | 
						|
    {
 | 
						|
        if (candidate->is_master())
 | 
						|
        {
 | 
						|
            master = candidate;
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return master;
 | 
						|
}
 | 
						|
 | 
						|
std::pair<int, int> get_slave_counts(SRWBackendList& backends, SRWBackend& master)
 | 
						|
{
 | 
						|
    int slaves_found = 0;
 | 
						|
    int slaves_connected = 0;
 | 
						|
 | 
						|
    /** Calculate how many connections we already have */
 | 
						|
    for (SRWBackendList::const_iterator it = backends.begin(); it != backends.end(); it++)
 | 
						|
    {
 | 
						|
        const SRWBackend& backend = *it;
 | 
						|
 | 
						|
        if (backend->can_connect() && valid_for_slave(backend, master))
 | 
						|
        {
 | 
						|
            slaves_found += 1;
 | 
						|
 | 
						|
            if (backend->in_use())
 | 
						|
            {
 | 
						|
                slaves_connected += 1;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return std::make_pair(slaves_found, slaves_connected);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Select and connect to backend servers
 | 
						|
 *
 | 
						|
 * @param inst               Router instance
 | 
						|
 * @param session            Client session
 | 
						|
 * @param backends           List of backend servers
 | 
						|
 * @param current_master     The current master server
 | 
						|
 * @param sescmd_list        List of session commands to execute
 | 
						|
 * @param expected_responses Pointer where number of expected responses are written
 | 
						|
 * @param type               Connection type, ALL for all types, SLAVE for slaves only
 | 
						|
 *
 | 
						|
 * @return True if session can continue
 | 
						|
 */
 | 
						|
bool RWSplit::select_connect_backend_servers(MXS_SESSION* session,
 | 
						|
                                             SRWBackendList& backends,
 | 
						|
                                             SRWBackend& current_master,
 | 
						|
                                             SessionCommandList* sescmd_list,
 | 
						|
                                             int* expected_responses,
 | 
						|
                                             connection_type type)
 | 
						|
{
 | 
						|
    SRWBackend master = get_root_master(backends);
 | 
						|
    const Config& cnf {config()};
 | 
						|
 | 
						|
    if (!master && cnf.master_failure_mode == RW_FAIL_INSTANTLY)
 | 
						|
    {
 | 
						|
        MXS_ERROR("Couldn't find suitable Master from %lu candidates.", backends.size());
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    auto select_criteria = cnf.slave_selection_criteria;
 | 
						|
 | 
						|
    if (mxs_log_is_priority_enabled(LOG_INFO))
 | 
						|
    {
 | 
						|
        log_server_connections(select_criteria, backends);
 | 
						|
    }
 | 
						|
 | 
						|
    if (type == ALL)
 | 
						|
    {
 | 
						|
        /** Find a master server */
 | 
						|
        for (SRWBackendList::const_iterator it = backends.begin(); it != backends.end(); it++)
 | 
						|
        {
 | 
						|
            const SRWBackend& backend = *it;
 | 
						|
 | 
						|
            if (backend->can_connect() && master && backend == master)
 | 
						|
            {
 | 
						|
                if (backend->connect(session))
 | 
						|
                {
 | 
						|
                    MXS_INFO("Selected Master: %s", backend->name());
 | 
						|
                    current_master = backend;
 | 
						|
                }
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    auto counts = get_slave_counts(backends, master);
 | 
						|
    int slaves_connected = counts.second;
 | 
						|
    int max_nslaves = max_slave_count();
 | 
						|
 | 
						|
    mxb_assert(slaves_connected <= max_nslaves || max_nslaves == 0);
 | 
						|
 | 
						|
    SRWBackendVector candidates;
 | 
						|
    for (auto& sBackend : backends)
 | 
						|
    {
 | 
						|
        if (!sBackend->in_use()
 | 
						|
            && sBackend->can_connect()
 | 
						|
            && valid_for_slave(sBackend, master))
 | 
						|
        {
 | 
						|
            candidates.push_back(&sBackend);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    while (slaves_connected < max_nslaves && candidates.size())
 | 
						|
    {
 | 
						|
        auto ite = m_config->backend_select_fct(candidates);
 | 
						|
        if (ite == candidates.end())
 | 
						|
        {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        auto& backend = **ite;
 | 
						|
 | 
						|
        if (backend->connect(session, sescmd_list))
 | 
						|
        {
 | 
						|
            MXS_INFO("Selected Slave: %s", backend->name());
 | 
						|
 | 
						|
            if (sescmd_list && sescmd_list->size() && expected_responses)
 | 
						|
            {
 | 
						|
                (*expected_responses)++;
 | 
						|
            }
 | 
						|
 | 
						|
            ++slaves_connected;
 | 
						|
        }
 | 
						|
        candidates.erase(ite);
 | 
						|
    }
 | 
						|
    return true;
 | 
						|
}
 |