This article brings you relevant knowledge about redis, which mainly introduces what distributed locks are? How does Redis implement distributed locks? What conditions need to be met? Let’s take a look below, I hope it will be helpful to friends in need.
Distributed lock: A lock that is visible and mutually exclusive to multiple processes in a distributed system or cluster mode.
Conditions that distributed locks should meet:
There are three common distributed locks:
Mysql: mysql itself has a lock mechanism , but due to the average performance of mysql itself, when using distributed locks, it is actually relatively rare to use mysql as a distributed lock
Redis: redis is very common as a distributed lock This way of usage, now enterprise-level development basically uses redis or zookeeper as a distributed lock. Using the setnx method, if the key is inserted successfully, it means that the lock is obtained. If someone inserts successfully, and others fail to insert, it means that the lock cannot be obtained. Lock, use this set of logic to implement distributed locks
Zookeeper: zookeeper is also a better solution for implementing distributed locks in enterprise-level development
Two basic methods need to be implemented when implementing distributed locks:
Acquire lock:
Release lock:
Principle of distributed lock based on Redis:
SET resource_name my_random_value NX PX 30000
Using the atomicity of NX, when multiple threads are concurrent, only one thread can be set successfully. Successful setting means obtaining the lock. Subsequent business processing can be performed; if an exception occurs and the lock validity period expires, the lock is automatically released;
1. Define the ILock interface
public interface ILock extends AutoCloseable { /** * 尝试获取锁 * * @param timeoutSec 锁持有的超时时间,过期后自动释放 * @return true代表获取锁成功;false代表获取锁失败 */ boolean tryLock(long timeoutSec); /** * 释放锁 * @return */ void unLock(); }
2. Implement distributed lock based on Redis - RedisLock
public class SimpleRedisLock { private final StringRedisTemplate stringRedisTemplate; private final String name; public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) { this.stringRedisTemplate = stringRedisTemplate; this.name = name; } private static final String KEY_PREFIX = "lock:"; @Override public boolean tryLock(long timeoutSec) { //获取线程标识 String threadId = Thread.currentThread().getId(); //获取锁 Boolean success = stringRedisTemplate.opsForValue() .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS); return Boolean.TRUE.equals(success); } @Override public void unLock() { //通过del删除锁 stringRedisTemplate.delete(KEY_PREFIX + name); } @Override public void close() { unLock(); } }
Problem description:
Thread 1 holding the lock is blocked inside the lock. At this time, the lock is automatically released after timeout. At this time, thread 2 tries to obtain the lock. Then, during the execution of thread 2 holding the lock, thread 1 reacts and continues execution. When it comes to deleting the lock logic, the lock that should belong to thread 2 will be deleted. This is the case of accidentally deleting the lock.
Solution:
When storing the lock, put the identifier of your own thread. When deleting the lock, determine whether the identifier of the current lock is the one you saved. If it is entered, it will be deleted. If it is not, it will not be deleted.
public class SimpleRedisLock { private final StringRedisTemplate stringRedisTemplate; private final String name; public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) { this.stringRedisTemplate = stringRedisTemplate; this.name = name; } private static final String KEY_PREFIX = "lock:"; private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-"; @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); } @Override public void unLock() { // 获取线程标示 String threadId = ID_PREFIX + Thread.currentThread().getId(); // 获取锁中的标示 String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name); // 判断标示是否一致 if(threadId.equals(id)) { // 释放锁 stringRedisTemplate.delete(KEY_PREFIX + name); } } @Override public void close() { unLock(); } }
Problem analysis:
The above release The lock code still has the problem of accidentally deleting the lock. When thread 1 obtains the thread ID in the lock and determines that it is its own lock based on the ID, the lock is automatically released when it expires. It happens that thread 2 tries to acquire the lock and gets the lock. When thread 1 still performs the operation of releasing the lock, the lock held by thread 2 is accidentally deleted.
The reason is that the lock release process implemented by java code is not an atomic operation and has thread safety issues.
Solution:
Redis provides Lua script function. Writing multiple Redis commands in one script can ensure the atomicity of execution of multiple commands.
public class SimpleRedisLock implements ILock { private final StringRedisTemplate stringRedisTemplate; private final String name; public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) { this.stringRedisTemplate = stringRedisTemplate; this.name = name; } private static final String KEY_PREFIX = "lock:"; private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-"; @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); } @Override public void unLock() { String script = "if redis.call("get",KEYS[1]) == ARGV[1] then\n" + " return redis.call("del",KEYS[1])\n" + "else\n" + " return 0\n" + "end"; //通过执行lua脚本实现锁删除,可以校验随机值 RedisScript<Boolean> redisScript = RedisScript.of(script, Boolean.class); stringRedisTemplate.execute(redisScript, Collections.singletonList(KEY_PREFIX + name), ID_PREFIX + Thread.currentThread().getId()); } @Override public void close() { unLock(); } }
Recommended learning: "Redis Video Tutorial"
The above is the detailed content of Let's talk about the principle of distributed locks and how Redis implements distributed locks. For more information, please follow other related articles on the PHP Chinese website!