{"id":1249,"date":"2021-10-20T13:01:43","date_gmt":"2021-10-20T13:01:43","guid":{"rendered":"https:\/\/globalgoodplay.com\/?p=1249"},"modified":"2023-06-23T09:23:37","modified_gmt":"2023-06-23T09:23:37","slug":"load-balancing-smartfoxserver-2x-with-haproxy-2","status":"publish","type":"post","link":"https:\/\/globalgoodplay.com\/?p=1249","title":{"rendered":"Load Balancing SmartFoxServer 2X with HAProxy"},"content":{"rendered":"<p>In this article we are going to explore several ways to use the open source HAProxy <strong>load balancer<\/strong> in conjunction with <strong>SFS2X<\/strong>, to increase the <strong>scalability<\/strong> and <strong>availability<\/strong> of a multiplayer project.<\/p>\n<p>We are going to show different configurations for <strong>TCP<\/strong> and <strong>Websocket<\/strong> connections and several ways to setup the system for common use cases.<\/p>\n<p>To get the most out of this tutorial we require a basic knowledge of what a Load Balancer is and how it works, and some familiarity with the basics of networking and the OSI Model.<\/p>\n<h2>\u00bb Introducing HAProxy<\/h2>\n<p>In the words of its creators, HAProxy is <em>a free, very fast and reliable solution offering high availability, load balancing, and proxying for TCP and HTTP-based applications.<\/em><\/p>\n<p>This software is particularly well known in the Linux community and it is a a popular choice for many large traffic websites. It can operate both at <strong>layer 4<\/strong> (TPC) and <strong>layer 7<\/strong> (HTTP) of the OSI Model so it\u2019s perfectly suitable to load balance a multiplayer service such as<strong> SmartFoxServer 2X<\/strong>.<\/p>\n<p>In the next sections we\u2019re going to install and setup <strong>HAProxy<\/strong> on a dedicated Linux machine to act as a Load Balancer for multiple SFS2X instances, supporting both TCP and Websocket clients.<\/p>\n<h2>\u00bb Round robin Load Balancing<\/h2>\n<p>In our first setup we aim at running a number of SFS2X instances behind a single HAProxy and let it balance the incoming client traffic. All users will point to the balancer\u2019s IP address (or domain) which it will take care of passing the connection to one of the SFS2X instances.<\/p>\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-2081\" src=\"https:\/\/glossingdress.com\/lib\/img\/all\/553\/4075cfa6b34f7d197665d31e8b0e9dd0df07ce9fbf4cd89e99b29667292dd3f7\/464cb765ab98cf64829c82b93a494b1212e2dfb7f1ee8595bf89f7b2b1669883.png\" sizes=\"(max-width: 1024px) 100vw, 1024px\" srcset=\"https:\/\/glossingdress.com\/lib\/img\/all\/553\/4075cfa6b34f7d197665d31e8b0e9dd0df07ce9fbf4cd89e99b29667292dd3f7\/464cb765ab98cf64829c82b93a494b1212e2dfb7f1ee8595bf89f7b2b1669883.png 1024w, https:\/\/smartfoxserver.com\/blog\/wp-content\/uploads\/2021\/10\/Schermata-2021-10-20-alle-12.30.31-300x293.png 300w, https:\/\/smartfoxserver.com\/blog\/wp-content\/uploads\/2021\/10\/Schermata-2021-10-20-alle-12.30.31-768x749.png 768w, https:\/\/smartfoxserver.com\/blog\/wp-content\/uploads\/2021\/10\/Schermata-2021-10-20-alle-12.30.31-624x609.png 624w, https:\/\/smartfoxserver.com\/blog\/wp-content\/uploads\/2021\/10\/Schermata-2021-10-20-alle-12.30.31.png 1322w\" alt=\"\" width=\"1024\" height=\"999\" \/><\/figure>\n<p>In particular this will be done using the simple <strong>Round Robin<\/strong> algorithm, that distributes the connections across all instances evenly.<\/p>\n<p>To get started we launched a new Ubuntu machine in AWS EC2 and <strong>installed HAProxy<\/strong> with this command:<\/p>\n<p class=\"console-block\">sudo apt install haproxy<\/p>\n<p>After a few seconds the load balancer should be up and running. We can verify it with this:<\/p>\n<p class=\"console-block\">service haproxy status<\/p>\n<p>Before we dive into the <strong>HAProxy configuration<\/strong> we also need to setup couple of SmartFoxServer 2X instances. We\u2019ll skip over this section as you should already be familiar with this simple process and assume they are already set up.<\/p>\n<p>For our example we assume the two SFS2X servers have private IP addresses of <strong>10.0.0.10<\/strong> and <strong>10.0.0.11<\/strong><\/p>\n<h2>\u00bb Configuring HAProxy<\/h2>\n<p>We can now proceed by editing the HAProxy\u2019s configuration found under <strong>\/etc\/haproxy\/haproxy.cfg <\/strong><\/p>\n<p>For example:<\/p>\n<p class=\"console-block\">sudo nano \/etc\/haproxy\/haproxy.cfg<\/p>\n<p><strong>HAProxy<\/strong> has a vast number of settings but is relatively simple to configure and the default haproxy.cfg file is pretty minimalistic. Essentially there are <strong>four main sections<\/strong> in the configuration, called:<\/p>\n<ul>\n<li><strong>globals<\/strong>: process-wide settings for performance and security<\/li>\n<li><strong>defaults<\/strong>: default values that you may or may not override in the next sections<\/li>\n<li><strong>frontend<\/strong>: defines the interface with the clients, such as listening ports<\/li>\n<li><strong>backend<\/strong>: describes the servers that are available for HAProxy to load balance<\/li>\n<\/ul>\n<p>While the <strong>globals<\/strong> and <strong>defaults<\/strong> section appear only once in the .cfg file, there can be multiple <strong>frontend<\/strong> and <strong>backend<\/strong> sections. For example we could bind multiple ports on the frontend for different services, such as TCP socket and Websocket. Similarly we can define multiple backed sections that map the two frontends to their respective servers.<\/p>\n<p>Let\u2019s take a look at the configuration we have used for this setup to clarify what described so far.<\/p>\n<h3>Global<\/h3>\n<pre class=\"wp-block-code\"><code>global\n        log \/dev\/log    local0\n        log \/dev\/log    local1 notice\n        chroot \/var\/lib\/haproxy\n        stats socket \/run\/haproxy\/admin.sock mode 660 level admin expose-fd listeners\n        stats timeout 30s\n        user haproxy\n        group haproxy\n        daemon\n        maxconn 50000\n\n        # Default SSL material locations\n        ca-base \/etc\/ssl\/certs\n        crt-base \/etc\/ssl\/private\n\n        # See: https:\/\/ssl-config.mozilla.org\/#server=haproxy&amp;server-version=2.0.3&amp;config=intermediate\n        ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-&gt;\n        ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256\n        ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets<\/code><\/pre>\n<p>What you see here is 99% the standard settings from the default config file, we just added the <strong>maxconn<\/strong> parameter to allows a high number of incoming connections.<\/p>\n<p>For the moment we can ignore the rest of the settings and move on to the next section.<\/p>\n<h3>Defaults<\/h3>\n<pre class=\"wp-block-code\"><code>defaults\n        log     global\n        mode    tcp\n        timeout connect 5000\n        timeout client  50000\n        timeout server  50000\n        timeout tunnel  180s<\/code><\/pre>\n<p>The <strong>mode tcp<\/strong> command tells HAProxy that by default incoming connections should be treated as <strong>layer-4 TCP connections<\/strong>, rather than layer-7 HTTP ones. We\u2019ll return on this topic in a moment.<\/p>\n<p>The various <strong>timeout<\/strong> commands control the client-to-balancer and balancer-to-server timeouts and can be changed as preferred. The last <strong>timeout tunnel <\/strong>is specific for Websocket connections, once again regulating the length of an idle connection.<\/p>\n<p>Please note that unless otherwise specified, values are interpreted as milliseconds. In alternative you can add an \u201c<strong>s<\/strong>\u201d for seconds, \u201c<strong>m<\/strong>\u201d for minutes and \u201c<strong>h<\/strong>\u201d for hours.<\/p>\n<h3>Frontend<\/h3>\n<p>In this section of the <strong>.cfg file<\/strong> we can define two different <strong>frontends<\/strong>: one for the default <strong>TCP port 9933<\/strong> and one for the <strong>Websocket traffic, on port 8080<\/strong>.<\/p>\n<pre class=\"wp-block-code\"><code>frontend sfs-socket\n        bind *:9933\n        default_backend servers-socket\n\nfrontend sfs-websocket\n        mode http\n        bind *:8080\n        default_backend servers-websocket<\/code><\/pre>\n<p>The first thing to note is that we do not define a <strong>mode<\/strong> in the <em>sfs-socket<\/em> frontend as we have already declared <strong>mode tcp<\/strong> in the previous <strong>default<\/strong> section.<\/p>\n<p>However we do declare <strong>mode http<\/strong> in the <em>sfs-websocket<\/em> frontend declaration to override the defaults.<\/p>\n<p>Finally the <strong>default_backend<\/strong> command is referring to the <strong>backend<\/strong> blocks that we\u2019re about to define.<\/p>\n<h3>Backend<\/h3>\n<p>Here we declare the two referenced backed items:<\/p>\n<pre class=\"wp-block-code\"><code>backend servers-socket\n        balance roundrobin\n        server sfs1 10.0.0.10:9933\n        server sfs2 10.0.0.11:9933\n\nbackend servers-websocket\n        mode http\n        balance roundrobin\n        option  forwardfor\n        server sfs1 10.0.0.10:8080 check fall 3 rise 2\n        server sfs2 10.0.0.11:8080 check fall 3 rise 2<\/code><\/pre>\n<p>The <strong>servers-socket<\/strong> block defines the targets of TCP load balancing, using the standard SFS2X TCP port value (9933).<\/p>\n<p><strong>Please Note<\/strong>: when defining the server endpoints we always use the <strong>private IP addresses<\/strong> of these machines, as load balancer and relative nodes must communicate in a <strong>fast, low-latency<\/strong> private network.<\/p>\n<p>The <strong>server-websocket<\/strong> block is where we define the Websocket endpoints, using the default 8080 port.<\/p>\n<p>The <strong>option forwardfor<\/strong> directive tells HAProxy to pass the <strong>original client IP address<\/strong> to the endpoint by adding an <strong>X-Forwarded-For<\/strong> (XFF) entry to the HTTP header file. This in turn is supported by SmartFoxServer by enabling it in the <strong>AdminTool<\/strong> &gt; <strong>Server Configurator<\/strong> &gt; <strong>Web Server<\/strong><\/p>\n<p>Finally the <strong>check fall 3 rise 2<\/strong> parameters in the servers definition activates the Load Balancer\u2019s health check system, specifying how many checks are required to determine whether a server is active or not.<\/p>\n<ul>\n<li><strong>fall 3<\/strong>: means that after 3 consecutive failed checks the server is excluded from the load balancing pool<\/li>\n<li><strong>rise 2<\/strong>: indicates that after 2 consecutive successful checks the server is added to the load balancing pool<\/li>\n<\/ul>\n<h2>\u00bb Caveats<\/h2>\n<h3>Client IPs in TCP mode<\/h3>\n<p>When running in <strong>TCP mode<\/strong> there is no way for HAProxy to tell SmartFoxServer what is the original IP address of the client, therefore all users connecting via TCP will appear to have the IP address of the Load Balancer.<\/p>\n<p>While <strong>HAProxy<\/strong> knows the original IP of the sender it has no clue about the content of the TCP packets it is receiving and therefore there is no way to inject that information somewhere in the data stream.<\/p>\n<p>This is only possible when working in <strong>HTTP mode<\/strong>.<\/p>\n<h3>Other load balancing algorithms<\/h3>\n<p><strong>Round-robin<\/strong> is one of many possible load balancing systems that can be used with HAProxy. Depending on the <strong>mode<\/strong> in use (tcp vs http) it is possible to choose from a wide list of algorithms.<\/p>\n<p>You can learn more about what is available in the HAProxy documentation.<\/p>\n<h3>SSL Certificates<\/h3>\n<p>If you need to activate <strong>HTTPS<\/strong> for Websocket or TCP encryption you will need to perform extra configuration steps. In particular you will need to deploy your SSL certificate on the Load Balancer, since it is the entry point for all of your clients.<\/p>\n<p>It is beyond the scope of this tutorial to walk you through the HAProxy SSL setup process, but you can read all the details in the excellent documentation provided by on their website.<\/p>\n<h3>What about UDP?<\/h3>\n<p>HAProxy does not support UDP load balancing, so the solution presented here would not be work with an SFS2X project that requires it.<\/p>\n<p>Possible alternatives could be:<\/p>\n<ul>\n<li>IPVS a Linux-only, kernel-based load balancing tool<\/li>\n<li>Nginx another popular proxy\/load balancer\/web server<\/li>\n<\/ul>\n<h3>Single point of failure<\/h3>\n<p>As you have probably realized if all the client traffic goes through one Load Balancer this will become a <strong>single point of failure<\/strong>. How can we prevent a service blackout if the HAProxy becomes unavailable?<\/p>\n<p>Typically we would need to run an <strong>active HAProxy<\/strong> and one or more <strong>passive replicas<\/strong> (i.e. with exactly the same configuration) that can take over whenever it is necessary.<\/p>\n<p>Also these servers would need to be behind a <strong>Virtual IP<\/strong> address which can be pointed to a different machine when necessary, making the transition from a failed Load Balancer to an active one fully transparent.<\/p>\n<p>If you\u2019re interested in learning more about this technique you can find the details in this article by Oracle.<\/p>\n<h2>\u00bb Backup servers<\/h2>\n<p>An interesting feature in <strong>HAProxy<\/strong> is that of <strong>backup<\/strong> <strong>servers<\/strong>, which are instances defined in the <strong>backend<\/strong> section of the config that are not added to the load balancing pool until it becomes empty.<\/p>\n<p>Let\u2019s go back to our websocket setup for instance:<\/p>\n<pre class=\"wp-block-code\"><code>backend servers-websocket\n        mode http\n        balance roundrobin\n        option  forwardfor\n        server sfs1 172.31.21.251:8080 check fall 3 rise 2\n        server sfs2 172.31.24.55:8080 backup check fall 3 rise 2<\/code><\/pre>\n<p>Here we have slightly modified the configuration by adding a <strong>backup<\/strong> keyword to the <strong>sfs2<\/strong> instance (last line). What does it do?<\/p>\n<p>By marking this server as <strong>backup<\/strong> the Load Balancer will no longer distribute clients among the two instances but rather keep sending users to <strong>sfs1<\/strong> until it becomes unavailable.<\/p>\n<p>When this happens the <strong>sfs2 instance becomes active<\/strong> and stays like that until one ore more non-backup servers are added to the pool. For example when <strong>sfs1<\/strong> is restarted and becomes functional again.<\/p>\n<p>This simple option can be useful for different scenarios, such as running a <strong>large Lobby server<\/strong> acting as the entry point for users, who can then be sent to other servers to play games. The single Lobby server could be run behind an HAProxy and with a backup Lobby ready to take over when necessary.<\/p>\n<p>Here\u2019s an hypothetical architecture for a cluster that combines what we just described with the previous setup:<\/p>\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-2121\" src=\"https:\/\/glossingdress.com\/lib\/img\/all\/553\/4075cfa6b34f7d197665d31e8b0e9dd0df07ce9fbf4cd89e99b29667292dd3f7\/bbb7b371b200f24f00618b4b4dd3072b023f746a3863370fad5d76284cb26c7d.png\" sizes=\"(max-width: 1024px) 100vw, 1024px\" srcset=\"https:\/\/glossingdress.com\/lib\/img\/all\/553\/4075cfa6b34f7d197665d31e8b0e9dd0df07ce9fbf4cd89e99b29667292dd3f7\/bbb7b371b200f24f00618b4b4dd3072b023f746a3863370fad5d76284cb26c7d.png 1024w, https:\/\/smartfoxserver.com\/blog\/wp-content\/uploads\/2021\/10\/Schermata-2021-10-20-alle-17.31.02-300x200.png 300w, https:\/\/smartfoxserver.com\/blog\/wp-content\/uploads\/2021\/10\/Schermata-2021-10-20-alle-17.31.02-768x513.png 768w, https:\/\/smartfoxserver.com\/blog\/wp-content\/uploads\/2021\/10\/Schermata-2021-10-20-alle-17.31.02-1536x1026.png 1536w, https:\/\/smartfoxserver.com\/blog\/wp-content\/uploads\/2021\/10\/Schermata-2021-10-20-alle-17.31.02-624x417.png 624w, https:\/\/smartfoxserver.com\/blog\/wp-content\/uploads\/2021\/10\/Schermata-2021-10-20-alle-17.31.02.png 2012w\" alt=\"\" width=\"1024\" height=\"684\" \/><\/figure>\n<p>We now have a <strong>Lobby Balancer<\/strong> as the main entry point for our application: here users will login, manage their profile, search for other users, create and manage their Buddy Lists and chat with them.<\/p>\n<p>The <strong>Game Balancer<\/strong> instead will direct players to different game servers where they can be matched with other users and play.<\/p>\n<p>Finally we have a <strong>main database<\/strong>, shared among all servers, that can be user to track active users, access their state, game statistics, leaderboards, buddy lists and more.<\/p>\n<h2>\u00bb Wrapping up<\/h2>\n<p>The topic of high availability and scalability in clusters is a huge subject and we\u2019ve barely scratched the surface. We may be returning on this topic in the future with more articles but, for now, we hope to have provided enough concepts to get you started experimenting.<\/p>\n<p>As usual, if you have any comments, questions or doubts let us know via our SmartFoxServer support forum.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this article we are going to explore several ways to use the open source HAProxy load balancer in conjunction with SFS2X, to increase the scalability and availability of a multiplayer project. We are going to show different configurations for TCP and Websocket connections and several ways to setup the system for common use cases.<\/p>\n","protected":false},"author":1,"featured_media":1250,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6],"tags":[],"class_list":["post-1249","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog"],"_links":{"self":[{"href":"https:\/\/globalgoodplay.com\/index.php?rest_route=\/wp\/v2\/posts\/1249"}],"collection":[{"href":"https:\/\/globalgoodplay.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/globalgoodplay.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/globalgoodplay.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/globalgoodplay.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1249"}],"version-history":[{"count":2,"href":"https:\/\/globalgoodplay.com\/index.php?rest_route=\/wp\/v2\/posts\/1249\/revisions"}],"predecessor-version":[{"id":1304,"href":"https:\/\/globalgoodplay.com\/index.php?rest_route=\/wp\/v2\/posts\/1249\/revisions\/1304"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/globalgoodplay.com\/index.php?rest_route=\/wp\/v2\/media\/1250"}],"wp:attachment":[{"href":"https:\/\/globalgoodplay.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1249"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/globalgoodplay.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1249"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/globalgoodplay.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1249"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}