Building a specification REST API with ASP. NET Core 2.1--Caching and concurrency

Source: Internet
Author: User
Tags rfc

Some of the preparatory knowledge required in this article can be seen here: http://www.cnblogs.com/cgzl/p/9010978.html and http://www.cnblogs.com/cgzl/p/9019314.html

To establish a Richardson maturity Level 2 post, GET, PUT, PATCH, delete restful API see here: Https://www.cnblogs.com/cgzl/p/9047626.html and https:/ /www.cnblogs.com/cgzl/p/9080960.html and Https://www.cnblogs.com/cgzl/p/9117448.html

Hateoas:https://www.cnblogs.com/cgzl/p/9153749.html.

This article describes caching and concurrency, without having to look at the front article to understand it .

The exercise code required in this article (right-click Save, suffix to zip): https://images2018.cnblogs.com/blog/986268/201806/986268-20180611132306164-388387828.jpg

Cache

Based on rest constraints: "Each response should define whether it can be cached by itself." This article describes how to ensure that HTTP responses can be cached, the knowledge of HTTP caching is used, and HTTP caching is part of the HTTP standard (RFC 2616, RFC 7234).

"There is no use of caching unless performance can be greatly improved." The goal of the http/1.1 cache is to avoid sending requests in many scenarios and, in other cases, to avoid returning the full response ".

The cache uses an expiration mechanism for avoiding the number of requests sent.

The cache uses a validation mechanism for avoiding the return of a complete response.

What is a cache?

The cache is a standalone component that exists between the API and the API consumer.

the cache receives the API consumer's request andsends the request to the API;

The cache also receives the response from the API and, if the response is cacheable, saves the response and returns the response to the consumer of the API . If the same request is sent again, the cache may return the saved response to the API consumer.

The cache can be thought of as an intermediary in the request-response communication mechanism .

There are three kinds of caches in http:

    • Client Cache /browser cache, which exists on the client and is private (because it is not shared with other clients).
    • The gateway cache , which is a shared cache, is located on the server side and all API consumer clients will share this cache. It also has aliases for reverse proxy server caching, HTTP accelerators, and so on .
    • The proxy cache , which is located on the network, is shared, it is neither in the API consumer client nor on the API server, it is elsewhere on the network. This cache is often used by large enterprises or ISPs to serve large-scale users. (This does not introduce, I will not)

Expiration model

The expiration model gives the server the ability to declare the requested resource, which is how long the response information remains "fresh" . The cache can store this response, so subsequent requests can be cached to respond, as long as the cache is "fresh". For this purpose, you need to use two response Headers:

Expires Header, which contains an HTTP date that describes when the response will expire, for example: Expires:mon, June 2018 13:55:41 GMT. However, there may be some synchronization problems, so the time required for caching and the server is consistent . It has limited control over the type, time, and location of the response, because these things are controlled and restricted by the Cache-control header.

Cache-control header, such as Cache-control:public, max-age=60, contains two command public and max-age in this header. Max-age indicates that the response can be cached for 60 seconds, so clock synchronization is not a problem , while public means it can be cached by shared and private caches. So the server can decide whether the response is allowed to be cached by the gateway cache or the proxy cache. For outdated models, it is preferable to use the Cache-control header. Cache-control has a lot of other instructions, and a few common ones can be seen on the ASP. NET Core Official website: https://docs.microsoft.com/en-us/aspnet/core/performance/ Caching/response?view=aspnetcore-2.1#http-based-response-caching

How the expiration model works, see the following example:

The cache caches here can be private or shared.

The client program sends the request GET countries, there is no cached version of the response, so the cache will continue to send the request to the API server, and then the API returns the response to the cache, the response contains the Cache-control header, Cache-control declares that the response is kept "fresh" (or called valid) for half an hour, and the last cache returns the response to the client, but the cache copies a copy of the response saved.

Then, for example, after 10 minutes, the client sends the same request:

At this point, the response in the cache is still within the validity period, the cache will return the response directly , the response contains an age Header, for this example (10 minutes), the value is 600 (seconds).

In this case, the request to the API server is avoided, and the cache accesses the backend API server only if the cache expires (or is called stale Stale).

If the cache is private, such as in the Web app's Localstorage, or on a mobile device, the request stops.

If the cache is shared, such as caching on the server, the situation is different.

For example, 10 minutes later another client sends the same request, which must first come to the cache, and if the cache has not expired, then the cache will return the response directly to the client, the value of the age header is 1200 (seconds), 20 minutes:

