redis 기반 분포 식 잠 금 실현 및 spring-boot-starter 통합
이 글 은 redis 분포 식 자 물 쇠 를 어떻게 디자인 하 는 지 에 대해 redis 분포 식 자 물 쇠 를 구체 적 으로 실현 하고 기능 을 spring-boot 에 융합 시 켜 주해 화 를 실현 하 며 설정 이 매우 간소화 되 었 다.github 주 소 는 여기 있 습 니 다.
개술
이 글 은 주로 테스트 사례 를 결합 하여 redis 분포 식 잠 금 사용 을 소개 하고 실현 원 리 를 깊이 소개 한다.프로젝트 에 사용 하려 면 README 을 먼저 읽 으 세 요.
쓰다
1.가방 안내
clone 이 프로젝트 는 프로젝트 디 렉 터 리 에 들 어가 mvn install 을 실행 하고 가방 을 maven 창고 에 설치 합 니 다.
com.redislock
redislock-spring-boot-starter
1.0.0
버 전 요구 사항:spring-boot 2.0.0 이상,jdk 1.8 이상,redis 2.6.12 이상
2.잠 금 기능 을 실현 하 는 서비스 쓰기
다음 과 같다.
@Service
public class TestService {
@RedisSynchronized(value = "talk",fallbackMethod = "shutup")
public String myTurn(String speak){
// ,
// , sleep10s( 10s)
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return speak;
}
@Fallback(value = "shutup",replaceReturn = true)
private String notYourTurn(RedisLockJoinPoint redisLockJoinPoint){
//
return "silence";
}
}
3.redis 의 key 검사
redis 클 라 이언 트 를 설치 한 후 redis 서 비 스 를 시작 하여 redis 클 라 이언 트 에 로그 인 합 니 다.
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379>
현재 redis 에는 키 가 없습니다.
4.호출(잠 금 성공)
첫 번 째 단계 에서@RedisSynchronized 레이 블 을 호출 하 는 방법
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RedislockApplication.class)
public class RedislockTest {
@Autowired
private TestService testService;
@Test
public void testLock() {
System.out.println(" :" + testService.myTurn("bulaha", 0));
}
}
방법 실행 이 끝나 기 전에 redis 를 다시 보면 redis 에 key 가 하나 더 있 습 니 다.@RedisSynchronized 의 value 값 과 같 습 니 다.현재 방법 으로 자 물 쇠 를 얻 었 음 을 의미 합 니 다.
127.0.0.1:6379> keys *
1) "talk"
127.0.0.1:6379>
10s 후 실행 결 과 는 다음 과 같 습 니 다.
:bulaha
방법 이 실 행 된 후에 redis 를 다시 관찰 하면'talk'이 자동 으로 제거 되 었 습 니 다.즉,자물쇠 가 풀 렸 습 니 다.
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> keys *
1) "talk"
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379>
5.호출(잠 금 실패)
다른 스 레 드 나 프로 세 스 가 잠 금 을 선점 하 였 을 때 현재 스 레 드 잠 금 이 실 패 했 을 때의 상황 을 모 의 합 니 다.먼저 redis 에 key"talk"을 설정 합 니 다.
127.0.0.1:6379> set talk 1
OK
127.0.0.1:6379> keys *
1) "talk"
127.0.0.1:6379>
세 번 째 단계 에서 의 테스트 방법 을 다시 실행 합 니 다.실행 결 과 는 다음 과 같 습 니 다.
:silence
결국 자 물 쇠 를 성공 적 으로 얻 었 을 때 우리 가 원 하 는 결과 인'bulaha'를 출력 하 는 것 이 아니 라 첫 번 째 단계 에서@Fallback 방법의 반환 값 을 출력 하 는 것 을 볼 수 있 습 니 다.이것 이 바로 방법 강등 입 니 다.잠 금 에 실 패 했 을 때 대체 논 리 를 실행 하고 대체 값 을 되 돌려 줍 니 다.(강등 방법 을 지정 하지 않 아 도 됩 니 다.잠 금 실패 시 이상 을 던 집 니 다.)
현재 세 개의 주석 만 있 으 면@RedisSynchronized,@Fallback,@Fallback Handler 세 개의 설정 항목 redislock.prefix redislock.timeout redislock.heart-beat 는 README 참조
이루어지다
1.redislock 어떻게 실현
분산 잠 금 을 실현 하 는 정책 은:
1.setnx 를 사용 하여 redis 에 키 를 설정 하고 만 료 시간(redis 는 2.6.12 버 전에 서 강 화 된 set 명령 을 지원 합 니 다.즉,SET key value[expiration EX seconds|PX milliseconds][NX|XX]입 니 다.set 명령 하나 로 setnx+expire 를 실현 할 수 있 습 니 다.우 리 는 상기 글 에서 말 한 setnx 와 expire 가 각각 실 행 될 때 발생 하 는 잠 금 문 제 를 걱정 할 필요 가 없습니다.좋 지 않 습 니까?)구체 적 으로 자바 에서 redis 에 이 명령 을 어떻게 보 냅 니까?여 기 는 spring-data-redis 를 사용 합 니 다.코드 는 다음 과 같 습 니 다.
public class MyRedisTemplate{
private RedisTemplate redisTemplate;
public void setRedisTemplate(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public Boolean setifAbsent(String key, String value, long timeoutMilis){
Boolean execute = redisTemplate.execute(new MyRedisCallback(key,value,timeoutMilis));
return execute;
}
class MyRedisCallback implements RedisCallback{
private String key;
private String value;
private long timeoutMilis;
public MyRedisCallback(String key, String value, long timeoutMilis) {
this.key = key;
this.value = value;
this.timeoutMilis = timeoutMilis;
}
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
RedisStringCommands redisStringCommands = connection.stringCommands();
// set , true
Boolean set = redisStringCommands.set(key.getBytes(), value.getBytes(), Expiration.milliseconds(timeoutMilis), RedisStringCommands.SetOption.SET_IF_ABSENT);
return set;
}
}
public Boolean expire(String key, long time) {
return redisTemplate.expire(key,time,TimeUnit.MILLISECONDS);
}
public Boolean delete(String key) {
return redisTemplate.delete(key);
}
}
2.성공 적 으로 획득 한 자 물 쇠 를 하나의 집합 에 넣 고 Timer 를 열 어 집합 중의 자 물 쇠 를 연장 합 니 다.이 집합 은 스 레 드 간 에 공유 해 야 하기 때문에 스 레 드 안전 한 집합 을 사용 해 야 합 니 다.여 기 는 Concurrent HashMap 을 사용 합 니 다.원래 Set 를 사용 하려 고 했 는데 자바 의 concurrent 패키지 에 제공 되 지 않 았 으 니 Concurrent HashMap 으로 대체 합 니 다.사실 자바 의 Set 은 Map 으로 이 루어 집 니 다)
3.잠 금 을 풀 때 키 를'속 명 집합'에서 제거 한 다음 redis 에서 키 를 제거 합 니 다.여기 서 순 서 를 바 꿀 수 없습니다.A 스 레 드 가 먼저 redis 에서 key 를 제거 하면 바로 또 하나의 B 스 레 드 가 자 물 쇠 를 얻 고 key 를'속 명 집합'에 추가 한 후에 A 스 레 드 가 이 key 를'속 명 집합'에서 제거 할 수 있 기 때 문 입 니 다.그러면 스 레 드 B 가 얻 은 자 물 쇠 는 속 명 기능 이 없습니다.만약 이 스 레 드 가 하필 잠 금 이 만 료 되 어도 실행 되 지 않 았 다 면 잠 겨 있 는 자원 을 병행 하 는 상황 이 발생 했 을 것 이다.
public class RedisLock {
private long timeout = LOCK_TIMEOUT;
private long heartBeat = LOCK_HEART_BEAT;
private MyRedisTemplate myRedisTemplate;
public void setMyRedisTemplate(MyRedisTemplate myRedisTemplate) {
this.myRedisTemplate = myRedisTemplate;
}
private Map aliveLocks = new ConcurrentHashMap();
private final Timer timer = new Timer();
// bean timer, timer
@PostConstruct
public void init(){
timer.schedule(new TimerTask() {
@Override
public void run() {
keeplockAlive();
}
},0,heartBeat);
}
// timer
@PreDestroy
private void destroy(){
timer.cancel();
}
private void keeplockAlive(){
if(aliveLocks.size() > 0){
for (String key:
aliveLocks.keySet()) {
myRedisTemplate.expire(key,timeout);
}
}
}
public void unlock(String key) {
aliveLocks.remove(key);
myRedisTemplate.delete(key);
}
public boolean lock(String key){
return lock(key,true);
}
public boolean lock(String key,boolean keepAlive){
Boolean redisLock = myRedisTemplate.setifAbsent(key, "redisLock", timeout);
if(redisLock && keepAlive){
aliveLocks.put(key,"");
}
return redisLock;
}
}
2.어떻게 주해 화 를 실현 합 니까
현재 우 리 는 이전에 말 한 RedisLock 류 를 사용 하여 잠 금 해 제 를 실현 할 수 있 습 니 다.사실은 분포 식 잠 금 의 기본 기능 이 이미 실현 되 었 습 니 다.당신 은 다음 과 같이 사용 하여 분포 식 잠 금 을 실현 할 수 있 습 니 다.
redislock.lock("mykey");
//
redislock.unlock("mykey");
그러나 이렇게 사용 하 는 것 은 편리 하지 도 않 고 안전 하지 도 않 습 니 다.RedisLock 인 스 턴 스 를 주입 하여 스스로 추가 작업 을 해 야 합 니 다.우 리 는 Spring 의 방법 을 본 떠 서 자 물 쇠 를 넣 으 려 는 방법 을 표시 하 는 데 사용 할 주 해 를 설명 할 수 있 습 니 다.
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisSynchronized {
String value() default "";
String fallbackMethod() default "";
}
지금 우 리 는 자신의 주해 가 생 겼 으 니,너 는 마음대로 어떤 방법 에 그들 을 표시 할 수 있다.하지만 표 시 된 방법 을 몇 번 을 실행 하 더 라 도 원 하 는 대로 자 물 쇠 를 채 우지 않 는 다 는 것 은 당연한 일이 다.우 리 는 비록 자신의 주 해 를 가지 고 있 지만,우 리 는 진정 으로 자 물 쇠 를 넣 는 기능 을 우리 에 게 표시 하 는 방법 을 부여 하지 않 았 기 때문이다.그러면 우 리 는 어떻게 해야만 spring 과 다른 프레임 워 크 처럼 사용 할 때 주석 만 표시 하면 해당 하 는 기능 을 부여 할 수 있 습 니까?바로 동적 대리 입 니 다.이렇게 말 하면 Spring 생태 를 실현 하 는 두 가지 이기 라 고 할 수 있 는데 하 나 는 주해 이 고 다른 하 나 는 동적 대리 이다.빛 나 는 외모 에 대한 주 해 는 우리 에 게 우호 적 인 체험 을 제공한다.동적 대 리 는 배후 에 숨 어 전체 메커니즘 의 운행 을 지탱 한다.동적 에이전트 소개:spirng 의 동적 에이전트 사용 은 주로 두 가지 가 있 는데 하 나 는 jdk 동적 에이전트 이 고 하 나 는 cglib 동적 에이전트 입 니 다.그 중에서 jdk 동적 대 리 는 대리 류 에 의 해 반드시 인 터 페 이 스 를 계승 해 야 한다 고 요구 하지만 cglib 는 이러한 제한 이 없 는 것 도 cglib 가 성행 할 수 있 는 원인 중 하나 이다.그리고 cglib 도 여러 가지 버 전 으로 나 뉘 는데 일반적인 cglib 는 asm 와 같은 추가 의존 이 필요 합 니 다.그리고 cglib-nodp 는 말 그대로 no dependence 로 실제 사용 할 때 의존 하 는 가방 이 호 환 되 지 않 는 문 제 를 피 할 수 있 습 니 다.그리고 우리 가 사용 하 는 것 은 상기 두 가지 중에서 우리 가 사용 하 는 것 은 spring-core 가방 에 있 는 cglib 입 니 다.Spring 은 아주 좋 은 기능 을 추 가 했 습 니 다.다음은 제 가 소개 하 겠 습 니 다.
3.에이전트 생 성
우리 의 최종 목적 을 달성 하려 면 다음 과 같은 세 가지 일 을 해 야 합 니 다.1.대 리 를 하고 방법 에@RedisSynchronized 주 해 를 추가 한 임 의 업무 bean 은 잠 금 해제 기능 을 가 진 대 리 를 생 성 합 니 다.2.교체,@Autoware 를 사용 하여 bean 을 주입 할 때 우리 가 대리 한 bean 을 주입 해 야 합 니 다.3.타 이 밍,프 록 시 타 이 밍 을 선택 하 십시오.즉,저희 업무 코드 가 실행 되 기 전에 Spring 초기 화가 완료 되 기 전에 프 록 시 를 완성 하고 교체 해 야 합 니 다.상기 세 가지 요점 을 결합 하여 우 리 는 cglib 를 결합 하여 Spring 의 한 구성 요 소 를 도입 하면 모두 만족 할 수 있 습 니 다.그 가 바로 BeanPost Processor 입 니 다.BeanPostProcessor 소개:BeanPostProcessor 의 설명 을 참고 하여 용기 가 작 동 할 때 BeanPostProcessor 의 실현 류 를 자동 으로 감지 하여 미리 예화 시 키 고 그 다음 인 스 턴 스 의 bean 예화 시 인터페이스 방법 을 바 꿀 수 있 음 을 알 수 있 습 니 다.인터페이스 방법 postprocessBeforeinitialization 은 속성 설정 이 완료 되면 초기 화 방법(init-method)이 실행 되 기 전에 리 셋 됩 니 다.인터페이스 방법 postprocessAfter Initialization 초기 화 방법(init-method)이 실 행 된 후 되 돌 아 옵 니 다.리 셋 방법의 리 턴 값 은 원래 의 bean 일 수도 있 고 대 리 된 bean 일 수도 있 습 니 다(either the original or a wrapped one).
프로필 에서 우 리 는 BeanPostProcessor 를 계승 하면 시기 가 있 기 때문에 우 리 는 리 셋 방법 에서 대 리 를 할 수 있 고 대리 후에 생 성 된 대 리 를 직접 리 턴 값 으로 되 돌려 주 고 교체 하면 된다 는 것 을 이해 했다.실현
public class RedisLockAutoProxyCreator implements BeanPostProcessor{
private RedisLock redisLock;
public void setRedisLock(RedisLock redisLock) {
this.redisLock = redisLock;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class> aClass = AopUtils.getTargetClass(bean);
for (Method method : aClass.getDeclaredMethods()) {
// RedisSynchronized cglib , bean
if(method.isAnnotationPresent(RedisSynchronized.class)){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(aClass);
enhancer.setCallback(new MyHandler(bean));
return enhancer.create();
}
}
// RedisSynchronized bean
return bean;
}
class MyHandler implements InvocationHandler {
private Object o;
MyHandler(Object o){
this.o=o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// RedisSynchronized
if(!method.isAnnotationPresent(RedisSynchronized.class)){
return method.invoke(o, args);
}
// RedisSynchronized ,
String key = getKey(o,method,args);
boolean locked = redisLock.lock(key);
if(locked){
try{
return method.invoke(o, args);
}finally {
redisLock.unlock(key);
}
}
//
throw new LockFailedException("lock failed");
}
private String getKey(Object o, Method method, Object[] args) {
RedisSynchronized annotation = method.getAnnotation(RedisSynchronized.class);
String key = annotation.value();
if("".equals(key)){
key = method.toGenericString();
}
return key;
}
}
}
프 록 시 과정 을 더욱 명확 하 게 표현 하기 위해 이 코드 는 약간 복잡 한 잠 금 강등 부분 을 버 렸 습 니 다.이상 이 모두 이해 했다 면 clone 코드 는 잠 금 강등 의 실현 을 볼 수 있 습 니 다.
여기 서 우 리 는 주해 화 된 redis 자 물 쇠 를 완성 했다.
통합 spring-boot-starter
편폭 문제 로 여기까지 말씀 드 리 겠 습 니 다.통합 spring-boot-starter 에 관심 이 있 으 면 메 시 지 를 남 겨 주세요.저 는 따로 한 편 을 써 서 설명 하 겠 습 니 다.이 글 은 이미 다 써 서 전송 문 이다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Is Eclipse IDE dying?In 2014 the Eclipse IDE is the leading development environment for Java with a market share of approximately 65%. but ac...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.