SpringBoot 는 Redis 를 사용 하여 분포 식 잠 금 을 실현 합 니 다.

머리말
단일 컴퓨터 응용 시대 에 우 리 는 공 유 된 대상 을 다 중 스 레 드 로 방문 할 때 자바 의 synchronized 키워드 나 ReentrantLock 류 를 사용 하여 작업 대상 에 자 물 쇠 를 추가 하면 대상 의 스 레 드 안전 문 제 를 해결 할 수 있 습 니 다.
분산 식 응용 시대 에 이 방법 은 통 하지 않 습 니 다.우리 의 응용 은 여러 대의 기계 에 배치 되 고 서로 다른 JVM 에서 실 행 될 수 있 습 니 다.한 대상 이 여러 대의 기계 의 메모리 에 동시에 존재 할 수 있 습 니 다.공유 대상 을 어떻게 하면 한 스 레 드 로 만 처리 하 느 냐 가 문제 가 될 수 있 습 니 다.
분포 식 시스템 에서 한 대상 이 높 은 병행 상황 에서 한 라인 만 사용 할 수 있 도록 하기 위해 서 우 리 는 JVM 을 뛰 어 넘 는 상호 배척 메커니즘 으로 공유 자원 의 접근 을 제어 해 야 한다.이때 우리 의 분포 식 잠 금 을 사용 해 야 한다.
분포 식 자 물 쇠 는 일반적으로 세 가지 실현 방식 이 있다.1.데이터 베 이 스 를 통 해 분포 식 자 물 쇠 를 실현 한다.2.캐 시(Redis 등)를 통 해 분산 잠 금 을 실현 한다.3.Zookeeper 를 통 해 분포 식 잠 금 을 실현 합 니 다.이 글 은 레 디 스 를 통 해 분산 잠 금 을 실현 하 는 두 번 째 방식 을 소개 한다.
분산 식 자물쇠 의 필요 조건
분포 식 자물쇠 의 가용성 을 확보 하기 위해 서 는 다섯 가지 조건 이 필요 합 니 다.
1.같은 시간 에 기계 한 대의 스 레 드 만 자 물 쇠 를 가 질 수 있 도록 보장 합 니 다.
2.자물쇠 가 생기 면 안 되 고 자 물 쇠 를 가 진 기계 가 무 너 지면 자동 으로 자 물 쇠 를 풀 수 있어 야 합 니 다.
3.자 물 쇠 를 효율적으로 획득 하고 방출 한다.
4.비 차단 성 을 가지 고 자 물 쇠 를 얻 지 못 하면 바로 자 물 쇠 를 추가 하 는 데 실패 합 니 다.
5.독점 성,즉 자신 이 추가 한 자 물 쇠 는 자신 만 이 풀 수 있 습 니 다.
코드 구현
구성 요소 의존
우선 pom.xml 파일 에 의존 도 를 추가 합 니 다:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
잠 금 코드
코드 는 다음 과 같 습 니 다:

/**
 *    
 * @param lockKey  
 * @param identity     (           )
 * @param expireTime       (  : )
 * @return
 */
public boolean lock(String lockKey, String identity, long expireTime){
  boolean lockResult = redisTemplate.opsForValue().setIfAbsent(lockKey, identity, expireTime, TimeUnit.SECONDS);
  return opsForValue;
}
잠 금 을 추가 하 는 방법 은 세 가지 인자 만 필요 합 니 다:lockKey,idenity,expireTime.
  • 첫 번 째 매개 변 수 는 lockKey 이 고 하나의 자원 은 하나의 유일한 key 에 대응 합 니 다.
  • 두 번 째 매개 변수 idenity 는 신분 표지 로 이 key 에 대응 하 는 value 저장 소 로 서 자 물 쇠 를 풀 때 자 물 쇠 를 넣 는 신분 과 같 는 지 판단 하고 다른 사람 이 자 물 쇠 를 풀 지 않도록 합 니 다.
  • 세 번 째 매개 변수 expireTime 은 만 료 시간 입 니 다.이 매개 변 수 는 프로그램 이 잠 금 을 추가 한 후 붕괴 되 어 주동 적 으로 잠 금 을 풀 수 없 을 때 자동 으로 잠 금 을 풀 어 잠 금 이 생기 지 않도록 합 니 다.
  • 왜 setIfAbsent 방법 을 사용 합 니까?이 방법의 장점 은 redis 에 이 key 가 존재 하면 실 패 를 되 돌려 주 고 redis 의 데 이 터 를 바 꾸 지 않 으 면 다른 스 레 드 의 자 물 쇠 를 덮어 쓰 지 않 는 다 는 것 이다.
    잠 금 해제 코드
    코드 는 다음 과 같 습 니 다:
    
    /**
     *    
     * @param lockKey  
     * @param identity     (           )
     * @return
     */
    public boolean releaseLock(String lockKey, String identity){
      String luaScript =
        "if " +
        "  redis.call('get', KEYS[1]) == ARGV[1] " +
        "then " +
        "  return redis.call('del', KEYS[1]) " +
        "else " +
        "  return 0 " +
        "end";
      DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
      redisScript.setResultType(Boolean.class);
      redisScript.setScriptText(luaScript);
      List<String> keys = new ArrayList<>();
      keys.add(lockKey);
      boolean result = redisTemplate.execute(redisScript, keys, identity);
      return result;
    }
    잠 금 을 푸 는 방법 은 두 개의 인자 만 필요 합 니 다:lockKey,idenity.
  • 첫 번 째 매개 변 수 는 lockKey 이 고 하나의 자원 은 하나의 유일한 key 에 대응 합 니 다.
  • 두 번 째 매개 변수 idenity 는 신분 표지 로 이 key 에 대응 하 는 value 저장 소 로 서 자 물 쇠 를 풀 때 자 물 쇠 를 넣 는 신분 과 같 는 지 판단 하고 다른 사람 이 자 물 쇠 를 풀 지 않도록 합 니 다.
  • 여기 서 Lua 스 크 립 트 를 사용 하여 신분 을 판단 합 니 다.신분 이 다 르 면 데 이 터 를 조작 하지 않 고 되 돌 릴 수 없습니다.Lua 스 크 립 트 를 왜 쓰 는 거 죠?이것 은 작업 의 원자 성 을 확보 하기 위해 서 입 니 다.redis 는 Lua 스 크 립 트 를 실행 할 때 스 크 립 트 를 명령 으로 실행 합 니 다.redis 의 명령 은 모두 원자 조작 이라는 것 을 알 고 있 습 니 다.그러면 작업 의 원자 성 을 보장 합 니 다.
    테스트 코드
    
    package com.qixi.lock.demo.lockdemo.controller;
    
    import com.qixi.lock.demo.lockdemo.util.RedisLock;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     *       
     * @author ZhengNC
     * @date 2020/5/13 17:27
     */
    @RestController
    @RequestMapping("test")
    public class TestRedisLockController {
    
      private final String lockKeyName = "testKey";
    
      @Autowired
      private RedisLock redisLock;
    
      /**
       *     
       * @param id      id
       * @param identity     
       * @return
       */
      @GetMapping("lock")
      public String lock(@RequestParam("id") String id,
                @RequestParam("identity") String identity){
        String lockKey = lockKeyName+":"+id;
        boolean lockSuccess = redisLock.lock(lockKey, identity, 60);
        String result = "lock failed";
        if (lockSuccess){
          result = "lock success";
        }
        return result;
      }
    
      /**
       *      
       * @param id       id
       * @param identity     
       * @return
       */
      @GetMapping("release")
      public String release(@RequestParam("id") String id,
                @RequestParam("identity") String identity){
        String lockKey = lockKeyName+":"+id;
        boolean releaseSuccess = redisLock.releaseLock(lockKey, identity);
        String result = "release failed";
        if (releaseSuccess){
          result = "release success";
        }
        return result;
      }
    }
    
    package com.qixi.lock.demo.lockdemo.util;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.script.DefaultRedisScript;
    import org.springframework.stereotype.Component;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.TimeUnit;
    
    /**
     *     Redis   
     * @author ZhengNC
     * @date 2020/5/13 17:27
     */
    @Component
    public class RedisLock {
    
      @Autowired
      private RedisTemplate<String, String> redisTemplate;
    
      /**
       *    
       * @param lockKey  
       * @param identity     (           )
       * @param expireTime       (  : )
       * @return
       */
      public boolean lock(String lockKey, String identity, long expireTime){
        boolean lockResult = redisTemplate.opsForValue().setIfAbsent(lockKey, identity, expireTime, TimeUnit.SECONDS);
        return lockResult;
      }
    
      /**
       *    
       * @param lockKey  
       * @param identity     (           )
       * @return
       */
      public boolean releaseLock(String lockKey, String identity){
        String luaScript =
            "if " +
            "  redis.call('get', KEYS[1]) == ARGV[1] " +
            "then " +
            "  return redis.call('del', KEYS[1]) " +
            "else " +
            "  return 0 " +
            "end";
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
        redisScript.setResultType(Boolean.class);
        redisScript.setScriptText(luaScript);
        List<String> keys = new ArrayList<>();
        keys.add(lockKey);
        boolean result = redisTemplate.execute(redisScript, keys, identity);
        return result;
      }
    }
    결어
    제 글 을 읽 어 주 셔 서 감사합니다. 제 문 제 를 지적 해 주신 것 을 환영 합 니 다.여기 서 토론 을 통 해 공동의 발전 을 이 룰 수 있 기 를 바 랍 니 다.
    이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

    좋은 웹페이지 즐겨찾기