✔️ 동시성 이슈란?
동시성 뜻은 어떤 두 사건이 같은 시간에 일어나는 것을 이르는 말
예시) 특정 웹사이트에 100명이 동시에 버튼을 눌렀을 때 100번 DB의 입력이나 수정이 될텐데 누락되는 이슈
동시성 제어로 누락이 안되게 한다.
🤔 REDIS 동시성 제어
- 서버가 2대 이상 일때는 애플리케이션 레벨과 데이터베이스 레벨 사이에 REDIS를 넣는다.
- 예시) 스프링 부트 -> REDIS -> RDBMS
1. Redission (분산 락)
스프링 부트에서는 Redission 라이브러리를 사용
Redisson은 분산 할 때 Redis PUB/SUB을 사용
메세지가 올 때까지 계속 대기하고 완료 메시지가 오면은 진행하는걸 반복한다.
2. Lettuce (스핀 락)
스프링 부트에서 Redis setnx, setex를 사용하여 직접 구현한다.
Redis에 계속 키가 있는지 확인을 하기 때문에 부하가 생길수 있다.
🚗 분산 락 어노테이션 구현
어노테이션에 사용할 인터페이스 작성
/**
* Redisson Distributed Lock annotation
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {
/**
* 락의 이름
*/
String key();
/**
* 락의 시간 단위
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
/**
* 락을 기다리는 시간 (default - 5s)
* 락 획득을 위해 waitTime 만큼 대기한다
*/
long waitTime() default 5L;
/**
* 락 임대 시간 (default - 3s)
* 락을 획득한 이후 leaseTime 이 지나면 락을 해제한다
*/
long leaseTime() default 3L;
}
@Target : 부가기능을 부여할 대상을 의미합니다.
- @Target(ElementType.ANNOTATION_TYPE) : 어노테이션
- @Target(ElementType.CONSTRUCTOR) : 생성자
- @Target(ElementType.FIELD) : 필드(멤버 변수, Enum 상수)
- @Target(ElementType.LOCALVARIABLE) : 지역변수
- @Target(ElementType.METHOD) : 메서드
- @Target(ElementType.PACKAGE) : 패키지
- @Target(ElementType.PARAMETER) : 매개변수(파라미터)
- @Target(ElementType.TYPE) : 타입(클래스, 인터페이스, Enum)
- @Target(ElementType.TYPE_PARAMETER) : 타입 매개변수(제네릭과 같은 매개변수)
- @Target(ElementType.TYPE_USE) : 타입이 사용되는 모든 대상
@Retention : 애노테이션은 애노테이션의 라이프 사이클 즉, 애노테이션이 언제까지 살아 남아 있을지를 정하는 것입니다.
- RetentionPolicy.SOURCE : 소스 코드(.java)까지 남아있는다.
- RetentionPolicy.CLASS : 클래스 파일(.class)까지 남아있는다.(=바이트 코드)
- RetentionPolicy.RUNTIME : 런타임까지 남아있는다.(=사실상 안 사라진다.)
@Sl4j : 로그 클래스 지정 (logback 필요 구현체가 없기 때문)
어노테이션 실행 클래스
/**
* @DistributedLock 선언 시 수행되는 Aop class
*/
@Aspect
@Component
@RequiredArgsConstructor
@Sl4j
public class DistributedLockAop {
private static final String REDISSON_LOCK_PREFIX = "LOCK:";
private final RedissonClient redissonClient;
private final AopForTransaction aopForTransaction;
@Around("@annotation(com.kurly.rms.aop.DistributedLock)")
public Object lock(final ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
DistributedLock distributedLock = method.getAnnotation(DistributedLock.class);
String key = REDISSON_LOCK_PREFIX + CustomSpringELParser.getDynamicValue(signature.getParameterNames(), joinPoint.getArgs(), distributedLock.key());
RLock rLock = redissonClient.getLock(key); // (1)
try {
boolean available = rLock.tryLock(distributedLock.waitTime(), distributedLock.leaseTime(), distributedLock.timeUnit()); // (2)
if (!available) {
return false;
}
return aopForTransaction.proceed(joinPoint); // (3)
} catch (InterruptedException e) {
throw new InterruptedException();
} finally {
try {
rLock.unlock(); // (4)
} catch (IllegalMonitorStateException e) {
log.info("Redisson Lock Already UnLock {} {}",
kv("serviceName", method.getName()),
kv("key", key)
);
}
}
}
}
@Aspect : 프록시를 생성하고 적용하는 역할 뿐만 아니라 @Aspect를 Advisor로 변환해서 저장하는 기능도 수행
(선언 후 아래의 어노테이션으로 프록시 기능 수행)
- @Before : 메소드가 실행되기 이전에 실행됩니다.
- @After : 메소드의 종료 후 무조건 실행됩니다. (try-catch에서 finally와 같은 동작)
- @After-returning : 메소드가 성공적으로 완료되고 리턴한 다음에 실행됩니다.
- @After-throwing : 메소드 실행 중 예외가 발생하면 실행됩니다. (try-catch에서 catch와 같은 동작)
- @Around : 메소드 호출 자체를 가로채서 메소드 실행 전후에 처리할 로직을 삽입할 수 있습니다.
@Compoent : 스프링 컨테이너에 빈 등록
@RequiredArgsConstructor : Lombok 생성자 주입
매개변수 final ProceedingJoinPoint joinPoint : 어노테이션을 사용한 메소드 (@Around)
CustomSpringELParser.java
/**
* Spring Expression Language Parser
*/
public class CustomSpringELParser {
private CustomSpringELParser() {
}
public static Object getDynamicValue(String[] parameterNames, Object[] args, String key) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i], args[i]);
}
return parser.parseExpression(key).getValue(context, Object.class);
}
}
어노테이션 실행하는 클래스에서 CustomSpringELParser을 사용한다.
Spring Expression Language(SPEL)을 파싱하는 것
SPEL: 런타임에서 객체에 대한 쿼리와 조작(querying and manipulating)을 지원하는 강력한 표현 언어이다.
// 어노테이션 실행 내부에서 CustomSpringELParser 사용 할 경우
@DistributedLock(key = "#lockName")
public void shipment(String lockName) {
...
}
// CustomSpringELParser 사용 안 할경우
@DistributedLock(key = "#model.getName().concat('-').concat(#model.getShipmentOrderNumber())")
public void shipment(ShipmentModel model) {
...
}
AopForTransaction.java
/**
* AOP에서 트랜잭션 분리를 위한 클래스
*/
@Component
public class AopForTransaction {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Object proceed(final ProceedingJoinPoint joinPoint) throws Throwable {
return joinPoint.proceed();
}
}
@Compoent : 스프링 컨테이너에 빈 등록
@Transactional : 트랜잭션 선언 (오류 발생 시에 롤백이 가능)
@Transactional(propagation = Propagation.REQUIRES_NEW) : 같은 쓰레드에서 새로운 트랜잭션 생성을 한다.
참고한 사이트의 내용에 "그리고 반드시 트랜잭션 커밋 이후 락이 해제되게끔 처리했습니다." 가 있는데
어떻게 이게 가능한지 이해가 안갔다.
아래의 참고 사이트에서 이미지를 보고 이해를 했다.
컬리(풀필먼트 입고 서비스팀에서 분산락을 사용하는 방법 - Spring Redisson)
1. 어노테이션을 사용
2. 어노테이션 실행 클래스로 간다.
3. 실행 클래스에서 @Around를 사용해서 메소드를 원할때 joinPoint로 실행이 가능하다.
4. joinPoint를 AopForTransaction에서 proceed를 실행 시켜준다.
5. AopForTransaction에 proceed에 @Transactional이 있기에 커밋을 한 후 return을 해준다.
(우선순위는 @Transactional -> joinPoint.proceed() 순)
👍 참고 사이트
https://woodcock.tistory.com/40
Transactional REQUIRES_NEW에 대한 오해
서론 예전에 함께 스터디를 했던 스터디원이 트랜잭션에 관한 블로그 글을 공유하면서, 흥미로운 내용이라고 소개했다. 해당 글에서는 기존에 내가 알고있던 사실이 틀리다라고 얘기하는 내용
woodcock.tistory.com
https://helloworld.kurly.com/blog/distributed-redisson-lock/
풀필먼트 입고 서비스팀에서 분산락을 사용하는 방법 - Spring Redisson
어노테이션 기반으로 분산락을 사용하는 방법에 대해 소개합니다.
helloworld.kurly.com
https://galid1.tistory.com/497
Spring - Spring을 왜 사용하나요?(AOP) - 3
Spring을 왜 사용하나요(AOP) Spring의 2번째 특징인 AOP에 대한 포스팅입니다. 이제 막 공부하는 것이라 틀린 점이 있다거나 의견이 다른 부분이 있다면 댓글로 남겨주시면 감사드리겠습니다. 1. AOP(
galid1.tistory.com
https://ittrue.tistory.com/160
[Java] 자바 메타 어노테이션 정리 및 활용
메타 어노테이션 (Meta Annotation) 메타 어노테이션은 어노테이션에 붙이는 어노테이션이다. 즉, 사용할 어노테이션을 정의하는 데 사용한다. 메타 어노테이션은 사용할 어노테이션의 적용대상 또
ittrue.tistory.com
[Spring/스프링] AOP @Aspect 사용법
[Spring/스프링] 관점 지향 프로그래밍(AOP)과 용어정리, Advice 종류 ⚡️관점 지향 프로그래밍(Aspect Oriented Programming, AOP) 스프링의 핵심기능인 IoC가 객체들의 결합도를 느슨하게 만들어 의존 관계를
lasbe.tistory.com
https://jaimemin.tistory.com/2031
[SpringBoot] @Aspect 어노테이션
개요 기존에 빈 후처리기에 대해서 알아봤으며 gradle 혹은 pom.xml에 'org.springframework.boot:spring-boot-starter-aop' 라이브러리를 추가하면 AnnotationAwareAspectJAutoProxyCreator라는 자동 프록시 생성기가 스프링
jaimemin.tistory.com
https://devwithpug.github.io/spring/spring-spel/
Spring Expression Language(SpEL) 에 대해
스프링 프레임워크에서 사용되는 SpEL 표현법에 대해 알아보고 사용 예제에 대해 정리해보았다.
devwithpug.github.io
https://imiyoungman.tistory.com/9
[Spring] @Transactional의 이해
스프링에서 트랜잭션 처리를 위해 선언적 트랜잭션을 사용한다. 선언전 트랜잭션이란 설정 파일 or 어노테이션 방식으로 간편하게 트랜잭션에 관한 행위를 정의하는 것이다. (물론, 프로그래밍
imiyoungman.tistory.com
🔴 Beanstalk + GitHub Action + ElasticCache for Redis
AWS Elastic Redis는 VPC 내에서만 접근이 돼서 Github Action에는 CI가 오류가 뜨는데
테스트를 예외 처리를 해야한다. 근데 특정 패키지 예외 처리가 안됨
배포 할때를 체크를 해서 배포 빌드 할때만 테스트를 안하는게 맞는듯
'항해99' 카테고리의 다른 글
항해99 주특기 프로젝트 (8일차) (0) | 2024.01.20 |
---|---|
항해99 주특기 프로젝트 (7일차) (0) | 2024.01.19 |
항해99 주특기 프로젝트 (5일차) (0) | 2024.01.17 |
항해99 주특기 프로젝트 (4일차) (0) | 2024.01.17 |
항해99 주특기 프로젝트 (3일차) (0) | 2024.01.15 |