This article is licensed under a “CC BY 4.0” license. You are welcome to reprint or modify this article, but the source must be noted. 4.0 International (CC BY 4.0)

By Su Yang

Creation time: May 14, 2019 statistical word count: 6024 words reading time: 13 minutes to read this article links: soulteary.com/2019/05/14/…


Simple strategies make front-end resources highly available

A few days ago, a friend asked me about the front-end resource high availability solution I used at my former company. High availability of resources sounds like a “part of the job” for backend and operation students. But the high availability of front-end resources is not that simple. In the current complex network environment, do you expect users to refresh a few more times, or do you expect users to switch to 4G Wi-Fi and get lucky? With the cost of customer acquisition so high today, it is unwise to abandon users.

I have not written about the front end for a long time, so I decided to talk about it here. I hope I can help companies and teams in the start-up stage.

Before we get into the technical details, let’s talk about what front-end high availability is.

What does high resource availability have to do with the front end?

The demand of high availability of front-end resources should be strange to the students of “Dache”.

Because for large companies, there are a lot of redundant cloud hosting resources available for business teams, and there will be a certain scale of operations and maintenance team. When the monitoring system finds that online resources are unavailable, the system can automatically switch the problem resources to backup resources according to the policy. For those services that cannot be automatically switched over, some o&M students on duty will manually switch over the services in the first time to ensure high availability of services.

Smaller startups, however, are not so lucky. Resources are relatively tight, and there is even no perfect monitoring measures, let alone a relatively complete operation team.

One might think that throwing a static resource on a CDN is done for good. However, in the real world, the network environment is very complex, and the availability and access quality of the same host are different in different lines, different regions and different time periods. Therefore, using CDN is not a silver bullet to solve this problem, but using multiple CDN at the same time may be a more common solution at the current stage.

For example, by default, users in different regions access the website through different lines. If one of the lines is faulty, some users will not be able to access the services provided by the website.

At this point, we usually use the method of switching the request resource server to solve the problem, such as the following.

When a CDN/service line is out of order, we can change the domain name to solve the problem of resource access, but don’t forget one important thing:

It takes time for a domain name to take effect, and it takes a long time for a domain name to take effect in multiple regions. During this time window, your service quality will continue to suffer.

At this time, the page has been pushed to the user side, and the resource is unavailable. The user needs to refresh before it can request a new resource address, and this is on the premise that DNS can take effect. We know that many popular application clients in order to optimize the performance, Have a very long expiration date for resources (even pages), and it’s safe to say that this is not a very efficient solution.

So, if you’re going to do something like this, you have to make sure that the following four conditions are in place:

  • Your monitoring system found the problem and automatically switched resources.
  • The person in charge of your business, found the problem and manually switched resources.
  • You successfully switch resources and DNS takes effect quickly (network layer, client layer).
  • Your users refresh the page appropriately after you switch resources and the DNS takes effect, rather than just leaving.

Doesn’t that sound magical?

So is there a simple and reliable solution to this problem?

Yes, resources are automatically switched at the front end.

Project introduction

By monitoring resource loading errors in the front-end environment and automatically loading resources in other locations according to certain policies, resources on the front-end (user side) can be automatically switched over to ensure high availability of front-end resources and reduce service unavailability and user loss caused by front-end resource loading failures.

Environmental simulation

For a more intuitive demonstration of how the scheme works, HERE I use Docker to do a simulation of a common scenario.

Simulate multiple networks

Let’s start by creating a docker-compose. Yml that contains the following:

version: '3'

services:

  web:
    image: ${NGX_IMAGE}
4    expose:
      - 80
    networks:
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.frontend.rule=Host:${MAIN_HOST}"
      - "traefik.frontend.entryPoints=${SUPPORT_PROTOCOL}"
    volumes:
      - ./public/${MAIN_HOST}:/usr/share/nginx/html
    extra_hosts:
      - "${MAIN_HOST}: 127.0.0.1"

  cdn1:
    image: ${NGX_IMAGE}
    expose:
      - 80
    networks:
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.frontend.rule=Host:${CDN_HOST1}"
      - "traefik.frontend.entryPoints=${SUPPORT_PROTOCOL}"
      - "traefik.frontend.headers.customResponseHeaders=Access-Control-Allow-Origin:*"
    volumes:
      - ./public/${CDN_HOST1}:/usr/share/nginx/html
    extra_hosts:
      - "${CDN_HOST1}: 127.0.0.1"

  cdn2:
    image: ${NGX_IMAGE}
    expose:
      - 80
    networks:
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.frontend.rule=Host:${CDN_HOST2}"
      - "traefik.frontend.entryPoints=${SUPPORT_PROTOCOL}"
      - "traefik.frontend.headers.customResponseHeaders=Access-Control-Allow-Origin:*"
    volumes:
      - ./public/${CDN_HOST2}:/usr/share/nginx/html
    extra_hosts:
      - "${CDN_HOST2}: 127.0.0.1"

networks:
  traefik:
    external: true
Copy the code

As you can see, the choreography file defines an application website and two CDN services, to be closer to the real world. One CDN is the same as the root domain name of the application website, and the other takes a completely different domain name, such as the following.

# Default image usedNGX_IMAGE = nginx: 1.15.8 - alpine# Protocol that supports access
SUPPORT_PROTOCOL=https,http

# Domain name of the primary site
MAIN_HOST=demo.lab.io
# Simulate CDN with same root domain name
CDN_HOST1=demo-cdn.lab.io
# Simulate CDN with different root domain name
CDN_HOST2=demo.cdn2.io
Copy the code

Compose up: docker-compose up: docker-compose up: docker-compose up: docker-compose up

Simulate a regular scenario

