Recently, I encountered the problem of distributed locking in my work. The normally used reentrantlock can no longer meet the needs of distribution. The current popular distributed lock zookeeper Redis is waiting on the market. Now I have learned the distributed lock of Redis briefly. I will use it first, and then I will understand the principle deeply, let alone say more.
Requirements for distributed locks
- Mutual exclusion: Distributed locks need to be mutually exclusive between threads on different nodes. This is fundamental.
- Re-accessibility: The same thread on the same node can acquire the lock again if it acquires it.
- Lock timeout: Lock timeout is supported as local locks to prevent deadlocks.
- High Availability: Locking and unlocking need to be efficient, while also ensuring high availability to prevent distributed lock failures, which can increase downgrades.
- Supports blocking and non-blocking: lock and trylock as well as tryLock(long timeOut) are supported as ReentrantLock.
- Supports fair and unfair locks (optional): Fair locks mean that locks are acquired in the order in which they are requested, whereas unfair locks are unordered. This is generally less implemented.
This is the most basic
About Redisson Lock
In fact, we all know that ReentrantLock already has good lock performance and implementation. It has good performance and implementation in mutually exclusive, reentrant, lock timeout, blocking support, fair lock support, but it is not suitable for distributed scenarios. redisson is a distributed lock to make up for this deficiency (there are many distributed locks, others, not discussed here)The RLock interface inherits the Lock interface and naturally elegantly implements the above lock requirements.
principle
Required jar package
<!-- redis rely on commons-pool This dependency must be added --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <!--Redis Distributed Lock--> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.11.0</version> </dependency>
[application.yml]
Clear structure than properties file [yml file recommended]
# Implement Redis Distributed Lock spring: redis: database: 0 host: Your hostname password: Your password port: 6379 lettuce: pool: max-active: 100 max-wait: -1 max-idle: 8 min-idle: 0
Note that this does not necessarily need to be configured by the specified name, so it can be customized
[RedissionConfig]
import lombok.extern.slf4j.Slf4j; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author Xiang * @date 2021/9/8 - 22:27 */ @Slf4j @Configuration public class RedissionConfig { @Value("${spring.redis.password}") private String password; @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private String port; @Value("${spring.redis.database}") private int database; @Bean public RedissonClient redissonClient() { Config config = new Config(); String REDISSON_PREFIX = "redis://"; String url = REDISSON_PREFIX + host + ":" + port; // Single Redis config.useSingleServer() .setAddress(url) .setPassword(password) .setDatabase(database); // The actual development process should be clustering or sentinel mode, for example, cluster //String[] urls = {"127.0.0.1:6379", "127.0.0.2:6379"}; //config.useClusterServers() // .addNodeAddress(urls); try { return Redisson.create(config); } catch (Exception e) { log.error("RedissonClient init redis url:[{}], Exception:", url, e); return null; } } }
[DistributedRedisLock]
import lombok.extern.slf4j.Slf4j; 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; /** * @author Xiang * @date 2021/9/8 - 22:36 */ @Slf4j @Component public class DistributedRedisLock { @Autowired RedissonClient redissonClient; // Locking public Boolean lock(String lockName) { if (null == redissonClient) { log.info("DistributedRedisLock redissonClient is null"); return false; } try { RLock lock = redissonClient.getLock(lockName); // Lock automatically released for 10 seconds lock.lock(10, TimeUnit.SECONDS); log.info("Thread [{}] DistributedRedisLock lock [{}] success Lock succeeded", Thread.currentThread().getName(), lockName); // Lock Success return true; } catch (Exception e) { log.error("DistributedRedisLock lock [{}] Exception:", lockName, e); return false; } } // Release lock public Boolean unlock(String lockName) { if (redissonClient == null) { log.info("DistributedRedisLock redissonClient is null"); return false; } try { RLock lock = redissonClient.getLock(lockName); lock.unlock(); log.info("Thread [{}] DistributedRedisLock unlock [{}] success Unlock", Thread.currentThread().getName(), lockName); // Lock released successfully return true; } catch (Exception e) { log.error("DistributedRedisLock unlock [{}] Exception:", lockName, e); return false; } } }
[LockTestController]
import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * @author Xiang * @date 2021/9/8 - 22:40 */ @Slf4j @RestController @RequestMapping("/lock") public class LockController { @Autowired DistributedRedisLock distributedRedisLock; AtomicInteger ID = new AtomicInteger(0); AtomicInteger ID1 = new AtomicInteger(0); // Test Do Not Release Lock @GetMapping("/testLock") public void testLock() { CyclicBarrier cyclicBarrier = new CyclicBarrier(5); for (int i = 0; i < 5; i++) { new Thread(() -> { // distributedRedisLock.lock(LOCK); try { System.out.println(ID.addAndGet(1)+"Enter Wait"); cyclicBarrier.await(); System.out.println("Start execution"); post(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }).start(); } } // Test Do Not Release Lock @GetMapping("/testLock1") public void testLock1() { CyclicBarrier cyclicBarrier = new CyclicBarrier(5); for (int i = 0; i < 5; i++) { new Thread(() -> { // distributedRedisLock.lock(LOCK); try { System.out.println(ID1.addAndGet(1)+"Enter Wait"); cyclicBarrier.await(); System.out.println("Start execution"); post1(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }).start(); } } // How distributed locks are used in real-world business development public void post() throws InterruptedException { final String LOCK = "LOCK2LOCK"; try { if (distributedRedisLock.lock(LOCK)) { log.info("No. e Two ready to start business logic"); TimeUnit.SECONDS.sleep(1); // Business logic log.info("No. e Two Start Business Logics"); TimeUnit.SECONDS.sleep(1); } else { // Handling logic that failed to acquire locks log.info("Failed to acquire lock"); } } catch (Exception e) { log.error("Handle exceptions:", e); } finally { distributedRedisLock.unlock(LOCK); TimeUnit.SECONDS.sleep(1); } } // How distributed locks are used in real-world business development public void post1() throws InterruptedException { final String LOCK = "LOCK1LOCK"; try { if (distributedRedisLock.lock(LOCK)) { // Business logic log.info("First Start Business Logic"); TimeUnit.SECONDS.sleep(1); } else { // Handling logic that failed to acquire locks log.info("Failed to acquire lock"); } } catch (Exception e) { log.error("Handle exceptions:", e); } finally { distributedRedisLock.unlock(LOCK); TimeUnit.SECONDS.sleep(1); } } }
The test program here uses apache-jmeter-5.4.1 for performance pressure testing