Example #1
0
        /// <summary>Get the node that owns the specified hash code</summary>
        public CacheNode GetNodeForHash(int hashCode)
        {
            this.ringLock.EnterReadLock();

            try
            {
                if (this.SortedLocations == null || this.SortedLocations.Count == 0)
                {
                    throw new Exception("SortedLocations are null or empty");
                }

                // ---- n1 ----- n2 ----- n3 ----- n4 ----- n5
                // --------- h -------------------------------
                // 100  200 300  400 500  600 700  800 900  1000

                // For now simply iterate over the list until we find the bucket.
                // On the ring, the node to clockwise owns the angle between it and
                // the last node.  Above, n2 owns from 201 to 400, and therefore h.
                // We can surely speed this up by dividing and conquering.
                CacheNode firstNode = null;
                foreach (var kvp in this.SortedLocations)
                {
                    if (firstNode == null)
                    {
                        firstNode = kvp.Value;
                    }
                    if (kvp.Key >= hashCode)
                    {
                        return(kvp.Value);
                    }
                }

                // We wrapped around Int.MAX
                return(firstNode);
            }
            finally
            {
                this.ringLock.ExitReadLock();
            }
        }
Example #2
0
        /// <summary>Parse a line of the config file with a Node configuration</summary>
        public static CacheNode ParseNodeLine(string line)
        {
            string[] tokens = SplitLine(line);

            if (tokens.Length != 3)
            {
                throw new Exception("Invalid configuration line: " + line);
            }

            // # Node        host:port            MaxMem
            // Node            localhost:12346        24Mb

            string hostPortStr = tokens[1];
            string maxMemStr   = tokens[2];

            CacheNode node = new CacheNode();

            node.MaxNumBytes = ParseMaxNumBytes(maxMemStr);

            string[] hostPortTokens = hostPortStr.Split(':');

            if (hostPortTokens.Length != 2)
            {
                throw new Exception(
                          "Invalid configuration line (host:port): " + line);
            }

            node.HostName = hostPortTokens[0];
            int portNumber;

            if (!int.TryParse(hostPortTokens[1], out portNumber))
            {
                throw new Exception(
                          "Invalid configuration line (port): " + line);
            }
            node.PortNumber = portNumber;

            return(node);
        }
Example #3
0
        /// <summary>Adds a node to the ring</summary>
        /// <remarks>This is only called on the master.  Data nodes will
        /// simply reload everything when something changes</remarks>
        public void AddNode(CacheNode node)
        {
            this.ringLock.EnterWriteLock();
            try
            {
                // Make sure this node isn't already in the ring
                string nodeName = node.GetName();
                if (this.Nodes.ContainsKey(nodeName))
                {
                    throw new Exception("Already added node " + nodeName);
                }

                this.Nodes.Add(nodeName, node);

                DetermineNodeLocations();
                LookupEndPoints();
            }
            finally
            {
                this.ringLock.ExitWriteLock();
            }
        }
