use of services.moleculer.transporter.tcp.NodeDescriptor in project moleculer-java by moleculer-java.
the class TcpTransporter method sendGossipRequest.
/**
* Create and send a Gossip request packet.
*/
protected Tree sendGossipRequest() {
try {
// Update CPU
NodeDescriptor descriptor = getDescriptor();
int cpu = monitor.getTotalCpuPercent();
descriptor.writeLock.lock();
try {
descriptor.updateCpu(cpu);
} finally {
descriptor.writeLock.unlock();
}
// Are we alone?
if (nodes.isEmpty()) {
return null;
}
// Add "online" and "offline" blocks
Collection<NodeDescriptor> descriptors = nodes.values();
int size = nodes.size() + 32;
FastBuildTree online = new FastBuildTree(size);
FastBuildTree offline = new FastBuildTree(size);
// Add current node
descriptor.readLock.lock();
try {
ArrayList<Object> array = new ArrayList<>(3);
array.add(descriptor.seq);
array.add(descriptor.cpuSeq);
array.add(descriptor.cpu);
online.putUnsafe(nodeID, array);
} finally {
descriptor.readLock.unlock();
}
// Separate online and offline nodes
String[] liveEndpoints = new String[size];
String[] unreachableEndpoints = new String[size];
int liveEndpointCount = 0;
int unreachableEndpointCount = 0;
// Loop on registered nodes
for (NodeDescriptor node : descriptors) {
node.readLock.lock();
try {
if (node.offlineSince > 0) {
// Offline
if (unreachableEndpointCount < unreachableEndpoints.length) {
unreachableEndpoints[unreachableEndpointCount++] = node.nodeID;
}
if (node.seq > 0) {
offline.put(node.nodeID, node.seq);
}
} else {
if (!node.local) {
// Online
if (liveEndpointCount < liveEndpoints.length) {
liveEndpoints[liveEndpointCount++] = node.nodeID;
}
if (node.seq > 0) {
ArrayList<Object> array = new ArrayList<>(3);
array.add(node.seq);
array.add(node.cpuSeq);
array.add(node.cpu);
online.putUnsafe(node.nodeID, array);
}
}
}
} finally {
node.readLock.unlock();
}
}
// Create gossip request
FastBuildTree root = new FastBuildTree(4);
root.putUnsafe("ver", ServiceBroker.PROTOCOL_VERSION);
root.putUnsafe("sender", nodeID);
root.putUnsafe("online", online.asObject());
if (!offline.isEmpty()) {
root.putUnsafe("offline", offline.asObject());
}
// Serialize gossip packet (JSON, MessagePack, etc.)
byte[] packet = serialize(PACKET_GOSSIP_REQ_ID, root);
// Do gossiping with a live endpoint
if (liveEndpointCount > 0) {
sendGossipToRandomEndpoint(liveEndpoints, liveEndpointCount, packet, root);
}
// Do gossiping with a unreachable endpoint
if (unreachableEndpointCount > 0) {
// 10 nodes:
// 1 offline / (9 online + 1) = 0.10
// 3 offline / (7 online + 1) = 0.37
// 5 offline / (5 online + 1) = 0.83
// 9 offline / (1 online + 1) = 4.50
double ratio = (double) unreachableEndpointCount / ((double) liveEndpointCount + 1);
// Random number between 0.0 and 1.0
double random = rnd.nextDouble();
if (random < ratio) {
sendGossipToRandomEndpoint(unreachableEndpoints, unreachableEndpointCount, packet, root);
}
}
// For unit testing
return root;
} catch (Exception cause) {
logger.error("Unable to send gossip message to peer!", cause);
}
return null;
}
use of services.moleculer.transporter.tcp.NodeDescriptor in project moleculer-java by moleculer-java.
the class TcpTransporter method connect.
@Override
public void connect() {
try {
// Create reader and writer
disconnect();
reader = new TcpReader(this);
writer = new TcpWriter(this);
// Disable offline timeout when use host list
if (urls != null && urls.length > 0) {
offlineTimeout = 0;
nodes.clear();
for (String url : urls) {
int i = url.indexOf("://");
if (i > -1 && i < url.length() - 4) {
url = url.substring(i + 3);
}
url = url.replace('/', ':');
String[] parts = url.split(":");
if (parts.length < 3) {
logger.warn("Invalid URL format (" + url + ")! Valid syntax is \"tcp://host:port/nodeID\" or \"host:port/nodeID\"!");
continue;
}
int port;
try {
port = Integer.parseInt(parts[1]);
} catch (Exception e) {
logger.warn("Invalid URL format (" + url + ")! Valid syntax is \"tcp://host:port/nodeID\" or \"host:port/nodeID\"!");
continue;
}
String sender = parts[2];
if (sender.equals(nodeID)) {
// TCP server's port (port in URL list)
this.port = port;
continue;
}
String host = parts[0];
nodes.put(sender, new NodeDescriptor(sender, useHostname, host, port));
}
} else if (offlineTimeout > 0 && offlineTimeout < 15) {
offlineTimeout = 15;
}
// Process basic properties (eg. "prefix")
heartbeatTimeout = 0;
heartbeatInterval = 0;
// Start TCP server
reader.connect();
currentPort = reader.getCurrentPort();
// Create descriptor of current node
Tree info = registry.getDescriptor();
info.put("port", currentPort);
info.put("seq", "0");
cachedDescriptor = new NodeDescriptor(nodeID, useHostname, true, info);
// Start data writer (TCP client)
writer.connect();
// TCP + UDP mode ("zero config")
if (urls == null || urls.length == 0) {
locator = new UDPLocator(nodeID, this, scheduler);
locator.connect();
}
// Start gossiper
gossiperTimer = scheduler.scheduleWithFixedDelay(this::sendGossipRequest, gossipPeriod, gossipPeriod, TimeUnit.SECONDS);
// Start timeout checker's timer
if (checkTimeoutTimer == null && offlineTimeout > 0) {
int period = Math.max(offlineTimeout / 3, 10);
checkTimeoutTimer = scheduler.scheduleAtFixedRate(this::checkTimeouts, period, period, TimeUnit.SECONDS);
}
// Ok, transporter started
logger.info("Message receiver started on tcp://" + getHostName() + ':' + currentPort + ".");
} catch (Exception cause) {
String msg = cause.getMessage();
if (msg == null || msg.isEmpty()) {
msg = "Unable to start TCP transporter!";
} else if (!msg.endsWith("!") && !msg.endsWith(".")) {
msg += "!";
}
logger.warn(msg);
reconnect();
}
}
use of services.moleculer.transporter.tcp.NodeDescriptor in project moleculer-java by moleculer-java.
the class TcpTransporter method processGossipResponse.
// --- GOSSIP RESPONSE MESSAGE RECEIVED ---
protected void processGossipResponse(Tree data) throws Exception {
// Debug
if (debug) {
String sender = data.get("sender", (String) null);
logger.info("Gossip response received from \"" + sender + "\" node:\r\n" + data);
}
// Online / offline nodes in responnse
Tree online = data.get("online");
Tree offline = data.get("offline");
// Process "online" block
if (online != null) {
for (Tree row : online) {
// Get nodeID
String nodeID = row.getName();
if (this.nodeID.equals(nodeID)) {
continue;
}
int size = row.size();
if (!row.isEnumeration() || size < 1 || size > 3) {
logger.warn("Invalid \"offline\" block: " + row);
continue;
}
// Get parameters from input
Tree info = null;
long cpuSeq = 0;
int cpu = 0;
if (row.size() == 1) {
info = row.get(0);
} else if (row.size() == 2) {
cpuSeq = row.get(0).asLong();
cpu = row.get(1).asInteger();
} else if (row.size() == 3) {
info = row.get(0);
cpuSeq = row.get(1).asLong();
cpu = row.get(2).asInteger();
} else {
logger.warn("Invalid \"online\" block: " + row.toString(false));
continue;
}
if (info != null) {
// Update "info" block,
// send updated, connected or reconnected event
updateNodeInfo(nodeID, info);
}
if (cpuSeq > 0) {
// We update our CPU info
NodeDescriptor node = nodes.get(nodeID);
if (node != null) {
node.writeLock.lock();
try {
node.updateCpu(cpuSeq, cpu);
} finally {
node.writeLock.unlock();
}
}
}
}
}
// Process "offline" block
if (offline != null) {
for (Tree row : offline) {
String nodeID = row.getName();
NodeDescriptor node;
if (this.nodeID.equals(nodeID)) {
long seq = row.asLong();
node = getDescriptor();
node.writeLock.lock();
try {
long newSeq = Math.max(node.seq, seq + 1);
if (node.seq < newSeq) {
node.seq = newSeq;
node.info.put("seq", newSeq);
}
} finally {
node.writeLock.unlock();
}
continue;
}
node = nodes.get(nodeID);
if (node == null) {
return;
}
if (!row.isPrimitive()) {
logger.warn("Invalid \"offline\" block: " + row);
continue;
}
// Get parameters from input
boolean disconnected = false;
node.writeLock.lock();
try {
long seq = row.asLong();
if (node.seq < seq && node.markAsOffline(seq)) {
// We know it is online, so we change it to offline
// Remove remote actions and listeners
registry.removeActions(node.nodeID);
eventbus.removeListeners(node.nodeID);
writer.close(node.nodeID);
disconnected = true;
}
} finally {
node.writeLock.unlock();
}
if (node != null && disconnected) {
// Notify listeners (not unexpected disconnection)
logger.info("Node \"" + node.nodeID + "\" disconnected.");
broadcastNodeDisconnected(node.info, false);
}
}
}
}
use of services.moleculer.transporter.tcp.NodeDescriptor in project moleculer-java by moleculer-java.
the class TcpTransporter method registerAsNewNode.
protected void registerAsNewNode(String sender, String host, int port) {
// Check node
if (sender == null || sender.isEmpty()) {
throw new IllegalArgumentException("Empty sender field!");
}
if (host == null || host.isEmpty()) {
throw new IllegalArgumentException("Empty host field!");
}
if (port < 1) {
throw new IllegalArgumentException("Invalid port value (" + port + ")!");
}
if (nodeID.equalsIgnoreCase(sender)) {
return;
}
NodeDescriptor node = nodes.get(sender);
if (node == null) {
// Add as new, offline node
try {
nodes.put(sender, new NodeDescriptor(sender, useHostname, host, port));
logger.info("Node \"" + sender + "\" registered.");
} catch (Exception cause) {
logger.warn("Unable to register new node!", cause);
}
} else {
node.writeLock.lock();
try {
// Host or port number changed
if (!node.host.equalsIgnoreCase(host) || node.port != port) {
node.host = host;
node.port = port;
if (node.info != null) {
if (useHostname) {
node.info.put("hostname", host);
} else {
Tree ipList = node.info.get("ipList");
if (ipList == null) {
ipList = node.info.putList("ipList");
} else {
ipList.clear();
}
ipList.add(host);
}
node.info.put("port", port);
}
writer.close(sender);
}
} finally {
node.writeLock.unlock();
}
}
}
use of services.moleculer.transporter.tcp.NodeDescriptor in project moleculer-java by moleculer-java.
the class Transporter method checkTimeouts.
// --- TIMEOUT PROCESS ---
protected void checkTimeouts() {
// Compute timeout limits
long now = System.currentTimeMillis();
Iterator<NodeDescriptor> i;
NodeDescriptor node;
// Check offline timeout
if (offlineTimeout > 0) {
i = nodes.values().iterator();
long offlineTimeoutMillis = offlineTimeout * 1000L;
while (i.hasNext()) {
node = i.next();
node.readLock.lock();
try {
if (node.offlineSince > 0 && now - node.offlineSince > offlineTimeoutMillis) {
// Remove node from Map
i.remove();
logger.info("Node \"" + nodeID + "\" is no longer registered because it was inactive for " + offlineTimeout + " seconds.");
}
} finally {
node.readLock.unlock();
}
}
}
// Check heartbeat timeout
if (heartbeatTimeout > 0) {
long heartbeatTimeoutMillis = heartbeatTimeout * 1000L;
i = nodes.values().iterator();
LinkedList<NodeDescriptor> disconnectedNodes = new LinkedList<>();
while (i.hasNext()) {
node = i.next();
node.writeLock.lock();
try {
if (node.cpuWhen > 0 && now - node.cpuWhen > heartbeatTimeoutMillis && node.markAsOffline()) {
// Remove services and listeners
registry.removeActions(node.nodeID);
eventbus.removeListeners(node.nodeID);
disconnectedNodes.add(node);
}
} finally {
node.writeLock.unlock();
}
}
i = disconnectedNodes.iterator();
while (i.hasNext()) {
node = i.next();
// Notify listeners
logger.info("Node \"" + node.nodeID + "\" is no longer available because it hasn't submitted heartbeat signal for " + heartbeatTimeout + " seconds.");
broadcastNodeDisconnected(node.info, true);
}
}
}
Aggregations