forward from:
http://blog.csdn.net/forezp/article/details/70305336
This article is from Fang Zhipeng's blog
I wrote an article before "How to implement distributed locks in the springcloud distributed system? , because I just read the relevant books and consulted the relevant materials, I think that is feasible. The general idea of that article is to use the setNx command in conjunction with setEx. setNx is a time-consuming operation because it needs to query whether the key exists. Even if Redis has millions of qps, this operation is problematic in high concurrency scenarios. Regarding the implementation of distributed locks in redis , redis officially recommends the use of redlock.
1. Introduction to redlock
Distributed locks are a very useful technical means when different processes need to access shared resources mutually exclusive. There are three properties to consider to implement efficient distributed locks:
- Security properties: mutual exclusion, no matter when, only one client holds the lock
- Efficiency attribute A: no deadlock
- Efficiency attribute B: fault tolerance, as long as most redis nodes can work normally, the client can acquire and release locks.
Redlock is an algorithm officially proposed by redis to implement a distributed lock manager . This algorithm will be more secure and reliable than the general ordinary method. A discussion of this algorithm can be found in the official documentation .
2. How to use redlock with java
Introduce redis and redisson dependencies in the pom file:
<!-- redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- redisson--> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.3.2</version> </dependency>
The AquiredLockWorker interface class is mainly used for the logic that needs to be processed after acquiring the lock:
/** * Created by fangzhipeng on 2017/4/5. * The logic that needs to be processed after acquiring the lock */ public interface AquiredLockWorker<T> { T invokeAfterLockAquire() throws Exception; }
DistributedLocker gets the lock management class:
/** * Created by fangzhipeng on 2017/4/5. * Get the lock management class */ public interface DistributedLocker { /** * Get the lock * @param resourceName the name of the lock * @param worker processing class after acquiring the lock * @param <T> * @return The data to be returned after processing the specific business logic * @throws UnableToAquireLockException * @throws Exception */ <T> T lock(String resourceName, AquiredLockWorker<T> worker) throws UnableToAquireLockException, Exception; <T> T lock(String resourceName, AquiredLockWorker<T> worker, int lockTime) throws UnableToAquireLockException, Exception; }
UnableToAquireLockException , the exception class that cannot acquire the lock:
/** * Created by fangzhipeng on 2017/4/5. * exception class */ public class UnableToAquireLockException extends RuntimeException { public UnableToAquireLockException() { } public UnableToAquireLockException(String message) { super(message); } public UnableToAquireLockException(String message, Throwable cause) { super(message, cause); } }
RedissonConnector connection class:
/** * Created by fangzhipeng on 2017/4/5. * Get the RedissonClient connection class */ @Component public class RedissonConnector { RedissonClient redisson; @PostConstruct public void init(){ redisson = Redisson.create(); } public RedissonClient getClient(){ return redisson; } }
The RedisLocker class, which implements DistributedLocker:
import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; /** * Created by fangzhipeng on 2017/4/5. */ @Component public class RedisLocker implements DistributedLocker{ private final static String LOCKER_PREFIX = "lock:"; @Autowired RedissonConnector redissonConnector; @Override public <T> T lock(String resourceName, AquiredLockWorker<T> worker) throws InterruptedException, UnableToAquireLockException, Exception { return lock(resourceName, worker, 100); } @Override public <T> T lock(String resourceName, AquiredLockWorker<T> worker, int lockTime) throws UnableToAquireLockException, Exception { RedissonClient redisson= redissonConnector.getClient(); RLock lock = redisson.getLock(LOCKER_PREFIX + resourceName); // Wait for 100 seconds seconds and automatically unlock it after lockTime seconds boolean success = lock.tryLock(100, lockTime, TimeUnit.SECONDS); if (success) { try { return worker.invokeAfterLockAquire(); } finally { lock.unlock(); } } throw new UnableToAquireLockException(); } }
Test class:
@Autowired RedisLocker distributedLocker; @RequestMapping(value = "/redlock") public String testRedlock() throws Exception{ CountDownLatch startSignal = new CountDownLatch(1); CountDownLatch doneSignal = new CountDownLatch(5); for (int i = 0; i < 5; ++i) { // create and start threads new Thread(new Worker(startSignal, doneSignal)).start(); } startSignal.countDown(); // let all threads proceed doneSignal.await(); System.out.println("All processors done. Shutdown connection"); return "redlock"; } class Worker implements Runnable { private final CountDownLatch startSignal; private final CountDownLatch doneSignal; Worker(CountDownLatch startSignal, CountDownLatch doneSignal) { this.startSignal = startSignal; this.doneSignal = doneSignal; } public void run() { try { startSignal.await(); distributedLocker.lock("test",new AquiredLockWorker<Object>() { @Override public Object invokeAfterLockAquire() { doTask(); return null; } }); }catch (Exception e){ } } void doTask() { System.out.println(Thread.currentThread().getName() + " start"); Random random = new Random(); int _int = random.nextInt(200); System.out.println(Thread.currentThread().getName() + " sleep " + _int + "millis"); try { Thread.sleep(_int); } catch (InterruptedException e) { e.printStackTrace (); } System.out.println(Thread.currentThread().getName() + " end"); doneSignal.countDown(); } }
Run the test class:
Thread-48 start Thread-48 sleep 99millis Thread-48 end Thread-49 start Thread-49 sleep 118millis Thread-49 end Thread-52 start Thread-52 sleep 141millis Thread-52 end Thread-50 start Thread-50 sleep 28millis Thread-50 end Thread-51 start Thread-51 sleep 145millis Thread-51 end
From the running results, in the case of asynchronous tasks, it is true that the thread cannot be run until the lock is acquired. In any case, this is a solution officially recommended by redis, and the reliability is relatively high. If you have any questions, please leave a message.
3. References
https://github.com/redisson/redisson
"Redis Official Documentation" Building distributed locks with Redis
A Look at the Java Distributed In-Memory Data Model (Powered by Redis)