Example #4
0
        /// <summary>
        /// Parse the config file.
        /// </summary>
        public static CacheConfig Load(string fileName)
        {
            CacheConfig config = new CacheConfig();
            CacheRing   ring   = new CacheRing();

            config.Ring = ring; // Only gets populated for the master node

            int lineNum = 0;

            using (StreamReader sr = new StreamReader(fileName))
            {
                string line = null;
                while (true)
                {
                    line = sr.ReadLine();
                    if (line == null)
                    {
                        break;
                    }
                    lineNum++;
                    ConfigLine configLine = new ConfigLine();
                    configLine.Line = line;
                    config.ConfigLines.Add(lineNum, configLine);

                    line = line.Trim().ToLower();
                    if (line.Equals(String.Empty))
                    {
                        continue;
                    }
                    if (line.StartsWith("#"))
                    {
                        continue;
                    }

                    string[] tokens = SplitLine(line);

                    if (line.StartsWith("node"))
                    {
                        CacheNode node = ParseNodeLine(line);
                        node.Status = CacheNodeStatus.Up;
                        ring.AddNode(node);
                        configLine.IsNodeLine = true;
                    }
                    else if (line.StartsWith("listener"))
                    {
                        // # Listener        host        ip:port                IsMaster Yes|No
                        // Listener            localhost    127.0.0.1:12345        Yes

                        if (tokens.Length != 4)
                        {
                            throw new Exception("Invalid config line: " + line);
                        }

                        config.ListenerHostName = tokens[1];
                        string   ipPort       = tokens[2];
                        string[] ipPortTokens = ipPort.Split(':');
                        if (ipPortTokens.Length != 2)
                        {
                            throw new Exception("Invalid config line: " + line);
                        }
                        config.ListenerIP = ipPortTokens[0];
                        int lpn;
                        if (!int.TryParse(ipPortTokens[1], out lpn))
                        {
                            throw new Exception("Invalid config line: " + line);
                        }
                        config.ListenerPortNumber = lpn;
                        if (tokens[3].Equals("yes"))
                        {
                            config.IsMaster = true;
                        }
                        else if (tokens[3].Equals("no"))
                        {
                            config.IsMaster = false;
                        }
                        else
                        {
                            throw new Exception(
                                      "Invalid config line, IsMaster should be Yes or No: " + line);
                        }
                    }
                    else if (line.StartsWith("trace"))
                    {
                        // # Trace        On|Off        File
                        // Trace        On            C:\Loop\Logs\LoopCacheMaster.txt

                        if (tokens.Length != 3)
                        {
                            throw new Exception("Invalid config line: " + line);
                        }

                        if (tokens[1].Equals("on"))
                        {
                            config.IsTraceEnabled = true;
                        }
                        else if (tokens[1].Equals("off"))
                        {
                            config.IsTraceEnabled = false;
                        }
                        else
                        {
                            throw new Exception(
                                      "Invalid config line, Trace should be On of Off: " + line);
                        }

                        config.TraceFilePath = tokens[2];
                    }
                    else if (line.StartsWith("master"))
                    {
                        // # Master    host:port
                        // Master        localhost:12345

                        if (tokens.Length != 2)
                        {
                            throw new Exception(string.Format(
                                                    "Invalid config line, {0} tokens: {1}", tokens.Length, line));
                        }

                        string[] hostPortTokens = tokens[1].Split(':');
                        if (hostPortTokens.Length != 2)
                        {
                            throw new Exception("Invalid config line, host:port: " + line);
                        }

                        config.MasterHostName = hostPortTokens[0];
                        int mpn;
                        if (!int.TryParse(hostPortTokens[1], out mpn))
                        {
                            throw new Exception("Invalid config line, port number: " + line);
                        }
                        config.MasterPortNumber = mpn;
                    }
                }
            }

            return(config);
        }
