Redis
Redis is one of the most widely used storage middleware in the current Internet project development process. The technical application points of Redis include cache, distributed lock, flow limiting, etc. It is a standard backend technology that must be learned and understood.
The selection
Initially, there are two types of cache middleware: Redis and Memcached. I will list the characteristics of the two types of middleware in the following table. In fact, there is no superior or inferior concept of these two middleware, it is clear that the technology that suits the business is the good technology, regardless of the business alone talking about technology is playing rogue.
Redis | Memcached | |
---|---|---|
The data structure | String, Hash, List, Set, SortedSet | String (pure Key/Value structure) |
persistence | RDB (full storage), AOF (log storage) | – Not supported |
Master-slave synchronization | Master/Slave | – Not supported |
The cluster | Redis cluster |
– Not supported |
The efficiency of | Due to theRedis The use of single threads, as well as complex metadata structures, is less efficient in storageMemcached |
– |
Based on the above analysis, if the business is just a need-based String Get/Set, Memcached can do the job. If the business has more complex requirements, such as master/slave synchronization, or even clustering to maintain a high availability, and the ability to recover data when the server goes Down, Memcached can do the job. Redis is definitely the better choice.
The cache
Caching is one of the key features of Redis. It can take much of the load off the database when there is heavy traffic accessing data. There are three main problems to avoid when using cache: penetration, breakdown, and avalanche.
The cache to penetrate
Cache penetration in concept is easy to understand, that is, the requester sends a request to the invalid application, the application will go to the query cache again whether the data are cached, but as the parameter is illegal, the data will never be cached in Redis, so the application to query the database, but still no data in the database, so return empty. When all the illegal requests hit the database, the situation of the database can also be imagined.
This is a relatively standard request flow chart, why relatively standard, because the flow chart does not take into account the cache data synchronization problem, there is a lot to say about this aspect, I will talk about it next time. Facing this figure, assume that there is a table T in the database, the primary key ID is increased, there are 100 data in the table at present, the server provides a query interface, query parameter is ID, at present 100 data are stored in the cache. There is a user who continuously requests data with ID = -1 to the interface. The result is of course that the cache does not hit, so he directly queries the database. The database does not have corresponding data, but he cannot bear the request pressure, so he dies naturally. Of course, now there are small cute will say: “That I do a judgment in the interface, judgment ID is less than 0 directly return not on the line.” If ID < 0, what if the ID of the request is greater than 100? So for the service, as long as the request is not in the data interval, can be said to be cache penetration. As a workaround for cache penetration, consider using a Bloom filter at the interface for parameter verification. As for the principle and implementation of bloom filter, we will talk about it later if we have the opportunity.
Cache breakdown
Cache breakdown refers to when there are a large number of concurrent requests for the same Key, and the Key expires, a large number of concurrent requests are directed to the database, and the database dies instantly. Now that we know why, the solution is obvious. One is to lock the data. The first incoming request will fetch the data from the database and then store it in the cache, and the subsequent requests will fetch the data from the cache.
Pseudo code:
Object getData(String key){
Object result = getDataFromCache(key);
if(result == null){
lock.lock();
if((result = getDataFromCache(key)) == null){
result = getDataFromDB(key);
setDataToCache(key, result);
}
lock.unlock();
}
return result;
}
Copy the code
The second option is to update and replace the data without setting an expiration date.
Cache avalanche
A cache avalanche is similar to a cache breakdown, but the difference is that the breakdown point of a cache breakdown is a piece of data. A cache avalanche is when a large amount of data expires at the same time, causing the request to miss the cache and all the requests to the database. The first method is to set the data to never expire, only to be updated, not replaced. The second method is to add a random value to the expiration time of the data to ensure that large numbers of data do not expire at the same time.
SET key Value Expiration time + math.random () * 10000
A distributed lock
Nowadays, distributed system, SOA and microservices have stripped away the original integrated system, which not only improves the system availability but also brings the problem of consistency. In distributed system, to achieve synchronous access to the same resource, it is necessary to use distributed lock. There are many ways to achieve distributed lock. Here we mainly talk about the realization of Redis.
The implementation of distributed lock is based on Redis SETNX command.
The Redis website describes SETNX as follows: Set key to hold string value if key does not exist. In that case, it is equal to SET. When key already holds a value, no operation is performed. SETNX is short for “SET if Not eXists”.
That is, if the Key does not exist, set the Value of the Key to Value. If the Key already exists, nothing will change. Redis returns 1 on success and 0 on failure.
According to the above features, the idea of how to act as a distributed lock has been very clear. Each service will operate Redis before accessing resources synchronously. If the value is saved successfully, the lock will be acquired. Otherwise, spin or other operations can be performed.
Pseudo code:
if(setNX(key, 1) == 1){else{// failed to obtain lock}Copy the code
Through the above code, resources can be locked in a distributed system. But there’s more to consider during actual development. Consider this situation: a service hangs after locking the resource, but the lock is not released. Then, even if the service is restarted, re-locking the resource finds that the Key already exists, and the lock fails to be obtained.
In this case, we need to consider setting a timeout on the Key. After the timeout, the Key will automatically become invalid, that is, the lock will be released and other services can regain the lock permission. Since SETNX and EXPIRE operate separately, the atomicity of a command cannot be guaranteed. Fortunately, the NX and XX parameters of the SET command were added after Redis2.6.12, allowing the atomic operation to be performed through the SET command.
Pseudo code:
if(set(key, anyValue, 10, NX) == 1){// Obtain the lock and set timeout to 10 seconds}else{// failed to obtain lock}Copy the code
The above code solves the problem of the lock being permanently held after the lock obtaining service has been suspended, but smart kids should have noticed the problem. What if the service that acquires the lock cannot complete the operation within 10 seconds? The lock will be released directly and acquired by another service lock, at which point two services will operate on the resource at the same time and the lock will be broken. As for the solution, extend the lock expiration time? If I extend it, I won’t be able to complete the corresponding operation. Shaw has a more subtle approach.
Pseudo code:
if(set(key, anyValue, 10 NX) == 1){Thread Thread = new Thread(() -> {// expire(key, 10); }); // set thread to thread.setdaemon (true); / / every 9 SECONDS, perform a prolonged lock operation ScheduledExecutorService. The schedule (thread, 9, TimeUtil. SECONDS); / /... Specific business logic ScheduledExecutorService. Shutdown (); // Close the daemon thread del(key); // release the lock}else{// failed to obtain lock}Copy the code
Based on the code above, let’s plug in the previous problems and see if we can solve them.
- The thread hangs after the lock is acquired, and the lock is permanently held and cannot be released
- This section describes how to resolve the problem that the lock cannot be released by setting the expiration time
- Locking steps
SETNX
,EXPIRE
A non-atomic operation does not solve the problem of a locked server that cannot be released- through
SET key value [EX seconds] [PX milliseconds] [NX|XX]
Command to complete atomic locking and set lock timeout
- through
- The service execution time cannot be determined after the lock is locked, causing the lock release problem
- After the service obtains the lock, it starts a daemon thread that executes periodically. When the lock is about to expire, it extends the lock expiration time to ensure that the lock is held. In this case, even if the service that acquired the lock hangs, because there is no daemon thread to extend the expiration of the lock, the lock will still be released after the expiration date and will be reclaimed by other services.
At this point, the Redis distributed lock setting process is almost complete. However, the specific practical operation needs to be determined according to the actual business development situation.
summary
Redis related, I think that is worth writing, that’s the point now, if there are other want to add in the future will rely on words to fill again, about Redis, actually I think it is a bit like the industry application of essential oils, but is a lot of things can do a lot of things is not very perfect, so the technical selection of project development, Or do you want to make a comprehensive decision on a technology or a piece of middleware.
Here is ShawJie, I will update some of my recent reading or want to write things from time to time. Welcome to my blog shawjie. me