분산 잠금 및 동기화 잠금

여기에 이미지 설명 삽입

분산 잠금이란 무엇입니까?

분산 잠금은 공유 리소스에 대한 여러 노드의 액세스를 조정하기 위해 분산 시스템에서 사용되는 메커니즘입니다. 분산 시스템에서는 작업을 병렬로 실행하는 여러 노드의 존재로 인해 경쟁 조건 및 데이터 불일치가 발생할 수 있습니다. 분산 잠금은 한 노드만 동시에 잠금을 획득할 수 있도록 제한하여 공유 리소스에 대한 독점 액세스를 보장함으로써 이러한 문제를 해결합니다.

분산 잠금 구현은 일반적으로 다음 특성을 충족해야 합니다.

  1. 상호 배제: 동시에 하나의 노드만 잠금을 보유할 수 있으며 다른 노드는 잠금을 획득할 수 없습니다.

  2. 재진입: 동일한 노드가 교착 상태 없이 동일한 잠금을 여러 번 획득하도록 허용합니다.

  3. 내결함성: 잠금 보유자 노드 장애 또는 네트워크 이상 발생 시 잠금이 적시에 해제될 수 있습니다.

  4. 시간 초과 처리: 교착 상태 또는 장기 차단으로 인한 시스템 성능 저하를 방지하기 위해 잠금 획득을 위한 최대 대기 시간 설정을 지원합니다.

  5. 고가용성: 고가용성 성능으로 일부 노드에 장애가 발생하더라도 정상적으로 서비스를 제공할 수 있습니다.

분산 잠금을 구현하는 방법에는 여러 가지가 있으며 일반적인 방법은 다음과 같습니다.

  1. 데이터베이스 기반 분산 잠금: 데이터베이스 트랜잭션 및 고유 인덱스의 특성을 사용하고 데이터베이스 테이블을 사용하여 잠금 상태를 기록합니다. 상호 배타적인 액세스는 특정 행 잠금을 획득하고 해제함으로써 달성됩니다.

  2. 캐시 기반 분산 잠금: 분산 캐시(예: Redis)의 원자적 작업 및 만료 시간을 활용하고 잠금 상태를 나타내기 위해 전역적으로 고유한 키-값 쌍을 설정합니다. 잠금을 성공적으로 획득한 노드만이 이 키-값 쌍을 캐시에 설정할 수 있으며 다른 노드는 잠금을 설정하고 획득할 수 없습니다.

분산 잠금이란 무엇입니까?

동기화 잠금은 다중 스레드 환경에서 공유 리소스에 대한 액세스를 보호하는 데 사용되는 동시성 제어 메커니즘입니다. 여러 스레드가 동시에 크리티컬 섹션 코드에 액세스하는 것을 방지하여 동시 액세스로 인한 데이터 불일치 또는 충돌을 방지합니다.

동기화 잠금의 원리는 잠금을 획득하여 임계 영역의 코드에 대한 독점 액세스 권한을 얻는 것입니다. Java에서 일반적으로 사용되는 동기화 잠금 메커니즘에는 기본 제공 잠금(모니터 잠금이라고도 함)과 명시적 잠금이 포함됩니다.

  1. 기본 제공 잠금(Intrinsic Lock): 객체 모니터 잠금 또는 동기화 잠금이라고도 합니다. 메서드 또는 코드 블록이 synchronized데코레이트되면 개체에 기본 제공 잠금이 존재합니다. 잠금을 획득한 스레드만이 수정된 메서드나 코드 블록을 실행할 수 있으며 다른 스레드는 잠금이 해제될 때까지 기다려야 합니다.

샘플 코드:

public class Example {
    
    
    private Object lock = new Object();
    
    public void synchronizedMethod() {
    
    
        synchronized (lock) {
    
    
            // 临界区代码
        }
    }
}
  1. 명시적 잠금: java.util.concurrent.locksReentrantLock과 같은 패키지의 잠금 인터페이스 및 해당 구현 클래스를 사용합니다. 기본 제공 잠금과 비교할 때 명시적 잠금은 재진입, 공정성 및 조건 변수와 같은 보다 유연한 잠금 메커니즘을 제공하여 다중 스레드 코드를 보다 정확하게 제어할 수 있습니다.

샘플 코드:

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

동기화 잠금을 사용하면 여러 스레드에서 공유 리소스에 대한 안전하지 않은 액세스를 효과적으로 방지하고 데이터 일관성과 동시 실행의 정확성을 보장할 수 있습니다. 그러나 동기화 잠금을 과도하게 사용하면 스레드 경쟁 및 성능 저하가 발생할 수 있으므로 다중 스레드 응용 프로그램을 설계할 때 동기화 잠금 메커니즘을 합리적으로 사용할 필요가 있습니다.

두 종류의 잠금 사용 시나리오

분산 잠금 및 동기화 잠금은 서로 다른 환경에서 동시성 제어를 구현하는 데 사용되는 두 가지 메커니즘입니다.

  1. 분산 잠금 사용 시나리오:
    분산 잠금은 일반적으로 분산 시스템에서 여러 노드의 공유 리소스에 대한 액세스를 조정하는 데 사용됩니다. 일반적인 시나리오는 분산 작업 스케줄링, 분산 캐시 업데이트 등과 같은 분산 환경에서 고유한 리소스에 대한 독점 액세스를 달성하는 것입니다.

샘플 코드(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);
    }
}

위의 코드에서 setnx()메서드를 호출하여 잠금을 획득하려고 시도하고 반환 값이 1이면 분산 잠금이 성공적으로 획득되었음을 의미합니다. 잠금을 해제하는 작업은 del()잠금의 키를 삭제하는 메서드를 호출하는 것입니다.

  1. 동기화 잠금의 사용 시나리오:
    동기화 잠금은 주로 다중 스레드 프로그래밍에서 다중 스레드의 공유 리소스에 대한 동시 액세스를 제어하는 ​​데 사용됩니다. 고전적인 시나리오는 데이터 경합 및 충돌을 피하기 위해 다중 스레드 환경에서 코드의 중요한 섹션에 대한 배타적 액세스를 보호하는 것입니다.

샘플 코드(Java 내장 잠금을 기반으로 한 동기화 잠금):

public class SynchronizedCounter {
    
    
    private int count;

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

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

위의 코드에서 synchronized키워드는 메서드를 수정하는 데 사용되므로 이러한 메서드를 실행할 때 다중 스레드가 개체의 기본 제공 잠금을 자동으로 획득합니다. 이렇게 하면 동시 액세스를 피하면서 한 번에 하나의 스레드만 increment()or 메서드를 실행할 수 있습니다.decrement()

분산 잠금 구현

분산 잠금은 분산 시스템의 리소스에 대한 상호 배타적인 액세스를 구현하는 데 사용되는 메커니즘입니다. 다음은 두 가지 일반적인 분산 잠금 구현 방법을 소개합니다.

  1. 데이터베이스 기반 분산 잠금:
    데이터베이스를 분산 잠금의 영구 저장소로 사용하고 테이블에 고유 인덱스를 생성하거나 비관적 잠금을 사용하여 하나의 노드만 성공적으로 잠금을 획득할 수 있도록 합니다.

    // 获取分布式锁
    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) {
          
          
            // 处理异常
        }
    }
    

    위의 예에서 데이터베이스 테이블은 distributed_lock분산 잠금의 상태를 저장하는 데 사용됩니다. 하나의 노드만 해당 잠금 이름을 테이블에 삽입할 수 있도록 해당 SQL 문을 실행하여 잠금을 획득하고 해제합니다. 동일한 잠금 이름을 삽입하려는 다른 노드의 시도는 고유 인덱스 또는 비관적 잠금으로 인해 실패하므로 배타적 액세스가 가능합니다.

  2. Redis 기반 분산 잠금:
    Redis를 분산 잠금의 스토리지 센터로 사용하고 Redis의 원자적 작동 특성과 만료 시간을 사용하여 분산 잠금 획득 및 해제를 실현합니다.

    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) {
          
          
            // 处理异常
        }
    }
    

    위의 예에서 Redis set명령은 Redis에 잠금 정보를 원자적으로 저장하고 만료 시간을 설정하는 데 사용됩니다. 잠금을 획득할 때 고유한 요청 식별자를 전달 requestId하고 잠금을 해제할 때 해당 식별자의 유효성을 검사하면 잠금을 보유한 노드만 잠금을 해제할 수 있습니다.

이 두 가지 분산 잠금의 구현 방법에는 고유한 장단점이 있으며 적절한 방법의 선택은 특정 비즈니스 시나리오 및 시스템 요구 사항에 따라 다릅니다. 분산 잠금의 설계 및 구현은 여러 노드 간에 리소스에 대한 올바른 상호 액세스를 보장하기 위해 분산 환경의 동시성, 안정성 및 성능 문제를 고려해야 합니다.

추천

출처blog.csdn.net/weixin_53742691/article/details/131745340