SpringBoot 는 Redis 를 통합 하여 분포 식 잠 금 을 정확하게 실현 하 는 예제 코드 입 니 다.

머리말
최근 블록 업 로드 를 하 는 업 무 는 업로드 과정 에서 블록 번 호 를 유지 하기 위해 레 디 스 를 사용 했다.
업로드 가 완 료 될 때마다 파일 의 블록 집합 을 가 져 오고 새로 업로드 한 번 호 를 추가 합 니 다.수 동 인터페이스 테스트 에서 문제 가 없습니다.전단 은 동시 업로드 호출 을 통 해 문제 가 발생 합 니 다.동시 다발 get 재 set 는 덮어 쓰기 현상 이 존재 하여 마지막 블록 데이터 가 잘못 되 어 블록 합병 요청 을 촉발 할 수 없습니다.
동시 다발 에 부 딪 히 면 먼저 자 물 쇠 를 잠 그 고 실행 코드 블록 에 JVM 자 물 쇠 를 추가 한 후에 문 제 는 해결 되 었 다.
곰 곰 이 생각해 보 니 그렇지 않 습 니 다.프로젝트 는 분포 식 으로 배치 되 었 고 부하 균형 을 이 루 었 습 니 다.한 노드 의 코드 가 잠 겨 있 습 니 다.다른 노드 에 문의 하 시 면 덮어 쓸 수 있 습 니까?문 제 를 해결 하지 못 했 습 니 다.
어 쩔 수 없어,분포 식 자 물 쇠 를 써 야 지.이전에 분포 식 자물쇠 의 이론 에 대해 잘 알 고 있 었 다.비교적 좋 은 응용 장면 이 없 으 면 구체 적 인 코드 를 쓰 지 않 았 고 이 기 회 를 틈 타 분포 식 자 물 쇠 를 사용 하 는 것 을 배 웠 다.
이론.
분산 식 잠 금 은 분포 식 시스템 간 에 공유 자원 을 동기 화 하 는 방식 이다.분포 식 시스템 에서 서로 다른 시스템 이나 같은 시스템 의 서로 다른 호스트 가 같은 자원 을 공유 하 는 문 제 를 해결 하기 위해 서로 배척 하여 프로그램의 일치 성 을 확보한다.

