Redis 분산 잠 금 기반 구현 (주파수 제한)
호텔 자원 시스템 은 주문 과 견적 을 조회 할 때 제3자 공급 업 체 시스템 을 호출 합 니 다.사용자 가 많 고 주문량 이 많 으 며 QPS 가 높 은 배경 이 있 기 때문에 자원 시스템 은 클 러 스 터 배치 방식 으로 12 대의 기 계 를 배 치 했 고 nginx 를 사용 하여 부하 균형 을 이 루 며 각 노드 에 골 고루 쳤 다.그러나 시스템 성능 요 소 를 위해 공급 업 체 인터페이스 에 주파수 제한 을 추가 해 분당 2000 회 를 넘 으 면 안 된다.
2. 수요 분석
자원 시스템 은 1 분 동안 모든 서비스 노드 를 합 친 호출 공급 업 체 인터페이스 요구 수 는 2000 을 초과 할 수 없다.
따라서 같은 시간 에 한 기계 의 다음 라인 에 만 실 행 될 수 있 도록 분포 식 자 물 쇠 를 추가 해 야 한다.
3. 분포 식 자 물 쇠 는 어떤 조건 을 갖 추어 야 합 니까?
(1) 같은 시간 에 한 방법 은 한 기계 의 다음 라인 에 만 실 행 될 수 있 도록 보증한다.(2) 높 은 사용 가능 한 잠 금 해제 와 잠 금 획득 (3) 고성능 의 잠 금 해제 와 잠 금 획득 (4) 은 재 접속 성 (재 접속 으로 이해 할 수 있 으 며, 한 스 레 드 는 여러 번 같은 방법 을 호출 하 며, 데이터 오 류 를 걱정 하지 않 아 도 된다) (5) 잠 금 체제 의 실 효 를 가지 고 있 으 며, 잠 금 (6) 이 차단 되 지 않 은 잠 금 기능 을 가지 고 있다. 즉, 잠 금 을 얻 지 못 하면 바로 잠 금 획득 실패 로 돌아 갈 것 이다.
4. 흔히 볼 수 있 는 분포 식 자물쇠 의 실현
5. Redis 분포 식 자물쇠 의 실현 원 리 를 바탕 으로
1. 자물쇠 추가
가장 쉬 운 방법 은 setnx 명령 입 니 다. key 가 존재 하지 않 는 다 면 이 스 레 드 가 자 물 쇠 를 성공 적 으로 가 져 왔 고 자 물 쇠 를 추가 하여 1 을 되 돌려 줍 니 다.키 가 존재 하면 잠 금 쟁탈 에 실 패 했 음 을 설명 하고 0 을 되 돌려 줍 니 다.호텔 방 상태 인 터 페 이 스 를 가 져 오 면 key = hoteld + checkIn + checkOut + 입주 인원 num, 위조 코드 는 다음 과 같 습 니 다.
setnx(key,value)
2. 잠 금 해제
자물쇠 가 있 으 면 자물쇠 가 있어 야 한다.잠 긴 스 레 드 가 작업 을 마 쳤 을 때 다른 스 레 드 가 들 어 갈 수 있 도록 잠 금 을 풀 어야 합 니 다.자 물 쇠 를 풀 수 있 는 가장 쉬 운 방법 은 del 명령 을 실행 하 는 것 입 니 다. 위조 코드 는 다음 과 같 습 니 다.
del(key)
3. 잠 금 시간 초과
만약 에 자 물 쇠 를 얻 은 스 레 드 가 작업 을 수행 하 는 과정 에서 끊 어 지면 자 물 쇠 를 명시 적 으로 풀 지 못 하면 이 자원 은 영원히 잠 겨 있 고 다른 스 레 드 는 더 이상 들 어 올 생각 을 하지 마 세 요.따라서 setnx 의 key 는 시간 초과 시간 을 설정 하여 명시 적 으로 풀 리 지 않 더 라 도 이 자 물 쇠 는 일정 시간 후에 자동 으로 풀 려 나 야 합 니 다.setnx 는 시간 초과 인 자 를 지원 하지 않 기 때문에 추가 명령 이 필요 합 니 다. 의사 코드 는 다음 과 같 습 니 다.
if(setnx(key,value) == 1){
expire(key,30)
try {
do something ......
} finally {
del(key)
}
}
상기 코드 에 존재 하 는 치 명 적 인 문제:
현상 1: setnx 와 expire 의 비 원자 성
· 노드 1 이 setnx 를 실행 하고 자 물 쇠 를 성공 적 으로 가 져 온 후에 expire 를 실행 하지 못 했 습 니 다. 노드 1 이 끊 겼 습 니 다. 그러면 만 료 시간 을 설정 하지 않 았 습 니 다.다른 노드 도 이 자 물 쇠 를 얻 을 수 없어 서 자물쇠 가 사라 집 니 다.
해결 방법: setnx 대신 set 명령 을 사용 하거나 lua 스 크 립 트 를 사용 하여 작업 의 원자 성 을 확보 합 니 다.
set(lock_sale_ ID,1,30,NX)
lua :
eval:“if redis.call('set',KEYS[1],ARGV[1]) then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end”
현상 2: del 오류 발생
만약 에 특정한 스 레 드 가 자 물 쇠 를 가 져 온 후에 시간 초과 시간 을 30 초 로 설정 하지만 어떤 이유 로 업무 수행 이 느 려 서 30 초 동안 실행 되 지 않 았 습 니 다.이 때 노드 1 은 자 물 쇠 를 풀 고 노드 2 는 자 물 쇠 를 가 져 옵 니 다.노드 1 은 업무 코드 를 실행 한 후에 도 del 작업 을 수행 합 니 다. 실제로 이때 삭 제 된 것 은 노드 2 의 자물쇠 입 니 다.
해결 방법: del 삭 제 를 실행 하기 전에 값 판단 을 추가 하여 현재 자물쇠 가 자신 이 추가 한 자물쇠 인지 확인 합 니 다.
lua :
eval: "if redis.call('get',KEYS[1]) == ARGV[1] then return
redis.call('del',KEYS[1]) else return 0 else”
현상 3: 병발 가능성, 노드 2 와 노드 1 이 동시에 자원 을 방문 할 가능성 을 버린다.
현상 2 유형 과 비슷 한 노드 1: 자 물 쇠 를 가 져 오고 업무 코드 를 실행 하 며 시간 초과 노드 1: 자 물 쇠 를 방출 하 는 노드 2: 노드 1 에서 방출 되 는 자물쇠 노드 1: 업무 코드 를 실행 하고 자 물 쇠 를 방출 하 는 노드 2: 자 물 쇠 를 방출 할 때 노드 1 에서 노드 2 의 자 물 쇠 를 방출 하지 않 는 것 을 피한다.그러나 노드 2 에서 노드 1 이 너무 오래 실행 되 어 풀 린 자 물 쇠 를 얻 는 것 을 피 할 수 없다.이것 도 우리 가 원 하 는 결과 가 아니다.
해결 방법: (1) 실패 노드 1 업무 스크롤 기록 로 그 를 풀 고 (2) 노드 2 는 데 몬 이 시간 초과 에 대한 연장 을 사용 하여 업무 가 완료 되 지 않 았 을 때 오류 가 잠 겨 있 는 것 을 방지 합 니 다.또한 입장 연장 횟수 를 일정 하 게 제한 하여 자원 을 장시간 점용 하지 않도록 해 야 합 니 다.
lua :
eval:" if redis.call('set',KEYS[1],ARGV[1]) then if redis.call('expire',KEYS[1],ARGV[2])
end local ttlTime = redis.call('ttl',KEYS[1]) if ttlTime == -1 then
redis.call('expire',KEYS[1],ARGV[2]) end”
이번 수요 로 돌아 가 redis 의 자체 증가 카운터 로 주파수 제한 에 도달 하려 면 공급 업 체 방법 을 호출 하기 전에 redis + incr 명령 을 사용 하여 지정 한 key 에 저 장 된 수치 에 원자 추가 1 작업 을 수행 합 니 다.지정 한 key 가 존재 하지 않 으 면 incr 작업 을 수행 하기 전에 값 을 0 으로 설정 합 니 다.주파수 제한 을 위 한 코드: KEYS [1]: key ARGV [1]: 만 료 시간 ARGV [2]: 주파수 제한
lua :
“local count = redis.call('incr',KEYS[1]) if count == 1 then redis.call('expire',KEYS[1]
,ARGV[1]) end local ttlTime = redis.call('ttl',KEYS[1]) if ttlTime == -1
then redis.call('expire',"KEYS[1] , ARGV[1]) end return count”
6. 전체 코드:
AOP 서 라운드 절단면 을 사용 하여 key, value, 주파수 제한 을 동적 으로 설정 하고 redis 를 사용 하여 분포 식 잠 금 을 실현 합 니 다.
@RedisAPILimit(apiKey = “Key”,limit = count,sec = 60)
public String getCtripRatePlan(Req req) throws Exception {}
사용자 정의 설명:
@Target(value = {ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisAPILimit {
//
int limit() default 5;
// 5
int sec() default 5;
String apiKey() default "";
}
AOP+Redis:
@Around("@annotation(redisAPILimit)")
public Object around(ProceedingJoinPoint proceedingJoinPoint,RedisAPILimit redisAPILimit) throws Throwable {
if (redisAPILimit == null) {
return proceedingJoinPoint.proceed();
}
int limit = redisAPILimit.limit();
int sec = redisAPILimit.sec();
String apiKey = redisAPILimit.apiKey();
Integer currentCount = (Integer)redisTemplate.opsForValue().get(apiKey);
if (currentCount != null && currentCount>limit) {
Long expire = redisTemplate.getExpire(apiKey, TimeUnit.SECONDS);
log.info("{} , {} , :{} , {} ",apiKey,currentCount,limit,expire);
// ,
//throw new CtripException
return null;
}
String script = "local count = redis.call('incr',KEYS[1]) if count == 1 then redis.call('expire',KEYS[1] , " +
"ARGV[1]) end local ttlTime = redis.call('ttl',KEYS[1]) if ttlTime == -1 then redis.call('expire'," +
"KEYS[1] , ARGV[1]) end return count " ;
RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Long incNum = (Long) redisTemplate.execute(redisScript, Collections.singletonList(apiKey),sec);
return proceedingJoinPoint.proceed();
}
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
강 한 캐 시 와 협상 캐 시 상세 설명클 라 이언 트 는 자원 을 요청 할 때마다 만 료 여 부 를 봅 니 다.만 료 되 어야 서버 에 문의 할 수 있 습 니 다.그래서 강 캐 시 는 클 라 이언 트 에 게 자급자족 용 으로 사용 하기 위 한 것 이다....
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.