The principle of caching is to save a copy of the response to request resources after the client makes the first request and store it in the client. When the user initiates the same request again, the request is intercepted if the cache matches the request and the cached response copy is returned to the user to avoid sending the request to the server again.

There are many types of caching technologies, such as proxy caching, browser caching, gateway caching, load balancer and content delivery network, which can be roughly divided into two categories, shared caching and private caching.

Shared caches refer to caches that can be used by multiple users, such as a Web proxy set up within a company. Private caches refer to caches that can only be used by individual users, such as a browser cache.

HTTP cache is one of the most common caching mechanisms in front-end development, which can be subdivided into mandatory cache and negotiated cache. The biggest difference between the two is whether the browser needs to ask the server if the cache hits. The forced cache does not ask, whereas the negotiated cache still needs to ask the server.

  • Mandatory cache

In the case of the forced cache, if the browser determines that the requested target resource is a valid hit, it can return the request’s response directly from the forced cache without any communication with the server. This means that the forced caching is done on the client side, which is fast.

The two fields that enforce caching are Expires and cache-Control.

Expires is a timestamp field declared in the HTTP1.0 protocol to control the expiration date of the cache. It is specified by the server and told to the browser through the response header.

The browser then sends the same request and compares expires to the current local timestamp. If the local timestamp of the current request is less than the expires value, the cache has not expired and can be used directly. Otherwise, the cache expires and re-sends the request to the server to obtain the response body.

res.writeHEAD(200, {
    Expires: new Date('the 2021-6-18 12:51:00').toUTCString(),
})
Copy the code

One big problem with Expires is the over-reliance on local timestamps. If the client’s local time is out of sync with the server’s time, or if the client’s time is changed, the cache expiration judgment may not match expectations.

To address this issue, HTTP1.1 added the cache-Control field to extend and improve expires capabilities.

Cache-control is set to maxage= XXX to control the validity period of the response resource. XXX is a length of time in seconds, indicating that the resource is valid for a period of time when it is requested. This avoids the problem caused by server and client timestamps being out of sync.

res.writeHEAD(200, {
    'Cache-Control': 'maxage=1000',})Copy the code

In addition to maxage, you can set other parameters, such as no-cache and no-store.

No-cache indicates that the negotiation cache is forcibly implemented. For each request, the negotiation cache is directly implemented instead of determining whether the mandatory cache expires. Negotiation caching will be covered later.

No-store indicates that no caching policy is allowed and each request from the client is directly fetched from the server. So no cache.

No-cache and no-store are mutually exclusive and cannot be set at the same time.

res.writeHEAD(200, {
    'Cache-Control': 'no-cache',})Copy the code

There are also private and public, which are mutually exclusive properties of cache-control that specify whether the response resource can be cached by the proxy server.

Publicb indicates that response resources can be cached by both the browser and the proxy server. Private limits response resources to be cached only by the browser.

Common caches are usually used for files that do not change in the application such as images, JS, CSS, font libraries, etc.

res.writeHEAD(200, {
    'Cache-Control': 'public, max-age=31600',})Copy the code

In addition to max-age, there is s-maxage, which indicates how long the server tells the browser that the response resource has expired. It is generally sufficient to use. However, proxy caches are often involved in large project architectures, so the effectiveness of caches on proxy servers needs to be considered, which is why S-Maxage exists. He indicates that the expiration time cached on the proxy server needs to be used with public.

Cache-control can be a complete replacement for Expires, which is currently only used as a compatibility.

  • Negotiate the cache

Negotiation cache means that before using the local cache, you need to issue a GET request to the server to negotiate whether the local cache saved by the current browser has expired.

The main problem solved by negotiated cache is that resources are not updated under forced cache.

After the client obtains the local cache, it sends a GET request to the server. This request header contains the if-Modified-since field, which is the last-Modified field in the response header and the last modification time of the resource. That is, when a client requests a resource, the server returns the response and the modification time. The modification time is stored in the Last-Modified field. If the client stores last-modified at request time, it sends its value to the server in the if-Modified-since field.

After receiving the request, the server compares the time sent from the front end and the modification time of the resource. If the two are the same, it indicates that the cache is not expired, and tells the browser to directly use the file in the cache. If the file expires, it will return the corresponding file and return the new modification date. The client continues to cache the new change time.

