[distributed application] distributed lock learning summary

Keywords: Redis Distribution Cache

catalogue

Application scenario:

Basic principle:

Implementation method 1:

Using redis as distributed lock

Implementation mode 2:

Using redistribution as a distributed lock

Application scenario:

In current distributed applications, there are often scenarios where high concurrent traffic accesses db. In order to prevent the database from being hung up, a cache layer is usually added above the db layer.

Redis is the most widely used cache in the market. As we all know, there are three common problems with redis cache:

  • Cache penetration: data with high concurrent access is not available in the database (db,cache) at all, resulting in traffic passing through the cache and directly hitting db
  • Cache avalanche: a large amount of data fails in the cache at the same time, resulting in traffic directly hitting db
  • Cache breakdown: high concurrency traffic accesses hot data. When hot data fails in the cache, high concurrency directly hits db

The solutions to these problems are often:

  • Cache penetration: set the empty result cache and set the expiration time so that nonexistent data can also use the cache
  • Cache avalanche: set the expiration time (plus random value) so that the data probability will not fail at the same time
  • Cache breakdown: lock. When the hot data fails in the cache, only one thread in the high concurrent traffic will enter the db and set the cache. Then the traffic can access the cache to obtain data

For locking in solving the third problem,

When there is no distributed lock, only similar to synchronize(this) is used   If local locks are added in this way, each service will use its own lock for distributed applications, resulting in many threads entering db in the whole distributed application, which may lead to unpredictable errors.

Therefore, it is necessary to add a distributed lock to lock the whole distributed application.

Basic principle:

At the same time, go to a place to "occupy the pit". If it is occupied, execute Luo Jiong, otherwise you must wait until the lock is released.

"Zhankeng" can go to redis, database and any place that everyone can access.

Waiting can use spin.

Implementation method 1:

Using redis as distributed lock

Idea:

Use redis's SET "lock"   The value NX method accounts for the pit. Here, we should pay attention to several points.

1. Set the expiration time to prevent setnx from occupying a good bit, abnormal business code or redis from downtime during business execution, resulting in no lock deletion logic and deadlock.

2. The set expiration time and placeholder must be atomic. redis supports the setnx ex command.

3. Because the business time may be very long and the lock itself has expired, we can delete it directly. It is possible to delete the lock held by others. Therefore, we need to specify the value of the lock as uuid, and everyone can delete the lock only after matching their own lock.

4. Continue with the previous one. If it is judged to be the current value and the lock is about to be deleted, the lock has expired and someone else has set it to a new value, then we are deleting someone else's lock. Therefore, it is necessary to ensure that the lock deletion is atomic and completed using the redis+Lua script.

//Fetching data from the database (using redis as a distributed lock)
    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
        //1. Account for distributed locks. Go to redis zhankeng
        // Setting the expiration time must be synchronized with locking to ensure atomicity (avoid deadlock)
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        if (lock) {
            System.out.println("Successfully obtained distributed lock");
            Map<String, List<Catelog2Vo>> dataFromDb;
            try {
                //Lock successfully, execute business
                dataFromDb = getDataFromDb();
            } finally {
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                //First go to redis to query and ensure that the current lock is your own
                //Get value comparison, comparison successfully deleted = Atomic lua script unlocked
                //Unlock
                redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
            }
            return dataFromDb;
        } else {
            System.out.println("Failed to acquire distributed lock...Wait for retry...");
            //Locking failed... Retry mechanism
            //Sleep for 100 milliseconds
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return getCatalogJsonFromDBWithRedisLock();     //Spin mode
        }
    }

Implementation mode 2:

Using redistribution as a distributed lock

Introduce redistribution dependency

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.16.0</version>
</dependency>

Redisson distributed reentrant lock based on Redis   RLock objects implement Java   Lock interface under concurrent package.

1, Configure RedissionClient

@Configuration
public class MyRedissonConfig {
    /**
     * All use of Redisson is through RedissonClient
     * @return
     * @throws IOException
     */
    @Bean(destroyMethod="shutdown")
    public RedissonClient redisson() throws IOException {
        //1. Create configuration
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.56.10:6379");

        //2. Create RedissonClient instance according to Config
        //Redis url should start with redis:// or rediss://
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}

2, Automatic injection and use  

    @Autowired
    private RedissonClient redisson;
    @ResponseBody
    @GetMapping(value = "/hello")
    public String hello() {

        //1. Get a lock. As long as the name of the lock is the same, it is the same lock
        RLock myLock = redisson.getLock("my-lock");

        //2. Lock
        myLock.lock();      
        
        try {
            System.out.println("Lock successfully, execute business..." + Thread.currentThread().getId());
            try {
                TimeUnit.SECONDS.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            //3. If the unlocking code is not running, will Redisson deadlock
            System.out.println("Release lock..." + Thread.currentThread().getId());
            myLock.unlock();
        }

        return "hello";
    }

be careful:

The lock is blocked waiting. The default expiration time of false locks is 30s.

The lock implemented by redistribution has a "watchdog" mechanism, which can automatically renew the lock. If the business is too long, the lock will be automatically renewed at runtime. There is no need to worry about the long business time, and the lock will automatically expire and be deleted. As long as the operation of the locked business is completed, the current lock will not be renewed. Even if it is not unlocked manually, the lock will automatically expire within 30s by default, and there will be no deadlock problem.

There are two locking methods, i.e. whether to specify the expiration time:

1,myLock.lock()

2,myLock.lock(10,TimeUnit.SECONDS)   // Automatic unlocking takes 10 seconds. The automatic unlocking time must be greater than the service execution time

For method 1: if we do not specify the timeout time of the lock, we use lockWatchdogTimeout = 30 * 1000 [watchdog default time]. As long as the lock is successfully occupied, a timing task will be started [reset the expiration time for the lock, and the new expiration time is the watchdog default time]. It will be automatically renewed every 10 seconds (internalLockLeaseTime), Continue for 30 seconds

//internalLockLeaseTime [watchdog time] / 3                       10 seconds

For mode 2: if we pass the lock timeout, we will send it to redis to execute the script to occupy the lock. The default timeout is the time we set, but there is a problem that the lock will not be automatically renewed when it reaches the time limit.

Posted by RockyShark on Thu, 28 Oct 2021 06:17:25 -0700