그것은 무엇? 레디 스 분산 잠금 장치를 작동?

저자 : 링 스토리

분산 잠금

1. 어떻게 분산 잠금입니다

분산 잠금은 분산 제어 시스템 사이의 공유 리소스에 대한 액세스를 동기화하는 방법입니다. 분산 시스템에서, 종종 자신의 행동을 조정해야합니다. 이러한 리소스에 액세스 한 후 때 다른 시스템 또는 하나 또는 다른 호스트 사이의 자원 세트 시스템과 공유, 종종이 경우, 일관성을 보장하기 위해 서로 간섭을 방지하기 위해 상호 배타적이 필요한 경우, 우리는 필요 분산 잠금을 사용합니다.

2. 사용하는 이유 분산 잠금

메소드 또는 속성을 보장하기 위해 단지 종래의 단일 독립형 애플리케이션 구축의 경우, 동시에 높은 동시성에서 동일한 스레드로 수행 될 수 있고, 자바 (예 ReentrantLock와 또는 동기하여) 관련 API를 처리하는 동시에 사용될 수있다 뮤텍스 제어 할 수 있습니다. 독립 실행 형 환경에서 자바는 API에 관련된 많은 동시 처리를 제공합니다. 그러나, 사업 개발의 ​​요구로, 원래 단일 컴퓨터 배포 시스템으로 인해 분산 시스템의 다중 스레드, 다중 프로세스에 분산 클러스터 시스템으로 발전하고, 다른 시스템에 분산하는 동시에 원래의 독립 실행 형 배포 아래의 것입니다 제어 잠금 전략은 실패 간단한 자바 API는 분산 잠금 할 수있는 기능을 제공하지 않습니다. 분산 잠금 문제가 해결 될 수있다 우리가 공유 리소스에 대한 액세스를 제어하는 ​​JVM에 걸쳐 상호 배제 메커니즘이 필요합니다이 문제를 해결하려면!

예를 들면 :

두 시스템은 가용성 성능, 동일에 기계 A는 시스템 B는 클러스터, A, 프로그램 B입니다.

, 그렇지 않으면 오류가 발생하지, A는 B 기계는 시간 초과 일 오후 2 당신은 시간 제한 작업을 수행 할 필요가 매일 가지고 있지만,이 작업의 타이밍은 한 번만 수행 할 수 있습니다 A는, B 실행에 두 개의 시스템이 필요한 때 잡아되지 실적이 잠금을, 잡고 자물쇠를 잡아, 최대 수행하지 마십시오!

3. 잠금 핸들

  • 사용 잠금 장치에 대한 하나의 응용 프로그램 (단일 프로세스 멀티 스레드)
synchronize
  • 분산 시스템 간의 자원에 대한 동기화 분산 잠금 액세스를 제어하는 ​​한 가지 방법

       분산 잠금 장치는 동일한 질문 분산 시스템의 제어 사이에 공유 자원 동기화입니다

분산 잠금 4. 구현

  • 낙관적 잠금의 데이터를 바탕으로 잠금 분산 달성 할
  • 분산 잠금 사육사의 임시 노드
  • 분산 잠금 레디 스를 기반으로

5. 레디 스 분산 잠금

  • 잠금 획득 :

집합 명령에서 명령의 동작을 수정하는 데 사용할 수있는 여러 옵션이 있습니다, set 명령 옵션의 기본 구문을 사용할 것입니다

레디 스 127.0.0.1:6379>SET KEY VALUE [EX 초] [PX 밀리 초] [NX | XX]

redis 127.0.0.1:6379>SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]

	- EX seconds  设置指定的到期时间(单位为秒)
	- PX milliseconds 设置指定的到期时间(单位毫秒)
	- NX: 仅在键不存在时设置键
	- XX: 只有在键已存在时设置

옵션 1 : 없음

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

		public static boolean getLock(JedisCluster jedisCluster, String lockKey, String requestId, int expireTime) {
        // NX: 保证互斥性
        String result = jedisCluster.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

옵션 2 :

public static boolean getLock(String lockKey,String requestId,int expireTime) {
     Long result = jedis.setnx(lockKey, requestId);
     if(result == 1) {
         jedis.expire(lockKey, expireTime);
         return true;
     }
     return false;
 }

참고 : 방법 2 setnx의 두 개의 작업을 만료가 원자 작동하지 않기 때문에 setnx 문제가 교착 상태가 발생할 경우 1 번, 그것은 추천 방법 (1)

  • 잠금을 해제 :

모드 1 : 달성하기 위해 델 명령

public static void releaseLock(String lockKey,String requestId) {
    if (requestId.equals(jedis.get(lockKey))) {
        jedis.del(lockKey);
    }
}
 

모드 2 : 권장 달성하기 위해 + 루아 스크립트를 레디 스

public static boolean releaseLock(String lockKey, String requestId) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return
redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey),
Collections.singletonList(requestId));
        if (result.equals(1L)) {
            return true;
}
        return false;
    }

6. 사육사 분산 잠금

분산 잠금을 달성 사육사 6.1 원리

자물쇠의 원리를 이해 한 후에는 사육사가 본질적으로 분산 잠금 자엽입니다 찾을 수 있습니다.

첫째, 각 노드 사육사, 발행 고유 일련 번호입니다.

당신이 아래의 각 노드에서 자식 노드를 만들 때, 단순히 창조의 유형이 주문한 선택한 다음 새 자식 노드 뒤에를 유형 (EPHEMERAL_SEQUENTIAL이 임시 또는 영구가 PERSISTENT_SEQUENTIAL을 주문 순서), 시퀀스 번호를 추가합니다. 시퀀스 번호는 시퀀스 번호 더하기에 발생

예를 들어, 노드는 "/ 테스트 / 잠금"의 빠 생성되고, 그는 같은 접두사를 "가정, 아래의 부모 노드에서 동일한 접두사로 자식 노드를 만들 수 있습니다, / 테스트 / 잠금 / seq- 아버지 노드이었다 당신이 자식 노드를 생성하고 표시 할 때 "종류를 주문한다. 자식 노드가 처음 생성되면, 자식 노드가 / 테스트 / 잠금 / 서열-0000000000 생성, 다음 노드 / 테스트 / 잠금 / SEQ-0000000001, 등등, 등등이었다.

[그림 체인이 실패 덤프 외부 소스 스테이션은 보안 체인 메커니즘을 가질 수있다, 바로 아래 업로드 한 사진을 저장하는 것이 좋습니다

그림 삽입 설명 여기

둘째, 증분 사육사 노드, 당신은 잠금을 얻을 노드의 최소 수를 지정할 수 있습니다.

사육사 분산 잠금, 당신은 부모를 만들 수있는 최초의 필요성, 지속적인 노드 (PERSISTENT 유형)으로 시도하고, 노드에서 노드의 임시 순서를 만들 각 스레드에 대한 잠금을 얻을 인해 증가 수에 행을 지정할 수 있습니다 잠금을 얻을 최소 번호. 따라서, 먼저 그는 현재 행 번호가 아닙니다 것으로 판단, 잠금을 차지하기 위해 시도하기 전에 각 스레드는 최소이기 때문에 경우 잠금을 획득 할 수 있습니다.

셋째, 사육사 노드 감지기구는 상기 잠금 순서화 효율적인의 손잡이를 보호 할 수있다.

각 스레드가 잠금을 압류하기 전에, 자신의 ZNode을 만들 수있는 첫 번째 숫자를 잡아. 잠금이 해제 될 때 마찬가지로, 당신은 Znode 잡아 번호를 제거해야합니다. 그렇지 않으면 성공적인 잡아 수 후, 가장 작은 정렬 노드는 통지를 대기 상태에있다. 그는 누구 통지? 다른 사람들은 단지 그것의 Znode 통지하기 전에 대기 할 필요가 없습니다. 현재 시간을 삭제 Znode 그들이 잠금을 보유하고있는 시간의 차례입니다. 제 통지 꽃 전사 드럼 차례 다시 같은 두 번째, 세 번째 제 통지.

사육사 노드 감지 메커니즘은 꽃 드럼 전송 정보 전달과 같은이를 달성하기 위해 완벽 할 수 있다고 할 수 있습니다. 노드의 znode 통지의 각각은 자신의 앞면과 자신의 즉시 해당 노드를 이전의 것을 정리 linsten 또는 시계 모니터를들을 필요가 같이 특정 방법이다. 오래 노드로 제거 될 때, 그것은 다시 그가 그 노드의 최소 번호가 아닌 있는지 판단하고, 만약 그렇다면, 잠금을 얻을.

노드가 사육사의 메커니즘을 청취 왜, 완벽하다고 할 수 있는가?

중간 컷 그것을 두려워하지 리어 모니터 앞에, 끝내는 원 스톱 끝? 예를 들어, 네트워크 또는 서버에 의한 분산 환경에서 끊었, 또는 다른 이유가 아니더라도 프로그램을 성공적으로 삭제할 수 노드의 앞에, 후자의 노드는 영원히 기다리지 않는다?

사실, 노드를 모니터링하기 위해 후자를 가능하게 할 것이다 사육사 내부 메커니즘은 일반적으로 삭제하고 잠금을 얻을 수 있습니다. 노드의 수를 가지고 만들기, 임시가 아닌 영구적를 만들려고 znode znode 노드 노드, 클라이언트와 사육사 클러스터 서버가 접촉을 잃은 znode 일단 임시 znode가 자동으로 삭제됩니다. 그 뒤에 해당 노드에서 온, 당신은 잠금을 얻기 위해 그렇게 같이 이벤트를 삭제받을 수 있습니다.

노드를 기울인다 메커니즘 사육사, 그것은 매우 완벽했다. 이유가있다.

후방 모니터 모드의 앞 끝이 종료 사육사 목축 회피. 소위 목축는 노드가 정지 할 때, 그래서 노드의 임시 순서가 각 노드는 끊고, 모든 노드가 듣고하려고하고 이렇게하면 서버에서 엄청난 압력을 만들 것이라는 점을 반영 할 것입니다 그냥 뒤에 하나 그 응답하기 전에 노드입니다.

실시 예 ### 6.2 잠금 분산 달성 할 사육사

사육사 분산 잠금은 임시 노드를 통해 얻을 수있다.

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.junit.Before;
import org.junit.Test;

/**
 * @ClassName ZookeeperLock
 * @Description TODO
 * @Author lingxiangxiang
 * @Date 2:57 PM
 * @Version 1.0
 **/
public class ZookeeperLock {
    // 定义共享资源
    private static int NUMBER = 10;

    private static void printNumber() {
        // 业务逻辑: 秒杀
        System.out.println("*********业务方法开始************\n");
        System.out.println("当前的值: " + NUMBER);
        NUMBER--;
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("*********业务方法结束************\n");

    }

    // 这里使用@Test会报错
    public static void main(String[] args) {
        // 定义重试的侧策略 1000 等待的时间(毫秒) 10 重试的次数
        RetryPolicy policy = new ExponentialBackoffRetry(1000, 10);

        // 定义zookeeper的客户端
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString("10.231.128.95:2181,10.231.128.96:2181,10.231.128.97:2181")
                .retryPolicy(policy)
                .build();
        // 启动客户端
        client.start();

        // 在zookeeper中定义一把锁
        final InterProcessMutex lock = new InterProcessMutex(client, "/mylock");

        //启动是个线程
        for (int i = 0; i <10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 请求得到的锁
                        lock.acquire();
                        printNumber();
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        // 释放锁, 还锁
                        try {
                            lock.release();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }

    }
}

7. 데이터 기반 잠금 분산

기반 데이터베이스 프로그램을 제외 할 때 종종 먼저 우리는 본능적으로이 프로그램이 충분하지 않다고 느낄 것이다 분산 잠금 장치의 사용을 논의 "수석." 캐시> 사육사, etcd> 데이터베이스 : 데이터베이스 프로그램의 성능에 따라 성능의 관점에서하지 우수한 전반적인 성능 비교를 수행합니다. 또한 프로그램 문제를 훨씬 덜 신뢰성을 기반으로 데이터베이스를 제안되었다. 데이터베이스 프로그램은 자주 작성을 동작 적합하지 않을 수 있습니다.

의 데이터베이스 (MySQL의) 프로그램을 기반으로 살펴 보자, 일반적으로 세 가지 범주로 나누어 : 기록을 테이블, 낙관적 및 비관적 잠금을 기반으로.

테이블 레코드를 기반으로 7.1

분산 달성하기 위해 잠금 가능한 가장 쉬운 방법은 바로 다음 표 작업의 데이터를 통해 실현 잠금 테이블을 만드는 것입니다. 우리는 잠금을 획득 할 때이 기록을 삭제하려면 잠금을 해제 할 때, 테이블에 레코드를 추가 할 수 있습니다.

더 나은 프리젠 테이션을 위해, 우리는 먼저 데이터베이스 테이블을 만들고, 다음을 참조하십시오 :

CREATE TABLE `database_lock` (
	`id` BIGINT NOT NULL AUTO_INCREMENT,
	`resource` int NOT NULL COMMENT '锁定的资源',
	`description` varchar(1024) NOT NULL DEFAULT "" COMMENT '描述',
	PRIMARY KEY (`id`),
	UNIQUE KEY `uiq_idx_resource` (`resource`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据库分布式锁表';
  • 잠금을 얻습니다

우리는 데이터의 조각을 삽입 할 수 있습니다 :

INSERT INTO database_lock(resource, description) VALUES (1, 'lock');

테이블 database_lock 자원이 고유 인덱스, 데이터베이스에 제출 그래서 다른 요청이기 때문에, 그것은 오류, 성공 삽입하지 않는 것, 단 하나의 삽입에 삽입 할 수 있습니다에 성공, 우리는 잠금을 얻을 것이다

  • 잠금을 해제
INSERT INTO database_lock(resource, description) VALUES (1, 'lock');

이 구현은 매우 간단하지만, 필요는 다음을 참고 :

  1. 잠금 실패로 이어질 것입니다 잠금 해제의 조작이 데이터베이스에 기록 된 후이 잠금 실패에 시간이 아니다, 다른 스레드가 잠금을 얻을 수 없습니다. 이 결함은 예를 들어, 정기적 정리에 시간 제한 작업을 수행 할 수 있습니다, 또한 좋은 솔루션입니다.
  2. 이 잠금은 데이터베이스의 신뢰성에 따라 달라집니다. 단일 점을 피하기 위해, 라이브러리에 의해 설정된 권장 더욱 신뢰성을 향상시킬 수있다.
  3. 이 잠금 때문에 직접 실패 오류 데이터를 삽입 한 후, 비 차단, 당신은 다시 운영의 필요성에 대한 잠금을 얻을합니다. 당신은 차단해야하는 경우 for 루프를 잡아 반면, 루프와 같은 INSERT 성공까지 다시 할 수 있습니다.
  4. 잠금이 해제되지 전에 동일한 스레드 레코드와 데이터베이스에 있기 때문에 이미 다시 잠금을 획득 할 수 없기 때문에이 잠금은 또한 비 재진입입니다. 현재 호스트 정보 및 스레드 정보가 될 수 있다면 재진입 잠금을 달성하고자, 당신은 먼저 데이터를 조회 할 수 있습니다 같은 잠금을 획득하는 시점에서 다시 등 호스트 정보 잠금, 스레드 정보에 대한 액세스와 같은 데이터베이스의 일부 필드를 추가 할 수 있습니다 발견, 직접 잠글 할당 할 수 있습니다.

7.2 낙관적 잠금

이름에서 알 수 있듯이,이 시스템은 대부분의 경우 업데이트 데이터가 데이터베이스 업데이트 작업이 제출 된 경우에만 충돌 감지 데이터를 물고기, 충돌없는 생각한다. 테스트 결과가 예상 데이터와 일치하지 나타날 경우 오류 정보가 반환됩니다.

[그림 체인이 실패 덤프 외부 소스 스테이션은 보안 체인 메커니즘을 가질 수있다, 바로 아래 업로드 한 사진을 저장하는 것이 좋습니다

가장 낙관적 잠금을 달성하기 위해, 기록 장치의 데이터 버전 (버전)에 기초한다. 어떤 데이터의 버전 번호? 데이터는 버전 ID를 추가하고, 데이터베이스 테이블의 "버전"필드를 추가하여 데이터를 판독 할 때 일반적으로 달성 될 데이터베이스 테이블 기반 솔루션의 버전이 서로 버전 읽기, 갱신 후, 이 버전 번호가 증가된다. 버전 번호와 일치가 업데이트가 실패 할 경우, 업데이트시 버전 번호가 일치하는 경우, 변경되지 않은, 그것은 성공적으로이 작업을 실행합니다, 비교됩니다.

더 나은 실제 프로젝트에 사용 낙관적 잠금 데이터베이스를 이해하려면, 여기에는 업계의 재고 일상의 예를 들었다. 사용자가 재고 작업의 구매 (1 개 대표가 판매 한 재고 마이너스) 할 때, 전자 비즈니스 플랫폼의 재고가있을 것입니다. 한 사용자가 데이터베이스를 운영하는 경우 자체는 사용자 작업의 정확성을 보장 할 수있을 것이며, 동시성의 경우 예상치 못한 문제가 발생합니다 :

예를 들어, 두 사용자가 제품을 구입, 데이터베이스 재고 수준의 실제 작업을해야 마이너스 2 명 작업,하지만 인해 높은 동시성의 경우에, 첫 번째 사용자는이 때문에, 데이터를 읽고 감소 현재 재고의 구매를 완료 작업이 완전히 구현 완료되지 않습니다. 두 번째 사용자는 일반적으로 단일 JVM 케이스 잠금 사용할 경우 인벤토리를 더티 데이터 [스레드] 안전 동작의 출현에 LED 소자 (1 개) 동작을 감소되지 않을 수 동일한 상품을 구입 이때의 재고를 확인 입사 내장 JAVA 이야기는 데이터베이스 수준에 초점을 맞추고에 나는 멀티 JVM의 경우, 분산 잠금 장치의 사용은 후자도 [메이크업]를 실감 할 수있을 것입니다 경우, 스레드 안전을 보장하고, Benpian 수 있습니다.

위의 문제를 들어, 데이터베이스 낙관적 잠금이 스레드 안전을 보장 할 수, 코드 수준은 보통 헤이 우리 모두가 할 :

select goods_num from goods where goods_name = "小本子";
update goods set goods_num = goods_num -1 where goods_name = "小本子";

SQL 위의 동시의 경우는,이 사항이 두 사람이 구입 3 후 상품으로 원래의 재고가 발생할 수 있습니다 때 일반적으로 첫째, 수정 재고를 뺀 goods_num 1을 운영 한 후 현재 goods_num을 확인하고, 집합입니다 상황이 남아있는 재고가 많은 상품을 판매 할 이어질 것입니다. 그런 다음 데이터베이스 낙관적 잠금은 그것을 달성하는 방법은?
우선, 각 조작이되고, 버전 필드는 버전 번호로서 사용되는 정의 :

select goods_num,version from goods where goods_name = "小本子";
update goods set goods_num = goods_num -1,version =查询的version值自增 where goods_name ="小本子" and version=查询出来的version;

사실, 또한 낙관적 잠금 얻을 수있는 업데이트 된 타임 스탬프 (updated_at) 및 버전의 사용과 유사한 방식으로 필드 : 업데이 트를 일선 캡처에 현재 업데이트 시간 여부를 감지하고 업데이트를 업데이트 제출하는 현재 업데이트 시간을 시작할 때 업데이트 타임 스탬프는 동일.

7.3 비관적 잠금

작업 데이터베이스 테이블이 될 수있는 기록 추가 및 삭제뿐만 아니라, 우리는 또한 분산 데이터베이스 잠금의 수단에 의해 달성 될 수는 잠금 장치와 함께 제공됩니다. UPDATE에 대한 쿼리의 증가 뒤에, 데이터베이스는 단독 잠금으로 알려진, 쿼리 프로세스에 비관적 잠금 데이터베이스 테이블로 증가 할 것이다. 레코드가 비관적 잠금을 추가하면, 다른 스레드는 더 이상에 비관적 잠금을 높이기 위해 전환되지 않습니다.

항상 최악의 경우를 가정 비관적 잠금, 반대와 낙관적 잠금, 그것은 대부분의 경우 업데이트 데이터가 충돌을 생산할 예정 간주합니다.

비관적 잠금을 사용하는 동안, 우리는 잠금의 수준에서 볼 필요가있다. 고정시의 MySQL 이노는 명시 적으로 지정된 행 잠금 그렇지 MySQL이 (전체 데이터 시트를 고정) 테이블 잠금을 수행한다 (단지 잠금 선택된 데이터)의 일차 키 (또는 인덱스)를 구현한다 일으켰 .

비관적 잠금을 사용하는 경우, 우리는 자동으로 업데이트를 수행 할 때, MySQL은 결과를 즉시 제출한다 MySQL의 기본 자동 커밋 모드 때문에, (아래 예 참조) 속성을 제출 MySQL 데이터베이스를 닫아야합니다.

mysql> SET AUTOCOMMIT = 0;
Query OK, 0 rows affected (0.00 sec)

따라서 사용이 다음 UPDATE 대응하는 서비스 로직의 로크를 획득하기 위해 수행 될 수 있고, 이후의 실행 후 잠금을 해제하기 위해 커밋.

우리는 어떤 특정 사용을 표현하기 위해 이전 database_lock 테이블을 계속하고 싶습니다. 잠금을 획득하고, 그것은 다음과 같이하고, 대응하는 동작을 실행 스레드 필요가있는 가정 :

- STEP1는 로크 획득 : database_lock FROM SELECT *를 WHERE ID = UPDATE 1].
STEP2 - 비즈니스 로직의 실행.
STEP3 - 잠금 해제 : COMMIT.

게시 된 280 개 원래 기사 · 원 찬양 1245 · 조회수 1,180,000 +

추천

출처blog.csdn.net/FL63Zv9Zou86950w/article/details/104896896