All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.astyanax.connectionpool.impl.SimpleHostConnectionPool Maven / Gradle / Ivy

There is a newer version: 3.10.2
Show newest version
/*******************************************************************************
 * Copyright 2011 Netflix
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package com.netflix.astyanax.connectionpool.impl;

import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.netflix.astyanax.connectionpool.BadHostDetector;
import com.netflix.astyanax.connectionpool.Connection;
import com.netflix.astyanax.connectionpool.ConnectionFactory;
import com.netflix.astyanax.connectionpool.ConnectionPoolConfiguration;
import com.netflix.astyanax.connectionpool.ConnectionPoolMonitor;
import com.netflix.astyanax.connectionpool.Host;
import com.netflix.astyanax.connectionpool.HostConnectionPool;
import com.netflix.astyanax.connectionpool.LatencyScoreStrategy;
import com.netflix.astyanax.connectionpool.RetryBackoffStrategy;
import com.netflix.astyanax.connectionpool.exceptions.ConnectionException;
import com.netflix.astyanax.connectionpool.exceptions.InterruptedOperationException;
import com.netflix.astyanax.connectionpool.exceptions.HostDownException;
import com.netflix.astyanax.connectionpool.exceptions.IsDeadConnectionException;
import com.netflix.astyanax.connectionpool.exceptions.PoolTimeoutException;
import com.netflix.astyanax.connectionpool.exceptions.ThrottledException;
import com.netflix.astyanax.connectionpool.exceptions.TimeoutException;
import com.netflix.astyanax.connectionpool.exceptions.UnknownException;

/**
 * Pool of connections for a single host and implements the {@link HostConnectionPool} interface
 * 
 * 

* Salient Features

* * The class provides a bunch of counters for visibility into the connection pool status e.g no of * that are available / active / pending / blocked etc.

* * This class also provides an async mechanism to create / prime and borrow {@link Connection}(s) using a {@link LinkedBlockingQueue}
* Clients borrowing connections can wait at the end of the queue for a connection to be available. They send a {@link SimpleHostConnectionPool#tryOpenAsync()} request * to create a new connection before waiting, but don't necessarily wait for the same connection to be opened, since they could be unblocked by * another client returning a previously used {@link Connection} * * The class also provides a {@link SimpleHostConnectionPool#markAsDown(ConnectionException)} method which helps purge all connections and then * attempts to init a new set of connections to the host. * *

* * @author elandau * */ public class SimpleHostConnectionPool implements HostConnectionPool { private final static Logger LOG = LoggerFactory.getLogger(SimpleHostConnectionPool.class); private final static int MAX_PRIME_CONNECTIONS_RETRY_ATTEMPT = 2; private final static int PRIME_CONNECTION_DELAY = 100; /** * Interface to notify the owning connection pool of up/down state changes. * This give the owning connection pool a chance to remove a downed host * from its internal state until it's back up. * * @author elandau * * @param */ public interface Listener { void onHostDown(HostConnectionPool pool); void onHostUp(HostConnectionPool pool); } private static final AtomicLong poolIdCounter = new AtomicLong(0); private final long id = poolIdCounter.incrementAndGet(); private final BlockingQueue> availableConnections; private final AtomicInteger activeCount = new AtomicInteger(0); private final AtomicInteger pendingConnections = new AtomicInteger(0); private final AtomicInteger blockedThreads = new AtomicInteger(0); private final AtomicInteger openConnections = new AtomicInteger(0); private final AtomicInteger failedOpenConnections= new AtomicInteger(0); private final AtomicInteger closedConnections = new AtomicInteger(0); private final AtomicLong borrowedCount = new AtomicLong(0); private final AtomicLong returnedCount = new AtomicLong(0); private final AtomicInteger connectAttempt = new AtomicInteger(0); private final AtomicInteger markedDownCount = new AtomicInteger(0); private final AtomicInteger errorsSinceLastSuccess = new AtomicInteger(0); private final ConnectionFactory factory; private final Host host; private final AtomicBoolean isShutdown = new AtomicBoolean(false); private final AtomicBoolean isReconnecting = new AtomicBoolean(false); private final ScheduledExecutorService executor; private final RetryBackoffStrategy.Instance retryContext; private final BadHostDetector.Instance badHostDetector; private final LatencyScoreStrategy.Instance latencyStrategy; private final Listener listener; private final ConnectionPoolMonitor monitor; protected final ConnectionPoolConfiguration config; public SimpleHostConnectionPool(Host host, ConnectionFactory factory, ConnectionPoolMonitor monitor, ConnectionPoolConfiguration config, Listener listener) { this.host = host; this.config = config; this.factory = factory; this.listener = listener; this.retryContext = config.getRetryBackoffStrategy().createInstance(); this.latencyStrategy = config.getLatencyScoreStrategy().createInstance(); this.badHostDetector = config.getBadHostDetector().createInstance(); this.monitor = monitor; this.availableConnections = new LinkedBlockingQueue>(); this.executor = config.getHostReconnectExecutor(); Preconditions.checkNotNull(config.getHostReconnectExecutor(), "HostReconnectExecutor cannot be null"); } @Override public int primeConnections(int numConnections) throws ConnectionException, InterruptedException { if (isReconnecting()) { throw new HostDownException("Can't prime connections on downed host."); } // Don't try to create more than we're allowed int remaining = Math.min(numConnections, config.getMaxConnsPerHost() - getActiveConnectionCount()); // Attempt to open 'count' connections and allow for MAX_PRIME_CONNECTIONS_RETRY_ATTEMPT // retries before giving up if we can't open more. int opened = 0; Exception lastException = null; for (int i = 0; opened < remaining && i < MAX_PRIME_CONNECTIONS_RETRY_ATTEMPT;) { try { reconnect(); opened++; } catch (Exception e) { lastException = e; Thread.sleep(PRIME_CONNECTION_DELAY); i++; } } // If no connection was opened then mark this host as down if (remaining > 0 && opened == 0) { this.markAsDown(null); throw new HostDownException("Failed to prime connections", lastException); } return opened; } /** * Create a connection as long the max hasn't been reached * * @param timeout * - Max wait timeout if max connections have been allocated and * pool is empty. 0 to throw a MaxConnsPerHostReachedException. * @return * @throws TimeoutException * if timeout specified and no new connection is available * MaxConnsPerHostReachedException if max connections created * and no timeout was specified */ @Override public Connection borrowConnection(int timeout) throws ConnectionException { Connection connection = null; long startTime = System.currentTimeMillis(); try { // Try to get a free connection without blocking. connection = availableConnections.poll(); if (connection != null) { return connection; } boolean isOpenning = tryOpenAsync(); // Wait for a connection to free up or a new one to be opened if (timeout > 0) { connection = waitForConnection(isOpenning ? config.getConnectTimeout() : timeout); return connection; } else throw new PoolTimeoutException("Fast fail waiting for connection from pool") .setHost(getHost()) .setLatency(System.currentTimeMillis() - startTime); } finally { if (connection != null) { borrowedCount.incrementAndGet(); monitor.incConnectionBorrowed(host, System.currentTimeMillis() - startTime); } } } /** * Internal method to wait for a connection from the available connection * pool. * * @param timeout * @return * @throws ConnectionException */ private Connection waitForConnection(int timeout) throws ConnectionException { Connection connection = null; long startTime = System.currentTimeMillis(); try { blockedThreads.incrementAndGet(); connection = availableConnections.poll(timeout, TimeUnit.MILLISECONDS); if (connection != null) return connection; throw new PoolTimeoutException("Timed out waiting for connection") .setHost(getHost()) .setLatency(System.currentTimeMillis() - startTime); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedOperationException("Thread interrupted waiting for connection") .setHost(getHost()) .setLatency(System.currentTimeMillis() - startTime); } finally { blockedThreads.decrementAndGet(); } } /** * Return a connection to this host * * @param connection */ @Override public boolean returnConnection(Connection connection) { returnedCount.incrementAndGet(); monitor.incConnectionReturned(host); ConnectionException ce = connection.getLastException(); if (ce != null) { if (ce instanceof IsDeadConnectionException) { noteError(ce); internalCloseConnection(connection); return true; } } errorsSinceLastSuccess.set(0); // Still within the number of max active connection if (activeCount.get() <= config.getMaxConnsPerHost()) { availableConnections.add(connection); if (isShutdown()) { discardIdleConnections(); return true; } } else { // maxConnsPerHost was reduced. This may end up closing too many // connections, but that's ok. We'll open them later. internalCloseConnection(connection); return true; } return false; } @Override public boolean closeConnection(Connection connection) { returnedCount.incrementAndGet(); monitor.incConnectionReturned(host); internalCloseConnection(connection); return true; } private void internalCloseConnection(Connection connection) { try { closedConnections.incrementAndGet(); connection.close(); } finally { activeCount.decrementAndGet(); } } private void noteError(ConnectionException reason) { if (errorsSinceLastSuccess.incrementAndGet() > 3) markAsDown(reason); } /** * Mark the host as down. No new connections will be created from this host. * Connections currently in use will be allowed to continue processing. */ @Override public void markAsDown(ConnectionException reason) { // Make sure we're not triggering the reconnect process more than once if (isReconnecting.compareAndSet(false, true)) { markedDownCount.incrementAndGet(); if (reason != null && !(reason instanceof TimeoutException)) { discardIdleConnections(); } listener.onHostDown(this); monitor .onHostDown(getHost(), reason); retryContext.begin(); try { long delay = retryContext.getNextDelay(); executor.schedule(new Runnable() { @Override public void run() { Thread.currentThread().setName("RetryService : " + host.getName()); try { if (activeCount.get() == 0) reconnect(); // Created a new connection successfully. try { retryContext.success(); if (isReconnecting.compareAndSet(true, false)) { monitor .onHostReactivated(host, SimpleHostConnectionPool.this); listener.onHostUp(SimpleHostConnectionPool.this); } } catch (Throwable t) { LOG.error("Error reconnecting client", t); } return; } catch (Throwable t) { // Ignore //t.printStackTrace(); } if (!isShutdown()) { long delay = retryContext.getNextDelay(); executor.schedule(this, delay, TimeUnit.MILLISECONDS); } } }, delay, TimeUnit.MILLISECONDS); } catch (Exception e) { LOG.error("Failed to schedule retry task for " + host.getHostName(), e); } } } private void reconnect() throws Exception { try { if (activeCount.get() < config.getMaxConnsPerHost()) { if (activeCount.incrementAndGet() <= config.getMaxConnsPerHost()) { connectAttempt.incrementAndGet(); Connection connection = factory.createConnection(SimpleHostConnectionPool.this); connection.open(); errorsSinceLastSuccess.set(0); availableConnections.add(connection); openConnections.incrementAndGet(); } else { activeCount.decrementAndGet(); } } } catch (ConnectionException e) { failedOpenConnections.incrementAndGet(); activeCount.decrementAndGet(); noteError(e); throw e; } catch (Throwable t) { failedOpenConnections.incrementAndGet(); activeCount.decrementAndGet(); ConnectionException ce = new UnknownException(t); noteError(ce); throw ce; } } @Override public void shutdown() { isReconnecting.set(true); isShutdown.set(true); discardIdleConnections(); config.getLatencyScoreStrategy().removeInstance(this.latencyStrategy); config.getBadHostDetector().removeInstance(this.badHostDetector); } /** * Try to open a new connection asynchronously. We don't actually return a * connection here. Instead, the connection will be added to idle queue when * it's ready. */ private boolean tryOpenAsync() { Connection connection = null; // Try to open a new connection, as long as we haven't reached the max if (activeCount.get() < config.getMaxConnsPerHost()) { try { if (activeCount.incrementAndGet() <= config.getMaxConnsPerHost()) { // Don't try to open too many connections at the same time. if (pendingConnections.incrementAndGet() > config.getMaxPendingConnectionsPerHost()) { pendingConnections.decrementAndGet(); } else { try { connectAttempt.incrementAndGet(); connection = factory.createConnection(this); connection.openAsync(new Connection.AsyncOpenCallback() { @Override public void success(Connection connection) { openConnections.incrementAndGet(); pendingConnections.decrementAndGet(); availableConnections.add(connection); // Sanity check in case the connection // pool was closed if (isShutdown()) { discardIdleConnections(); } } @Override public void failure(Connection conn, ConnectionException e) { failedOpenConnections.incrementAndGet(); pendingConnections.decrementAndGet(); activeCount.decrementAndGet(); if (e instanceof IsDeadConnectionException) { noteError(e); } } }); return true; } catch (ThrottledException e) { // Trying to open way too many connections here } finally { if (connection == null) pendingConnections.decrementAndGet(); } } } } finally { if (connection == null) { activeCount.decrementAndGet(); } } } return false; } @Override public boolean isShutdown() { return isShutdown.get(); } public boolean isReconnecting() { return isReconnecting.get(); } @Override public Host getHost() { return host; } @Override public int getActiveConnectionCount() { return activeCount.get(); } @Override public int getIdleConnectionCount() { return availableConnections.size(); } @Override public int getPendingConnectionCount() { return pendingConnections.get(); } @Override public int getBlockedThreadCount() { return blockedThreads.get(); } @Override public int getOpenedConnectionCount() { return openConnections.get(); } @Override public int getFailedOpenConnectionCount() { return failedOpenConnections.get(); } @Override public int getClosedConnectionCount() { return closedConnections.get(); } @Override public int getConnectAttemptCount() { return this.connectAttempt.get(); } @Override public int getBusyConnectionCount() { return getActiveConnectionCount() - getIdleConnectionCount() - getPendingConnectionCount(); } @Override public double getScore() { return latencyStrategy.getScore(); } @Override public void addLatencySample(long latency, long now) { latencyStrategy.addSample(latency); } @Override public int getErrorsSinceLastSuccess() { return errorsSinceLastSuccess.get(); } /** * Drain all idle connections and close them. Connections that are currently borrowed * will not be closed here. */ private void discardIdleConnections() { List> connections = Lists.newArrayList(); availableConnections.drainTo(connections); activeCount.addAndGet(-connections.size()); for (Connection connection : connections) { try { closedConnections.incrementAndGet(); connection.close(); // This is usually an async operation } catch (Throwable t) { // TODO } } } public String toString() { int idle = getIdleConnectionCount(); int open = getActiveConnectionCount(); return new StringBuilder() .append("SimpleHostConnectionPool[") .append("host=" ).append(host).append("-").append(id) .append(",down=" ).append(markedDownCount.get()) .append(",active=" ).append(!isShutdown()) .append(",recon=" ).append(isReconnecting()) .append(",connections(") .append( "open=" ).append(open) .append( ",idle=" ).append(idle) .append( ",busy=" ).append(open - idle) .append( ",closed=").append(closedConnections.get()) .append( ",failed=").append(failedOpenConnections.get()) .append(")") .append(",borrow=" ).append(borrowedCount.get()) .append(",return=" ).append(returnedCount.get()) .append(",blocked=").append(getBlockedThreadCount()) .append(",pending=").append(getPendingConnectionCount()) .append(",score=" ).append(TimeUnit.MILLISECONDS.convert((long)getScore(), TimeUnit.NANOSECONDS)) .append("]").toString(); } @Override public boolean isActive() { return !this.isShutdown.get(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy