레디 스 (3) - 분산 잠금 깊이 문의

원본 : 레디 스 (3) - 분산 잠금 깊이 문의

 


I. 서론 분산 잠금

잠금은 여러 실행 스레드를 해결하는 데 사용하는 기술입니다 공유 자원에 액세스 데이터의 도구 오류 또는 불일치를.

당신이 경우 집에 비해 서버를 넣어 , 다음 스레드는 가정 내부처럼 그들이 변기 시간과 같은 공통의 공유 리소스에 액세스 할 때 화장실 문에는 잠금 장치가없는 경우 더 나쁜 여전히 화장실 문을 설치하지 않은 .... .. 이것은 원칙의 문제가 될 것입니다 ..

잠금으로 마감, 우리는 본질적으로 마음의 그들보다 평화를, 사용하는 같은 시간이 하나의 가정 사용을 허용을 .

인터넷 세상의 발전과 함께, 모 놀리 식 응용 프로그램을 돌려 천천히 분산, 천천히 진화의 방향으로 이동, 높은 동시 인터넷의 복잡한 요구를 충족 점점 수 없게 한 가구 더 많은 수의 . 따라서 마찬가지로, 우리는 분산 응용 프로그램 사이의 공유 리소스에 동시 액세스의 문제를 해결하기 위해 분산 잠금 장치를 도입 할 필요가있다.

왜 우리는 분산 잠금이 필요합니까

정상적인 상황에서, 우리는 두 가지 시나리오가 있습니다 잠금 분산 사용

  1. 같은 작업을 다른 노드를 반복하지 않도록 : 예를 들어, 사용자는 서로 다른 노드가 여러 메시지를 보낼 수 있습니다 작업을 수행;
  2. 데이터의 정확성을 피하기 손상에 두 개의 노드가 데이터 오류 또는 불일치가 발생 될 수 있습니다 동일한 데이터에 동시에 작동하는 경우 경우;

자바 일반적인 방법은 구현

우리가 사용하는 위의 간단한 비유 잠금의 본질을 보여 한 번 만 사용자의 작업을 할 수 있습니다 . 그래서 이론적으로, 우리가 사용할 수있는 모든 도구의 요구에 부합 할 수 (즉, 다른 응용 프로그램은 우리가 잠겨 도움이 될 수 있습니다)를 :

  1. MySQL의에 본사를 둔 잠금 : MySQL은 그 자체가 내장 비관적 잠금이 for update키, 당신은 또한 자신의 목표를 달성하기 위해 자신의 비관적 / 낙관적 잠금을 달성 할 수있다;
  2. 기반 사육사는 노드 주문 : 사육사 그래서 클라이언트가 노드 목록을 얻을 때, 당신은 자식 노드의 현재 목록의 일련 번호를 확인할 수있는 잠금을 획득 할 수있는, 자식 노드를 만들 일시적으로 주문을 할 수를;
  3. 단일 스레드 레디 스 기준 : 레디 스 단일 스레드 때문에, 직렬 방식으로 실행되는 명령, 및 상기 이미지 자체에 제공되는 SETNX(set if not exists)명령들은, 그 자체가 상호 배타적되도록;

직관적으로 쉽게 이해할 수 있지만, 각 프로그램은 MySQL과 같은 자신의 장점과 단점을 가지고 있지만 추가 고려 사항이 구현해야 할 시간 제한 잠금 , 플러스 거래를 등등, 데이터베이스의 성능 제한, 그리고 우리가 논의하지 않은, 그래서 레디 스에 초점을 맞춘.

분산 잠금 문제를 레디 스

1) 잠금 시간 제한

지금 우리는 서비스 두 개의 병렬 서비스 AB, 있다고 가정 잠금을 획득 한 후 알 수없는 이유로 신비한 힘을 갑자기 끊었 후 B 서비스 잠금을 얻을 수 없을 것입니다 :

우리는 서비스의 가용성을 보장하기 위해 시간을 초과 설정하기위한 추가 시간이 필요합니다 그래서.

그러나 또 다른 문제를 온 : 잠금 사이의 논리 때문에 잠금 시간 제한을 넘어, 너무 오래 잠금 실행을 해제하는 경우 , 문제가 될 것입니다. 이때 첫번째 쓰레드 로크가 만료 보유 미리 번째 스레드가 로크를 동안 로직 임계 영역 코드의 임계 영역의 결과로 수행되지 않았기 때문에 엄격 직렬 수행 될 수 없다는 .

이 문제를 방지하려면 레디 스하지 오랜 시간 동안 잠금 작업을 분산 . 당신은 정말 가끔 수동 개입이 필요할 수 있습니다 작은 깨진 데이터에 의한 문제가있는 경우.

해결책이 조금 더 안전한 있습니다 잠그고 value값은 임의의 숫자로 설정 , 임의의 숫자 잠금 해제 한 다음 키를 제거하는 첫 경기는,이되는지 여부를 일치 현재 스레드가 다른 스레드가 해제되지 않습니다 잠금 소유하고 있는지 확인 하지 않는 한을 잠금이 만료 자동으로 서버를 출시 할 예정이다.

그러나 경기 value삭제 key레디 스에서 루아 스크립트가 할 수 있기 때문에, 처리하기 위해이 같은 루아 스크립트를 사용할 필요가있을 수 있습니다 더 유사한 보증 원자의 지시가없는, 원자 작동되지 않습니다 자성 실행 지침을 보장합니다 .

GC는 보안 문제가 발생할 수 있습니다 논의를 확장

마틴 Kleppmann는 받는 사람과 관련된 문제가있는 심도있는 토론을 실시 분산 잠금 보안 문제, 달성하기 위해 아버지 Antirez 레디 스 레디 스과 협력 GC를 .

학생들은 자바에 익숙 확실히 GC에 낯선되지 않으며, GC가 발생할 때 STW (정지 -에서 - 세계) 자체 가비지 컬렉터의 정상적인 실행을 보호하기 위해,하지만 다음과 같은 문제가 발생할 수 있습니다 :

서비스가 잠금을 획득하고 시간 제한을 설정하지만, 서비스 A와 STW는 STW 서비스의 완료 후,이 기간 잠금을 획득 할 수있는 서비스 B 동안 발표 된 분산 잠금 시간 종료의 결과로, 오랜 시간이 등장 재개 받는 주도 잠금, 동시에 서비스 A와 서비스 B 잠금에 도착 ,이 시간 분산 잠금 장치는 안전하지 않습니다.

레디 스, 사육사와 MySQL에 국한되지 같은 문제가 있습니다.

더 멜론 어린이 신발을 먹고, 다음 웹 사이트를 방문 레디 스 말했다 방법 Antirez의 아버지 볼 수 있습니다 http://antirez.com/news/101를

2) 단일 지점 / 멀티 포인트 문제

레디 스 독립 모드로 배포 된 경우, 레디 스 오류가,이 발생할 경우 전체 서비스를 사용할 수 있음을 의미합니다.

마스터 - 슬레이브 모드 배포를 사용하는 경우, 우리는 이러한 시나리오를 상상 : 서비스 및 A를 자물쇠로 신청 후, 호스트가 레디 스로 다운되면, 다음 서비스 B 응용 프로그램 잠금시는이 슬레이브 거기에서 얻을 것이다 잠금이 문제를 해결하기 위해, 레디 스 저자는 제안 RedLock 빨간색 잠금 알고리즘 (Jedis와 Redission을) :

// 三个 Redis 集群
RLock lock1 = redissionInstance1.getLock("lock1");
RLock lock2 = redissionInstance2.getLock("lock2");
RLock lock3 = redissionInstance3.getLock("lock3");

RedissionRedLock lock = new RedissionLock(lock1, lock2, lock2);
lock.lock();
// do something....
lock.unlock();

두, 레디 스 분산 잠금 구현

분산과 유사한 잠금 "구덩이를 차지"하고, SETNX(SET if Not eXists)우리는 한 번 봐 걸릴 명령은 하나의 작업입니다 만 클라이언트를 점유 할 수있다 소스 코드 (t_string.c / setGenericCommand를) 그것 :

// SET/ SETEX/ SETTEX/ SETNX 最底层实现
void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
    long long milliseconds = 0; /* initialized to avoid any harmness warning */

    // 如果定义了 key 的过期时间则保存到上面定义的变量中
    // 如果过期时间设置错误则返回错误信息
    if (expire) {
        if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != C_OK)
            return;
        if (milliseconds <= 0) {
            addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name);
            return;
        }
        if (unit == UNIT_SECONDS) milliseconds *= 1000;
    }

    // lookupKeyWrite 函数是为执行写操作而取出 key 的值对象
    // 这里的判断条件是:
    // 1.如果设置了 NX(不存在),并且在数据库中找到了 key 值
    // 2.或者设置了 XX(存在),并且在数据库中没有找到该 key
    // => 那么回复 abort_reply 给客户端
    if ((flags & OBJ_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
        (flags & OBJ_SET_XX && lookupKeyWrite(c->db,key) == NULL))
    {
        addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
        return;
    }
    
    // 在当前的数据库中设置键为 key 值为 value 的数据
    genericSetKey(c->db,key,val,flags & OBJ_SET_KEEPTTL);
    // 服务器每修改一个 key 后都会修改 dirty 值
    server.dirty++;
    if (expire) setExpire(c,c->db,key,mstime()+milliseconds);
    notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
    if (expire) notifyKeyspaceEvent(NOTIFY_GENERIC,
        "expire",key,c->db->id);
    addReply(c, ok_reply ? ok_reply : shared.ok);
}

레디 스 때문에 이전 버전에서, 실제로 상기 한 바와 같이 같이 SETNX하고 EXPIRE있지 원자 지시 때문에 실행에 문제가있을 것이다.

때문에,하지 여기에 해결 될 레디 스 트랜잭션을 사용하는 생각하지만, 수 EXPIRE명령에 의존 SETNX하지 않고 거래의 결과 if-else당신이 경우, 분기 논리를 SETNX잠금을 잡아하지 않았다, EXPIRE이 실행되지 않아야한다.

이 어려운 문제를 해결하기 위해, 레디 스 오픈 소스 커뮤니티는이 혼란을 제어하기 위해, 많은 분산 잠금 라이브러리를 등장 이후 버전은 추가하여, 2.8 레디 스 SET확장 매개 변수 설명 SETNX할 수와 EXPIRE함께 명령 실행 :

> SET lock:test true ex 5 nx
OK
... do something critical ...
> del lock:test

당신은 준수해야합니다 SET key value [EX seconds | PX milliseconds] [NX | XX] [KEEPTTL], 당신은 또한 바로 공식 참조 문서 아래 설정처럼 같은 형식 :

또한, 공식 문서는 SETNX문서 와 같은 생각 언급 : SETNX 키 <현재 유닉스 시간 + 잠금 제한 시간 + 1>에 설정된 값에 해당하는 , 그래서를 다른 클라이언트 액세스는 다음을받을 것인지 여부를 스스로 판단 할 수있을 때 값은 전술 한 형식의 잠금이다.

코드 구현

다음 Jedis는 다음의 아날로그 구현을 참조하여 아래에 키 코드 :

private static final String LOCK_SUCCESS = "OK";
private static final Long RELEASE_SUCCESS = 1L;
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";

@Override
public String acquire() {
    try {
        // 获取锁的超时时间,超过这个时间则放弃获取锁
        long end = System.currentTimeMillis() + acquireTimeout;
        // 随机生成一个 value
        String requireToken = UUID.randomUUID().toString();
        while (System.currentTimeMillis() < end) {
            String result = jedis
                .set(lockKey, requireToken, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
            if (LOCK_SUCCESS.equals(result)) {
                return requireToken;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    } catch (Exception e) {
        log.error("acquire lock due to error", e);
    }

    return null;
}

@Override
public boolean release(String identify) {
    if (identify == null) {
        return false;
    }

    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    Object result = new Object();
    try {
        result = jedis.eval(script, Collections.singletonList(lockKey),
            Collections.singletonList(identify));
        if (RELEASE_SUCCESS.equals(result)) {
            log.info("release lock success, requestToken:{}", identify);
            return true;
        }
    } catch (Exception e) {
        log.error("release lock due to error", e);
    } finally {
        if (jedis != null) {
            jedis.close();
        }
    }

    log.info("release lock failed, requestToken:{}, result:{}", identify, result);
    return false;
}
  • 아래에서 인용 참조 3 가 RedLock 구현 및 테스트, 찌를 수있는 어린이 신발에 관심이있는,

추천 도서

  1. 레디 스 자물쇠와 [공식 문서] 분산 - https://redis.io/topics/distlock
  2. 레디 스이 하나의 [항목] -! Https://www.wmyskxz.com/2018/05/31/redis-ru-men-jiu-zhe-yi-pian/
  3. Redission - 레디 스 자바 클라이언트 소스 코드 - https://github.com/redisson/redisson
  4. - Jedis 및 JedisPool 손으로 쓴 https://juejin.im/post/5e5101c46fb9a07cab3a953a을

참고 자료

  1. - 잠금 다시 누군가가 당신을 요청 분산,이 문서에서 그를 던져 https://juejin.im/post/5bbb0d8df265da0abd3533a5#heading-0
  2. 레디 스 자물쇠와 [공식 문서] 분산 - https://redis.io/topics/distlock
  3. - [시리즈] 분산 캐시 분산 잠금 레디 스 올바른 자세 달성 https://www.cnblogs.com/zhili/p/redisdistributelock.html을
  4. 레디 스 소스 분석 및 의견 (구) --- 문자열 명령 (T_STRING를) 달성 - https://blog.csdn.net/men_wen/article/details/70325566
  5. "레디 스 깊이 모험"- 키안 제품 / 앞으로
  • 이 문서는 내 Github에서 프로그래머 성장 시리즈에 포함 된 더 코드보다, 학습, [개 이상의 자바, 스타를 환영합니다 : https://github.com/wmyskxz/MoreThanJava
  • 개인 공공 번호 : wmyskxz, 각각의 독립적 인 도메인 이름의 블로그 : wmyskxz.com, 부착합니다 당신과 함께 성장하는 원래의 출력, 관심사, 2020 아래의 스캔 코드에!

당신이 수 많이 사람들이 감사 여기에서 볼 것을,이 문서가 잘 작성 당신이 생각하는 경우를, "나는 세 가지 마음이없는"작은 것들 , 다음 , 엄지 손가락을 추구주의를 추구 공유하고자하는 메시지를 찾을 수 있습니다!

쓰기 귀하의 지원과 인식, 쉬운 일이 아닙니다, 창조 내 최고의 동기 부여, 우리는 다음 문서를 참조하십시오!

추천

출처www.cnblogs.com/lonelyxmas/p/12514996.html