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

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

by GroovyArea 2022. 9. 27.

๋‚ด ํ”„๋กœ์ ํŠธ์˜ Payment๋ฅผ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ๊ฐ€์žฅ ๊ธฐ๋ณธ ์ค‘์— ๊ธฐ๋ณธ์ด ๋˜๋Š” ๋ฌธ์ œ๋ฅผ ์ง๋ฉดํ–ˆ์—ˆ๋‹ค.

๊ทธ๊ฒƒ์€ ๋ฐ”๋กœ ๋™์‹œ์„ฑ ๋ฌธ์ œ!

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

A๋ผ๋Š” ์ƒํ’ˆ (์žฌ๊ณ  3๊ฐœ) ์„ [๊ฐ€]๊ตฐ์ด 2๊ฐœ ๊ตฌ๋งคํ•˜๋ ค ํ•œ๋‹ค. ๋™์‹œ์— [๋‚˜]๊ตฐ์ด 2๊ฐœ ๊ตฌ๋งคํ•˜๋ ค ํ•œ๋‹ค.
๋ฏธ์„ธํ•˜๊ฒŒ ๋‚˜๋งˆ 0.00001์ดˆ์˜ ์ฐจ์ด๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค.

๊ฒฐ๊ตญ ๊ฐ๊ฐ์˜ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ฐ™์€ ์ƒํ’ˆ์˜ ์žฌ๊ณ ๋ฅผ ์กฐํšŒํ•œ๋‹ค. ์›๋ž˜๋Œ€๋กœ๋ผ๋ฉด ํ•œ ๋ช…์€ ๋ชป ์‚ฌ์•ผ ์ •์ƒ์ด๋‹ค.

์œ„ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์ด ๋ญ๊ฐ€ ์žˆ์„๊นŒ?

1. Synchronized

์ž๋ฐ”๋กœ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.
Thread-Safe ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋งค์šฐ ์ข‹์•„๋ณด์ด๋‚˜, ์„œ๋ฒ„๊ฐ€ ์ฆ์„ค๋  ๊ฒฝ์šฐ ์˜๋ฏธ๊ฐ€ ์—†์–ด์ง„๋‹ค.

2. Database Lock

Database์— Lock์„ ๊ฑธ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.
ํ…Œ์ด๋ธ”์— Lock์„ ๊ฑธ๊ฑฐ๋‚˜ Mysql์˜ Innodb์—”์ง„์€ ๋ ˆ์ฝ”๋“œ Lock์„ ์ œ๊ณตํ•˜๋ฏ€๋กœ ์ด๋ฅผ ํ†ตํ•ด ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.
ํ•˜์ง€๋งŒ DB์— ์ง์ ‘์ ์ธ Lock์„ ๊ฑฐ๋Š” ๊ฒƒ์€ ๊ฒฐ์ฝ” ์ข‹์€ ๋ฐฉ๋ฒ•์€ ์•„๋‹ˆ๋‹ค. ๊ต์ฐฉ ์ƒํƒœ(deadlock)์— ๋น ์งˆ ์ˆ˜๋„ ์žˆ๊ณ ,
๋ฌด์—‡๋ณด๋‹ค ์„ฑ๋Šฅ์ ์ธ ๋ถ€๋ถ„์—์„œ ์†๋„๊ฐ€ ๋А๋ ค์งˆ ์ˆ˜ ์žˆ๋‹ค๋Š” ๋ฌธ์ œ์ ์ด ์žˆ๋‹ค.

3. Redisson์„ ์ด์šฉํ•œ ๋ถ„์‚ฐ Lock

Redis๋ฅผ ์ด์šฉํ•ด์„œ Lock์„ ๊ฑธ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.
์„œ๋ฒ„๊ฐ€ ์—ฌ๋Ÿฌ ๋Œ€์ผ ๊ฒฝ์šฐ์—๋„ ๋ฌธ์ œ ์—†์ด ๋ถ„์‚ฐ Lock์„ ๊ฑธ์–ด ๋ฐ์ดํ„ฐ์˜ ์ •ํ•ฉ์„ฑ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.
์ž๋ฐ”์—์„œ ์ œ๊ณตํ•˜๋Š” Redisson ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ด์šฉํ•ด ๋ถ„์‚ฐ Lock์„ ํš๋“ํ•ด ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ๋„ ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