After we do docker-compose up, we’ll see that Docker automatically creates several directories for us.

./public ├── bass Exercises - ├─ bass Exercises - ├─ bass ExercisesCopy the code

We created the index. HTML file in the demo.lab. IO directory as the application entry.

<! DOCTYPE html> <html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="assets/app.js"></script>
</head>
<body>

</body>
</html>
Copy the code

Then in the/demo. Lab. IO/public/assets/app. Js, create a script file, write something, simulation resources are loaded.

document.addEventListener('DOMContentLoaded'.function () {
    var p = document.createElement('p');
    p.innerText = 'script excute success.';
    document.body.appendChild(p);
});
Copy the code

When we visit http://demo.lab.io/index.html, not surprisingly, you will see output by the script script excute success. The content.

We will. / public/demo. Lab. IO/assets/app. Js copy to the/public/demo – CDN. Lab. IO/assets/app. Js and the/public/demo cdn2. IO/assets/app. Js To simulate the scenario where resources are distributed to a CDN.

Simplest technical implementation

First, change the requested resource address to the “CDN” address and verify whether the “CDN” service is available.

<! DOCTYPE html> <html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="//demo-cdn.lab.io/assets/app.js"></script>
</head>
<body>

</body>
</html>
Copy the code

Then delete the script./public/ demo-cdN.lab. IO /assets/app.js to simulate the CDN resource failure scenario.

If your browser doesn’t have weird caching behavior, you’ll get a blank page with an error message:

default.html:8 GET http://demo-cdn.lab.io/assets/app.js 404 (Not Found)
Copy the code

If we encounter a domain name resolution error, we will get another error message:

GET http://demo-cdn.lab.io/assets/app.js net::ERR_NAME_NOT_RESOLVED
Copy the code

At this point, we can make some changes in the page to enable it to switch to another CDN resource if the resource loading error, such as:

<! DOCTYPE html> <html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
        function loadOthers(resource) {
            var script = document.createElement('script');
            script.src = resource.src.replace('demo-cdn.lab.io'.'demo.cdn2.io');
            document.head.appendChild(script);
        }
    </script>
    <script src="//demo-cdn.lab.io/assets/app.js" onerror="loadOthers(this)"></script>
</head>
<body>

</body>
</html>
Copy the code

Open the address again, you will find that the page is normal again.

The advanced version

In the above scenario, we simulate the way that the front-end automatically switches resources in the conventional scenario.

Next, let’s do some small optimizations to enable script loading to support more resource addresses for higher availability.

<! DOCTYPE html> <html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
        function loadResource(links, fnSuccess, fnError) {
            var script = document.createElement('script');
            script.onerror = function () {
                document.head.removeChild(script);
                fnError();
            };
            script.onload = fnSuccess
            script.src = links.shift();
            document.head.appendChild(script);
        }

        function autoSwitch(resourceList) {
            var resource = resourceList.shift();
            loadResource([resource], function (success) {
                console.log('loaded');
            }, function (err) {
                console.log('load error')
                autoSwitch(resourceList);
            });
        }
    </script>
</head>
<body>
    <script>
        var resourceList = [
            'http://demo-cdn.lab.io/assets/app.js'.'http://demo.cdn2.io/assets/app.js'.'assets/app.js',]; autoSwitch(resourceList); </script> </body> </html>Copy the code

In the above implementation, we made resource loading more general, added a successful and failed load callback, and made an additional function to automatically switch resources, and left page script resource loading to the script.

This solution solves most scenarios, but what if your resources are dependent on each other?

Used in conjunction with resource loaders

Using the AMD module specification as an example, let’s talk about using automatic resource switching in conjunction with RequireJS.

<! DOCTYPE html> <html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>

    <script src="Assets/the require - v2.3.6. Min. Js." "></script>

    <script>
        function autoSwitch(resourceList) {
            var resource = resourceList.shift();
            requirejs([resource], function (success) {
                console.log('loaded');
            }, function (err) {
                console.log('load error')
                autoSwitch(resourceList);
            });
        }
    </script>
</head>
<body>
    <script>
        var resourceList = [
            'http://demo-cdn.lab.io/assets/app.js'.'http://demo.cdn2.io/assets/app.js'.'assets/app.js',]; autoSwitch(resourceList); </script> </body> </html>Copy the code

Introduce RequireJS to your page, and then replace the loadResource method with the Requirejs method, and it doesn’t seem to make a difference.

However, you can configure requirejs.config so that the resource is downloaded and initialized first. Here are two practical examples:

Requirejs.config ({map: {// this is a hack. Refer to the official API documentation for detailsThe '*': { 'http://demo.cdn2.io/assets/app.js': 'lodash'}}});Copy the code
Requirejs.config ({shim:{// or so'http://demo.cdn2.io/assets/app.js':{
            deps:['vue']}}});Copy the code

Of course, you can modify the autoSwitch function to dynamically maintain dependencies yourself.

The pit of other

At this point, resource autoloading is almost finished, but there are actually a few additional pits.

For example, when used in conjunction with webPack, the most popular build tool, image resources are written at once and need to support dynamic.

In 17 years, I once submitted a solution, interested students can watch :github.com/soulteary/w… ** Not generating ouput with multiple entries**

The last

Many seemingly lofty schemes are actually very simple in nature. Instead of pursuing lofty concepts, it is better to calm down, dig into the details, and think about how technology can effectively serve business and generate value.

– EOF


I now have a small toss group, which gathered some like to toss about the small partners.

Without advertising, we would talk about software, HomeLab, programming issues, and also share some technical salon information in the group from time to time.

Like to toss friends welcome to scan code to add friends. (Please indicate source and purpose, otherwise it will not be approved)

All that stuff about being in a group