I. basic syntax 2: cache avalanche 1: Global lock, instance lock 2: string Lock 3: cache penetration 4: Talk about cache avalanche 5: Summary
Introduction
This article mainly describes cache usage experience and problems encountered in normal projects.
Directory
I. basic writing
II. cache avalanche
1: Global lock, instance lock
2: string lock
III. cache penetration
IV. cache avalanche
V. Summary
I. basic writing
To facilitate the demonstration, we use Runtime. Cache as the Cache container and define a simple operation class. As follows:
public class CacheHelper { public static object Get(string cacheKey) { return HttpRuntime.Cache[cacheKey]; } public static void Add(string cacheKey, object obj, int cacheMinute) { HttpRuntime.Cache.Insert(cacheKey, obj, null, DateTime.Now.AddMinutes(cacheMinute), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); } }
Simple read:
Public object GetMemberSigninDays1 () {const int cacheTime = 5; const string cacheKey = "mushroomsir"; var cacheValue = CacheHelper. Get (cacheKey); if (cacheValue! = Null) return cacheValue; cacheValue = "395"; // here the data is generally queried by SQL. Example: 395 sign-in days CacheHelper. Add (cacheKey, cacheValue, cacheTime); return cacheValue ;}
There are many such expressions in the project. There is no error in this writing, but there will be problems after the concurrency comes up. Continue watching
II. cache avalanche
The cache avalanche is caused by cache failure (expiration), and the new cache is not yet in progress.
During this intermediate time, all requests are directed to the database, which puts a lot of pressure on the CPU and memory of the database. the number of frontend connections is insufficient and the query is blocked.
The intermediate time is not that short, for example, 1 second for SQL query, and 0.5 seconds for transmission parsing. That is to say, all user queries within 1.5 seconds are directly queried from the database.
In this case, the most important thing we think of is lock queuing.
1: Global lock, instance lock
Public static object obj1 = new object (); public object GetMemberSigninDays2 () {const int cacheTime = 5; const string cacheKey = "mushroomsir"; var cacheValue = CacheHelper. get (cacheKey); if (cacheValue! = Null) return cacheValue; // lock (obj1) // Global lock // {// cacheValue = CacheHelper. Get (cacheKey); // if (cacheValue! = Null) // return cacheValue; // cacheValue = "395"; // here the data is generally queried by SQL. Example: 395 sign-in days // CacheHelper. Add (cacheKey, cacheValue, cacheTime); //} lock (this) {cacheValue = CacheHelper. Get (cacheKey); if (cacheValue! = Null) return cacheValue; cacheValue = "395"; // here the data is generally queried by SQL. Example: 395 sign-in days CacheHelper. Add (cacheKey, cacheValue, cacheTime);} return cacheValue ;}
First, lock (obj1) is A global lock that can be satisfied, but we need to declare an obj for each function. otherwise, when function A and function B lock obj1, it will surely block one of them.
Second, lock (this) locks the current instance and does not work for other instances. Use the Singleton mode to lock.
But in the current instance: function A locks the current instance, and the function reads and writes of other locks of the current instance are also blocked. Not available
2: string lock
Since the lock object does not work, we can use the character string feature to directly lock the cache key. Let's see
Public object GetMemberSigninDays3 () {const int cacheTime = 5; const string cacheKey = "mushroomsir"; var cacheValue = CacheHelper. Get (cacheKey); if (cacheValue! = Null) return cacheValue; const string lockKey = cacheKey + "n (* ≧ ▽ ≦ *) n"; // lock (cacheKey) // {// cacheValue = CacheHelper. get (cacheKey); // if (cacheValue! = Null) // return cacheValue; // cacheValue = "395"; // here the data is generally queried by SQL. Example: 395 sign-in days // CacheHelper. Add (cacheKey, cacheValue, cacheTime); //} lock (lockKey) {cacheValue = CacheHelper. Get (cacheKey); if (cacheValue! = Null) return cacheValue; cacheValue = "395"; // here the data is generally queried by SQL. Example: 395 sign-in days CacheHelper. Add (cacheKey, cacheValue, cacheTime);} return cacheValue ;}
First, there is a problem with lock (cacheName), because the string is also shared, it will block other operations using this string. For details, refer to the previous blog post c # language-multi-thread lock system (1 ).
Update: because the string is temporarily stored by the Common Language Runtime Library (CLR), this means that there is only one instance for any given string in the entire program. So the second type is used.
The second type: lock (lockKey) can meet the requirements. In fact, the goal is to ensure the minimum granularity and global uniqueness of the lock, and only lock the query behavior of the current cache.
III. cache penetration
For example, we generally cache user search results. If the database cannot be queried, no cache will be performed. However, if this keyword is frequently queried, the database will be directly queried each time.
This makes no sense to cache, which is also a frequently asked cache hit rate problem.
Public object GetMemberSigninDays4 () {const int cacheTime = 5; const string cacheKey = "mushroomsir"; var cacheValue = CacheHelper. Get (cacheKey); if (cacheValue! = Null) return cacheValue; const string lockKey = cacheKey + "n (* ≧ ▽ ≦ *) n"; lock (lockKey) {cacheValue = CacheHelper. get (cacheKey); if (cacheValue! = Null) return cacheValue; cacheValue = null; // The database cannot be queried. it is null. // If (cacheValue2 = null) // {// return null; // generally, this parameter is null and is not cached. //} if (cacheValue = null) {cacheValue = string. empty; // if it is found to be Empty, set a default value and cache it. } CacheHelper. Add (cacheKey, cacheValue, cacheTime);} return cacheValue ;}
In this example, we cache the results that cannot be found. This avoids cache penetration when the query is empty.
Of course, we can also set a separate cache area for the first layer of control validation. So that it is separated from the normal cache.
IV. cache avalanche
Isn't it solved by locking the queue? In fact, the lock queue is only used to reduce the DB pressure and does not increase the system throughput.
In high concurrency: During cache reconstruction, you are locked and 1000 requests are blocked. Poor user experience and a waste of resources: congested threads can handle subsequent requests.
Public object GetMemberSigninDays5 () {const int cacheTime = 5; const string cacheKey = "mushroomsir"; // cache tag. Const string cacheSign = cacheKey + "_ Sign"; var sign = CacheHelper. Get (cacheSign); // Obtain the cache value var cacheValue = CacheHelper. Get (cacheKey); if (sign! = Null) return cacheValue; // return directly if it has not expired. Lock (cacheSign) {sign = CacheHelper. Get (cacheSign); if (sign! = Null) return cacheValue; CacheHelper. add (cacheSign, "1", cacheTime); ThreadPool. queueUserWorkItem (arg) => {cacheValue = "395"; // here the data is generally queried by SQL. Example: 395 sign-in days CacheHelper. Add (cacheKey, cacheValue, cacheTime * 2); // Set the cache time to 2 times for dirty read. });} Return cacheValue ;}
In the code, we use multiple cache-marked keys for dual-check lock verification. It is set to a normal time. after expiration, other threads are notified to update the cached data.
Because the actual cache time is set to 2 times, dirty data can still be used for front-end display.
This will increase the throughput of many systems.
V. Summary
Supplement: blocking other functions here refer to locking the same object in high concurrency.
In actual use, cache layer encapsulation is often more complicated. To update the cache, you can open a single thread to run these tasks. you can easily throw the thread pool.
The specific application scenarios can be balanced based on the actual number of users.