/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltdb.client.exampleutils; import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; /** * Provides support for database connection pooling, allowing for optimal * application performance. From benchmarking results, optimal TCP socket * usage is attained when 50 threads share the same socket, sending * execution requests through it. The pool incorporate the logic necessary * to issue newly created connections or pre-existing connections in use to * client threads, as well as proper management of those connections * (releasing resources, etc.). * * @author Seb Coursol * @since 2.0 */ public class ClientConnectionPool { private static final ConcurrentHashMap<String,PerfCounterMap> Statistics = new ConcurrentHashMap<String,PerfCounterMap>(); private static final HashMap<String,ClientConnection> ClientConnections = new HashMap<String,ClientConnection>(); /** * No instantiation allowed. */ private ClientConnectionPool() {} /** * Gets a client connection to the given VoltDB server(s). No credentials or options are passed when connecting to the server. For custom credentials and options, see method overloads. * * @param servers the VoltDB server (or CSV list of servers) to connect to. * @param port the VoltDB native protocol port to connect to (usually 21212). * @return the client connection object the caller should use to post requests. * @see #get(String[] servers, int port) * @see #get(String servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) * @see #get(String[] servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) */ public static ClientConnection get(String servers, int port) throws Exception { return get(servers.split(","), port, "", "", false, 0); } /** * Gets a client connection to the given VoltDB server(s). No credentials or options are passed when connecting to the server. For custom credentials and options, see method overloads. * Retries connecting until the connection is successful. * * @param servers the VoltDB server (or CSV list of servers) to connect to. * @param port the VoltDB native protocol port to connect to (usually 21212). * @return the client connection object the caller should use to post requests. * @see #getWithRetry(String[] servers, int port) * @see #getWithRetry(String servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) * @see #getWithRetry(String[] servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) */ public static ClientConnection getWithRetry(String servers, int port) throws Exception { return getWithRetry(servers.split(","), port, "", "", false, 0); } /** * Gets a client connection to the given VoltDB server(s). No credentials or options are passed when connecting to the server. For custom credentials and options, see method overloads. * * @param servers the list of VoltDB servers to connect to. * @param port the VoltDB native protocol port to connect to (usually 21212). * @return the client connection object the caller should use to post requests. * @see #get(String servers, int port) * @see #get(String servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) * @see #get(String[] servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) */ public static ClientConnection get(String[] servers, int port) throws Exception { return get(servers, port, "", "", false, 0); } /** * Gets a client connection to the given VoltDB server(s). No credentials or options are passed when connecting to the server. For custom credentials and options, see method overloads. * Retries connecting until the connection is successful. * * @param servers the list of VoltDB servers to connect to. * @param port the VoltDB native protocol port to connect to (usually 21212). * @return the client connection object the caller should use to post requests. * @see #getWithRetry(String servers, int port) * @see #getWithRetry(String servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) * @see #getWithRetry(String[] servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) */ public static ClientConnection getWithRetry(String[] servers, int port) throws Exception { return getWithRetry(servers, port, "", "", false, 0); } /** * Gets a client connection to the given VoltDB server(s). * * @param servers the VoltDB server (or CSV list of servers) to connect to. * @param port the VoltDB native protocol port to connect to (usually 21212). * @param user the user name to use when connecting to the server(s). * @param password the password to use when connecting to the server(s). * @param isHeavyWeight the flag indicating callback processes on this connection will be heavy (long running callbacks). * By default the connection only allocates one background processing thread to process callbacks. If those * callbacks run for a long time, the network stack can get clogged with pending responses that have yet to be * processed, at which point the server will disconnect the application, thinking it died and is not reading * responses as fast as it is pushing requests. When the flag is set to 'true', an additional 2 processing * thread will deal with processing callbacks, thus mitigating the issue. * @param maxOutstandingTxns the number of transactions the client application may push against a specific connection * before getting blocked on back-pressure. * By default the connection allows 3,000 open transactions before preventing the client from posting more work, * thus preventing server fire-hosing. In some cases however, with very fast, small transactions, this limit * can be raised. * @return the client connection object the caller should use to post requests. * @see #get(String servers, int port) * @see #get(String[] servers, int port) * @see #get(String[] servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) */ public static ClientConnection get(String servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) throws Exception { return get(servers.split(","), port, user, password, isHeavyWeight, maxOutstandingTxns); } /** * Gets a client connection to the given VoltDB server(s). * Retries connecting until the connection is successful. * * @param servers the VoltDB server (or CSV list of servers) to connect to. * @param port the VoltDB native protocol port to connect to (usually 21212). * @param user the user name to use when connecting to the server(s). * @param password the password to use when connecting to the server(s). * @param isHeavyWeight the flag indicating callback processes on this connection will be heavy (long running callbacks). * By default the connection only allocates one background processing thread to process callbacks. If those * callbacks run for a long time, the network stack can get clogged with pending responses that have yet to be * processed, at which point the server will disconnect the application, thinking it died and is not reading * responses as fast as it is pushing requests. When the flag is set to 'true', an additional 2 processing * thread will deal with processing callbacks, thus mitigating the issue. * @param maxOutstandingTxns the number of transactions the client application may push against a specific connection * before getting blocked on back-pressure. * By default the connection allows 3,000 open transactions before preventing the client from posting more work, * thus preventing server fire-hosing. In some cases however, with very fast, small transactions, this limit * can be raised. * @return the client connection object the caller should use to post requests. * @see #getWithRetry(String servers, int port) * @see #getWithRetry(String[] servers, int port) * @see #getWithRetry(String[] servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) */ public static ClientConnection getWithRetry(String servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) throws Exception { return getWithRetry(servers.split(","), port, user, password, isHeavyWeight, maxOutstandingTxns); } /** * Gets a client connection to the given VoltDB server(s). * * @param servers the list of VoltDB servers to connect to. * @param port the VoltDB native protocol port to connect to (usually 21212). * @param user the user name to use when connecting to the server(s). * @param password the password to use when connecting to the server(s). * @param isHeavyWeight the flag indicating callback processes on this connection will be heavy (long running callbacks). * By default the connection only allocates one background processing thread to process callbacks. If those * callbacks run for a long time, the network stack can get clogged with pending responses that have yet to be * processed, at which point the server will disconnect the application, thinking it died and is not reading * responses as fast as it is pushing requests. When the flag is set to 'true', an additional 2 processing * thread will deal with processing callbacks, thus mitigating the issue. * @param maxOutstandingTxns the number of transactions the client application may push against a specific connection * before getting blocked on back-pressure. * By default the connection allows 3,000 open transactions before preventing the client from posting more work, * thus preventing server fire-hosing. In some cases however, with very fast, small transactions, this limit * can be raised. * @return the client connection object the caller should use to post requests. * @see #get(String servers, int port) * @see #get(String[] servers, int port) * @see #get(String servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) */ public static ClientConnection get(String[] servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) throws Exception { String clientConnectionKeyBase = getClientConnectionKeyBase(servers, port, user, password, isHeavyWeight, maxOutstandingTxns); String clientConnectionKey = clientConnectionKeyBase; synchronized (ClientConnections) { if (!ClientConnections.containsKey(clientConnectionKey)) ClientConnections.put( clientConnectionKey, new ClientConnection( clientConnectionKeyBase, clientConnectionKey, servers, port, user, password, isHeavyWeight, maxOutstandingTxns)); return ClientConnections.get(clientConnectionKey).use(); } } /** * Gets a client connection to the given VoltDB server(s). * Retries connecting until the connection is successful. * * @param servers the list of VoltDB servers to connect to. * @param port the VoltDB native protocol port to connect to (usually 21212). * @param user the user name to use when connecting to the server(s). * @param password the password to use when connecting to the server(s). * @param isHeavyWeight the flag indicating callback processes on this connection will be heavy (long running callbacks). * By default the connection only allocates one background processing thread to process callbacks. If those * callbacks run for a long time, the network stack can get clogged with pending responses that have yet to be * processed, at which point the server will disconnect the application, thinking it died and is not reading * responses as fast as it is pushing requests. When the flag is set to 'true', an additional 2 processing * thread will deal with processing callbacks, thus mitigating the issue. * @param maxOutstandingTxns the number of transactions the client application may push against a specific connection * before getting blocked on back-pressure. * By default the connection allows 3,000 open transactions before preventing the client from posting more work, * thus preventing server fire-hosing. In some cases however, with very fast, small transactions, this limit * can be raised. * @return the client connection object the caller should use to post requests. * @see #getWithRetry(String servers, int port) * @see #getWithRetry(String[] servers, int port) * @see #getWithRetry(String servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) */ public static ClientConnection getWithRetry(String[] servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) throws Exception { ClientConnection con = null; System.out.println("Connecting to servers: "); for(String server : servers) System.out.printf(" - %s:%d\n", server, port); System.out.printf( "Credentials:\n%s\nOptions:\n - Heavyweight: %s\n - MaxTxnQueue: %s\n" , user == "" ? " - None" : " - User: " + user + "\n - Password: ********" , isHeavyWeight ? "yes" : "no" , maxOutstandingTxns == 0 ? "(default)" : String.format("%,d", maxOutstandingTxns) ); int sleep = 1000; while(true) { try { con = ClientConnectionPool.get(servers, port); break; } catch (Exception e) { System.err.printf("Connection failed - retrying in %d second(s).\n", sleep/1000); try {Thread.sleep(sleep);} catch(Exception tie){} if (sleep < 8000) sleep += sleep; } } System.out.println("Connected."); return con; } /** * Releases a connection. This method (or connection.close() must be called by the user thread once the connection * is no longer needed to release it back into the pool where other threads can pick it up. Failure to do so will * cause a memory leak as more and more new connections will be created, never to be released and reused. * The pool itself will run the logic to decide whether the actual underlying connection should be kept alive (if * other threads are using it), or closed for good (if the calling thread was the last user of that connection). * * @param connection the connection to release back into the pool. */ public static void dispose(ClientConnection connection) { synchronized (ClientConnections) { connection.dispose(); if (connection.Users == 0) ClientConnections.remove(connection.Key); } } /** * Gets the global performance statistics for a connection. The statistics are pulled across the entire pool for connections with the same parameters as the given connection. * * @param connection the connection from which to retrieve statistics. * @return the counter map aggregated across all the connections in the pool with the same parameters as the provided connection object. * @see #getStatistics(ClientConnection connection) * @see #getStatistics(String servers, int port) * @see #getStatistics(String[] servers, int port) * @see #getStatistics(String servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) * @see #getStatistics(String[] servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) */ public static PerfCounterMap getStatistics(ClientConnection connection) { return getStatistics(connection.KeyBase); } /** * Gets the global performance statistics for a connection with the given parameters. The statistics are pulled across the entire pool for connections with the same parameters as provided. * * @param servers the VoltDB server (or CSV list of servers) to connect to. * @param port the VoltDB native protocol port to connect to (usually 21212). * @return the counter map aggregated across all the connections in the pool with the same parameters as provided. * @see #getStatistics(ClientConnection connection) * @see #getStatistics(String[] servers, int port) * @see #getStatistics(String servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) * @see #getStatistics(String[] servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) */ public static PerfCounterMap getStatistics(String servers, int port) { return getStatistics(getClientConnectionKeyBase(servers.split(","), port, "", "", false, 0)); } /** * Gets the global performance statistics for a connection with the given parameters. The statistics are pulled across the entire pool for connections with the same parameters as provided. * * @param servers the list of VoltDB servers to connect to. * @param port the VoltDB native protocol port to connect to (usually 21212). * @return the counter map aggregated across all the connections in the pool with the same parameters as provided. * @see #getStatistics(ClientConnection connection) * @see #getStatistics(String servers, int port) * @see #getStatistics(String servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) * @see #getStatistics(String[] servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) */ public static PerfCounterMap getStatistics(String[] servers, int port) { return getStatistics(getClientConnectionKeyBase(servers, port, "", "", false, 0)); } /** * Gets the global performance statistics for a connection with the given parameters. The statistics are pulled across the entire pool for connections with the same parameters as provided. * * @param servers the VoltDB server (or CSV list of servers) to connect to. * @param port the VoltDB native protocol port to connect to (usually 21212). * @param user the user name to use when connecting to the server(s). * @param password the password to use when connecting to the server(s). * @param isHeavyWeight the flag indicating callback processes on this connection will be heavy (long running callbacks). * @param maxOutstandingTxns the number of transactions the client application may push against a specific connection * before getting blocked on back-pressure. * @return the counter map aggregated across all the connections in the pool with the same parameters as provided. * @see #getStatistics(ClientConnection connection) * @see #getStatistics(String servers, int port) * @see #getStatistics(String[] servers, int port) * @see #getStatistics(String[] servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) */ public static PerfCounterMap getStatistics(String servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) { return getStatistics(getClientConnectionKeyBase(servers.split(","), port, user, password, isHeavyWeight, maxOutstandingTxns)); } /** * Gets the global performance statistics for a connection with the given parameters. The statistics are pulled across the entire pool for connections with the same parameters as provided. * * @param servers the list of VoltDB servers to connect to. * @param port the VoltDB native protocol port to connect to (usually 21212). * @param user the user name to use when connecting to the server(s). * @param password the password to use when connecting to the server(s). * @param isHeavyWeight the flag indicating callback processes on this connection will be heavy (long running callbacks). * @param maxOutstandingTxns the number of transactions the client application may push against a specific connection * before getting blocked on back-pressure. * @return the counter map aggregated across all the connections in the pool with the same parameters as provided. * @see #getStatistics(ClientConnection connection) * @see #getStatistics(String servers, int port) * @see #getStatistics(String[] servers, int port) * @see #getStatistics(String servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) */ public static PerfCounterMap getStatistics(String[] servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) { return getStatistics(getClientConnectionKeyBase(servers, port, user, password, isHeavyWeight, maxOutstandingTxns)); } /** * Gets the global performance statistics for a connection with the given base hash/key. The statistics are pulled across the entire pool for connections with the same parameters as provided. * * @param clientConnectionKeyBase the base hash/key identifying the connections from which stattistics will be pulled. * @return the counter map aggregated across all the connections in the pool with the same parameters as provided. */ protected static PerfCounterMap getStatistics(String clientConnectionKeyBase) { // Admited: could get a little race condition at the very beginning, but all that'll happen is that we'll lose a handful of tracking event, a loss far outweighed by overall reduced contention. if(!Statistics.containsKey(clientConnectionKeyBase)) Statistics.put(clientConnectionKeyBase, new PerfCounterMap()); return Statistics.get(clientConnectionKeyBase); } /** * Generates a hash/key for a connection based on the given list of connection parameters * * @param servers the list of VoltDB servers to connect to. * @param port the VoltDB native protocol port to connect to (usually 21212). * @param user the user name to use when connecting to the server(s). * @param password the password to use when connecting to the server(s). * @param isHeavyWeight the flag indicating callback processes on this connection will be heavy (long running callbacks). * @param maxOutstandingTxns the number of transactions the client application may push against a specific connection * before getting blocked on back-pressure. * @return the base hash/key for the given connection parameter */ private static String getClientConnectionKeyBase(String[] servers, int port, String user, String password, boolean isHeavyWeight, int maxOutstandingTxns) { String clientConnectionKeyBase = user + ":" + password + "@"; for(int i=0;i<servers.length;i++) clientConnectionKeyBase += servers[i].trim() + ","; clientConnectionKeyBase += ":" + Integer.toString(port) + "{" + Boolean.toString(isHeavyWeight) + ":" + Integer.toString(maxOutstandingTxns) + "}"; return clientConnectionKeyBase; } }