Distributed locks and synchronization locks

insert image description here

What is a distributed lock

A distributed lock is a mechanism used in a distributed system to coordinate multiple nodes' access to shared resources. In a distributed system, race conditions and data inconsistencies may arise due to the presence of multiple nodes executing tasks in parallel. Distributed locks solve these problems by restricting that only one node can obtain the lock at the same time, ensuring exclusive access to shared resources.

The implementation of distributed locks usually needs to meet the following characteristics:

  1. Mutual exclusion: Only one node can hold the lock at the same time, and other nodes cannot acquire the lock.

  2. Reentrancy: Allowing the same node to acquire the same lock multiple times without deadlock.

  3. Fault tolerance: In the event of a lock holder node failure or network anomaly, the lock can be released in time.

  4. Timeout processing: Support setting the maximum waiting time for acquiring locks to avoid system performance degradation due to deadlock or long-term blocking.

  5. High Availability: With high availability performance, services can be provided normally even if some nodes fail.

There are many ways to implement distributed locks, common ones include:

  1. Database-based distributed lock: use the characteristics of database transactions and unique indexes, and use database tables to record the status of locks. Mutually exclusive access is achieved by acquiring and releasing specific row locks.

  2. Cache-based distributed lock: Utilize the atomic operation and expiration time of distributed cache (such as Redis), and set a globally unique key-value pair to indicate the state of the lock. Only the node that successfully acquires the lock can set this key-value pair in the cache, and other nodes cannot set and acquire the lock.

What is a distributed lock

Synchronization lock is a concurrency control mechanism used to protect access to shared resources in a multi-threaded environment. It prevents multiple threads from accessing critical section code at the same time, thus avoiding data inconsistency or conflict caused by concurrent access.

The principle of synchronization lock is to obtain exclusive access to the code in the critical section by acquiring the lock. In Java, commonly used synchronization lock mechanisms include built-in locks (also known as monitor locks) and explicit locks.

  1. Built-in lock (Intrinsic Lock): Also known as object monitor lock or synchronized lock. When a method or code block is synchronizeddecorated, a built-in lock exists on the object. Only the thread that has acquired the lock can execute the modified method or code block, and other threads must wait for the release of the lock.

Sample code:

public class Example {
    
    
    private Object lock = new Object();
    
    public void synchronizedMethod() {
    
    
        synchronized (lock) {
    
    
            // 临界区代码
        }
    }
}
  1. Explicit Lock: Use java.util.concurrent.locksthe Lock interface and its implementation classes in the package, such as ReentrantLock. Compared with built-in locks, explicit locks provide more flexible locking mechanisms, such as reentrancy, fairness, and condition variables, making the control of multi-threaded code more precise.

Sample code:

public class Example {
    
    
    private Lock lock = new ReentrantLock();
    
    public void lockedMethod() {
    
    
        lock.lock();
        try {
    
    
            // 临界区代码
        } finally {
    
    
            lock.unlock();
        }
    }
}

The use of synchronization locks can effectively avoid unsafe access to shared resources by multiple threads, and ensure data consistency and correctness of concurrent execution. However, excessive use of synchronization locks may lead to thread competition and performance degradation, so it is necessary to use the synchronization lock mechanism reasonably when designing multi-threaded applications.

The usage scenarios of two kinds of locks

Distributed locks and synchronization locks are two mechanisms used to implement concurrency control in different environments.

  1. Distributed lock usage scenarios:
    Distributed locks are usually used in distributed systems to coordinate access to shared resources by multiple nodes. A common scenario is to achieve exclusive access to a unique resource in a distributed environment, such as distributed task scheduling, distributed cache update, etc.

Sample code (distributed lock based on Redis):

import redis.clients.jedis.Jedis;

public class DistributedLock {
    
    
    private Jedis jedis;
    private String lockKey;

    public boolean acquireLock() {
    
    
        long result = jedis.setnx(lockKey, "locked");
        return result == 1;
    }

