Varnish's elegant model and sacred model

Keywords: encoding JSP PHP Tomcat

Today I'm talking with my colleagues about tomcat hanging on the back end of the current network, which results in varnish thread stacking on the front end, and eventually leads to varnish hanging or busy front-end web processing. Since I have looked at varnish's documents in detail before, I remember that there was a God model or the Holy Grail model (due to the problem of English-Chinese translation, there may be N translation methods). Since it has been quite a while, I don't remember very well. Later, I went online to check it. Now the general name of Chinese translation - sacred mode and elegant mode can be applied to the above mentioned scenes. Here is a tag summary into a blog record.

Grace mode

Why use Grace mode? What are the advantages of elegant mode?

If you need thousands of clicks per second, the waiting queue will be huge. There are two potential problems: one is thundering herd problem. wiki has a special corresponding explanation. Suddenly adding 1,000 threads to provide content can make the load very high; second, nobody likes waiting. To solve this problem, we instruct varnish to keep the cached object longer than their TTL (that is, it should expire, do not let it expire), and to provide the old content to the waiting request. Since we want to provide old content, we must first have content to provide. So, we use the following VCL to make varnish keep all objects out of their TTL for 30 minutes.

sub vcl_fetch {
  set beresp.grace = 30m;
}

In this way, varnish will not provide old objects. In order to enable varnish to provide old objects, we must open it on the request. The following shows that we receive 15s old objects:

sub vcl_recv {
  set req.grace = 15s;
}

The advantage of varnish is memory-level cache, so the amount of memory determines the amount of data in cache. If elegant mode is enabled, after TTL expires, we will not remove it from mem, but wait for a period of time to clear it, which wastes resources for no reason. But if you turn on a health check, you can check if there is something wrong with the back end. If something goes wrong, we can provide old content for a long time. If there is no problem with the back end, we can set the time to be shorter. This ensures elegance in itself and reduces waste of resources. Its configuration is as follows:

if (! req.backend.healthy) {
   set req.grace = 5m;
} else {
   set req.grace = 15s;
}

So, to sum up. The main functions of the elegant model are as follows:

1. Merge requests. When N clients request the same page, varnish sends only one request to the back-end server, and then lets several other requests hang up and wait for the result to be returned. After the result is returned, the result of the replication request is sent to the client.

2. Avoid requests clustering by providing old content. If the back end provides old content, it reduces the pressure on the back end and front end requests, and provides time for the restart and switching of the back end.

2. Saint mode

Sometimes, servers are weird, they send random errors, and you need to notify varnish to handle it in a more elegant way, called Saint mode. Saint mode allows you to discard service obsolescence from one back-end server or another attempted back-end server or cache. Such as:

sub vcl_fetch {
    if (beresp.status == 500) {
        set beresp.saintmode = 10s;
        return (restart);
  }
  
    set beresp.grace = 5m;
}

As in the above configuration, when we set beresp.saintmode to 10 seconds, varnish will not request the server for 10 seconds. More or less a blacklist. When restart is executed, varnish requests it if we have other backends that can provide it. When no other backend is available, varnish provides the old content in the cache.

3. Limitations of grace and saint Models

The above two modes are not omnipotent either, for example, if your request fails while the request is being acquired, it will be thrown into vcl_error. Because vcl_error's access to the data set is displayed at the front end, you can't enable elegant and sacred modes. This problem will be solved in future releases, but we can try to avoid it here. The official text is as follows:

Declare a backend that is always sick.
Set a magic marker in vcl_error
Restart the transaction
Note the magic marker in vcl_recv and set the backend to the one mentioned
Varnish will now serve stale data is any is available

This passage is hard to understand. Some people have made a Chinese translation version of it.

1. Statements are always at the back end of the situation.
2. Setting magic marker in vcl_error
 3. Restart business
 4. Note the magic marker in vcl_recv and set the back end as mentioned earlier.
5. varnish will now provide any old available data

Note: magic marker is a part of its parameters, which can be referred to in detail. Official wiki The example above.

The above contents are mainly referred to the official following pages:

varnish 3.0.5 official website documents

varnish static book section

Complete examples

Because of version and parameter changes, the configuration in the example is not guaranteed to be fully applicable to the version you are using, but you can add or subtract from the example. Some of the parameters involved can also be appropriately changed compared with official documents.

