Distributed lock usage scenario
Protect shared resources with locks, such as generating a unique serial number, ensuring sufficient inventory before placing an order in the e-commerce system, etc.
The core principle of RedLock algorithm:
N Redis master nodes that are completely independent and have no master-slave relationship are used to ensure that they will not go down at the same time in most cases. N is generally odd. A client needs to do the following to obtain a lock:
- Gets the current time in milliseconds.
- Use the same key and random value to request locks on N nodes in turn. In this step, when the client requests locks on each master, there will be a much smaller timeout than the total lock release time. For example, if the automatic lock release time is 10 seconds, the timeout time of lock request of each node may be in the range of 5-50 milliseconds, which can prevent a client from blocking a failed master node for a long time. If a master node is unavailable, we should try the next master node as soon as possible.
- The client calculates the time spent in acquiring the lock in the second step. Only when the client successfully acquires the lock ((N/2) +1) on most master nodes and the total time consumed does not exceed the lock release time, the lock is considered to have been successfully acquired.
- If the lock is obtained successfully, the automatic lock release time is the initial lock release time minus the time consumed to obtain the lock before.
- If the lock acquisition fails, the client will release the lock on each master node, even those locks that he thinks have not been acquired successfully, whether it is because less than half of the successfully acquired locks (N/2+1) or because the total time consumed exceeds the lock release time.
To put it simply, it means using multiple master nodes to obtain locks at more than half of the master nodes; Otherwise, it will fail and roll back – delete the locks acquired on all nodes before.
Source code
Maven dependency
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.15.5</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.25</version> </dependency>
application.yml
server: port: 8091 spring: redisson: address: 127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002 # Redis cluster address try-time: 0 #Try time lock-time: 4 #Lock time password: # redis password scanInterval: 1000 # Scan interval retryAttempts: 5 # Command failure retries timeout: 3000 # Timeout
Redistribution configuration class
@Configuration public class RedissonConfig { @Value(value = "${spring.redisson.address}") private String redissonAddress; @Value(value = "${spring.redisson.password}") private String redissonPassword; @Value(value = "${spring.redisson.scanInterval}") private int redissonScanInterval; @Value(value = "${spring.redisson.retryAttempts}") private int redissonRetryAttempts; @Value(value = "${spring.redisson.timeout}") private int redissonTimeout; @Bean public RedissonClient redissonClient() { String[] nodes = redissonAddress.split(","); for (int i = 0; i < nodes.length; i++) { nodes[i] = "redis://" + nodes[i]; } Config config = new Config(); config.useClusterServers() //This is the cluster server used .setScanInterval(redissonScanInterval) //Set cluster status scanning time .addNodeAddress(nodes).setRetryAttempts(redissonRetryAttempts).setTimeout(redissonTimeout); if (StringUtils.isNotEmpty(redissonPassword)) { config.useClusterServers().setPassword(redissonPassword); } return Redisson.create(config); } }
Controller test class
@RestController @RequestMapping("api/redisson" ) @Slf4j public class RedissonLockController { /** * Lock test shared variable */ private Integer lockCount = 10; /** * Lock free test shared variable */ private Integer count = 10; /** * Number of simulated threads */ private static int threadNum = 1000; @Autowired private RedissonClient redissonClient; /** * Simulate concurrent testing with and without locks * @return */ @GetMapping("/test") public void lock(){ // Counter final CountDownLatch countDownLatch = new CountDownLatch(1); for (int i = 0; i < threadNum; i ++) { MyRunnable myRunnable = new MyRunnable(countDownLatch); Thread myThread = new Thread(myRunnable); myThread.start(); } // Free all threads countDownLatch.countDown(); } /** * Lock test */ private void testLockCount() { RLock lock = redissonClient.getLock("myLock"); try { // Lock, set the timeout of 2s, and unlock automatically 10 seconds after locking boolean getLock = lock.tryLock(2000,10000, TimeUnit.MILLISECONDS); if (!getLock) { log.info("Current thread:[{}]No lock obtained", Thread.currentThread().getName()); return ; } lockCount--; log.info("lockCount Value:"+lockCount); }catch (Exception e){ log.error(e.getMessage(),e); }finally { // Release lock if(null != lock && lock.isLocked() && lock.isHeldByCurrentThread()){ lock.unlock(); } } } /** * Lockless test */ private void testCount() { count--; log.info("count Value:"+count); } public class MyRunnable implements Runnable { /** * Counter */ final CountDownLatch countDownLatch; public MyRunnable(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void run() { try { // Block the current thread until the timer value is 0 countDownLatch.await(); } catch (InterruptedException e) { log.error(e.getMessage(),e); } // Lockless operation //testCount(); // Locking operation testLockCount(); } } }
RedisLockApplication startup class
@SpringBootApplication public class RedisLockApplication { public static void main(String[] args) { SpringApplication.run(RedisLockApplication.class, args); } }
test
Access: http://localhost:8091/api/redisson/test , the console outputs the synchronized serial number as follows:
reference resources
https://redis.io/topics/distlock
https://github.com/redisson/redisson/wiki/8.-Distributed-locks-and-synchronizers
https://github.com/redisson/redisson/blob/master/redisson-spring-boot-starter/README.md
https://github.com/redis/jedis/wiki/Getting-started
https://docs.spring.io/spring-boot/docs/2.3.0.RELEASE/reference/htmlsingle/