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)