일반적인 실현 방식 은 세 가지 가 있다.
  • MySQL 의 비관 적 인 자 물 쇠 를 바탕 으로 분포 식 자 물 쇠 를 실현 합 니 다.이런 방식 은 가장 적 게 사용 합 니 다.이런 실현 방식 의 성능 이 좋 지 않 고 자물쇠 가 생기 기 쉬 우 며 MySQL 은 원래 업무 스트레스 가 많 기 때문에 자 물 쇠 를 만 드 는 것 도 적당 하지 않 습 니 다
  • Redis 를 바탕 으로 분포 식 잠 금 을 실현 하고 단기 판 은 setnx 로 실현 할 수 있 으 며 다 중 판 은 Radission
  • 을 권장 합 니 다.
  • ZooKeeper 를 바탕 으로 분포 식 잠 금 을 실현 하고 ZooKeeper 순서 임시 노드 를 이용 하여 실현 합 니 다.
  • 분포 식 자물쇠 의 사용 을 확보 하기 위해 서 우 리 는 적어도 자물쇠 의 실현 을 확보 하고 다음 과 같은 네 가지 조건 을 만족 시 켜 야 한다.
  • 상호 배척 성.임의의 시간 에 한 클 라 이언 트 만 자 물 쇠 를 가 질 수 있 습 니 다.
  • 자물쇠 가 생기 지 않 습 니 다.한 클 라 이언 트 가 자 물 쇠 를 가지 고 있 는 동안 붕괴 되 어 주동 적 으로 자 물 쇠 를 풀 지 않 더 라 도 후속 적 인 다른 클 라 이언 트 가 자 물 쇠 를 추가 할 수 있 도록 보장 할 수 있 습 니 다.
  • 용 착 성 이 있다.대부분의 Redis 노드 가 정상적으로 실행 되면 클 라 이언 트 는 잠 금 을 추가 하고 잠 금 을 풀 수 있 습 니 다.
  • 결자해지.잠 금 추가 와 잠 금 해 제 는 같은 클 라 이언 트 여야 합 니 다.클 라 이언 트 자신 은 다른 사람 이 추가 한 자 물 쇠 를 풀 수 없습니다.
  • 본 고 는 Redis 의 setnx 를 사용 하여 이 루어 졌 습 니 다.만약 에 Redis 가 다 중 컴퓨터 버 전이 라면 Radssion 을 알 아 볼 수 있 고 포장 이 특히 좋 으 며 공식 적 으로 추천 하 는 것 입 니 다.
    코드
    1.더 의지
    Spring Boot 와 Redis 통합 의 빠 른 사용 의존 도입
    
     <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-redis</artifactId>
     </dependency>
    2.설정 추가
    application.properties 에 Redis 연결 설정 추가
    
    spring.redis.host=xxx
    spring.redis.port=6379
    spring.redis.database=0
    spring.redis.password=xxx
    spring.redis.timeout=10000
    
    #   jedis   
    spring.redis.jedis.pool.max-active=50
    spring.redis.jedis.pool.min-idle=20
    3.레 디 스 의 서열 화 규칙 다시 쓰기
    기본적으로 사용 하 는 JDK 의 직렬 화 는 Redis 의 데 이 터 를 스스로 설정 하지 않 으 면 알 아 볼 수 없습니다.
    
    /**
     * @author Chkl
     * @create 2020/6/7
     * @since 1.0.0
     */
    @Component
    public class RedisConfig {
      /**
       *   RedisTemplate,       ,            
       * @param connectionFactory
       * @return
       */
      @Bean
      public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(connectionFactory);
        //   key value      
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Object.class));
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate;
      }
    }
    4.어떻게 자 물 쇠 를 정확하게 잠 그 는가
    직접 코드
    
    @Component
    public class RedisLock {
    
      @Autowired
      private StringRedisTemplate redisTemplate;
    
      private long timeout = 3000;
    
      /**
       *   
       * @param key    
       * @param value     
       * @return     
       */
      public boolean lock(String key, String value) {
        long start = System.currentTimeMillis();
        while (true) {
          //      
          if (System.currentTimeMillis() - start > timeout) {
            return false;
          }
          //  set  
          Boolean absent = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.MILLISECONDS);//1
          //       
          if (absent) {
            return true;
          }
          return false;
        }
      }
    }
    핵심 코드 는
    
    Boolean absent = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.MILLISECONDS);
    setIfAbsent 방법 은 명령 행 의 Setnx 방법 과 같 습 니 다.지정 한 key 가 존재 하지 않 을 때 key 에 지정 한 값 을 설정 합 니 다.
    매개 변 수 는 각각key、value、
  • key 는 이 자원 에 대한 유일한 표지
  • 를 나타 낸다.
  • value 는 이 라인 에 대한 유일한 표 지 를 나타 낸다.왜 키 가 생기 면 value 를 설정 해 야 합 니까?바로 네 가지 조건 을 만족 시 키 기 위 한 마지막:결자해지.키 와 value 의 조합 을 통 해서 만 잠 금 을 풀 때 같은 라인 으로 잠 금 을 풀 수 있 습 니 다
  • 시간 초과,setnx 와 함께 작업 해 야 하 며,setnx 가 끝 난 후에 실행 할 수 없습니다.만약 에 자 물 쇠 를 추가 하 는 데 성공 하면 기한 이 지나 기도 전에 지연 되 고 자 물 쇠 는 영원히 기한 이 지나 지 않 아 자물쇠 가 됩 니 다.
  • 5.어떻게 잠 금 을 정확하게 풀 수 있 습 니까?
    
    @Component
    public class RedisLock {
      @Autowired
      private StringRedisTemplate redisTemplate;
    
      @Autowired
      private DefaultRedisScript<Long> redisScript;
    
      private static final Long RELEASE_SUCCESS = 1L;
    
    
      /**
       *   
       * @param key    
       * @param value     
       * @return     
       */
      public boolean unlock(String key, String value) {
        //  Lua  :            ,     
        Long result = redisTemplate.execute(redisScript, Arrays.asList(key,value));
        //      
        return RELEASE_SUCCESS.equals(result);
      }
    
    
      /**
       * @return lua  
       */
      @Bean
      public DefaultRedisScript<Long> defaultRedisScript() {
        DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<>();
        defaultRedisScript.setResultType(Long.class);
        defaultRedisScript.setScriptText("if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end");
        return defaultRedisScript;
      }
    }
    잠 금 해제 과정 은 두 단계 의 조작 이 필요 하 다.
    1.조작 스 레 드 가 잠 금 스 레 드 인지 판단
    2.잠 금 스 레 드 를 추가 하면 잠 금 해제 작업 을 수행 합 니 다.
    이 두 단계 작업 도 원자의 조작 이 필요 하지만 Redis 는 이 두 단계 의 합병 작업 을 지원 하지 않 기 때문에 lua 스 크 립 트 를 사용 하여 원자 성 을 확보 해 야 합 니 다.
    만약 에 자 물 쇠 를 넣 는 스 레 드 라 고 판단 한 후에 자 물 쇠 를 풀 기 전에 자물쇠 가 만 료 되 었 고 다른 스 레 드 에 의 해 자 물 쇠 를 얻 었 다.이때 자 물 쇠 를 풀 면 다른 스 레 드 의 자 물 쇠 를 풀 고 결자해지 에 만족 하지 못 하 게 할 것 이다.
    6.실제 응용
    분산 잠 금 을 사용 할 때 파일 블록 을 저장 하 는 코드 가 없습니다.
    
    	/**
       *          redis
       * @param chunkNumber    
       * @param identifier       
       * @return        
       */
      @Override
      public Integer saveChunk(Integer chunkNumber, String identifier) {
      	// Redis             
        Set<Integer> oldChunkNumber = (Set<Integer>) JSON.parseObject(redisOperator.get("chunkNumberList_"+identifier),Set.class);
        //         ,      
        if (Objects.isNull(oldChunkNumber)) {
          Set<Integer> newChunkNumber = new HashSet<>();
          newChunkNumber.add(chunkNumber);
          redisOperator.set("chunkNumberList_"+identifier, JSON.toJSONString(newChunkNumber),36000);
          return newChunkNumber.size();
         //           ,       
        } else {
          oldChunkNumber.add(chunkNumber);
          redisOperator.set("chunkNumberList_"+identifier, JSON.toJSONString(oldChunkNumber),36000);
          return oldChunkNumber.size();
        }
      }
    존재 하 는 문 제 는 동시 다발 요청 이 들 어 오 면 같은 상태의 집합 을 가 져 와 수정 할 수 있 습 니 다.수정 한 후에 직접 기록 하여 같은 상태 에서 얻 은 집합 작업 스 레 드 를 덮어 쓰 는 현상 을 초래 할 수 있 습 니 다.
    분포 식 잠 금 을 사용 하면 한 개의 스 레 드 만 집합 을 얻 고 수정 할 수 있 으 며 덮어 쓰기 현상 을 피 할 수 있 습 니 다.
    분산 잠 금 코드 사용
    
    /**
       *          redis
       * @param chunkNumber    
       * @param identifier       
       * @return        
       */
      @Override
      public Integer saveChunk(Integer chunkNumber, String identifier) {
      	//  UUID                value
        String threadUUID = CoreUtil.getUUID();
        //  ,       :      ,  key,     UUID  value
        redisLock.lock(identifier,threadUUID);
        // Redis             
        Set<Integer> oldChunkNumber = (Set<Integer>) JSON.parseObject(redisOperator.get("chunkNumberList_"+identifier),Set.class);
        //         ,      
        if (Objects.isNull(oldChunkNumber)) {
          Set<Integer> newChunkNumber = new HashSet<>();
          newChunkNumber.add(chunkNumber);
          redisOperator.set("chunkNumberList_"+identifier, JSON.toJSONString(newChunkNumber),36000);
          //  
          redisLock.unlock(identifier,threadUUID);
          return newChunkNumber.size();
         //           ,       
        } else {
          oldChunkNumber.add(chunkNumber);
          redisOperator.set("chunkNumberList_"+identifier, JSON.toJSONString(oldChunkNumber),36000);
          //  
          redisLock.unlock(identifier,threadUUID);
          return oldChunkNumber.size();
        }
      }
    코드 에서 사용 하 는 공유 자원 표 지 는 파일 의 유일한 번호 identifier 로 잠 금 코드 세그먼트 의 유일한 자원,즉 key"chunkNumberList_"+identifier의 집합 을 표시 할 수 있 습 니 다.
    코드 에 사 용 된 스 레 드 의 유일한 표 지 는 UUID 로 잠 금 추가 와 잠 금 해제 시 얻 은 표지 가 중복 되 지 않도록 보장 합 니 다.
    SpringBoot 통합 Redis 가 분포 식 잠 금 을 정확하게 실현 하 는 예제 코드 에 관 한 이 글 은 여기까지 소개 되 었 습 니 다.더 많은 SpringBoot 통합 Redis 분포 식 잠 금 내용 은 우리 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 조회 하 시기 바 랍 니 다.앞으로 많이 지 켜 주세요!

    좋은 웹페이지 즐겨찾기