Example #5
0
        /// <summary>Test basic ring functionality.</summary>
        /// <remarks>Throws an exception or returns false on failure</remarks>
        public static bool Test()
        {
            CacheRing ring = new CacheRing();

            CacheNode nodeA = new CacheNode();

            nodeA.HostName    = "localhost";
            nodeA.PortNumber  = 1;
            nodeA.MaxNumBytes = CacheConfig.ParseMaxNumBytes("48Mb");
            ring.Nodes.Add(nodeA.GetName(), nodeA);

            CacheNode nodeB = new CacheNode();

            nodeB.HostName    = "localhost";
            nodeB.PortNumber  = 2;
            nodeB.MaxNumBytes = CacheConfig.ParseMaxNumBytes("12Mb");
            ring.Nodes.Add(nodeB.GetName(), nodeB);

            CacheNode nodeC = new CacheNode();

            nodeC.HostName    = "localhost";
            nodeC.PortNumber  = 3;
            nodeC.MaxNumBytes = CacheConfig.ParseMaxNumBytes("64Mb");
            ring.Nodes.Add(nodeC.GetName(), nodeC);

            // Hard-code some locations so we can make sure objects get assigned
            // to the correct virtual node.

            ring.SortedLocations.Add(10, nodeA);
            nodeA.Locations.Add(10);
            ring.SortedLocations.Add(-10, nodeB);
            nodeB.Locations.Add(-10);
            ring.SortedLocations.Add(20, nodeC);
            nodeC.Locations.Add(20);
            ring.SortedLocations.Add(50, nodeA);
            nodeA.Locations.Add(50);
            ring.SortedLocations.Add(60, nodeC);
            nodeC.Locations.Add(60);

            var node = ring.GetNodeForHash(5);

            if (node != nodeA)
            {
                throw new Exception(string.Format
                                        ("Hash 5 should belong to nodeA, not {0}", node.GetName()));
            }

            node = ring.GetNodeForHash(int.MaxValue);
            if (node != nodeB)
            {
                throw new Exception(string.Format
                                        ("Hash Integer.MAX should belong to nodeB, not {0}", node.GetName()));
            }

            node = ring.GetNodeForHash(20);
            if (node != nodeC)
            {
                throw new Exception(string.Format
                                        ("Hash 20 should belong to nodeC, not {0}", node.GetName()));
            }

            node = ring.GetNodeForHash(25);
            if (node != nodeA)
            {
                throw new Exception(string.Format
                                        ("Hash 25 should belong to nodeA, not {0}", node.GetName()));
            }

            // Now get rid of those hard coded locations and let the algorithm decide
            ring.DetermineNodeLocations();

            // Make sure the master list and the nodes agree
            foreach (var n in ring.Nodes.Values)
            {
                foreach (var location in n.Locations)
                {
                    if (!ring.SortedLocations.ContainsKey(location) ||
                        ring.SortedLocations[location] != n)
                    {
                        throw new Exception(string.Format
                                                ("Location {0} in node {1} not found", location, node.GetName()));
                    }
                }
            }

            foreach (var loc in ring.SortedLocations)
            {
                string nodeName = loc.Value.GetName();
                if (!ring.Nodes.ContainsKey(nodeName))
                {
                    throw new Exception(string.Format
                                            ("ring.Nodes missing {0}", nodeName));
                }
                if (!loc.Value.Locations.Contains(loc.Key))
                {
                    throw new Exception(string.Format
                                            ("node {0} does not have {1}", nodeName, loc.Key));
                }
            }

            //Console.WriteLine(ring.GetTrace());

            // Now let's place a bunch of values and see how many of them change
            // when we make changes to the node configuration.

            //Console.WriteLine("About to place objects");

            // nodeName => List of hashes
            Dictionary <string, List <int> > map = new Dictionary <string, List <int> >();

            for (int i = 0; i < 100000; i++)
            {
                Guid       g        = Guid.NewGuid();
                int        hash     = CacheHelper.GetConsistentHashCode(g.ToString());
                CacheNode  n        = ring.GetNodeForHash(hash);
                string     nodeName = n.GetName();
                List <int> hashes;
                if (!map.TryGetValue(nodeName, out hashes))
                {
                    hashes        = new List <int>();
                    map[nodeName] = hashes;
                }
                hashes.Add(hash);
            }

            //foreach (var nodeName in map.Keys)
            //{
            //	Console.WriteLine("{0} has {1} hashes",
            //			nodeName, map[nodeName].Count);
            //}

            //Console.WriteLine("Modifying sizes and replacing objects");
            nodeC.MaxNumBytes = CacheConfig.ParseMaxNumBytes("48Mb");             // was 64Mb
            ring.PopulateNodes();
            int numChanged = 0;
            int numTotal   = 0;

            foreach (var nodeName in map.Keys)
            {
                foreach (int hash in map[nodeName])
                {
                    numTotal++;
                    CacheNode n = ring.GetNodeForHash(hash);
                    if (!(nodeName.Equals(n.GetName())))
                    {
                        numChanged++;
                    }
                }
            }

            if (numChanged >= numTotal)
            {
                throw new Exception("Number of changed hases >= total number of hashes");
            }

            // TODO - Caclulate an acceptable percentage

            //Console.WriteLine("{0} hashes changed which node they were assigned to",
            //		numChanged);

            return(true);
        }