4. ๊ตฌํ˜„ ๊ณผ์ •

 

Build.gradle ์˜์กด์„ฑ ์ถ”๊ฐ€

// Redisson
implementation 'org.redisson:redisson-spring-boot-starter:3.17.6'

 

Redisson Configuration

  • Redisson Client์˜ ์„ค์ •์„ ์ถ”๊ฐ€ํ•ด Bean์œผ๋กœ ๋“ฑ๋กํ•œ๋‹ค.
  • ์•ž์„  bean๋“ค์€ ๊ธฐ์กด์— ์‚ฌ์šฉํ•˜๋˜ Lettuce ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๊ณ , ์ถ”๊ฐ€ํ•˜๊ณ ์ž ํ•˜๋Š” Redisson client๋ฅผ Bean์œผ๋กœ ๋“ฑ๋กํ•˜์ž.
@Configuration
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Value("${spring.redis.password}")
    private String password;

    @Value("${spring.redis.connectionTimeout}")
    private Long connectionTimeout;

    /**
     * redis์™€ connection์„ ์ƒ์„ฑํ•ด ์ฃผ๋Š” ๊ฐ์ฒด
     * @return LettuceConnectionFactory
     */
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration =
                new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setPassword(password);

        return new LettuceConnectionFactory(redisStandaloneConfiguration);
    }

    /**
     * redis ์„œ๋ฒ„์™€ ํ†ต์‹  ๋ฐ ์œ ์ €์—๊ฒŒ redis ๋ชจ๋“ˆ ๊ธฐ๋Šฅ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด ์ถ”์ƒํ™”๋ฅผ ํ†ตํ•ด
     * ์˜คํผ๋ ˆ์ด์…˜ ์ œ๊ณต
     * @return redisTemplate
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate =
                new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate() {
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(redisConnectionFactory());
        stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
        stringRedisTemplate.setValueSerializer(new StringRedisSerializer());
        return stringRedisTemplate;
    }

    @Bean
    public RedissonClient redissonClient() {
        String address = "redis://" + host + ":" + port;

        Config config = new Config();
        config.useSingleServer()
                .setAddress(address)
                .setPassword(password)
                .setConnectTimeout(connectionTimeout.intValue());

        return Redisson.create(config);
    }
}

 

Lock์„ ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ง์ ‘ ์ฝ”๋“œ์— ์ ์šฉ ์‹œํ‚ค๋ฉด ํ•„์š” ์ด์ƒ์˜ ๋น„์ฆˆ๋‹ˆ์Šค๊ฐ€ ํ•˜๋‚˜์˜ ๋ฉ”์„œ๋“œ์— ๋“ค์–ด๊ฐˆ ๊ฒƒ ๊ฐ™๋‹ค.
๊ทธ๋ž˜์„œ AOP๋ฅผ ์ด์šฉํ•ด์„œ ๋ถ„์‚ฐ Lock์„ ํ•„์š”๋กœ ํ•˜๋Š” ๊ณณ์— ์ ์šฉ ์‹œํ‚ค๊ธฐ ์œ„ํ•ด ์• ๋…ธํ…Œ์ด์…˜์„ ์„ ์–ธํ•ด ์ ์šฉํ–ˆ๋‹ค.

์• ๋…ธํ…Œ์ด์…˜ ์ •์˜

  • Lock ์œ ์ง€ ์‹œ๊ฐ„๊ณผ ๋Œ€๊ธฐ ์‹œ๊ฐ„์„ ํ”„๋กœํผํ‹ฐ๋กœ ์ •์˜
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLocked {

    /**
     * redisson lock ์œ ์ง€ ์‹œ๊ฐ„ <br>
     * ๋‹จ์œ„ : milliseconds
     */
    long leaseTime() default 1000;

    /**
     * redisson lock ํš๋“ ๋Œ€๊ธฐ ์‹œ๊ฐ„ <br>
     * ๋‹จ์œ„ : milliseconds
     */
    long waitTime() default 3000;
}

 

AOP ๊ตฌํ˜„

  • Lock์˜ key๋Š” ํ•„์š”์— ๋”ฐ๋ผ ์ž‘๋ช…์„ ํ•˜๋ฉด ๋  ๊ฒƒ์ด๋‹ค.
  • tryLock์„ ํ†ตํ•ด lock์˜ ํš๋“ ์—ฌ๋ถ€ ํŒŒ์•…๊ณผ ํš๋“์„ ์ง„ํ–‰ํ•˜๊ณ , @Around๋กœ ์ •์˜ํ•ด ์ค‘๊ฐ„์— ํ•ต์‹ฌ๊ด€์‹ฌ์‚ฌ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.
@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class RedisLockAspect {

    private final RedissonClient redissonClient;

    private static final String LOCK_SUFFIX = ":lock";

    @Around("@annotation(com.daniel.mychickenbreastshop.global.aspect.annotation.RedisLocked)")
    public Object executeWithLock(ProceedingJoinPoint joinPoint) {
        String key = getLockableKey(joinPoint);
        return execute(key, joinPoint);
    }


    private String getLockableKey(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        String methodName = method.getName();
        String className = method.getDeclaringClass().toString();
        return methodName + className + LOCK_SUFFIX;
    }

    private Object execute(String key, ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        long leaseTime = method.getAnnotation(RedisLocked.class).leaseTime();
        long waitTime = method.getAnnotation(RedisLocked.class).waitTime();

        RLock lock = redissonClient.getLock(key);

        Object result;

        try {
            boolean tryLock = lock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS);

            if (!tryLock) {
                log.error("Lock ํš๋“์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");
            }

            result = joinPoint.proceed();
        } catch (Throwable e) {
            throw new InternalErrorException(e);
        } finally {
            unlock(lock);
            log.info("Redis unlocked!");
        }
        return result;
    }

    private void unlock(RLock lock) {
        if (lock != null && lock.isLocked()) {
            lock.unlock();
        }
    }
}

 

์• ๋…ธํ…Œ์ด์…˜ ํ™œ์šฉ

  • ์• ๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ•ด ๋ถ„์‚ฐ ๋ฝ์ด ํ•„์š”ํ•œ ๋ฉ”์„œ๋“œ์— ๋‹ฌ์•„์ฃผ๋ฉด ๋œ๋‹ค.
  • ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ๊ณณ์„ AOP๋Š” ํ”„๋ก์‹œ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํŠธ๋žœ์žญ์…˜ ์ „ํŒŒ๋ฅผ ๋”ฐ๋กœ ์ ์šฉํ–ˆ๋‹ค.
  • ์ด๋ ‡๊ฒŒ ํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ์ง์ ‘ ์ปค๋ฐ‹๊ณผ ๋กค๋ฐฑ์„ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.