const http = require('http');
const fs = require('fs');
const url = require(' 'url'); http.creatServer((req, res) => { const { pathname } = url.parse(req.url); Fs.stat (' WWW /${pathName} ', (err, stat) => {if (err) {res.writeheader (404); fs.stat(' WWW /${pathName} ', (err, stat) => {if (err) {res.writeheader (404); res.write('Not Found'); res.end(); } else { if (req.headers['if-modified-since']) { const oDate = new Date(req.headers['if-modified-since']); const time_client = Math.floor(oDate.getTime() / 1000); const time_server = Math.floor(stat.mtime.getTime() / 1000); If (time_server > time_client) {// The server file time is longer than the client sendFileToClient(); } else { res.writeHeader(304); res.write('Not Modified'); res.end(); } } else { sendFileToClient(); } function sendFileToClient() { let rs = fs.createReadStream(`www/${pathname}`); res.setHeader('Last-Modifyed', state.mtime.toGMTString()); rs.pipe(res); rs.on('error', err => { res.writeHeader(404); res.write('Not Found'); res.end(); }) } } }) }).listen(8080);Copy the code

There are two problems with this caching method. First, it only determines the timestamp of the last modification of the resource. If the file is not changed, the modification time will also change. Second, the time is seconds, and if the changes are particularly fast and complete in milliseconds (as program changes can be), then the cache expiration will not be recognized.

The main reason is that the server cannot recognize the real update based only on the timestamp of the resource modification, resulting in inaccurate cache.

To solve this problem, the HTTP1.1 specification added an ETag header, entity tag. The content is a string generated by the hash operation of the server for different resources. The string is similar to the fingerprint of a file. As long as the encoding of the file content is different, the corresponding ETag tag value will be different.

const etag = require('etag')

res.setHeader('etag', etag(data));
Copy the code

Requests sent based on ETag are passed to the server in the request header with if-none-match.

ETag is not a last-Modified alternative in the negotiated cache but a complementary one, because it has some problems.

First, the server needs to pay extra computational overhead to generate eTAGS for file resources. If the resource volume is large, the number is large, and the modification is frequent, the process of generating ETAGS will affect the server performance. Secondly, ETag values are divided into strong validation and weak validation. Strong validation is generated according to the resource content to ensure that every byte is the same. Weak validation is generated based on partial attribute values of the resource, which is fast but does not ensure that each byte is the same. In the server cluster scenario, the effectiveness of negotiation cache and the success of verification will be reduced due to the inaccuracy.

The proper way is to select the appropriate cache verification mode according to the specific resource usage scenarios.

  • Caching strategies

HTTP caching technology is mainly to enhance the performance of the website, if you don’t consider the client cache capacity and server computing the ideal situation, of course, we hope that the client browser cache on the trigger rate is as high as possible, keep time as long as possible, while the ETag implementation revalidate when updating resources effectively.

However, the actual situation is often limited in capacity and computing capacity, so it is necessary to specify an appropriate cache strategy to use effective resources to achieve the optimal performance.

Define the boundaries of your needs and strive to do your best within them.

When using caching techniques to optimize performance, one of the insuperable problems is that we want the cache to last as long as possible on the client side and to be updated as resources change. These are two mutually exclusive requirements. How do you do both?

You can disassemble the resources required by the website according to different types and develop corresponding cache strategies for different types of resources.

First, the HTML file is the main file that contains other files. To ensure that it can be updated when it changes, it should be set to a negotiated cache.

cache-control: no-cache
Copy the code

The modification of image files is basically replacement. At the same time, considering the number and size of image files may cause a large overhead on the client’s cache space, the forced cache can be adopted and the expiration time should not be too long.

cache-control: max-age=86400
Copy the code

CSS stylesheet belongs to a text file, there may be changes on a regular basis, not want to use compulsory cache to improve the efficiency of reuse, so can consider to increase in the stylesheet file naming fingerprints or version number (usually for hash value), such changes after different files will have different fingerprint is the requested url is different. Therefore, the CSS cache time can be set to longer, a year.

cache-control: max-age=31536000
Copy the code

Js script files can be set up like stylesheets, with fingerprints and long expiration times. If the JS contains the user’s private information and you don’t want the intermediate proxy to cache it, you can add the private property.

cache-control: private, max-age=31536000
Copy the code

A cache strategy is a combination of forced caches for different resources, negotiation of cache and file fingerprints or version numbers, which can achieve multiple benefits, timely changes and updates, long cache expiration times, and control of where the cache can be cached.

Note That there is no optimal cache policy applicable to all scenarios. Appropriate cache policies must be determined based on specific scenarios. The cache decision takes into account the following situations.

  1. Split source code, subcontract loading

For a large project, the code is very large, and if the changes are concentrated in a few important modules, it is obvious that a full code update would be cumbersome, so consider packaging the modules into separate files during the code build process. This reduces the amount of content that needs to be downloaded by only pulling the changed module code package when the extraction is updated after each change.

  1. Estimate the cache aging of resources

Cache update invalidation of response is planned according to different requirements of different resources, appropriate max-age is specified for mandatory cache, and ETag entity tag for validation update is provided for negotiated cache.

  1. Controls the caching of the intermediate proxy

If the user’s privacy information is involved, try to avoid the cache of the intermediate proxy. If all users respond to the same resource, you can consider letting the intermediate proxy also cache.

  1. Avoid redundant urls

Caching is performed based on the URL of the requested resource. Different resources may have different urls, so try not to set the same resource to different urls. This will invalidate the cache.

  1. Plan the cache hierarchy

Not only the type of resource requested, but also the hierarchy of file resources will have an impact on the cache strategy.

  • CDN cache

The whole process of CND is Content Delivery Network. It is a virtual intelligent Network built on the basis of the existing Network. It relies on edge servers deployed in various places and through load balancing, scheduling, Content distribution and other functional modules of the central platform, users can get the Content they need to access nearby when they request it. In this way, network congestion can be reduced and the response speed of resources to users can be improved.

If there is no CDN, assuming that our server is in Beijing, then users in Hainan need to travel thousands of miles to link to the server in Beijing to obtain resources when visiting our website, which is relatively slow.

The working principle of CDN is the nearest response. If we place resources on CDN, when users in Hainan visit the website, the resource request will first be resolved by DNS. At this time, DNS will ask the CDN server whether there is a nearby server, and if there is, it will link the IP address of the nearby service to obtain resources.

The DNS server assigns the domain name resolution authority of the CDN to the dedicated DNS server pointed by the CNAME. Therefore, domain name resolution is performed on the DEDICATED DNS server for the CDN. The resolved IP address is not the CDN cache server address, but the CDN load balancer address.

The browser sends a request to the load balancer again. After calculating the distance of the user’s IP address, location of the requested resource content, and status of each server, the browser returns the cache server IP address determined by the user.

If the required resource is not found during this process, the cache server will continue to request the query until it traces back to the server where the site is located and pulls the resource to the local cache.

Although this process seems a little complicated, users are not aware of it, and it can significantly improve the speed of resource loading. Therefore, for all front-line Internet products, the use of CDN is no longer a suggestion but a regulation.

CDN is mainly aimed at static resources and is not applicable to all resource types of a website. Static resources are those resources that do not require the service server to participate in the calculation, such as third-party libraries, JS scripts, CSS style files, images, etc. Dynamic resources such as HTML rendered by the server are not suitable for CDN.

CDN network core functions include two points, the cache and back to the source, the cache static resource file is copied to the required one to CDN cache server, back to the source is if you do not look for to the target in the CDN cache server resources, has expired or CDN cache server resources, roots are traced back to the site again server process and related resources.

CDN optimization has many aspects, such as CDN performance optimization, static resource marginalization, domain name merger optimization and multi-level cache architecture optimization. These may need to be done in conjunction with the front and back ends.

In general, CDN will be distinguished from the host domain name. The advantage of this is to avoid static resource requests carrying unnecessary cookie information, and to consider the browser’s restrictions on concurrent requests under the same domain name.

Cookie access follows the same origin policy, and all requests under the same domain name carry all cookie information. Although the cookie storage space is not large, if all resources are placed under the domain name of the master site, all requests carry a large amount of data. Therefore, it is very valuable to distinguish the domain name of the CDN server from that of the master site.

Second, because browsers have limitations on concurrent requests under the same domain name, Chrome’s concurrency limit is usually 6. You can increase the number of concurrent requests by adding something like a domain name. Of course, this approach is unfriendly to cache hits. If the same resource is concurrently requested using a different domain name, the previous cache is meaningless.