backend web1 {
    .host = "172.16.2.31";
    .port = "80";
    .probe = {
        .url = "/";
        .interval = 10s;
        .timeout = 2s;
        .window = 3;
        .threshold = 3;
    }
}
backend web2 {
    .host = "172.16.2.32";
    .port = "80";
    .probe = {
        .url = "/";
        .interval = 10s;
        .timeout = 2s;
        .window = 3;
        .threshold = 3;
    }
}
# Define load balancing group
director webgroup random {
    {
       .backend = web1;
       .weight = 1;
    }
    {
       .backend = web2;
       .weight = 1;
    }
}
# ip that allows refresh of caches
acl purgeAllow {
     "localhost";
     "172.16.2.5";
}
sub vcl_recv {
    # Refresh cache settings
    if (req.request == "PURGE") {
        #Determine whether ip is allowed
        if (!client.ip ~ purgeAllow) {
             error 405 "Not allowed.";
        }
        #To find in the cache
        return (lookup);
    }
    std.log("LOG_DEBUG: URL=" + req.url);
    set req.backend = webgroup;
    if (req.request != "GET" && req.request != "HEAD" && req.request != "PUT" && req.request != "POST" && req.request != "TRACE" && req.request != "OPTIONS" && req.request != "DELETE") {
         /* Non-RFC2616 or CONNECT which is weird. */
         return (pipe);
    }
    # Cache only GET and HEAD requests
    if (req.request != "GET" && req.request != "HEAD") {
        std.log("LOG_DEBUG: req.request not get!  " + req.request );
        return(pass);
    }
    # http-authenticated pages also pass
  if (req.http.Authorization) {
        std.log("LOG_DEBUG: req is authorization !");
        return (pass);
    }
    if (req.http.Cache-Control ~ "no-cache") {
        std.log("LOG_DEBUG: req is no-cache");
        return (pass);
    }
    # Ignore admin, verify, servlet directories, end with. jsp and. do, and URL s with? To read content directly from the back-end server
    if (req.url ~ "^/admin" || req.url ~ "^/verify/" || req.url ~ "^/servlet/" || req.url ~ ".(jsp|php|do)($|?)") {
        std.log("url is admin or servlet or jsp|php|do, pass.");
        return (pass);
    }
    # Cache only requests with specified extensions and remove cookie s
    if (req.url ~ "^/[^?]+.(jpeg|jpg|png|gif|bmp|tif|tiff|ico|wmf|js|css|ejs|swf|txt|zip|exe|html|htm)(?.*|)$") {
        std.log("*** url is jpeg|jpg|png|gif|ico|js|css|txt|zip|exe|html|htm set cached! ***");
        unset req.http.cookie;
        # Specification request headers, Accept-Encoding retains only the necessary content
        if (req.http.Accept-Encoding) {
            if (req.url ~ ".(jpg|png|gif|jpeg)(?.*|)$") {
                remove req.http.Accept-Encoding;
            } elsif (req.http.Accept-Encoding ~ "gzip") {
                set req.http.Accept-Encoding = "gzip";
            } elsif (req.http.Accept-Encoding ~ "deflate") {
                set req.http.Accept-Encoding = "deflate";
            } else {
                remove req.http.Accept-Encoding;
            }
        }
        return(lookup);
    } else {
        std.log("url is not cached!");
        return (pass);
    }
}
sub vcl_hit {
    if (req.request == "PURGE") {
        set obj.ttl = 0s;
        error 200 "Purged.";
    }
    return (deliver);
}
sub vcl_miss {
    std.log("################# cache miss ################### url=" + req.url);
    if (req.request == "PURGE") {
        purge;
        error 200 "Purged.";
    }
}
sub vcl_fetch {
    # If the back-end server returns an error, enter saintmode
    if (beresp.status == 500 || beresp.status == 501 || beresp.status == 502 || beresp.status == 503 || beresp.status == 504) {
        std.log("beresp.status error!!! beresp.status=" + beresp.status);
        set req.http.host = "status";
        set beresp.saintmode = 20s;
        return (restart);
    }
    # If the backend static cache, skip
    if (beresp.http.Pragma ~ "no-cache" || beresp.http.Cache-Control ~ "no-cache" || beresp.http.Cache-Control ~ "private") {
        std.log("not allow cached!   beresp.http.Cache-Control=" + beresp.http.Cache-Control);
    return (hit_for_pass);
    }
    if (beresp.ttl <= 0s || beresp.http.Set-Cookie || beresp.http.Vary == "*") {
        /* Mark as "Hit-For-Pass" for the next 2 minutes */
        set beresp.ttl = 120 s;
        return (hit_for_pass);
    }
    if (req.request == "GET" && req.url ~ ".(css|js|ejs|html|htm)$") {
        std.log("gzip is enable.");
        set beresp.do_gzip = true;
        set beresp.ttl = 20s;
    }
    if (req.request == "GET" && req.url ~ "^/[^?]+.(jpeg|jpg|png|gif|bmp|tif|tiff|ico|wmf|js|css|ejs|swf|txt|zip|exe)(?.*|)$") {
        std.log("url css|js|gif|jpg|jpeg|bmp|png|tiff|tif|ico|swf|exe|zip|bmp|wmf is cache 5m!");
        set beresp.ttl = 5m;
    } elseif (req.request == "GET" && req.url ~ ".(html|htm)$") {
        set beresp.ttl = 30s;
    } else {
        return (hit_for_pass);
    }
    # If the back end is not healthy, return the cached data for 1 minute first
    if (!req.backend.healthy) {
        std.log("eq.backend not healthy! req.grace = 1m");
        set req.grace = 1m;
    } else {
        set req.grace = 30s;
    }
     return (deliver);
}
# Send to client
sub vcl_deliver {
    if ( obj.hits > 0 ) {
    set resp.http.X-Cache = "has cache";
    } else {
    #set resp.http.X-Cache = "no cache";
    }
    return (deliver);
}

Posted by mentor on Mon, 01 Apr 2019 12:00:29 -0700