In general , private caching reduces the need for network bandwidth while reducing requests from caching to the API .

The shared cache does not save the network bandwidth that is cached to the API, but it drastically reduces requests to the API . For example, at the same time 10,000 clients issued the same request to the API, the first arrival of the request will come to the API program here, while the other requests will only come to the cache, which also means that the amount of code execution will be greatly reduced, the number of access to the database will be greatly reduced, and so on.

So it's good to combine private and shared caches (client-side caching and public/gateway caching). However, this kind of caching is more suitable for more static resources, such as tablets, content pages , and often changes in the data of the API is not very appropriate . If the API adds a piece of data, then for the 10,000 clients, the cached data is wrong, for this example it is possible to return incorrect data for half an hour, then you need to use the validation model .

Validating models

The validation model is used to verify that the cached response data is kept up-to-date.

In this case, when the cached data is going to be the response of the client request, it first checks the source server or the intermediate cache with the latest data to see if the data it caches is still up to date. This is where the authenticator is going to be used.

Validator

There are two types of validators: strong validators , weak validators .

Strong Authenticator : If the body or header of the response changes, the strong validator will change . The typical example is the etag (Entity Tag) response header, for example: ETag: "12345678", which is an opaqueidentity sent by the Web server or API, which represents a specific version of a resource . Strong validators can be used in any context with a cache, and a strong validator can be used to do concurrency checks when updating resources.

weak validators : When responses change, weak validators do not always change, and the server decides when to change , and the usual practice is to "change only when important changes occur". A typical example is the header of the last-modified (last modified time), for example: Mon, June 2018 13:55:41 GMT, which is a bit weaker when the bread has the last modified time of the resource, because it is accurate to the second, Because it is possible to update resources more than two times in a second. But even for the weak authenticator, the clock must be synchronized, so it has the same problem as the expires header, so the ETag is a better choice.

There is also a weak etag, which begins with w/, such as the ETag: "w/123456789", which is treated as a weak authenticator, but is still determined by the server. When the etag is in this format, it does not necessarily change if the response changes.

weak validators can be used only if they are allowed to be equivalent (roughly equal), but are not available under the requirement of full equality.

The HTTP standard suggests that the two headers of the ETag and last-modified should be sent at the same time, if possible.

Here's how it works. When the client first requests, the request arrives at the cache and the cache does not, then the cache sends the request to the API;API to return the response, which contains the two headers of the ETag and last-modified, the response is sent to the cache, and then the cache is then sent to the client. At the same time, the cache holds a copy of the response.

After 10 minutes, the client sends the same request again, the request comes to the cache, but there is no guarantee that the cached response is "fresh", this example does not use the Cache-control Header, so the cache must go to the server's API to check. At this point it adds two headers:If-none-match, which is set to the value of the etag that cached the response data;if-modified-since, It is set to the last-modified value of the cached response data. Now the request is based on the situation, and the server receives the request and compares the headers or generates a response based on the card.

If the check is qualified, the server does not need to generate a response, it returns 304 not Modified, and the cache returns the cached response, which also contains an up-to-date last-modified Header (if last-modifed is supported);

If the response resource changes, the API generates a new response.

If it is a private cache, then the request will be stopped here.

But if it is a shared cache, if another client sends a request after 10 minutes, the request will reach the cache and then the same process as above:

In general, the same response will only be generated once.

Compare:

Private Cache : Subsequent requests save network bandwidth, we need to communicate with the API, but the API does not need to return the full response, and if the resource does not change, it only needs to return 304.

Shared Cache : The bandwidth between the cache and the API is saved, and if the validation passes, the API does not need to regenerate the response and resend it back.

outdated models and validation models are often used in combination .

Combining an outdated model with a validation model

You can do this:

If a private cache is used, the response is returned directly from the private cache as long as the response does not expire. The benefit of this is that it reduces communication with the API and reduces the effort of the API generation response to reduce bandwidth requirements. If the private cache expires, it will still have access to the API. If only an expiration (model) check is available, this means that the response will have to be regenerated if the API expires. But if we use validation (model) checks, we may avoid this situation. Because the cached response expires does not mean that the cached response is not valid, the API checks the authenticator and returns 304 if the response is still valid. As a result, network bandwidth and response generation actions are likely to be drastically reduced.