@Override
@RedisLocked(leaseTime = 3000)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public PaymentResult cancelPayment(PayCancelRequestDto payCancelRequestDto, String loginId) {

 

์ด๋ ‡๊ฒŒ ๊ฐ„๋‹จํ•˜๊ฒŒ Redisson์„ ์ด์šฉํ•˜์—ฌ ๋ถ„์‚ฐ Lock์„ ๊ตฌํ˜„ํ•ด๋ดค๋‹ค.
์ข€ ๋” ๋‚˜์€ ์„ฑ๋Šฅ๊ณผ ๋‚˜์€ ๋ฐฉ๋ฒ•์„ ์ฐพ๋Š” ๊ฒƒ์€ ์ฐธ ์‰ฝ์ง€ ์•Š์€ ๊ฒƒ ๊ฐ™๋‹ค. ๋˜, ์ตœ์„ ์˜ ๋ฐฉ๋ฒ•์„ ๊ณ„์† ์ฐพ๋А๋ผ ์‹œ๋„ ์กฐ์ฐจ ์•ˆ ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์ผ๋‹จ ๋‹จ๊ณ„์— ๋งž๋Š” ์‹œ๋„๋ฅผ ๋ฐŸ์•„๋‚˜๊ฐ€๋ฉฐ ํ•„์š”์— ์˜ํ•ด ์ตœ์„ ์˜ ๋ฐฉ๋ฒ•์„ ์ ์šฉ ์‹œํ‚ค๋Š” ๊ณผ์ •์ด ํ•„์š”ํ•  ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

 

ํŠธ๋žœ์žญ์…˜ ๊ด€๋ จ ์˜ˆ์™ธ ๋ฌธ์ œ ํ•ด๊ฒฐ ์ฒ˜๋ฆฌ ํฌ์ŠคํŒ…!

https://sweeeetgoguma.tistory.com/entry/Redisson-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EB%AC%B8%EC%A0%9C-%EB%B0%9C%EC%83%9D-%EB%B0%8F-%ED%95%B4%EA%B2%B0

 

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

์ง€๋‚œ ํฌ์ŠคํŠธ [Redisson]์„ ์ด์šฉํ•œ ๋ถ„์‚ฐ Lock ๊ตฌํ˜„ & ๋™์‹œ์„ฑ ๋ฌธ์ œ ํ•ด๊ฒฐ ๋‚ด ํ”„๋กœ์ ํŠธ์˜ Payment๋ฅผ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ๊ฐ€์žฅ ๊ธฐ๋ณธ ์ค‘์— ๊ธฐ๋ณธ์ด ๋˜๋Š” ๋ฌธ์ œ๋ฅผ ์ง๋ฉดํ–ˆ์—ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ๋ฐ”๋กœ ๋™์‹œ์„ฑ ๋ฌธ์ œ! ์Šคํ”„๋ง๋ถ€ํŠธ์˜ ๋‚ด

sweeeetgoguma.tistory.com


์ฐธ๊ณ  ์ž๋ฃŒ
https://it-hhhj2.tistory.com/102

 

redis ์„ค์น˜ ๋ฐ redisson์„ ์ด์šฉํ•œ ๋ถ„์‚ฐ๋ฝ ๊ตฌํ˜„

์„ค์น˜ redis ์„ค์น˜ ์œ„ ๋ธ”๋กœ๊ทธ ๋”ฐ๋ผ ์ˆœ์กฐ๋กญ๊ฒŒ ์„ค์น˜ ํ›„ ํ™•์ธ ์™„๋ฃŒ Redis์™€ ๋ถ„์‚ฐ๋ฝ ๋ถ„์‚ฐ๋ฝ(Distributed Lock) ์—ฌ๋Ÿฌ ๋…๋ฆฝ๋œ ํ”„๋กœ์„ธ์Šค์—์„œ ํ•˜๋‚˜์˜ ๊ณต์œ  ์ž์›์— ์ ‘๊ทผํ•  ๋•Œ, ๋ฐ์ดํ„ฐ์— ๊ฒฐํ•จ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋„๋ก ์›์ž

it-hhhj2.tistory.com

https://devroach.tistory.com/82

 

Redisson ์œผ๋กœ ๋ถ„์‚ฐ Lock ๊ตฌํ˜„ํ•˜๊ธฐ

ํšŒ์‚ฌ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋˜ ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ถ„์‚ฐ๋ฝ์ด ํ•„์š”ํ•  ๊ฒƒ ๊ฐ™๋‹ค๋Š” ํŒ๋‹จ์ด ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ๋ถ„์‚ฐ๋ฝ์ด๋ž€ ๋ฌด์—‡์ผ๊นŒ์š”? ์•„์ฃผ ๊ฐ€๋ณ๊ฒŒ ์„ค๋ช…ํ•˜์ž๋ฉด ์˜ˆ๋ฅผ ๋“ค์–ด, ๊ฐ€๊ฒŒ๋Š” ํ•˜๋‚˜์˜ ์ฃผ๋ฌธ๋งŒ ๋ฐ›

devroach.tistory.com

 

๋ฐ˜์‘ํ˜•