    public void releaseLock() {
    
    
        jedis.del(lockKey);
    }
}

In the above code, setnx()try to acquire the lock by calling the method, if the return value is 1, it means that the distributed lock has been successfully acquired. The operation of releasing the lock is to call del()the method to delete the key of the lock.

  1. Usage scenarios of synchronization locks:
    Synchronization locks are mainly used in multi-threaded programming to control concurrent access to shared resources by multiple threads. A classic scenario is to protect exclusive access to a critical section of code in a multi-threaded environment to avoid data races and conflicts.

Sample code (synchronization lock based on Java built-in lock):

public class SynchronizedCounter {
    
    
    private int count;

    public synchronized void increment() {
    
    
        count++;
    }

    public synchronized void decrement() {
    
    
        count--;
    }
}

In the above code, synchronizedthe keyword is used to modify the method, so that the multi-thread will automatically acquire the built-in lock of the object when executing these methods. This ensures that only one thread can execute the increment()or decrement()method at a time, avoiding concurrent access

Distributed lock implementation

Distributed locks are a mechanism used to implement mutually exclusive access to resources in a distributed system. Two common distributed lock implementation methods are introduced below:

  1. Database-based distributed lock:
    use the database as the persistent storage of distributed locks, and ensure that only one node can successfully acquire the lock by creating a unique index in the table or using pessimistic locks.

    // 获取分布式锁
    public boolean acquireLock(String lockName) {
          
          
        try (Connection connection = dataSource.getConnection()) {
          
          
            String sql = "INSERT INTO distributed_lock (lock_name) VALUES (?)";
            PreparedStatement statement = connection.prepareStatement(sql);
            statement.setString(1, lockName);
            int affectedRows = statement.executeUpdate();
            return affectedRows > 0;
        } catch (SQLException e) {
          
          
            // 处理异常
        }
        return false;
    }
    
    // 释放分布式锁
    public void releaseLock(String lockName) {
          
          
        try (Connection connection = dataSource.getConnection()) {
          
          
            String sql = "DELETE FROM distributed_lock WHERE lock_name = ?";
            PreparedStatement statement = connection.prepareStatement(sql);
            statement.setString(1, lockName);
            statement.executeUpdate();
        } catch (SQLException e) {
          
          
            // 处理异常
        }
    }
    

    In the above example, a database table is used distributed_lockto store the state of the distributed lock. Acquire and release locks by executing corresponding SQL statements to ensure that only one node can insert the corresponding lock name into the table. Attempts by other nodes to insert the same lock name will fail due to unique indexes or pessimistic locks, thereby achieving exclusive access.

  2. Redis-based distributed lock:
    Use Redis as the storage center of distributed locks, and use Redis' atomic operation characteristics and expiration time to realize the acquisition and release of distributed locks.

    public boolean acquireLock(String lockKey, String requestId, int expireTime) {
          
          
        try (Jedis jedis = jedisPool.getResource()) {
          
          
            String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
            return "OK".equalsIgnoreCase(result);
        } catch (Exception e) {
          
          
            // 处理异常
        }
        return false;
    }
    
    public void releaseLock(String lockKey, String requestId) {
          
          
        try (Jedis jedis = jedisPool.getResource()) {
          
          
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
        } catch (Exception e) {
          
          
            // 处理异常
        }
    }
    

    In the above example, the Redis setcommand is used to atomically store the lock information in Redis and set the expiration time. By passing a unique request identifier when acquiring the lock requestId, and validating that identifier when releasing the lock, it is ensured that only the node holding the lock can release it.

The implementation methods of these two distributed locks have their own advantages and disadvantages, and the choice of the appropriate method depends on the specific business scenario and system requirements. The design and implementation of distributed locks need to take into account the issues of concurrency, reliability, and performance in a distributed environment to ensure correct mutual access to resources among multiple nodes.

Guess you like

Origin blog.csdn.net/weixin_53742691/article/details/131745340