In the case of a shared cache, the cached response is returned as long as it has not expired, so that the network bandwidth between the client and the cache is not saved, but the network bandwidth between the cache and the API is saved, and the number of requests to the API is significantly reduced, which is greater than the private cache amplitude. Because the shared cache is shared with the client that may be all. If the cached response expires, the cache must communicate with the API, but this does not necessarily mean that the response must be regenerated. If the validation succeeds, it returns 304, without responding to the body, which may reduce the network bandwidth requirements between the cache and the API, or return the response from the cache to the client.

So in summary, the client is equipped with a private cache, and the server level with shared cache should be the best practice .

Cache-control's instructions.

Let's take a look at the Cache-control common Directives for response :

  • Freshness :
    • max-age defines the lifetime of the response, exceeding this value, the cached response expires, and the unit is in seconds.
    • S-maxage It overrides the Max-age value for the shared cache. Therefore, the expiration time of the response in the private cache and the shared cache may be different.
  • Storage location :
    • Public, which indicates that the response can be cached by any buffer, either private or shared.
    • private, which indicates that the whole or part of the response information is prepared for one user and cannot be cached by the shared cache.
  • Verification :
    • No-cache, which indicates that the response cannot be used by subsequent requests until the source server has been re-validated.
    • must-revalidate, use it to declare if the response is not fresh (expired), then it needs to be re-verified. This allows the server to force the cache to be re-validated, even if the client considers an expired response to be possible.
    • Proxy-revalidate, he is similar to must-revalidate, but not for private caches.
  • Other:
      • No-store, which indicates that the cache does not allow any part of the message to be stored.
      • No-transform , which indicates that the cache cannot convert the media type that responds to the body.

The above is determined by the server, but the client can override some of these settings.

of the request Cache-control Common directives:

    • Freshness:
      • max-age, which indicates that the client does not want to receive a response that has exceeded this value's validity period
      • Min-fresh, which indicates that the client can accept such a response, its validity period is not less than its current age plus the set value (in seconds), that is, the client wants to respond can also be kept fresh for a specified period of time.
      • Max-stale, which indicates that the client can receive an expired response.
    • Verify:
      • No-cache, which indicates that the cache is not able to satisfy the request with a stored response. The source server needs to re-verify success and generate a response.
    • Other:
      • No-store, and the same as the response.
      • No-transform, and the same as the response.
      • only-if-cached, which indicates that the client only wants the cached response and does not re-authenticate and build with the source server. This comparison applies to very poor state of the network state.

To the present also introduced a few instructions, in fact, most of the use of max-age and public, private can ...

For more instructions, see: https://tools.ietf.org/html/rfc7234#section-5.2

Cache Headers

Based on rest constraints, in order to support HTTP caching, we need a component that can generate the correct response header, and can check the header of the sent request, so we can return 304 not Modified or 412 preconditioned Failed.

