Talk about Redis's setnx published in 2015-09-14 in Redis, the so-called SETNX, is "set if not exists" abbreviation, that is, only when there is no time to set up, you can use it to achieve the effect of the lock, but many people do not realize that setnx have traps! For example: A query database interface, because the call volume is large, so add a cache, and set the cache expires after the refresh, the problem is when the concurrency is larger than the time, if there is no lock mechanism, then cache expired instantaneous, a large number of concurrent requests will penetrate the cache directly query the database, causing avalanche effect, if there is a lock mechanism, Then you can control only one request to update the cache, and the other requests will either wait for the situation or use an outdated cache. The following is an example of the most popular Phpredis extension in the PHP community, which implements a demo code: <?php$ok = $redis->setnx ($key, $value), if ($ok) {$cache->update (); $redis->del ($key);}? > When the cache expires, the lock is acquired through SETNX, and if successful, the cache is updated and then the lock is deleted. It seems that logic is very simple, but there is a problem: if the request execution exits unexpectedly for some reason, causing the lock to be created but the lock is not deleted, the lock will persist so that the cache is never updated again. So we need to add an expiration time to the lock safekeeping: <?php$redis->multi (); $redis->setnx ($key, $value); $redis->expire ($key, $TTL); $ Redis->exec ();? > Because SETNX does not have the ability to set expiration time, we need to use Expire to set up, and we need to wrap the two with multi/exec to ensure the atomic nature of the request, so that setnx success Expire failed. Unfortunately, there is a problem: When multiple requests arrive, although only one of the requested SETNX can be successful, but the Expire of any request can be successful, this means that even if the lock is not acquired, the expiration time can be refreshed, if the request is more dense, then the expiration time will be refreshed, Causes the lock to remain valid. So we need to perform the Expire conditionally while guaranteeing atomicity, and then we have the following Lua code: Local key = keys[1]local value = keys[2]local ttl = keys[3]local ok = Redis.call (' setnx ', key, value) if ok = = 1 then redis.call (' expire ', Key, TTL) end return OK I didn't expect to implement a seemingly simple function and use the Lua foot. Ben, it's a bit of a hassle. In fact, Redis has taken into account the plight of everyone, starting from 2.6.12, set covers the function of Setex, and the set itself already contains the function of setting expiration time, that is to say, the function that we need before can be implemented only with set. <?php$ok = $redis->set ($key, $value, Array (' NX ', ' ex ' = $ttl)), if ($ok) {$cache->update (); $redis->del ($key);}? > is the code perfect? The answer is almost as yet! Imagine if a request to update the cache for a long time, or even longer than the duration of the lock, resulting in the cache during the update process, the lock is invalidated, the other request will acquire the lock, but the previous request when the cache is updated, if you do not judge to delete the lock directly, There will be a case where the lock created by the other request is mistakenly deleted, so we need to introduce a random value when creating the Lock: <?php$ok = $redis->set ($key, $random, Array (' NX ', ' ex ' = = $ttl)); $ok) {$cache->update (); if ($redis->get ($key) = = $random) {$redis->del ($key); }}?> so basic to achieve a single-machine lock, if you want to achieve the distribution of locks, please refer to: Distributed locks with Redis, here is not in-depth discussion, summary: Avoid falling into the setnx trap the best way is never to use it!
Talk about the setnx of Redis