๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ“• Spring Framework/Spring Project

[Redisson] ํŠธ๋žœ์žญ์…˜ ๋ฌธ์ œ ๋ฐœ์ƒ ๋ฐ ํ•ด๊ฒฐ

by GroovyArea 2022. 10. 1.

์ง€๋‚œ ํฌ์ŠคํŠธ

 

[Redisson]์„ ์ด์šฉํ•œ ๋ถ„์‚ฐ Lock ๊ตฌํ˜„ & ๋™์‹œ์„ฑ ๋ฌธ์ œ ํ•ด๊ฒฐ

๋‚ด ํ”„๋กœ์ ํŠธ์˜ Payment๋ฅผ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ๊ฐ€์žฅ ๊ธฐ๋ณธ ์ค‘์— ๊ธฐ๋ณธ์ด ๋˜๋Š” ๋ฌธ์ œ๋ฅผ ์ง๋ฉดํ–ˆ์—ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ๋ฐ”๋กœ ๋™์‹œ์„ฑ ๋ฌธ์ œ! ์Šคํ”„๋ง๋ถ€ํŠธ์˜ ๋‚ด์žฅ ์„œ๋ฒ„๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ํ†ฐ์บฃ, ์–ธ๋”ํ† ์šฐ ๋“ฑ๋“ฑ์˜ WAS๋กœ ๋Œ์•„๊ฐ€๋Š”๋ฐ ์ด

sweeeetgoguma.tistory.com

์ง€๋‚œ ํฌ์ŠคํŠธ์—์„œ Redisson์„ ์ด์šฉํ•˜์—ฌ ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๊ตฌํ˜„ํ–ˆ๋‹ค.

 

ํ”„๋กœ์ ํŠธ ๋ฆฌํŒฉํ† ๋ง์ด ๊ฑฐ์˜ ๋๋‚˜๊ฐ€ ์กฐํšŒ API๋ฅผ ๊ตฌ์ฒดํ™”ํ•˜์—ฌ ๋ช‡ ๊ฐœ ์ถ”๊ฐ€ํ•˜๋˜ ๋„์ค‘, ์Šค๋ ˆ๋“œ 100๊ฐœ์˜ ๋™์‹œ ์š”์ฒญ์„ ์ง์ ‘์ ์œผ๋กœ ๋ฐ›๋Š” ๊ณผ์ •์„ ํ™•์ธํ•˜๊ณ  ์‹ถ์–ด์กŒ๋‹ค.

 

๊ทธ๋ž˜์„œ ์‹คํ—˜ํ•ด๋ดค๋‹ค.

 

๊ฒฐ๊ณผ๋Š”??

์ฒ˜์ฐธํ•˜๋‹ค..

 

๋ฌด์—‡์ด ๋ฌธ์ œ์˜€์„๊นŒ

ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ๊ฐ€ ์”นํ˜”๋‹ค. 

@GetMapping("/test")
public void test() throws InterruptedException {
    int threadCount = 100;
    ExecutorService executorService = Executors.newFixedThreadPool(100);
    CountDownLatch latch = new CountDownLatch(threadCount);

    for (int i = 0; i < threadCount; i++) {
        int finalI = i;
        executorService.submit(() -> {
            try {
                log.info(finalI +"๋ฒˆ์งธ ์ผ๊พผ ์ผํ•œ๋‹ค.");
                kakaopayStrategyApplication.test("lala");
            } finally {
                latch.countDown();
            }
        });
    }

    latch.await();

}

์ด ํ…Œ์ŠคํŠธ๋ฅผ ์ด์šฉํ•ด์„œ 100๊ฐœ์˜ ์š”์ฒญ์„ ๋™์‹œ์— ์š”์ฒญํ•˜๋ฉด,

 

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void test(String id) {
    log.info("์ผํ•ด๋ผ!");
    Product product = productRepository.findById(1L).orElseThrow(() -> new RuntimeException("์—ํ—ค์ด"));
    log.info("์ƒํ’ˆ ์ „ ๊ฐœ์ˆ˜:" + product.getQuantity());
    product.decreaseItemQuantity(1);
    log.info("์ƒํ’ˆ ํ›„ ๊ฐœ์ˆ˜:" + product.getQuantity());
}

์›๋ž˜ ์ƒํ’ˆ 200๊ฐœ์—์„œ 100๊ฐœ๊ฐ€ ๋˜์–ด์•ผ ์ •์ƒ์ด๋‹ค.

ํ•˜์ง€๋งŒ, ๊ณ„์† ์ด์ƒํ•œ ์žฌ๊ณ ๋Ÿ‰์ด ๋‚จ์•˜๋‹ค..

@Transactional(propagation = Requires_New)๋ฅผ ํ•˜๋ฉด ํŠธ๋žœ์žญ์…˜์˜ ์ƒˆ๋กœ์šด ์‹œ์ž‘์œผ๋กœ ์ธํ•ด, AOP ์ด์šฉํ•  ๊ฒฝ์šฐ ๋ฌธ์ œ๊ฐ€ ์—†์„ ๊ฑฐ๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

 

 

๋ฌธ์ œ ์ธ์ง€

๋‚ด ํ•œ์ค„๊ธฐ ๋น›..

์ด ๋ถ„์˜ ๊ธ€์„ ์ฝ๊ณ , ๊นจ๋‹ฌ์•˜๋‹ค..

temp method์— @Transactional์„ ์‚ฌ์šฉํ•˜๋ฉด ์œ„ํ—˜ํ•ฉ๋‹ˆ๋‹ค.
๋‚ด๋ถ€์ ์œผ๋กœ AOP๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— unlock์ด ๋œ ํ›„ commit์ด ๋˜๋ฏ€๋กœ ๋™๊ธฐํ™”๊ฐ€ ์ œ๋Œ€๋กœ ๋˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋ž˜์„œ redis์— lock์„ ์žก๊ณ  db์™€ redis์˜ transaction ์ฒ˜๋ฆฌ๋ฅผ ๋”ฐ๋กœ ํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํก์‚ฌ ์‹ ์˜ ๋ชฉ์†Œ๋ฆฌ..

 

์Šคํ”„๋ง์—์„œ ์ œ๊ณตํ•˜๋Š” @Transactional ์• ๋…ธํ…Œ์ด์…˜์€ ํŠธ๋žœ์žญ์…˜ ๋กœ์ง์„ AOP๋ฅผ ํ™œ์šฉํ•ด์„œ ํƒ€๊ฒŸ ๋ฉ”์„œ๋“œ๋ฅผ ํ”„๋ก์‹œ๋กœ ๊ฐ์‹ผ๋‹ค.

๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ฒ˜๋ฆฌ๊ฐ€ ๊น”๋”ํ•ด์ง€๋Š” ์ด์ ์ด ์žˆ๋‹ค.

 

๋‚ด๊ฐ€ Redisson lock์„ ์ด์šฉํ•˜๊ธฐ ์œ„ํ•ด ์ด ๋˜ํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์กฐ๊ธˆ ์„ž์—ฌ ๋“ค์–ด๊ฐ€๋ฏ€๋กœ, ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ AOP๋กœ ๊ฐ์ŒŒ๋‹ค. 

 

@Transactional๊ณผ @Redislocked(๋‚ด๊ฐ€ ๊ตฌํ˜„ํ•œ redisson lock ์• ๋…ธํ…Œ์ด์…˜ - AOP ํ™œ์šฉ) ์˜ ๋™์‹œ ์‚ฌ์šฉ์œผ๋กœ ์ธํ•ด AOP๊ฐ€ ์ œ๋Œ€๋กœ ์ˆœ์ฐจ์ ์œผ๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์•„ ํŠธ๋žœ์žญ์…˜์ด ์”นํžŒ๊ฒƒ์ด๋‹ค.

 

์•„ ๋งž๋‹ค.. ์Šคํ”„๋ง AOP๋Š” ํ”„๋ก์‹œ๋กœ ๊ฐ์‹ธ๋Š” ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•˜๋Š”๋ฐ, ๋‚ด๊ฐ€ ์ด๊ฑธ ์™œ ๋†“์ณค์„๊นŒ..

์ˆœ์„œ๊ฐ€ ์ค‘์š”ํ•œ ๋ฒ•์ด๋‹ค. ์ด๋ฒˆ ๊ธฐํšŒ์— ๋‹ค์‹œ ํ•œ๋ฒˆ ๊ฐœ๋…์„ ์žก๊ณ  ๊ฐ„๋‹ค.

 

 

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

ํŠธ๋žœ์žญ์…˜ ๋กœ์ง์„ ๋˜ ๋‹ค๋ฅธ Aspect๋กœ ๋ถ„๋ฆฌํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค.

์• ๋…ธํ…Œ์ด์…˜์— ํŠธ๋žœ์žญ์…˜ ์˜ต์…˜ ์ถ”๊ฐ€

 

/**
 * ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ aop
 */
@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
@Order(value = 2)
public class TransactionAspect {

    private final RedisFunctionProvider redisFunctionProvider;

    @Around("@annotation(com.daniel.mychickenbreastshop.global.aspect.annotation.RedisLocked)")
    public Object executeWithTransaction(ProceedingJoinPoint joinPoint) {
        if (!isTransactional(joinPoint)) {
            try {
                return joinPoint.proceed();
            } catch (Throwable e) {
                throw new InternalErrorException(e);
            }
        }

        RTransaction transaction = redisFunctionProvider.startRedisTransacton();
        TransactionStatus status = redisFunctionProvider.startDBTransacton();

        Object result;

        try {
            result = joinPoint.proceed();

            redisFunctionProvider.commitRedis(transaction);
            redisFunctionProvider.commitDB(status);
        } catch (Throwable e) {
            redisFunctionProvider.rollbackRedis(transaction);
            redisFunctionProvider.rollbackDB(status);
            throw new InternalErrorException(e);
        }
        return result;
    }

    private boolean isTransactional(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        return method.getAnnotation(RedisLocked.class).transactional();
    }
}

ํŠธ๋žœ์žญ์…˜ ๋กœ์ง์œผ๋กœ ๊ฐ์‹ธ๋Š” AOP ์ถ”๊ฐ€

@Order๋กœ AOP์˜ ๊ฐ์‹ธ์ง€๋Š” ์ˆœ์„œ๋ฅผ ์ง€์ •ํ•˜์˜€๋‹ค. 

(์ˆซ์ž๋Š” ์ƒ๋Œ€์  ํฌ๊ธฐ ๋น„๊ต, ์ˆซ์ž๊ฐ€ ํด์ˆ˜๋ก ํ•ต์‹ฌ ๊ด€์‹ฌ ๋ชจ๋“ˆ์— ๋จผ์ € ๊ฐ์‹ธ์ง)

 

 

์‹คํ–‰

๊ธฐ์กด ์ƒํ’ˆ ์ˆ˜๋Ÿ‰ 20๊ฐœ

 

๊ธฐ์กด 100๊ฐœ ๋™์‹œ ์Šค๋ ˆ๋“œ ์š”์ฒญ ์‹คํ–‰

 

์•ผ๋ฌด์ง„ ๋กœ๊ทธ๋“ค
100๊ฐœ๊ฐ€ ์ •ํ™•ํ•˜๊ฒŒ ๊ฐ์†Œํ–ˆ๋‹ค

 

=> ์ด๋ ‡๊ฒŒ ๋™์‹œ ์š”์ฒญ ๊ฑด์— ๋Œ€ํ•œ Redisson ๋ถ„์‚ฐ๋ฝ์„ ์ •ํ™•ํ•˜๊ฒŒ ์ ์šฉํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. ํŠธ๋žœ์žญ์…˜์˜ ์ฒ˜๋ฆฌ๋„ ๋ฌด์ฒ™์ด๋‚˜ ์ค‘์š”ํ•จ์„ ๊นจ๋‹ฌ์•˜๊ณ , AOP๋ฅผ ์ ์šฉํ•˜๋ฉด์„œ ํ”„๋ก์‹œ ์›๋ฆฌ๋ฅผ ๋‹ค์‹œ ํ•œ๋ฒˆ ๊นจ๋‹ซ๋Š” ๊ณ„๊ธฐ๊ฐ€ ๋˜์—ˆ๋‹ค.

๋ฐ˜์‘ํ˜•