- [spring] ๋ถ์ฐ๋ฝ์ ๊ฑธ์ด๋ ๋์์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค?! (feat. ํธ๋์ญ์ AOP)2024๋ 12์ 12์ผ 21์ 50๋ถ 19์ด์ ์ ๋ก๋ ๋ ๊ธ์ ๋๋ค.์์ฑ์: @kimyu0218
๐ [redis] redisson์ผ๋ก ๋ถ์ฐ๋ฝ ๊ฑธ์ด์ ๋์์ฑ ๋ฌธ์ ํด๊ฒฐํ๊ธฐ (๊ทผ๋ฐ ํด๊ฒฐ ์๋จ)
๋๋ ๋ถ๋น์ผ๋ก ๊ต์ก ๊ฐ๋ ๊ธธ.. ๊ฐ ๊ธธ๋ ๋ฉ๊ณ ๊ฐ๋ฐฉ์ด ๋ฌด๊ฑฐ์ ๋ ธํธ๋ถ์ ๊ฐ์ ธ๊ฐ์ง ์์๋ค. ๊ทผ๋ฐ ์์นจ 7์๋ถํฐ ์ฌ๋ ์๋ฆผ์ด 100๊ฐ ๋๊ฒ ์๋ค. (๐ : ์ด? ๋ ธํธ๋ถ ๋๊ณ ๊ฐ๋ค ์ฅ์ ๊ฐ์ด๋ค) ์ฑ ์ง์ ์์ ์ ์ฌ์ฉํ๋ API + ์ฌ์ฉ์์ ํฌ๊ธฐํ์ง ์๋ ์ธ๋ด์ฌ์ด ํฉ์ณ์ ธ ํ์๋คํํ ์ฌ๋ ์๋ฆผ ํ ๋ฌ๋ฅผ ์์ํ๋ค.
ํด๋น ํฌ์คํ ์์ ์ฒ๋ฆฌํ ์ค๋ก๋ง ์์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋์์ฑ์ด ์์ธ์ด์๋ค. ์ฐ๋ฆฌ ์๋น์ค๋ ํ๋ฃจ์ ํ ๋ฒ๋ง ๋ฏธ์ ์ธ์ฆ์ ํ ์ ์๋๋ฐ Repeatable Read ๊ฒฉ๋ฆฌ ์์ค์ด INSERT ์์ ์ ์๋ฐฉํ ์ ์์ด API๋ฅผ ๋น ๋ฅด๊ฒ ์ฐ๋ฌ์ ํธ์ถํ๋ฉด ์ฌ๋ฌ ๋ฒ ์ธ์ฆํ๋ ๊ฒ์ด ๊ฐ๋ฅํ๋ค.
์ด๋ฌํ ๋์์ฑ ๋ฌธ์ ๋ฅผ ๋ง๊ณ ์ ๋ถ์ฐ๋ฝ์ ์ ์ฉํ๊ณ ์ ํด๊ฒฐ๋ ์ค ์์์ผ๋ ์ฌ๋ ํ ๋ฌ๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ค...!๋ฝ ํ๋/๋ฐํ์ ์ ๋๊ณ ์์ต๋๋ค๋ง
ํฌ์คํ ์์ ํ์ธํ ์ ์๋ฏ์ด ๋ถ์ฐ๋ฝ์ ์ํ ์ด๋ ธํ ์ด์ ์ ๋ง๋ค๊ณ AOP๋ฅผ ํตํด ๋ฝ์ ํ๋/๋ฐํํ๋ ๋ก์ง์ ์์ฑํ๋ค.
@Around("@annotation(com.nexters.goalpanzi.common.annotation.RedissonLock)") public Object lockMissionVerification(final ProceedingJoinPoint joinPoint) throws Throwable { ... var args = joinPoint.getArgs(); LockKey lockKey = LockKey.of(lockTarget, args); RLock lock = redissonClient.getLock(lockKey.toString()); // joinPoint ์ํ ์ ์ ๋ฝ ํ๋ํ๊ธฐ boolean lockable = lock.tryLock(annotation.waitTime(), annotation.timeUnit()); ... log.info("{} acquired {} lock with key: {}.", methodName, lockTarget, lockKey); // joinPoint ์ํ joinPoint.proceed(); // joinPoint ์ํ ํ ๋ฝ ๋ฐํํ๊ธฐ lock.unlock(); log.info("{} released {} lock with key: {}.", methodName, lockTarget, lockKey); ... }
`docker logs [DOCKER_CONTAINER_ID] | grep "lock"`์ผ๋ก ๋ก๊ทธ๋ฅผ ์ถ๋ ฅํ๋ฉด ๋ฏธ์ ์ธ์ฆ ์์ฒญ ์ ๋ฝ์ด ์ ๊ฑธ๋ฆฌ๊ณ ์๋ ๊ฒ์ ํ์ธํ ์ ์๋ค. ์ฌ์ง์ด ํ ์ฌ์ฉ์๊ฐ ํ ๋ฏธ์ ์ ๋ํด ์ฌ๋ฌ ๋ฒ ์์ฒญ์ ๋ณด๋ด๋ ์์ฐจ์ ์ผ๋ก ์คํ๋๊ณ ์์๋ค.
2024-12-11 21:18:45 [http-nio-8080-exec-6] INFO c.n.g.common.aop.RedissonLockAspect - createVerification acquired MissionVerification lock with key: LOCK:MissionVerification:[CreateMissionVerificationCommand{memberId=107, missionId=183}]. 2024-12-11 21:18:46 [http-nio-8080-exec-6] INFO c.n.g.common.aop.RedissonLockAspect - createVerification released MissionVerification lock with key: LOCK:MissionVerification:[CreateMissionVerificationCommand{memberId=107, missionId=183}]. 2024-12-11 21:22:53 [http-nio-8080-exec-4] INFO c.n.g.common.aop.RedissonLockAspect - createVerification acquired MissionVerification lock with key: LOCK:MissionVerification:[CreateMissionVerificationCommand{memberId=110, missionId=183}]. 2024-12-11 21:22:53 [http-nio-8080-exec-4] INFO c.n.g.common.aop.RedissonLockAspect - createVerification released MissionVerification lock with key: LOCK:MissionVerification:[CreateMissionVerificationCommand{memberId=110, missionId=183}].
๋ฝ ํ๋ ์ ์ ํธ๋์ญ์ ์ด ์์๋๋ค๊ณ ?!
๋์์ฑ ๋ฌธ์ ๋ ํธ๋์ญ์ ๊ณผ ๊ด๋ จ์๊ธฐ ๋๋ฌธ์ ํธ๋์ญ์ ์ด ์ธ์ ์์๋๊ณ ์ธ์ ๋๋๋์ง ๋ถ์ํ๊ธฐ๋ก ํ๋ค. `application.yml` ํ์ผ์ ๋ค์ ์ฝ๋๋ฅผ ์ถ๊ฐํ์ฌ ํธ๋์ญ์ ์ ์์๊ณผ ์ข ๋ฃ ๋ก๊ทธ๋ฅผ ์ฐ๋๋ค.
logging: level: org: springframework: transaction: interceptor: TRACE
์ค์ ์๋น์ค๋ ํ ์คํธํ๋ ๋ฐ ์ค๋ ์๊ฐ์ด ๊ฑธ๋ฆฌ๊ธฐ ๋๋ฌธ์ ์ด์ ์ ์์ฑํ ํ ์คํธ ์ฝ๋์ `@Transactional` ์ด๋ ธํ ์ด์ ์ ๋ถ์ด๊ณ ํ ์คํธ ์ฝ๋๋ฅผ ์คํํ๋ค.
@Transactional public class RedissonLockTestBean { @RedissonLock(waitTime = 1L) void serializeFunction(final Object args) throws InterruptedException { ... } }
2024-12-11T23:12:22.488+09:00 TRACE 11616 --- [pool-2-thread-2] o.s.t.i.TransactionInterceptor : Getting transaction for [com.nexters.goalpanzi.config.redisson.RedissonLockTestBean.serializeFunction] 2024-12-11T23:12:22.488+09:00 TRACE 11616 --- [pool-2-thread-1] o.s.t.i.TransactionInterceptor : Getting transaction for [com.nexters.goalpanzi.config.redisson.RedissonLockTestBean.serializeFunction] 2024-12-11T23:12:22.645+09:00 INFO 11616 --- [pool-2-thread-2] c.n.g.common.aop.RedissonLockAspect : serializeFunction acquired Unknown lock with key: LOCK:Unknown:[Resource1]. 2024-12-11T23:12:22.645+09:00 INFO 11616 --- [pool-2-thread-1] c.n.g.common.aop.RedissonLockAspect : serializeFunction acquired Unknown lock with key: LOCK:Unknown:[Resource0]. 2024-12-11T23:12:24.707+09:00 INFO 11616 --- [pool-2-thread-2] c.n.g.common.aop.RedissonLockAspect : serializeFunction released Unknown lock with key: LOCK:Unknown:[Resource1]. 2024-12-11T23:12:24.708+09:00 INFO 11616 --- [pool-2-thread-1] c.n.g.common.aop.RedissonLockAspect : serializeFunction released Unknown lock with key: LOCK:Unknown:[Resource0]. 2024-12-11T23:12:24.708+09:00 TRACE 11616 --- [pool-2-thread-2] o.s.t.i.TransactionInterceptor : Completing transaction for [com.nexters.goalpanzi.config.redisson.RedissonLockTestBean.serializeFunction] 2024-12-11T23:12:24.708+09:00 TRACE 11616 --- [pool-2-thread-1] o.s.t.i.TransactionInterceptor : Completing transaction for [com.nexters.goalpanzi.config.redisson.RedissonLockTestBean.serializeFunction]
๋ก๊ทธ์์ ์ ์ ์๋ฏ์ด ํธ๋์ญ์ ์ ์์ํ ํ์ ๋ถ์ฐ๋ฝ์ ๊ฑด๋ค. ์ฐ๋ฆฌ ์๋น์ค๋ฅผ ๊ธฐ์ค์ผ๋ก ์ค๋ช ํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ผ์ด ๋ฐ์ํ ์ ์๋ค.์์ ๊ฐ์ ๋์์ฑ ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด์๋ ํธ๋์ญ์ ์์ ์ ์ ๋ถ์ฐ๋ฝ์ ๊ฑธ์ด์ผ ํ๋ค.
Repeatable Read ๊ฒฉ๋ฆฌ ์์ค์์ INSERT๋ก ์ธํ ๋์์ฑ ๋ฌธ์ ์๋ฐฉํ๊ธฐ
์๋น์ค์์ ๋ฐ์ํ ๋์์ฑ ๋ฌธ์ ๋ฅผ ๋ค์ ์ ๋ฆฌํด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
- ํ๋ฃจ์ ํ ๋ฒ๋ง ๋ฏธ์ ์ ์ธ์ฆ(INSERT)ํ ์ ์๋ ์๋น์ค์์
- Repeatable Read ๊ฒฉ๋ฆฌ ์์ค์ผ๋ก ์ธํด ๋์์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๊ณ ,
- ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด redis ๋ถ์ฐ๋ฝ์ ์ ์ฉํ์ง๋ง
- ๋จผ์ ์คํ๋ ํธ๋์ญ์ ์ด ์ปค๋ฐ๋๊ธฐ ์ ์ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ผ๋ฉด์ ์ธ์ฆ์ด ์ฌ๋ฌ ๋ฒ ์ํ๋์๋ค.
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์๋ ํธ๋์ญ์ ์์ ์ ํ์ ๋ถ์ฐ๋ฝ์ ํ๋/๋ฐํํด์ผ ํ๋ค. ๋์ผํ ์กฐ์ธ ํฌ์ธํธ์์ `@Transactional`์ ์ํ AOP, `@RedissonLock`์ ์ํ AOP๊ฐ ์คํ๋๊ธฐ ๋๋ฌธ์ ๊ทธ ์์๋ฅผ ์กฐ์ ํด์ผ ํ๋ค. ์ฆ, ์กฐ์ธ ํฌ์ธํธ์ ์ด๋๋ฐ์ด์ค๋ค์ ์ ์ ํ๊ฒ ๋ผ์๋ฃ์ด์ผ ํ๋ค.
๐ ์กฐ์ธ ํฌ์ธํธ, ์ด๋๋ฐ์ด์ค, AOP ๊ฐ๋ - [spring] ์คํ๋ง AOP
@Order๋ก AOP ์์ ์ค์ ํ๊ธฐ
`@Order`๋ ์ปดํฌ๋ํธ์ ์์๋ฅผ ์ง์ ํ๋ค. ๊ธฐ๋ณธ๊ฐ์ `Ordered.LOWEST_PRECEDENCE`๋ก ๊ฐ์ฅ ๋ฎ์ ์ฐ์ ์์๋ฅผ ์๋ฏธํ๋ค.
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) @Documented public @interface Order { int value() default Ordered.LOWEST_PRECEDENCE; }
public interface Ordered { int HIGHEST_PRECEDENCE = Integer.MIN_VALUE; int LOWEST_PRECEDENCE = Integer.MAX_VALUE; int getOrder(); }
`@Transactional`์ `Ordered.LOWEST_PRECEDENCE`๋ฅผ, ์์๋ฅผ ์ง์ ํด์ฃผ์ง ์์ `@RedissonLock` ๋ํ `Ordered.LOWEST_PRECEDENCE`๋ฅผ ๊ฐ๋๋ค. ๋ถ์ฐ๋ฝ์ด ํธ๋์ญ์ ๋ณด๋ค ๋จผ์ ์คํ๋๊ธฐ ์ํด์๋ ํธ๋์ญ์ ๋ณด๋ค ๋์ ์ฐ์ ์์๋ฅผ ๊ฐ์ ธ์ผ ํ๋ค. ๋ฐ๋ผ์ `Ordered.LOWEST_PRECEDENCE`๋ณด๋ค ์์ ์ ์๋ฅผ `value`๋ก ๊ฐ๋ `@Order` ์ด๋ ธํ ์ด์ ์ ๋ถ์ฌ์ค๋ค.
@Order(Ordered.HIGHEST_PRECEDENCE) // ๋ถ์ฐ๋ฝ์ ๊ฐ์ฅ ๋จผ์ ์คํํ๋ค!! @RequiredArgsConstructor @Slf4j @Aspect @Component public class RedissonLockAspect { ... }
์์ ๊ฐ์ด ์์๋ฅผ ์กฐ์ ํ ํ ํ ์คํธ ์ฝ๋๋ฅผ ์ฌ์คํํ๋ฉด ํธ๋์ญ์ ์์ ์ ์ ๋ถ์ฐ๋ฝ์ ๋จผ์ ํ๋ํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
2024-12-11T23:22:24.979+09:00 INFO 16148 --- [pool-2-thread-2] c.n.g.common.aop.RedissonLockAspect : serializeFunction acquired Unknown lock with key: LOCK:Unknown:[Resource1]. 2024-12-11T23:22:24.979+09:00 INFO 16148 --- [pool-2-thread-1] c.n.g.common.aop.RedissonLockAspect : serializeFunction acquired Unknown lock with key: LOCK:Unknown:[Resource0]. 2024-12-11T23:22:25.010+09:00 TRACE 16148 --- [pool-2-thread-1] o.s.t.i.TransactionInterceptor : Getting transaction for [com.nexters.goalpanzi.config.redisson.RedissonLockTestBean.serializeFunction] 2024-12-11T23:22:25.010+09:00 TRACE 16148 --- [pool-2-thread-2] o.s.t.i.TransactionInterceptor : Getting transaction for [com.nexters.goalpanzi.config.redisson.RedissonLockTestBean.serializeFunction] 2024-12-11T23:22:27.012+09:00 TRACE 16148 --- [pool-2-thread-1] o.s.t.i.TransactionInterceptor : Completing transaction for [com.nexters.goalpanzi.config.redisson.RedissonLockTestBean.serializeFunction] 2024-12-11T23:22:27.012+09:00 TRACE 16148 --- [pool-2-thread-2] o.s.t.i.TransactionInterceptor : Completing transaction for [com.nexters.goalpanzi.config.redisson.RedissonLockTestBean.serializeFunction] 2024-12-11T23:22:27.074+09:00 INFO 16148 --- [pool-2-thread-2] c.n.g.common.aop.RedissonLockAspect : serializeFunction released Unknown lock with key: LOCK:Unknown:[Resource1]. 2024-12-11T23:22:27.074+09:00 INFO 16148 --- [pool-2-thread-1] c.n.g.common.aop.RedissonLockAspect : serializeFunction released Unknown lock with key: LOCK:Unknown:[Resource0].
๋ ์ด์ ๊ฐ์ ๋์์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์๊ธธ ๋ฐ๋ผ๋ฉฐ... (์ด๊ฒ ์์ธ์ด ์๋๋ผ๋ฉด 2ํ์ผ๋ก ๋์์ต๋๋ค ๐)
์ฐธ๊ณ ์๋ฃ
'backend > ์ฝ์ง์ผ๊ธฐ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[node] ๋น์ ์ socket.io๊ฐ ๋์ํ์ง ์๋ ์ด์ (Feat. socket.io๋ ํ์ฑ์ ํด) (0) 2023.12.23 ๋ค์๊ธ์ด ์์ต๋๋ค.์ด์ ๊ธ์ด ์์ต๋๋ค.๋๊ธ