In my own project recently, I met the demand for optimistic locking. But Redis does not have this operation, helpless, see memcache natural Support this concurrency primitive, namely: Gets and CAS operation. Therefore, the readiness to continue using Redis, the business is not so strong timing execution requirements, so you can use the algorithm without CAs to some extent to solve.
Why should we use this concurrency primitive? If it is a stand-alone version, we can solve the timing problem by locking the synchronization. But our application is distributed, stateless application servers are load balanced and deployed to multiple units. Locking also solves the sequential execution of multiple servers.
If you do not use CAs, you have the following scenario:
The first step, a takes out the data object X;
In the second step, B takes out the data object X;
In the third step, B modifies the data object X and puts it into the cache;
Fourth, a modifies the data object X and puts it into the cache.
We can see that the fourth step results in a data write conflict.
if the CAS protocol is used, it is the following scenario.
The first step, a takes out the data object X, and obtains to the CAS-ID1;
In the second step, B takes out the data object X and gets to the CAS-ID2;
In the third step, B modifies the data object X, before writing the cache, to check whether the Cas-id is consistent with the cas-id of the data in the cache space. The result is "consistent", which writes the modified X with Cas-id2 to the cache.
Fourth, a modifies the data object Y, before writing the cache, to check whether the Cas-id is consistent with the cas-id of the data in the cache space. The result is "inconsistent", the write is rejected, and the storage failure is returned.
We can resolve the failure of the fourth step setting by retrying, or by other business logic. no CAS program
Redis is generally used with this algorithm, which can be guaranteed to a certain degree of timing.
Setnx Key value
Set the value of key to value when and only if key does not exist.
return value:
Set success, return 1.
Setting failed, returns 0.
The initial solution:
Use the atomicity of the memcached add operation to control concurrency in the following ways:
1. Request a lock: Before verifying whether the activity was created, the add Operation key is key, and if the add operation fails, it means that there is another process that creates an activity for the key concurrently, and returns the creation failure. Otherwise, no concurrency is indicated
2. Execute the Create activity
3. Release Lock: After the creation activity is complete, delete the key by performing a delete operation.
if (memcache.get (key) = = NULL) {
//3 min timeout to avoid mutex holder crash
if (Memcache.add (Key_mutex, 3 * 60 * () = = True) {
value = Db.get (key);
Memcache.set (key, value);
Memcache.delete (Key_mutex);
} else {
sleep ();
Retry ();
}
}
Problem:
1.memcached stored in the value of the expiration date, that is, after the expiry of the automatic invalidation, such as add after M1, M1 failure, can be successful in this add
2. Even through the configuration, you can make memcached permanent effective, that is, there is no expiration date, memcached capacity limit, when the capacity is not enough will be automatically replaced, that is, it is possible to add over M1, M1 by the other key value replacement, then add can be successful.
3. In addition, the memcached is memory-based, the data will be lost after power-down, causing all MemberID to be re-add after reboot.
Solution Solutions
For the above problems, the root cause is that the add operation is timely, expired, replaced, restarted, will invalidate the original add operation. There are ways to solve this problem 1. Mitigate the effects of timeliness by using memcached CAS (check and set) mode.
The fundamentals of CAs
The basic principle is very simple, word, is the "version number." Each stored data object has more than one version number. We can understand from the following examples:
Final getsresponse<object> response = memcachedclient.gets ("key");
Boolean flag = Memcachedclient.cas ("Key", new Casoperation<object> () {
@Override public
int Getmaxtries ( {
//Retry count
return 1;
}
@Override Public
Object Getnewvalue (Long Currentcas,
object currentvalue) {
Oldcas = Response.getcas (); C11/>if (Checkcas ()) {//compare cas value value
= Db.get (key);
return value;
} else{
//throws an exception, or other business logic}}
);