1. Introduction
(1) Basic principles and characteristics
In cluster mode, multiple jvms will have multiple lock monitors, so the lock added to a certain thread will not be seen by threads under other jvms.
The distributed lock will enable multiple jvms to share a lock monitor, making a lock visible to multiple processes.
Distributed lock: A lock that is visible and mutually exclusive to multiple processes in a distributed system or cluster mode.
(2) Implementation method
There are three common
2. Distributed lock based on redis
Use the setnx operation of redis to set a lock that is automatically released after a few seconds, the primary version of the distributed lock
However, such logic may also cause the problem of accidental deletion of locks, as shown in the figure
When thread 1 is blocked for too long for some reason, causing the lock to be released early, thread 2 gets the lock and starts to execute the task. If thread 1 ends the blocking and starts to execute the task and releases the lock, thread 2 will Lost lock. At this time, thread 3 gets the lock, and threads 2 and 3 operate at the same time.
Therefore, it is necessary to perform thread judgment before releasing the lock. Only when the lock is the lock of the current thread, the current thread can release the lock.
When storing the thread ID, in order to solve the problem of thread ID conflict, uuid is used as the thread ID.
In addition, thread blocking may also occur between [judging whether it is your own lock] and [releasing the lock], which will also cause a similar problem to the previous one. as shown in the picture
When I thought everything was going well, after judging that it was my own lock, just about to release it, thread 1 was blocked. In this way, the blocking time is too long, beyond the automatic release time of the lock expiration, thread 2 gets the lock and starts to execute the task. If at this time, thread 1 ends blocking, it will immediately release the lock indiscriminately, and thread 3 will take advantage of it and run in parallel with thread 2.
In order to make the operations of [judging whether it is your own lock] and [between releasing the lock] atomic, I first thought of redis transactions, but redis transactions are actually a kind of batch processing. If you put these two in one transaction, it may will cause the release to fail.
A better solution is: use Lua script (Lua is a lightweight and compact scripting language).
So, how to call redis in Lua language? In fact, Lua has strong scalability and provides a redis-specific interface to call redis. like
-- Execute set name jack redis.call('set','name','jack')
For more information, please check the rookie tutorial: Lua tutorial | rookie tutorial
The process of analyzing and releasing the lock is mainly: get the thread id in the current lock –> get the current thread id –> compare the same –> release the lock. write lua script
-- KEYS[1] here is the key of the lock, and ARGV[1] here is the current thread flag -- Obtain the mark in the lock, and judge whether it is consistent with the current thread mark if (redis. call('GET',KEYS[1]) == ARGV[1]) then -- Consistent, delete the lock return redis. call('DEL', KEYS[1]) end -- If inconsistent, return directly return 0
The Redis script uses the Lua interpreter to execute the script, and the common command to execute the script is EVAL.
Then in the java code, the api corresponding to eval is execute in stringRedisTemplate.
【Define interfaces and tool classes】
public interface ILock { boolean tryLock(long timeoutSec); void unlock(); }
public class SimpleRedisLock implements ILock{ private String name; private StringRedisTemplate stringRedisTemplate; public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate){ this.name=name; this.stringRedisTemplate=stringRedisTemplate; } private static final String KEY_PREFIX="lock:"; private static final String ID_PREFIX= UUID.randomUUID().toString(true) + "-"; private static final DefaultRedisScript<Long> UNLOCK_SCRIPT; static { UNLOCK_SCRIPT=new DefaultRedisScript<>(); UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua")); UNLOCK_SCRIPT.setResultType(Long.class); }//Initialization script @Override public boolean tryLock(long timeoutSec) { String threadId = ID_PREFIX + Thread.currentThread().getId(); Boolean success = stringRedisTemplate.opsForValue() .setIfAbsent(KEY_PREFIX + name, threadId , timeoutSec, TimeUnit.SECONDS); return Boolean.TRUE.equals(success); //If you directly return the success of the Boolean type, it will be unboxed, which will have a security risk } //Use lua script to make the release lock atomic @Override public void unlock() { //load lua script stringRedisTemplate.execute( UNLOCK_SCRIPT, Collections.singletonList(KEY_PREFIX + name),//The name of the current lock, passed into the script, get the thread id in the lock ID_PREFIX + Thread.currentThread().getId()//current thread id ); } //The original does not have atomic release lock operation /* @Override public void unlock() { // Get the current thread id String threadId = ID_PREFIX + Thread.currentThread().getId(); //Get the thread id in the current lock String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name); //Judge whether the current thread id is consistent with the thread id stored in the current lock if(threadId. equals(id)){ //freed stringRedisTemplate.delete(KEY_PREFIX + name); } }*/ }
Modify the original order code of flash coupons, call the distributed lock, and lock the order creation business.
//Coupons are enough to create an order Long userId = UserHolder. getUser(). getId(); //Create redis distributed lock instead of pessimistic lock SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate); boolean isLock = lock. tryLock(1200); if(!isLock){ //failed to acquire lock return Result.fail("Cannot place repeated orders"); } try { IVoucherOrderService proxy = (IVoucherOrderService) AopContext. currentProxy(); return proxy.createVoucherOrder(voucherId); } finally { lock. unlock(); }