/// <summary> /// Processes a heartbeat message /// </summary> /// <param name="reg">The registry received from the remote node</param> private void ProcessHearbeat(GossipRegistry reg) { try { // Merge received registry with local lock (registryLock) { foreach (var node in reg.GossipList) { //If remote node has node suspended, do not add or update it (stale data) if (node.Status != GossipStatus.Suspended) { // Get matching node from registry var localRegNode = registry.GossipList.FirstOrDefault(n => n.NodeId.Equals(node.NodeId)); if (localRegNode == null) { // Add the node to the local registry registry.GossipList.Add(node); if (this.OnNodeAdded != null) { this.OnNodeAdded(this, node); } localRegNode = registry.GossipList.FirstOrDefault(n => n.NodeId.Equals(node.NodeId)); } // Update heartbeats to lowest values localRegNode.GossipHeartbeats = Math.Min(localRegNode.GossipHeartbeats, node.GossipHeartbeats); localRegNode.SuspectMatrix = node.SuspectMatrix; if (!localRegNode.SuspectMatrix.ContainsKey(localNode.NodeId)) { localRegNode.SuspectMatrix.Add(localNode.NodeId, false); } // Apply updates if version is newer if (node.UpdateVersion > localRegNode.UpdateVersion) { //Update node if (localRegNode.ListenerAddressList != node.ListenerAddressList) { localRegNode.ListenerAddressList = node.ListenerAddressList; } if (localRegNode.Partition != node.Partition) { localRegNode.Partition = node.Partition; } if (localRegNode.HostName != node.HostName) { localRegNode.HostName = node.HostName; } //Do not update status. Every node must determine status on it's own. //if (localRegNode.Status != node.Status) { localRegNode.Status = node.Status; } localRegNode.UpdateVersion = node.UpdateVersion; } } } } } catch (Exception ex) { //TODO: Log Error } }
/// <summary> /// Starts the cluster /// </summary> public void Start() { //Configure listener socket gossipListenerContext = new ZContext(); gossipListenerSocket = new ZSocket(gossipListenerContext, ZSocketType.ROUTER); gossipListenerSocket.IdentityString = localNode.NodeId.ToString(); foreach (string e in localNode.ListenerAddressList) { gossipListenerSocket.Bind(e); } //Configure listener thread listener = new Task(() => { var poll = ZPollItem.CreateReceiver(); ZMessage incomming; ZError error; while (true) { try { // Receive if (gossipListenerSocket.PollIn(poll, out incomming, out error, TimeSpan.FromMilliseconds(64))) { using (incomming) { messageQueue.Add(incomming[1].Read()); Debug.WriteLine("Received {0}", incomming[1].ReadString()); } } } catch (Exception ex) { //TODO: Log errors } if (listenerCancellation.Token.IsCancellationRequested) { //listenerSource.Token.ThrowIfCancellationRequested(); break; } } }, listenerCancellation.Token); queueProcessor = new Task(() => { while (true) { try { byte[] m = messageQueue.Take(queueCancellation.Token); //First 4 bytes indicates type of message byte[] buffer = new byte[4]; Array.Copy(m, buffer, 4); GossipMessageType msgType = (GossipMessageType)BitConverter.ToInt32(buffer, 0); //Read the rest of the message into a stream using (MemoryStream ms = new MemoryStream(m, 4, m.Length - 4)) { //Process message by type switch (msgType) { case GossipMessageType.Heartbeat: try { //Deserialize message GossipRegistry reg = Serializer.Deserialize<GossipRegistry>(ms); //Verify cluster key if (reg.ClusterKey.Equals(localNode.ClusterKey)) { ProcessHearbeat(reg); } else { //TODO: Log Bad Key } } catch (Exception ex) { //TODO: Log Error } break; case GossipMessageType.Join: try { //Deserialize message GossipNode joinUpdate = Serializer.Deserialize<GossipNode>(ms); //Verify cluster key if (joinUpdate.ClusterKey.Equals(localNode.ClusterKey)) { ProcessJoin(joinUpdate); } else { //TODO: Log Bad Key } } catch (Exception ex) { //TODO: Log Error } break; case GossipMessageType.Leave: try { //Deserialize message GossipNode leaveUpdate = Serializer.Deserialize<GossipNode>(ms); //Verify cluster key if (leaveUpdate.ClusterKey.Equals(localNode.ClusterKey)) { ProcessLeave(leaveUpdate); } else { //TODO: Log Bad Key } } catch (Exception ex) { //TODO: Log Error } break; } } } catch (Exception ex) { //TODO: Handle exception } if (queueCancellation.IsCancellationRequested) { break; } } }, queueCancellation.Token); //Start processor queueProcessor.Start(); //Start listener listener.Start(); //Check for seed node bool joinedCluster = false; foreach (var seedHost in seedAddressList) { //Attempt to join cluster through seed node string gossipSeedAddress = string.Format("tcp://{0}:{1}", seedHost.Address.ToString(), seedHost.Port); Console.WriteLine("Contacting seed node {0}", gossipSeedAddress); registry = new GossipRegistry() { ClusterKey = localNode.ClusterKey, GossipList = new List<GossipNode>() }; // Join cluster (cluster will add node to registry and return it back) DateTime joinRequestDate = DateTime.Now; List<byte> serializedJoinRequest = new List<byte>(); using (MemoryStream ms = new MemoryStream()) { //Serialize local node Serializer.Serialize(ms, localNode); ms.Position = 0; byte[] serialBuffer = new byte[ms.Length]; ms.Read(serialBuffer, 0, serialBuffer.Length); serializedJoinRequest.AddRange(BitConverter.GetBytes((int)GossipMessageType.Join)); serializedJoinRequest.AddRange(serialBuffer); //Send local node to seed using (var joinContext = new ZContext()) using (var joinSocket = new ZSocket(joinContext, ZSocketType.DEALER)) { joinSocket.Linger = System.TimeSpan.FromMilliseconds(50); // Bind joinSocket.Connect(gossipSeedAddress); //hbSocket.IdentityString = gossipListenerAddress; joinSocket.Send(new ZFrame(serializedJoinRequest.ToArray())); var joinReceiver = ZPollItem.CreateReceiver(); ZMessage joinMessage; ZError joinError; //joinSocket.PollIn(joinReceiver, out joinMessage, out joinError, TimeSpan.FromMilliseconds(100)); System.Threading.Thread.Sleep(1); } // While we haven't timed out while (DateTime.Now.Subtract(joinRequestDate).TotalMilliseconds < gossipJoinTimeout) { //Sleep 1 second and check registry to see if we heard back and we're added. System.Threading.Thread.Sleep(1000); if (registry.GossipList.Any(n => n.NodeId.Equals(localNode.NodeId))) { break; } } } if (registry.GossipList.Any(n => n.NodeId.Equals(localNode.NodeId))) { // We heard back from the cluster and we're a member! joinedCluster = true; Console.WriteLine("Successfully joined cluster. Starting Gossip."); break; } else { //Join timeout joinedCluster = false; Console.WriteLine("Timeout joining cluster. Exiting."); } } //If we didn't join the cluster through the seed node, start a new cluster if (!joinedCluster) { Console.WriteLine("Starting new cluster."); // Start new cluster registry = new GossipRegistry() { ClusterKey = localNode.ClusterKey, GossipList = new List<GossipNode>() }; registry.GossipList.Add(localNode); } //Start gossip timer t = new System.Timers.Timer(gossipInterval); t.Elapsed += GossipInterval_Elapsed; t.Enabled = true; t.Start(); }