From fc75f3fa22fdf690de9433ba7486c607e6ce1925 Mon Sep 17 00:00:00 2001 From: Derek S <44935661+Derek-R-S@users.noreply.github.com> Date: Tue, 6 Apr 2021 23:20:25 -0500 Subject: [PATCH] Added caching to load balancer server list --- .../LRM_LoadBalancer/Endpoint.cs | 56 ++++++------ .../LRM_LoadBalancer/Program.cs | 89 ++++++++++++------- .../LRM/Endpoint.cs | 3 + .../LRM/Program/Program.cs | 16 +++- 4 files changed, 106 insertions(+), 58 deletions(-) diff --git a/LoadBalancerProject-DONT-IMPORT-INTO-UNITY/LRM_LoadBalancer/Endpoint.cs b/LoadBalancerProject-DONT-IMPORT-INTO-UNITY/LRM_LoadBalancer/Endpoint.cs index 817d44c..a1ef00d 100644 --- a/LoadBalancerProject-DONT-IMPORT-INTO-UNITY/LRM_LoadBalancer/Endpoint.cs +++ b/LoadBalancerProject-DONT-IMPORT-INTO-UNITY/LRM_LoadBalancer/Endpoint.cs @@ -18,6 +18,8 @@ namespace LightReflectiveMirror.LoadBalancing [RestResource] public class Endpoint { + public static string cachedServerList = "[]"; + private LoadBalancerStats _stats { get => new() @@ -39,7 +41,7 @@ namespace LightReflectiveMirror.LoadBalancing public async Task ReceiveAuthKey(IHttpContext context) { var req = context.Request; - string receivedAuthKey = req.Headers["x-Auth"]; + string receivedAuthKey = req.Headers["Authorization"]; string endpointPort = req.Headers["x-EndpointPort"]; string gamePort = req.Headers["x-GamePort"]; string publicIP = req.Headers["x-PIP"]; @@ -69,6 +71,31 @@ namespace LightReflectiveMirror.LoadBalancing await context.Response.SendResponseAsync(HttpStatusCode.Forbidden); } + /// + /// Called on the load balancer when a relay node had a change in their servers. This recompiles the cached values. + /// + /// + /// + [RestRoute("Get", "/api/roomsupdated")] + public async Task ServerListUpdate(IHttpContext context) + { + // Dont allow unauthorizated access waste computing resources. + string auth = context.Request.Headers["Authorization"]; + + if(!string.IsNullOrEmpty(auth) && auth == Program.conf.AuthKey) + { + var relays = Program.instance.availableRelayServers.ToList(); + List masterList = new(); + + for(int i = 0; i < relays.Count; i++) + { + masterList.AddRange(await Program.instance.RequestServerListFromNode(relays[i].Key.Address, relays[i].Key.EndpointPort)); + } + + cachedServerList = JsonConvert.SerializeObject(masterList); + } + } + /// /// Hooks into from unity side, client will call this to /// find the least populated server to join @@ -102,13 +129,7 @@ namespace LightReflectiveMirror.LoadBalancing // if the string is still dummy then theres no servers if (lowest.Key.Address != "Dummy") { - // ping server to ensure its online. - var chosenServer = await Program.instance.ManualPingServer(lowest.Key.Address, lowest.Key.EndpointPort); - - if (chosenServer.HasValue) - await context.Response.SendResponseAsync(JsonConvert.SerializeObject(lowest.Key)); - else - await context.Response.SendResponseAsync(HttpStatusCode.BadGateway); + await context.Response.SendResponseAsync(JsonConvert.SerializeObject(lowest.Key)); } else { @@ -124,24 +145,7 @@ namespace LightReflectiveMirror.LoadBalancing [RestRoute("Get", "/api/masterlist/")] public async Task GetMasterServerList(IHttpContext context) { - var relays = Program.instance.availableRelayServers.ToList(); - List masterList = new(); - foreach (var relay in relays) - { - var serversOnRelay = await Program.instance.GetServerListFromIndividualRelay(relay.Key.Address, relay.Key.EndpointPort); - - if (serversOnRelay != null) - { - masterList.AddRange(serversOnRelay); - } - else { continue; } - } - // we have servers, send em! - if (masterList.Any()) - await context.Response.SendResponseAsync(JsonConvert.SerializeObject(masterList)); - // no servers or maybe no relays, fuck you - else - await context.Response.SendResponseAsync(HttpStatusCode.RangeNotSatisfiable); + await context.Response.SendResponseAsync(cachedServerList); } /// diff --git a/LoadBalancerProject-DONT-IMPORT-INTO-UNITY/LRM_LoadBalancer/Program.cs b/LoadBalancerProject-DONT-IMPORT-INTO-UNITY/LRM_LoadBalancer/Program.cs index 3202f47..2f639c8 100644 --- a/LoadBalancerProject-DONT-IMPORT-INTO-UNITY/LRM_LoadBalancer/Program.cs +++ b/LoadBalancerProject-DONT-IMPORT-INTO-UNITY/LRM_LoadBalancer/Program.cs @@ -12,8 +12,7 @@ namespace LightReflectiveMirror.LoadBalancing partial class Program { /// - /// Keeps track of all available relays. - /// Key is server address, value is CCU. + /// Keeps track of all the LRM nodes registered to the Load Balancer. /// public Dictionary availableRelayServers = new(); @@ -64,6 +63,14 @@ namespace LightReflectiveMirror.LoadBalancing } + /// + /// Called when a new server requested that we add them to our load balancer. + /// + /// + /// + /// + /// + /// public async Task AddServer(string serverIP, ushort port, ushort endpointPort, string publicIP) { var relayAddr = new RelayAddress { Port = port, EndpointPort = endpointPort, Address = publicIP, EndpointAddress = serverIP }; @@ -74,13 +81,22 @@ namespace LightReflectiveMirror.LoadBalancing return; } - var stats = await ManualPingServer(serverIP, endpointPort); + var stats = await RequestStatsFromNode(serverIP, endpointPort); if (stats.HasValue) + { + Logger.ForceLogMessage($"LRM Node Registered! {serverIP}:{port}", ConsoleColor.Green); availableRelayServers.Add(relayAddr, stats.Value); + } } - public async Task ManualPingServer(string serverIP, ushort port) + /// + /// Called when we want to get the server info from a server. + /// + /// + /// + /// + public async Task RequestStatsFromNode(string serverIP, ushort port) { using (WebClient wc = new WebClient()) { @@ -103,7 +119,38 @@ namespace LightReflectiveMirror.LoadBalancing } } - public async Task> GetServerListFromIndividualRelay(string serverIP, ushort port) + /// + /// Called when we want to check if a server is alive. + /// + /// + /// + /// + public async Task HealthCheckNode(string serverIP, ushort port) + { + using (WebClient wc = new WebClient()) + { + try + { + await wc.DownloadStringTaskAsync($"http://{serverIP}:{port}{API_PATH}"); + + // If it got to here, then the server is healthy! + return true; + } + catch (Exception e) + { + // Server failed to respond + return false; + } + } + } + + /// + /// Called when we want to get the list of rooms in a specific LRM node. + /// + /// + /// + /// + public async Task> RequestServerListFromNode(string serverIP, ushort port) { using (WebClient wc = new WebClient()) { @@ -111,6 +158,8 @@ namespace LightReflectiveMirror.LoadBalancing { string receivedStats = await wc.DownloadStringTaskAsync($"http://{serverIP}:{port}/api/servers"); var stats = JsonConvert.DeserializeObject>(receivedStats); + + // If they have no servers, it will return null as json for some reason. if (stats == null) return new List(); else @@ -124,6 +173,9 @@ namespace LightReflectiveMirror.LoadBalancing } } + /// + /// A thread constantly running and making sure LRM nodes are still healthy. + /// async void PingServers() { while (true) @@ -135,32 +187,9 @@ namespace LightReflectiveMirror.LoadBalancing for (int i = 0; i < keys.Count; i++) { - string url = $"http://{keys[i].EndpointAddress}:{keys[i].EndpointPort}{API_PATH}"; - - using (WebClient wc = new WebClient()) + if(!await HealthCheckNode(keys[i].EndpointAddress, keys[i].EndpointPort)) { - try - { - var serverStats = wc.DownloadString(url); - var deserializedData = JsonConvert.DeserializeObject(serverStats); - - Logger.WriteLogMessage("Server " + keys[i].Address + " still exists, keeping in collection."); - - // get current server list - deserializedData.serversConnectedToRelay = await GetServerListFromIndividualRelay(keys[i].Address, keys[i].Port); - - if (availableRelayServers.ContainsKey(keys[i])) - availableRelayServers[keys[i]] = deserializedData; - else - availableRelayServers.Add(keys[i], deserializedData); - - } - catch - { - // server doesnt exist anymore probably - Logger.WriteLogMessage("Server " + keys[i] + " does not exist anymore, removing", ConsoleColor.Red); - availableRelayServers.Remove(keys[i]); - } + Logger.ForceLogMessage($"Server {keys[i].Address}:{keys[i].Port} failed a health check, removing from load balancer.", ConsoleColor.Red); } } diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Endpoint.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Endpoint.cs index e96156e..9dc4e99 100644 --- a/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Endpoint.cs +++ b/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Endpoint.cs @@ -41,6 +41,9 @@ namespace LightReflectiveMirror.Endpoints { _cachedServerList = JsonConvert.SerializeObject(_rooms, Formatting.Indented); _cachedCompressedServerList = _cachedServerList.Compress(); + + if (Program.conf.UseLoadBalancer) + Program.instance.UpdateLoadbalancerServers(); } [RestRoute("Get", "/api/stats")] diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Program/Program.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Program/Program.cs index 270da65..695f86a 100644 --- a/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Program/Program.cs +++ b/ServerProject-DONT-IMPORT-INTO-UNITY/LRM/Program/Program.cs @@ -201,6 +201,19 @@ namespace LightReflectiveMirror } } + public async void UpdateLoadbalancerServers() + { + try + { + using (WebClient wc = new()) + { + wc.Headers.Add("Authorization", conf.LoadBalancerAuthKey); + await wc.DownloadStringTaskAsync($"http://{conf.LoadBalancerAddress}:{conf.LoadBalancerPort}/api/roomsupdated"); + } + } + catch {} // LLB might be down, ignore. + } + private async Task RegisterSelfToLoadBalancer() { Endpoint.lastPing = DateTime.Now; @@ -215,7 +228,7 @@ namespace LightReflectiveMirror string gamePort = 7777.ToString(); HttpWebRequest authReq = (HttpWebRequest)WebRequest.Create(uri); - authReq.Headers.Add("x-Auth", conf.LoadBalancerAuthKey); + authReq.Headers.Add("Authorization", conf.LoadBalancerAuthKey); authReq.Headers.Add("x-EndpointPort", endpointPort); authReq.Headers.Add("x-GamePort", gamePort); authReq.Headers.Add("x-PIP", publicIP); // Public IP @@ -230,7 +243,6 @@ namespace LightReflectiveMirror WriteLogMessage("Error registering - Load Balancer probably timed out.", ConsoleColor.Red); return false; } - } void CheckMethods(Type type)