This component should be located at the back end of the cache, and the ASP. NET core has its own property tag [Responsecache] (https://docs.microsoft.com/en-us/aspnet/core/ Performance/caching/response?view=aspnetcore-2.1#responsecache-attribute), which can be applied to the controller's actions. To set the appropriate response cache header it can specify the required parameters. It can only do this and cannot store the response in the cache, it is not a cache store. And because it does not seem to support the etag, it is not used for the time being.

Consider Cachecow, which can generate an etag and also support. NET Core, but it does not have built-in middleware to return 304. So what I'm using here is Marvin.Cache.Headers.

Installation:

The Configureservices method in startup is configured:

You can also configure the header's build options, but temporarily use the default configuration.

Then in the Configure method, add this middleware before APP.USEMVC ():

This is the logic that handles and returns 304.

Also need to set up a postman, to ensure that the send No-cache header this item is off:

To send a request test:

This is the first access, the action method is executed, and then the response is returned. The header of the response, as shown, contains the cache-related header.

The default Cache-control is Public,max-age is 60 seconds. The Expires header also reflects the expiration time, which is 1 minutes later.

The ETag and last-modified used for validation are also generated and added, and Last-modified is now the time.

The generation logic of the etag is not part of the standard, which can be decided by ourselves. It is up to us to decide whether the response is equivalent or exactly equal.

By default, this middleware takes into account the request path, the Accept, the accept-language headers, and the body of the response.

Send the request again, because it has been more than 1 minutes, so will still walk the action method:

Then send the request again within 1 minutes:

Or take this action Method!!

The header still has a change.

This is not a problem because the library is only responsible for generating headers and validation, which is not cache memory.

To cache data, you need a cache memory that can be private, public, or both. I'll talk about this later.

Let's take a look at the validation, if a response is not fresh (out of date), we know that the cache must be re-validated, preferably with an etag, and he assigns the value of the ETag to the If-none-match header:

The 304 not Modified is returned, and the action method is not executed.

Test the put action below:

After updating the data, I send the previous GET request again:

The action method was executed again, indicating that the validation failed because the ETag was inconsistent, and when I sent the put request, a new etag was generated.

We can also configure how to generate headers to open the Configureservices method for startup:

There are many configuration parameters, and here I have modified a parameter for the expiration model and the validation model, respectively.

The max-age of the expiration model is set to 600 seconds. The validation model adds a must-revalidate directive for Cache-control, which means that if the cached response expires, it must be re-validated.

Send the GET request again:

The action method is re-executed, and you can see the change in the response header.

Cache storage

Before just generating a cache-related header, and not really storing it, now it's about storing this part.

The cache is private, shared, and so on.

Private is not within the scope of our discussion because it is on the client side.

Private and shared caches, some caches are a mixture of the two, depending on where you use it to determine the type. such as Cachecow.

Microsoft provides a shared cache that supports. NET Core:responsecaching middleware (https://docs.microsoft.com/en-us/aspnet/core/ performance/caching/middleware?view=aspnetcore-2.1).

This middleware examines the header generated by the Marvin.Cache.Headers middleware and puts the responses into the cache and serves them to the client based on the header, but the responsecaching middleware itself does not generate these headers .

Register in Configureservices:

Then in the Configure method, add this cache store to the pipeline:

Note the order, to ensure that it is before Usehttpcacheheaders ().

To test, send a GET request:

This time the action method is executed and the response is returned.

Send the GET request again:

This time, instead of going into the action method, I returned from the cache, and there was an age header, which told me the "ages" of the response, and he had lived for 123 seconds.

Request again:

Age becomes 243 seconds, or less than 600 seconds. Obviously this improves the performance of the application ...

We can now generate the Cache-control and ETag headers, but there is no other function for the ETag:

concurrency control

Look at the following situation, which is common:

Two clients 1 and 2, the customer 1 first obtained the ID of 1 country resource, then the customer 2 also acquired this resource, then the customer 2 has modified the resources, first put action to update, then the customer 1 modified country then put to the server.

At this point, the customer 1 will be 2 of the customer changes completely covered out, this is a common problem.

For such a problem, we need to use some strategies for dealing with concurrency conflicts: pessimistic concurrency control and optimistic concurrency control .

pessimistic concurrency control means that the resource is locked for customer 1, and as long as the resource is in a locked state, others cannot modify it, and only 1 of the customer can modify it. But pessimistic concurrency control cannot be implemented under rest because rest has a stateless constraint.

optimistic concurrency control , which means that customer 1 will get a token, and allow him to update the resources, as long as token is reasonable and effective, then customer 1 has been able to update the resource. This is achievable in rest, and this token is a validator and requires a strong validator, so we can use the ETag.

Back to the example:

Customer 1 sends a GET request and returns a response with the ETag Header. Customer 2 then sends the same request, returning the same response and ETag.

The customer 2 is updated first, and the value of the ETag is assigned to If-match Header,api to check the Header and compare it to the ETag value saved by the response, this response generates a new etag, and the response contains the new ETag.

Then the customer 1 for the put update operation, its If-match header value is the value of the ETag obtained by the customer before 1, after reaching the API, the API will know that this is not the same value as the resource's latest etag, so the API returns 412 precondition Failed.

So the customer 1 update was unsuccessful because it was using an older version of the resource. This is how optimistic concurrency control works.

Here's a look at the test

Customer 1 Get First:

Customer 2GET:

Note that the etag of both of them is the same.

Then customer 2 is updated first:

Last customer 1 re-update (using the old ETag):

Returns 412.

This article is relatively short, some of the content about caching technology is not written, the topic of rest is a bit far away.

The documentation for the cache portion of the ASP. NET core here: https://docs.microsoft.com/en-us/aspnet/core/performance/caching/?view=aspnetcore-2.1

The source of this series in: Https://github.com/solenovex/ASP.NET-Core-2.0-RESTful-API-Tutorial

Building a specification REST API with ASP. NET Core 2.1--Caching and concurrency

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.