com.aerospike.client.cluster.Node Maven / Gradle / Ivy
/*******************************************************************************
* Copyright 2012-2014 by Aerospike.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
******************************************************************************/
package com.aerospike.client.cluster;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import com.aerospike.client.AerospikeException;
import com.aerospike.client.Host;
import com.aerospike.client.Info;
import com.aerospike.client.Log;
/**
* Server node representation. This class manages server node connections and health status.
*/
public class Node {
/**
* Number of partitions for each namespace.
*/
public static final int PARTITIONS = 4096;
private static final int FULL_HEALTH = 100;
protected final Cluster cluster;
private final String name;
private final Host host;
private Host[] aliases;
protected final InetSocketAddress address;
private final ArrayBlockingQueue connectionQueue;
private final AtomicInteger health;
private int partitionGeneration;
protected int referenceCount;
protected boolean responded;
protected final boolean useNewInfo;
protected volatile boolean active;
/**
* Initialize server node with connection parameters.
*
* @param cluster collection of active server nodes
* @param nv connection parameters
*/
public Node(Cluster cluster, NodeValidator nv) {
this.cluster = cluster;
this.name = nv.name;
this.aliases = nv.aliases;
this.address = nv.address;
this.useNewInfo = nv.useNewInfo;
// Assign host to first IP alias because the server identifies nodes
// by IP address (not hostname).
this.host = aliases[0];
connectionQueue = new ArrayBlockingQueue(cluster.connectionQueueSize);
health = new AtomicInteger(FULL_HEALTH);
partitionGeneration = -1;
active = true;
}
/**
* Request current status from server node.
*
* @param friends other nodes in the cluster, populated by this method
* @throws Exception if status request fails
*/
public final void refresh(List friends) throws Exception {
Connection conn = getConnection(1000);
try {
HashMap infoMap = Info.request(conn, "node", "partition-generation", "services");
verifyNodeName(infoMap);
restoreHealth();
responded = true;
addFriends(infoMap, friends);
updatePartitions(conn, infoMap);
putConnection(conn);
}
catch (Exception e) {
conn.close();
decreaseHealth();
throw e;
}
}
private final void verifyNodeName(HashMap infoMap) throws AerospikeException {
// If the node name has changed, remove node from cluster and hope one of the other host
// aliases is still valid. Round-robbin DNS may result in a hostname that resolves to a
// new address.
String infoName = infoMap.get("node");
if (infoName == null || infoName.length() == 0) {
decreaseHealth();
throw new AerospikeException.Parse("Node name is empty");
}
if (! name.equals(infoName)) {
// Set node to inactive immediately.
active = false;
throw new AerospikeException("Node name has changed. Old=" + name + " New=" + infoName);
}
}
private final void addFriends(HashMap infoMap, List friends) throws AerospikeException {
// Parse the service addresses and add the friends to the list.
String friendString = infoMap.get("services");
if (friendString == null || friendString.length() == 0) {
return;
}
String friendNames[] = friendString.split(";");
for (String friend : friendNames) {
String friendInfo[] = friend.split(":");
String host = friendInfo[0];
int port = Integer.parseInt(friendInfo[1]);
Host alias = new Host(host, port);
Node node = cluster.findAlias(alias);
if (node != null) {
node.referenceCount++;
}
else {
if (! findAlias(friends, alias)) {
friends.add(alias);
}
}
}
}
private final static boolean findAlias(List friends, Host alias) {
for (Host host : friends) {
if (host.equals(alias)) {
return true;
}
}
return false;
}
private final void updatePartitions(Connection conn, HashMap infoMap)
throws AerospikeException, IOException {
String genString = infoMap.get("partition-generation");
if (genString == null || genString.length() == 0) {
throw new AerospikeException.Parse("partition-generation is empty");
}
int generation = Integer.parseInt(genString);
if (partitionGeneration != generation) {
if (Log.debugEnabled()) {
Log.debug("Node " + this + " partition generation " + generation + " changed.");
}
cluster.updatePartitions(conn, this);
partitionGeneration = generation;
}
}
/**
* Get a socket connection from connection pool to the server node.
*
* @param timeoutMillis connection timeout value in milliseconds if a new connection is created
* @return socket connection
* @throws AerospikeException if a connection could not be provided
*/
public final Connection getConnection(int timeoutMillis) throws AerospikeException.Connection {
Connection conn;
while ((conn = connectionQueue.poll()) != null) {
if (conn.isValid()) {
try {
conn.setTimeout(timeoutMillis);
return conn;
}
catch (Exception e) {
// Set timeout failed. Something is probably wrong with timeout
// value itself, so don't empty queue retrying. Just get out.
conn.close();
throw new AerospikeException.Connection(e);
}
}
conn.close();
}
return new Connection(address, timeoutMillis, cluster.maxSocketIdle);
}
/**
* Put connection back into connection pool.
*
* @param conn socket connection
*/
public final void putConnection(Connection conn) {
if (! active || ! connectionQueue.offer(conn)) {
conn.close();
}
}
/**
* Set node status as healthy after successful database operation.
*/
public final void restoreHealth() {
// There can be cases where health is full, but active is false.
// Once a node has been marked inactive, it stays inactive.
health.set(FULL_HEALTH);
}
/**
* Decrease server health status after a connection failure.
*/
public final void decreaseHealth() {
health.decrementAndGet();
}
/**
* Has consecutive node connection errors become critical.
*/
public final boolean isUnhealthy() {
return health.get() <= 0;
}
/**
* Return server node IP address and port.
*/
public final Host getHost() {
return host;
}
/**
* Return whether node is currently active.
*/
public final boolean isActive() {
return active;
}
/**
* Return server node name.
*/
public final String getName() {
return name;
}
/**
* Return server node IP address aliases.
*/
public final Host[] getAliases() {
return aliases;
}
/**
* Add node alias to list.
*/
public final void addAlias(Host aliasToAdd) {
// Aliases are only referenced in the cluster tend thread,
// so synchronization is not necessary.
Host[] tmpAliases = new Host[aliases.length + 1];
int count = 0;
for (Host host : aliases) {
tmpAliases[count++] = host;
}
tmpAliases[count] = aliasToAdd;
aliases = tmpAliases;
}
/**
* Close all server node socket connections.
*/
public final void close() {
active = false;
closeConnections();
}
@Override
public final String toString() {
return name + ' ' + host;
}
@Override
public final int hashCode() {
return name.hashCode();
}
@Override
public final boolean equals(Object obj) {
Node other = (Node) obj;
return this.name.equals(other.name);
}
@Override
protected final void finalize() throws Throwable {
try {
// Close connections that slipped through the cracks on race conditions.
closeConnections();
}
finally {
super.finalize();
}
}
protected void closeConnections() {
// Empty connection pool.
Connection conn;
while ((conn = connectionQueue.poll()) != null) {
conn.close();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy