쳉 야 오진 고대 "세 축"메가 트론 수나라와 당나라 왕조, 기존의 I "세 가지 축이"큰 틈은 분산

많은 장면 분산 시스템에서, 우리는 최종 데이터 일관성은 분산 트랜잭션, 분산 잠금으로 지원하기 위해 기술 솔루션을 많이 필요로하는지 확인해야합니다.

때때로, 우리는 같은 스레드 만 동시에 실행할 수 있도록하는 방법이 필요합니다. 독립 실행 형 환경에서 자바는 사실, API에 관련된 동시 처리를 많이 제공하지만, 분산 시나리오에서 API는 아무것도 할 수 없습니다. 즉 단순히 자바 API 광고 분산 잠금 기능을 제공하지 않습니다.

현재 분산 잠금 장치의 구현을위한 다양한 프로그램이 있습니다 :

  1. 분산 데이터베이스 잠금의 구현을 기반으로
  2. 기반 캐싱 (레디 스, memcached를) 분산 잠금을 달성하기 위해
  3. 기반 사육사 분산 잠금을 달성

우리가 먼저 우리가해야 할 일에 대해 생각 구현의 이러한 유형을 분석하기 전에 잠금 방법의 종류 배포하는? (이 방법은 잠금, 예를 들면, 리소스를 로크 공감)

응용 프로그램 클러스터가 분산 배포 보장 할 수 있습니다, 같은 방법은 동시에 기계의 스레드에서 실행할 수 있습니다.

  • 재진입 잠금 (교착 상태를 피하기 위해) 경우이 잠금
  • 이 잠금은 (이 사업은 고려할 필요가 있는지 여부를) 가장 차단 잠금입니다
  • 이 잠금을 획득 매우 사용할 수 있으며 잠금 기능을 해제
  • 더 나은 성능 잠금 장치를 확인하고 잠금을 해제

A. 분산 데이터베이스 잠금의 구현을 기반으로

데이터베이스 테이블을 기반으로 1.1

분산 달성하기 위해 잠금 가능한 가장 쉬운 방법은 바로 다음 표 작업의 데이터를 통해 실현 잠금 테이블을 만드는 것입니다.

우리는 방법이나 자원 중 하나를 고정 할 때이 기록을 삭제하려면 잠금을 해제하고자 할 때, 우리는 테이블에 레코드를 추가 할 수 있습니다.

이러한 데이터베이스 테이블 만들기 :

CREATE TABLE `methodLock` (
    `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
    `method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名',
    `desc` varchar(1024) NOT NULL DEFAULT '备注信息',
    `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON 
    UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';

우리가 어떤 방법을 잠글 때, 다음과 같은 SQL을 수행합니다 :

insert into methodLock(method_name,desc) values (‘method_name’,‘desc’)

우리가 동시에 데이터베이스에 제출 여러 요청이있는 경우, 데이터베이스가 하나의 작업이 성공적으로 될 수 있도록 할 것이다 METHOD_NAME, 유일한 제약 조건을했기 때문에, 우리는 수행 할 수있는 방법의 잠금을 얻기 위해 해당 스레드에게 작업의 성공을 가정 할 수있다 메소드의 본문 내용.

방법이 완료되면, 우리는 다음과 같은 SQL을 수행 할 필요가 잠금을 해제하려면 :

delete from methodLock where method_name ='method_name'

위의이 간단한 실현은 다음과 같은 질문이 :

  • 이 잠금은 데이터베이스의 가용성에 크게 의존, 데이터베이스는 데이터베이스가 중지되면, 시스템이 서비스를 사용할 수없는 원인이됩니다, 하나의 포인트입니다.

  • 이 잠금 해제 작업이 실패하면, 그것은 잠금이 데이터베이스에 기록 된에, 다른 스레드가 다시 잠금을 얻을 수없는 이어질 것입니다, 만기 시간이 없다.

  • 삽입 작업 데이터를, 실패하면 직접 오류를 삽입하기 때문에이 잠금은, 비 차단 될 수 있습니다. 스레드가 잠금을하지 않고 다시 잠금을 획득하기 위해 트리거를 잠글 다시 가야, 큐 큐를 입력하지 않습니다.

  • 이 잠금이 비 재진입, 잠금이 해제되지 전에 동일한 스레드가 다시 잠금을 얻을 수 없습니다. 데이터의 데이터가 이미 존재하기 때문에.

물론, 우리는 또한 위의 문제를 해결하는 다른 방법이있을 수 있습니다.

를 들어 데이터베이스 하나의 포인트입니다 문제는 이전 두 개의 데이터베이스, 양방향 데이터 동기화 않습니다. 위로 정지하면 신속하게 백업 저장소로 전환합니다.

들어 실패 할 시간이 없다 ? 그냥 일반 일을 정기적으로 다시 데이터베이스 타임 아웃의 데이터를 정리합니다.

들어 비 블록 의? 삽입 성공적인 복귀 성공 때까지 while 루프에 참여.

대한 비 재진입 의? 현재 호스트 시스템 정보와 데이터베이스의 스레드 정보를 찾을 수있는 경우 잠금을 획득하기 위해 데이터베이스 테이블에서 필드를 현재 스레드에 대한 기계의 호스트 정보와 정보를 기록 추가, 당신은 잠금을 획득 한 후 다음 첫 번째 쿼리는 데이터베이스, 다음 지시 할 때 잠급 그에게 할당합니다.

1.2 기반 데이터베이스 단독 잠금

삭제 정보 테이블보다는 기록 동작에 추가하여, 실제로 잠금을 제공 할 수있는 데이터는 분산 로크를 달성하는 것을 의미한다.

우리는 또한 데이터베이스 테이블은 방금 만든 간다 사용합니다. 분산 잠금은 데이터베이스에 대한 배타적 잠금을 통해 달성 될 수있다. MySQL의 이노 엔진에 기초하여, 잠금 작동은 다음 방법을 사용하여 구현 될 수있다 :

public boolean lock(){
    connection.setAutoCommit(false)
    while(true){
        try{
            result = select * from methodLock where method_name=xxx 
            for update;
            if(result==null){
                return true;
            }
        }catch(Exception e){

        }
        sleep(1000);
    }
    return false;
}

쿼리 후 업데이트를 증가, 데이터베이스 쿼리 중 데이터베이스 테이블 단독 잠금으로 증가 할 것이다. 레코드가 배타적 잠금을 추가하면, 다른 스레드는 더 이상 배타적 잠금을 높이기 위해 라인에 기록 할 수 없습니다.

우리는 다음과 같은 방법으로 잠금 해제 방법의 실행 후, 비즈니스 로직 방법을 실행할 수, 잠금이 획득 될 때, 분산 잠금을 얻을 수있는 단독 잠금 스레드를 획득하는 생각할 수 있습니다 :

public void unlock(){
    connection.commit();
}

Connection.commit () 조작으로 잠금을 해제합니다.

문제를 효과적으로 해결할 수있는이 방법은 상술 한 잠금 및 차단 잠금을 해제 할 수 없다.

잠금을 차단 ? 업데이트 문이 성공적으로 실행 후 즉시 반환을 위해 성공할 때까지, 실패를 수행 할 때하는 것은, 차단 된 상태에있다.

잠금 후 서비스 다운 타임을 해제 할 수 없습니다? 이 방법, 데이터베이스 서비스 다운 타임이 해제 잠금을 소유 한 후.
하지만 여전히 단일 지점에서 직접 데이터베이스 재진입 문제를 해결할 수 없습니다.

1.3 요약

하나는 현재이 래치되어 있는지 여부를 확인하기 위해 테이블에있는 레코드의 존재입니다,이 두 가지 방법은, 데이터베이스의 테이블에 의존하는 잠금 장치를 사용하여 분산 데이터베이스를 구현하는 방법을 요약하면, 다른 하나는 데이터베이스를 통해입니다 단독 잠금 분산 잠금을 구현합니다.

분산 데이터베이스 잠금 구현 장점 : 데이터베이스를 통해 이해하기 쉽게 직접.

분산 데이터베이스 잠금 구현 환급 : 전체 구조가 더 복잡하게 만들 것입니다 문제를 해결하는 과정에서 다양한 문제가있을 것입니다.

데이터베이스의 운영이 특정 비용을 필요로 성능 문제를 고려해야합니다.

II. 분산 잠금 기반 캐시 구현

분산 데이터베이스 스키마를 구현하기 위해 비교하는 것은 잠금을 기반으로 기반 캐시는 조금 더 보여줍니다 성능면에서 달성했다. 그리고 많은 클러스터링 할 수 캐시를 배포, 단일 포인트 문제가 해결 될 수있다.

등등에 memcached와 레디 스 등 많은 정교한 캐싱 제품은 있습니다.

몇 가지 주요 포인트를 구현할 때주의해야 할 :

  1. 정보 교착 주도 스레드 잠금의 장기 보유를 할 수 없습니다, 제한 시간 만료 잠금해야합니다;

  2. 같은 시간, 단 하나 개의 스레드가 잠금을 획득합니다.

레디 스 명령에 대한 몇 가지 사용 :

SETNX (키, 값) : 키 - 값이 존재하지 않는 경우, 성공적으로 캐시에 합류 한 반환 "없습니다 종료 될 경우 세트", 그렇지 않으면 0을 반환합니다.

GET (키) : 전무가 반환하는 경우 값은 키 값에 대응하여 얻어진.

getset (키, 값) : 다음 이전 값이 새 값으로 업데이트됩니다 전무를,이 반환하는 경우 첫째, 키 값을 해당 값을 얻는다.

만료 (키, 초) : 초로 표현 유효한 키 - 값을 설정합니다.

흐름도 봐 :

쳉 야 오진 고대 "세 축"메가 트론 수나라와 당나라 왕조, 기존의 I "세 가지 축이"큰 틈은 분산

이 과정에서, 그것은 교착 상태로 이어질하지 않습니다.

나는의 코드 특정 구현을 살펴 보자, 레디 스 클라이언트 API로 Jedis을 사용했다.

(1) 우선 연결 풀 레디 스를 생성한다.

public class RedisPool {

    private static JedisPool pool;//jedis连接池

    private static int maxTotal = 20;//最大连接数

    private static int maxIdle = 10;//最大空闲连接数

    private static int minIdle = 5;//最小空闲连接数

    private static boolean testOnBorrow = true;//在取连接时测试连接的可用性

    private static boolean testOnReturn = false;//再还连接时不测试连接的可用性

    static {
        initPool();//初始化连接池
    }

    public static Jedis getJedis(){
        return pool.getResource();
    }

    public static void close(Jedis jedis){
        jedis.close();
    }

    private static void initPool(){
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(maxTotal);
        config.setMaxIdle(maxIdle);
        config.setMinIdle(minIdle);
        config.setTestOnBorrow(testOnBorrow);
        config.setTestOnReturn(testOnReturn);
        config.setBlockWhenExhausted(true);
        pool = new JedisPool(config, "127.0.0.1", 6379, 5000, "liqiyao");
    }
}

(2) 캡슐 포장 작업이 일부 구현을 필요로하는 API를 Jedis의, 분산 잠금 사용.

public class RedisPoolUtil {

    private RedisPoolUtil(){}

    private static RedisPool redisPool;

    public static String get(String key){
        Jedis jedis = null;
        String result = null;
        try {
            jedis = RedisPool.getJedis();
            result = jedis.get(key);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
            return result;
        }
    }

    public static Long setnx(String key, String value){
        Jedis jedis = null;
        Long result = null;
        try {
            jedis = RedisPool.getJedis();
            result = jedis.setnx(key, value);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
            return result;
        }
    }

    public static String getSet(String key, String value){
        Jedis jedis = null;
        String result = null;
        try {
            jedis = RedisPool.getJedis();
            result = jedis.getSet(key, value);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
            return result;
        }
    }

    public static Long expire(String key, int seconds){
        Jedis jedis = null;
        Long result = null;
        try {
            jedis = RedisPool.getJedis();
            result = jedis.expire(key, seconds);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
            return result;
        }
    }

    public static Long del(String key){
        Jedis jedis = null;
        Long result = null;
        try {
            jedis = RedisPool.getJedis();
            result = jedis.del(key);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
            return result;
        }
    }
}

(3) 분산 잠금 도구

public class DistributedLockUtil {

    private DistributedLockUtil(){
    }

    public static boolean lock(String lockName){//lockName可以为共享变量
    名,也可以为方法名,主要是用于模拟锁信息
        System.out.println(Thread.currentThread() + "开始尝试加锁!");
        Long result = RedisPoolUtil.setnx
        (lockName, String.valueOf(System.currentTimeMillis() + 5000));
        if (result != null && result.intValue() == 1){
            System.out.println(Thread.currentThread() + "加锁成功!");
            RedisPoolUtil.expire(lockName, 5);
            System.out.println(Thread.currentThread() + "执行业务逻辑!");
            RedisPoolUtil.del(lockName);
            return true;
        } else {
            String lockValueA = RedisPoolUtil.get(lockName);
            if (lockValueA != null && Long.parseLong(lockValueA) >= 
            System.currentTimeMillis()){
                String lockValueB = RedisPoolUtil.getSet(lockName, 
                String.valueOf(System.currentTimeMillis() + 5000));
                if (lockValueB == null || lockValueB.equals(lockValueA)){
                    System.out.println(Thread.currentThread() + "加锁成功!");
                    RedisPoolUtil.expire(lockName, 5);
                    System.out.println(Thread.currentThread() + "执行业务逻辑!");
                    RedisPoolUtil.del(lockName);
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        }
    }
}

III. 기반은 사육사 분산 잠금을 달성

분산 잠금 사육사 일시적으로 주문 노드는 달성 될 수있다. 특정 방법을 잠글 수있는 각 클라이언트가, 사육사 방법에 노드에 해당하는 지정된 디렉토리는 고유를 생성 일반적인 생각입니다

과도 노드를 명령했다. 잠금을 획득 할 것인지 여부를 결정하는 것은 매우 간단 주문 노드의 최소 결정 번호가 필요합니다. 잠금이 해제되면, 단순히이 노드 순간이 될 수 삭제합니다. 동시에, 잠금으로 인한 서비스 다운 타임이 해제되지 않을 수 있으며, 교착 상태가 생성 방지 할 수 있습니다.

위에서 언급 한 문제를 해결할 수없는 사육사 봐.

잠금 해제 할 수 없습니다?
사용 사육사 효과적으로 잠금을 만들 때의 클라이언트가 갑자기 잠금 (연결이 끊긴 세션)을 걸어 도착하면, 클라이언트가 임시 노드 ZK를 생성하기 때문에 잠금의 문제가 다음 임시 해제 할 수 없습니다 해결할 수 노드는 자동으로 삭제됩니다. 다른 클라이언트는 다시 잠금을 얻을 수 있습니다.

잠금을 비 차단?
사육사는, 클라이언트가 노드의 순서로 ZK를 만들 수있는 차단 잠금을 사용하여 노드에 리스너 바인딩을 달성 할 수, 노드 변경 한 번, 사육사가 클라이언트에 통지합니다, 노드를 확인할 수 있습니다 클라이언트는 자신이 현재없는 만들 자신이 잠겨 얻을 그렇다면 최소한의 모든 노드는, 당신은 비즈니스 로직을 수행 할 수 있습니다.

비 재진입 ?
효과적으로 클라이언트 노드 생성, 호스트 정보 및 현재 클라이언트 스레드 정보가 노드, 당신은 잠금 및 현재 최소 노드를 취득하려는 다음 시간에 직접 기록 재입국의 문제를 해결할 수없는 사육사를 사용하는 경우 이상의 데이터는 클릭합니다. 당신과 당신의 정보가 동일한 경우, 자신이 직접 잠금, 경우 동일하지 다음 큐에 참여 노드의 임시 순서를 만들 수 있습니다.

단일 포인트 문제 ?
사용 사육사는 효과적으로 우리는 서비스를 제공 할 수있는, 단일 지점의 문제가, ZK는 생존을 위해 클러스터에있는 기계의 절반 이상만큼, 클러스터에 배포 해결할 수 있습니다.
타사 라이브러리 큐레이터의 사육사 클라이언트로 사용할 수 있습니다, 클라이언트는 재진입 잠금 서비스를 캡슐화합니다.

public boolean tryLock(long timeout, TimeUnit unit) throws 
InterruptedException {
    try {
        return interProcessMutex.acquire(timeout, unit);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return true;
}
public boolean unlock() {
    try {
        interProcessMutex.release();
    } catch (Throwable e) {
        log.error(e.getMessage(), e);
    } finally {
        executorService.schedule(new Cleaner(client, path), 
        delayTimeForClean, TimeUnit.MILLISECONDS);
    }
    return true;
}

InterProcessMutex 큐레이터의 서비스는 분산 잠금을 달성하는 것입니다. 획득 된 사용자에있어서, 상기 잠금 해제를위한 잠금 해제 프로세스를 획득.

ZK는 분산 잠금을 사용하여 완전히 우리의 모든 기대 분산 잠금이 문서의 시작 부분에 맞춰 것으로 보인다 구현했습니다. 그러나, 사실이 아니라, 사육사는 잠금 서비스 성능이 캐시되지 않을 수 있습니다 단점 실제로이 실현 분산
너무 높이가. 의 생성 및 릴리스 잠금,시마다 수 있기 때문에 동적으로 생성 및 잠금을 달성하기 위해 과도 노드를 파괴했다. ZK 만들고 노드 만 리더 서버에서 수행 할 수 삭제, 데이터는 모든 시스템 플로워에 동일하지 않습니다.

개요

달성하기 위해 사육사 분산 잠금을 사용하는 이점 : 단일 지점의 효율적인 솔루션이 아닌 재진입 문제는, 비 블록 잠금 문제와 문제는 해제 할 수 없습니다. 구현이 비교적 간단.

성능을 달성하기 위해 캐시에 분산 잠금을 사용하는 것만 큼 좋지 않다 : 사육사 분산 잠금 단점을 사용하여 달성된다. 우리는 ZK의 원리를 이해할 필요가있다.

네의 비교. 세 계획

데이터베이스> 캐시> 사육사 : 감사 용이성의 관점 (로우에서 하이로)에서

보기의 구현 지점 복잡성 (낮은 높이로)에서 : 사육사> = 캐시> 데이터베이스

캐시> 사육사> = 데이터베이스 : (높음에서 낮음) 성능 관점에서

신뢰성의 관점에서 (내림차순에서) : 사육사> 캐시> 데이터베이스

그래서 개인적으로 달성하기 위해 더 많은 캐시를 사용하는 경향이, 우리는 레디 스 패키지 후속 기사를 기반으로 분산 잠금을 소유합니다.

추천

출처blog.51cto.com/14570694/2445387