preface
In Java concurrent programming, we usually use synchronized and Lock thread locks. In Java, locks are only guaranteed to be valid for threads in the same JVM. In a distributed cluster environment, this is where we need to use distributed locks.
Implementation of distributed lock scheme
- Implement distributed lock based on database
- Distributed lock based on cache Redis
- Temporary serialized node based on Zookeeper to achieve distributed lock
Redis implements distributed locking
Scenario: In the case of high concurrency, there may be a large number of requests to the database to query the level 3 classification data, and this data will not change often. You can introduce cache to store the data queried from the database for the first time, and other threads can obtain data from the cache to reduce the database query pressure.
In a clustered environment, distributed locks can be used to control the number of times the database is queried.
Phase one
private Map<String, A List < Catelog2Vo > > getCatalogJsonDBWithRedisLock () {/ / to take positions in the Redis Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111"); Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb(); / / remove lock stringRedisTemplate. Delete (" lock "); return dataFromDb; }else {// spin lock // sleep 100ms try {thread.sleep (100); } catch (InterruptedException e) { e.printStackTrace(); } return getCatalogJsonDBWithRedisLock(); }}Copy the code
After obtaining the lock, we should confirm it in the cache again. If there is no lock, we need to continue the query. After retrieving the data from the database, we should put the data in the cache first, and then return the data.
Private Map<String, List<Catelog2Vo>> getDataFromDb() { If there is no need to continue to query String catalogJson = stringRedisTemplate. OpsForValue () get (" catalogJson "); if (! Stringutils.isempty (catalogJson)) {// Convert the deserialization to the specified object Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {}); return result; } system.out. println(" query database......") ); List<CategoryEntity> categoryEntityList = basemapper.selectList (null); List<CategoryEntity> leave1Categorys = getParent_cid(categoryEntityList, 0L); Map<String, List<Catelog2Vo>> listMap = leave1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), l1 -> { List<CategoryEntity> categoryL2List = getParent_cid(categoryEntityList, l1.getCatId()); List<Catelog2Vo> catelog2Vos = null; if (categoryL2List ! = null) { catelog2Vos = categoryL2List.stream().map(l2 -> { Catelog2Vo catelog2Vo = new Catelog2Vo(l2.getParentCid().toString(), null, l2.getCatId().toString(), l2.getName()); List<CategoryEntity> categoryL3List = getParent_cid(categoryEntityList, l2.getCatId()); if (categoryL3List ! = null) { List<Catelog2Vo.Catelog3Vo> catelog3Vos = categoryL3List.stream().map(l3 -> { Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName()); return catelog3Vo; }).collect(Collectors.toList()); catelog2Vo.setCatalog3List(catelog3Vos); } return catelog2Vo; }).collect(Collectors.toList()); } return catelog2Vos; })); String jsonString = json.tojsonString (listMap); stringRedisTemplate.opsForValue().set("catalogJson", jsonString, 1L, TimeUnit.DAYS); return listMap; }Copy the code
Phase two
private Map<String, A List < Catelog2Vo > > getCatalogJsonDBWithRedisLock () {/ / to take positions in the Redis Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111"); If (lock) {/ / to lock the execute business. / / set the expiration time stringRedisTemplate expire (" lock ", 3, TimeUnit. SECONDS); Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb(); / / remove lock stringRedisTemplate. Delete (" lock "); return dataFromDb; }else {// spin lock // sleep 100ms try {thread.sleep (100); } catch (InterruptedException e) { e.printStackTrace(); } return getCatalogJsonDBWithRedisLock(); }}Copy the code
Stage three
private Map<String, A List < Catelog2Vo > > getCatalogJsonDBWithRedisLock () {/ / to take positions in the Redis To ensure atomicity Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111",300,TimeUnit.SECONDS); Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb(); / / remove lock stringRedisTemplate. Delete (" lock "); return dataFromDb; }else {// spin lock // sleep 100ms try {thread.sleep (100); } catch (InterruptedException e) { e.printStackTrace(); } return getCatalogJsonDBWithRedisLock(); }}Copy the code
Phase four
private Map<String, List<Catelog2Vo>> getCatalogJsonDBWithRedisLock() { String uuid = UUID.randomUUID().toString(); / / to take positions in the Redis Guarantee atomicity Boolean lock. = stringRedisTemplate opsForValue () setIfAbsent (" lock ", uuid, 300, TimeUnit. SECONDS); Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb(); String s = stringRedisTemplate.opsForValue().get("lock"); If (uuid. Equals (s)) {/ / remove lock stringRedisTemplate. Delete (" lock "); } return dataFromDb; }else {// spin lock // sleep 100ms try {thread.sleep (100); } catch (InterruptedException e) { e.printStackTrace(); } return getCatalogJsonDBWithRedisLock(); }}Copy the code
Stage 5
private Map<String, List<Catelog2Vo>> getCatalogJsonDBWithRedisLock() { String uuid = UUID.randomUUID().toString(); / / to take positions in the Redis Guarantee atomicity Boolean lock. = stringRedisTemplate opsForValue () setIfAbsent (" lock ", uuid, 300, TimeUnit. SECONDS); Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb(); String s = stringRedisTemplate.opsForValue().get("lock"); 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"; Long lock1 = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class) , Arrays.asList("lock"), uuid); return dataFromDb; }else {// spin lock // sleep 100ms try {thread.sleep (100); } catch (InterruptedException e) { e.printStackTrace(); } return getCatalogJsonDBWithRedisLock(); }}Copy the code
A small summary
- StringRedisTemplate. OpsForValue (.) setIfAbsent (” lock “, uuid, 300, TimeUnit. SECONDS);
- StringRedisTemplate. Execute (new DefaultRedisScript (script, Long. Class), Arrays. The asList (” lock “), uuid);
- To implement distributed locks using Redis, the atomicity of lock adding and lock deleting should be guaranteed.
- How can I automatically renew a Redis lock whose expiration time is less than the service execution time?
- Set an expiration time that is longer than the service time
Redisson's watchdog mechanism
Redisson implements distributed locking
Redisson profile
Redisson is a Java in-memory Data Grid based on Redis. It not only provides a set of distributed Java common objects, but also provides a number of distributed services. These include BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Redisson provides the simplest and most convenient way to use Redis. The goal of Redisson is to promote the Separation of Concern for Redisso that users can focus more on business logic.
The principle of mechanism
Integrate the Spring Boot project
-
Introduction of dependencies [starter can be introduced in Spring Boot package]
<! -- https://mvnrepository.com/artifact/org.redisson/redisson --> <dependency> <groupId>org.redisson</groupId> < artifactId > redisson < / artifactId > < version > 3.12.0 < / version > < / dependency >Copy the code
-
Adding a Configuration Class
@Configuration public class MyRedissonConfig { @Bean(destroyMethod = "shutdown") public RedissonClient redissonClient(){ Redis :// Config Config = new Config(); Config. UseSingleServer (.) setAddress (" redis: / / 192.168.26.104:6379 "); RedissonClient RedissonClient = redisson.create (config); return redissonClient; }}Copy the code
Reentrant Lock
- Get a lock redissonClient.getLock(” my-lock “);
- Lock business code lock.lock();
- Unlock the lock, unlock ();
- The watchdog lock is automatically renewed
@ ResponseBody @ GetMapping ("/hello ") public String hello () {/ / 1, to get a lock, as long as the lock name, RLock lock = redissonClient.getLock("my-lock"); // The default lock is [watchdog time] 30s //1), the automatic renewal of the lock, if the service is too long, the lock will be automatically renewed during the running of a new 30s, do not worry about the long service time, the automatic expiration of the lock will be deleted //2), the current lock will not be renewed as long as the service is completed. Lock. Lock () is automatically deleted after 30 seconds by default, even if it is not unlocked manually. Try {system.out. println(" lock successful.......") +Thread.currentThread().getId()); Thread.sleep(30000); } catch (InterruptedException e) {}finally {// The lock is not deadlocked if the unlock is not performed, the lock has an expiration date and will be deleted lock.unlock(); System.out.println(" unlock successfully......" +Thread.currentThread().getId()); } return "hello"; }Copy the code
The lock method has an overloaded method lock(long leaseTime, TimeUnit unit)
public void lock(long leaseTime, TimeUnit unit) { try { this.lock(leaseTime, unit, false); } catch (InterruptedException var5) { throw new IllegalStateException(); }}Copy the code
** Note: when ** specifies an expiration time, the lock will not be automatically renewed. If there are multiple threads, the lock will be released when the expiration time expires, and other threads will fight for the lock, even if the business is still executing.
Comparison of two methods
-
If the expiration time is set, it will occur to execute the script to Redis to capture the lock and set the expiration time to the time we specify.
-
If no expiration time is set, the default watchdog time LockWatchdogTimeout 30 x 1000 is used
-
Only methods that do not specify an expiration date have automatic renewal
-
Automatic renewal implementation mechanism: As long as the lock is successful, it will automatically start a scheduled task [Reset the lock expiration time. The new expiration time is the default watchdog time], every 10 seconds [(internalLockLeasTime) /3] will automatically renew.
-
The machine holding the lock is down, because it is too late to renew, so the lock is automatically released, when the machine is restored again, because its background daemon thread is ScheduleTask, so it will immediately execute a watchDog renewal logic after recovery, during the execution process, it will feel that they have lost the lock, so there is no common holding problem.
Read-write lock ReadWriteLock
Ensure that the latest data can be read. During modification, the write lock is a mutex lock and the read lock is a shared lock.
- Write + read write lock is not released, read lock must wait
- Write + write blocking mode
- Read + Write Write lock Lock can be locked until the read lock is released
- Read + read is equivalent to concurrent read without lock
@ResponseBody @GetMapping("/write") public String writeLock(){ RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock"); RLock rLock = readWriteLock.writeLock(); String s = ""; try { rLock.lock(); System.out.println(" write lock lock success......" +Thread.currentThread().getId()); s = UUID.randomUUID().toString(); stringRedisTemplate.opsForValue().set("writeLock",s); Thread.sleep(30000); } catch (InterruptedException e) { e.printStackTrace(); }finally { rLock.unlock(); System.out.println(" Write lock released......") +Thread.currentThread().getId()); } return s; } @ResponseBody @GetMapping("/read") public String readLock(){ RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock"); RLock rLock = readWriteLock.readLock(); rLock.lock(); String s = ""; Try {system.out. println(" read lock successfully......") +Thread.currentThread().getId()); s = stringRedisTemplate.opsForValue().get("writeLock"); }catch (Exception e){ e.printStackTrace(); }finally {system.out. println(" read lock free......") +Thread.currentThread().getId()); rLock.unlock(); } return s; }Copy the code
The signal Semaphore
Use semaphores for distributed limiting
@ResponseBody @GetMapping("/park") public String park() throws InterruptedException { RSemaphore park = redissonClient.getSemaphore("park"); Boolean b = park.tryacquire (); If (b){}else {return "error"; } return "ok=>"+b; } @ResponseBody @GetMapping("/go") public String go() { RSemaphore park = redissonClient.getSemaphore("park"); Park.release (); park.release(); return "ok"; }Copy the code
Atresia CountDownLatch
Simulation scenario: Waiting for the class to leave, the security guard closed the school gate.
@ResponseBody @GetMapping("/lockdoor") public String lockDoor() throws InterruptedException { RCountDownLatch door = redissonClient.getCountDownLatch("door"); door.trySetCount(5); // Wait for the lock to complete door.await(); Return "holiday....." ; } @GetMapping("/gogogo/{id}") @ResponseBody public String gogogo(@PathVariable("id")Long id){ RCountDownLatch door = redissonClient.getCountDownLatch("door"); Count.countdown (); Return id+" Class gone...." ; }Copy the code
Redisson solves the Redis query problem above
* @return */ private Map<String, List<Catelog2Vo>> getCatalogJsonDBWithRedissonLock() { RLock lock = redissonClient.getLock("catalogJson-lock"); // This method blocks other threads from executing down until the lock is released; Map<String, List<Catelog2Vo>> dataFromDb = null; try { dataFromDb = getDataFromDb(); }finally { lock.unlock(); } return dataFromDb; }Copy the code
The last
Welcome to pay attention to the public number: the future has light, receive a line of large factory Java interview questions summary + the knowledge points learning thinking guide + a 300 page PDF document Java core knowledge points summary!