DB/REDIS

[Redis] ๋ ˆ๋””์Šค ๋ถ„์‚ฐ ๋ฝ ์ดํ•ดํ•˜๊ธฐ

YeopJu
๋ฐ˜์‘ํ˜•

์ด์ „ ๊ธ€์— ์ด์–ด์„œ ๋ถ„์‚ฐ๋ฝ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ ํƒ ๊ธฐ์ค€๊ณผ ๋ ˆ๋””์Šค๋ฅผ ํ†ตํ•œ ๋ถ„์‚ฐ ๋ฝ ์ ์šฉ๊ณผ์ •์— ๋Œ€ํ•ด ๋‹ค๋ค„๋ณผ ์˜ˆ์ •์ด๋‹ค.

 


โ˜‘๏ธ Lettuce / Redisson

 

์ด ๋‘˜ ๋ชจ๋‘ ๋ ˆ๋””์Šค์˜ ๋ถ„์‚ฐ ๋ฝ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค. ๋ถ„์‚ฐ ๋ฝ์„ ์ฒ˜์Œ ๊ตฌํ˜„ํ–ˆ์„ ๋•Œ ๋‚˜๋Š” ๋‹จ์ˆœํ•œ ๋ถ„์‚ฐ ๋ฝ๋งŒ์„ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋ผ๋ฉด Lettuce๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ถ„์‚ฐ๋ฝ์„ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ํฌ๊ธฐ๋ฅผ ๊ณ ๋ คํ–ˆ์„ ๋–„ ๋” ํšจ์œจ์ ์ผ ๊ฒƒ์ด๋ผ๊ณ  ํŒ๋‹จํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ Lettuce์—๋Š” ์Šคํ•€๋ฝ์œผ๋กœ ์ธํ•œ ๋ถ€ํ•˜, Atomicํ•œ ํƒ€์ž„์•„์›ƒ ์„ค์ • ๋ถˆ๊ฐ€๋Šฅ ๋“ฑ ์—ฌ๋Ÿฌ ๋‹จ์ ์ด ์กด์žฌํ–ˆ๊ณ  ์ด๋Ÿฌํ•œ ๋‹จ์ ๋“ค์„ ๋ณด์™„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ Redisson์— ๋Œ€ํ•ด ์•Œ์•„๋ดค๋‹ค.

 

1. Atomic TimeOut

Redisson์€ tryLock ๋ฉ”์„œ๋“œ์— ํƒ€์ž„์•„์›ƒ์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์ด ์ฒซ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ๋ฝ ํš๋“์„ ๋Œ€๊ธฐํ•  ํƒ€์ž„์•„์›ƒ์„, ๋‘ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ์—๋Š” ๋ฝ์ด ๋งŒ๋ฃŒ๋˜๋Š” ์‹œ๊ฐ„์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฒซ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋งŒํผ์˜ ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด false๊ฐ€ ๋ฐ˜ํ™˜๋˜๋ฉฐ ๋‘ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋งŒํผ์˜ ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด ๋ฝ์ด ๋งŒ๋ฃŒ๋˜์–ด ์‚ฌ๋ผ์ง€๊ธฐ ๋•Œ๋ฌธ์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ง์ ‘ ๋ฝ์„ ํ•ด์ œํ•ด์ฃผ์ง€ ์•Š๋”๋ผ๋„ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์—์„œ ๋ฝ์„ ํš๋“ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด๋กœ ์ธํ•ด ๋ฝ์ด ํ•ด์ œ๋˜์ง€ ์•Š์€ ์ฑ„๋กœ ์•ฑ์ด ์˜ˆ๊ธฐ์น˜ ์•Š๊ฒŒ ์ข…๋ฃŒ๋˜์–ด ๋ฌดํ•œ ๋ฃจํ”„์— ๋น ์งˆ ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

// RedissonLock์˜ tryLock ๋ฉ”์„œ๋“œ
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException

 

 

2. PUBSUB

`setnx`, `setex`์™€ ๊ฐ™์€ ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด ์ง€์†์ ์œผ๋กœ Redis์—๊ฒŒ ๋ฝ์ด ํ•ด์ œ๋˜์—ˆ๋Š”์ง€ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ์Šคํ•€ ๋ฝ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋Š” Lettuce์™€ ๋‹ฌ๋ฆฌ Redisson์€ pub/sub ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ๋ฝ ํ•ด์ œ ์‹œ ์•Œ๋ฆผ์„ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ณด๋‚ด ๋ฝ ํš๋“ ๊ฐ€๋Šฅ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•œ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด Redis์— ๊ฐ€ํ•ด์ง€๋Š” ๋ถˆํ•„์š”ํ•œ ํŠธ๋ž˜ํ”ฝ์„ ์ค„์ผ ์ˆ˜ ์žˆ๊ณ , ๋ฝ์ด ํ•ด์ œ๋˜๋ฉด ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›์€ ํด๋ผ์ด์–ธํŠธ๋Š” ๋ฝ ํญ๋“์„ ๋‹ค์‹œ ์‹œ๋„ํ•˜๊ฒŒ ๋œ๋‹ค. ๋˜ํ•œ ๋ฝ ํš๋“์€ tryLock ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒฝํ•ฉ์ด ์—†์„ ๋•Œ ๋น ๋ฅด๊ฒŒ ์ˆ˜ํ–‰๋˜๋ฉฐ, ์‹คํŒจ ์‹œ pub/sub์„ ํ†ตํ•ด ๋Œ€๊ธฐํ•˜๋ฉด์„œ ํƒ€์ž„์•„์›ƒ์‹œ๊นŒ์ง€ ์žฌ์‹œ๋„๋ฅผ ๋ฐ˜๋ณตํ•œ๋‹ค.

 

3. Lua Script

๋ฝ ๊ด€๋ จ ์—ฐ์‚ฐ์€ ์›์ž์ ์ด์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋ช…๋ น์–ด๊ฐ€ ์„ž์ด์ง€ ์•Š๋„๋ก Lua ์Šคํฌ๋ฆฝํŠธ๋ฅผ ํ™œ์šฉํ•˜์—ฌ Atomicํ•œ ์—ฐ์‚ฐ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ฝ์˜ ํš๋“ ๋ฐ ํ•ด์ œ ๊ณผ์ •์—์„œ ์—ฌ๋Ÿฌ ์—ฐ์‚ฐ์„ ๋ฌถ์–ด ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•˜๋ฉฐ, ํŠนํžˆ pub/sub ์•Œ๋ฆผ๊ณผ ๋ฝ ํ•ด์ œ๋ฅผ ์›์ž์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ด์•ผํ•œ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๋ฝ ํš๋“์ด ๊ฐ€๋Šฅํ•˜๋‹ค๊ณ  ์‘๋‹ต๋ฐ›์€ ๋‹ค์Œ, ๋ฝ ํš๋“์‹œ๋„๋ฅผ ํ–ˆ๋Š”๋ฐ ๊ทธ ์‚ฌ์ด์— ์ด๋ฏธ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์—์„œ ๋ฝ์„ ํš๋“ํ•ด๋ฒ„๋ ค์„œ ๋ฝ ํš๋“์„ ์‹คํŒจํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ๋‹ค. ๊ฒฐ๋ก ์ ์œผ๋กœ, ์ด๋ฅผ ํ†ตํ•ด ์„ฑ๋Šฅ์„ ์œ ์ง€ํ•˜๋ฉด์„œ๋„ Redis์— ๊ฐ€ํ•ด์ง€๋Š” ๋ถ€ํ•˜๋ฅผ ์ค„์ด๊ณ , ์ •ํ™•ํ•œ ๋ฝ ๋™์ž‘์„ ๋ณด์žฅํ•œ๋‹ค. Redisson์€ ์ด๋Ÿฌํ•œ Lua Script๋ฅผ ์ ๊ทน์ ์œผ๋กœ ํ™œ์šฉํ•˜๊ณ  ์žˆ๋‹ค.

 

String luaScript = 
    "if redis.call('exists', KEYS[1]) == 0 then " + 
    "redis.call('set', KEYS[1], ARGV[1]); " + 
    "redis.call('pexpire', KEYS[1], ARGV[2]); " + 
    "return 1; " + 
    "else " + 
    "return 0; " + 
    "end;";

// Lua script ์‹คํ–‰์ฝ”๋“œ
Object result = redisson.getScript().eval(RScript.Mode.READ_WRITE,
    luaScript, 
    RScript.ReturnType.INTEGER, 
    Collections.singletonList(lockKey), 
    lockValue, lockTTL);

 

 

โ˜‘๏ธ ๋ถ„์‚ฐ๋ฝ ๊ตฌํ˜„

 

๋ถ„์‚ฐ๋ฝ์€ ํšก๋‹จ ๊ด€์‹ฌ์‚ฌ์— ํฌํ•จ๋˜๊ธฐ ๋•Œ๋ฌธ์— AOP๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•œ๋‹ค๋ฉด ๋ฉ”์ธ ๋กœ์ง๊ณผ์˜ ๋ถ„๋ฆฌํ•จ์œผ๋กœ์จ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๋†’์ด๊ณ  ์ง๊ด€์ ์ธ ๊ตฌํ˜„์ด ๊ฐ€๋Šฅํ•ด์ง„๋‹ค. ๋˜ํ•œ ๋ถ„์‚ฐ๋ฝ์€ ํŠน์ • ๋„๋ฉ”์ธ์— ํ•œ์ •๋˜์–ด ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— `execution`์ด๋‚˜ `args` ํฌ์ธํŠธ์ปท๋ณด๋‹ค๋Š” `annotation`์„ ์‚ฌ์šฉํ–ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ๋จผ์ € ์• ๋…ธํ…Œ์ด์…˜์„ ๊ตฌํ˜„ํ–ˆ๋‹ค.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {

    String key();
    TimeUnit timeUnit() default TimeUnit.SECONDS;
    long waitTime() default 5L;
    long leaseTime() default 3L;
}

 

AOP๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ์— ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ `@Around` ์–ด๋“œ๋ฐ”์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค. ๋ถ„์‚ฐ ๋ฝ์„ ๊ตฌํ˜„ํ•  ๋•Œ๋Š” ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ „ํ›„์— ์‹คํ–‰๋˜์–ด์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ์–ด๋“œ๋ฐ”์ด์Šค๋ฅผ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ, ์ด์™€ ๊ฐ™์€ ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด `@Before`, `@AfterReturning`๊ณผ ๊ฐ™์€ ์–ด๋“œ๋ฐ”์ด์Šค๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ์ œ์•ฝ์„ ๋‘๋Š” ๊ฒƒ์ด ๋” ์ข‹์€ ์„ค๊ณ„๋ผ๋Š” ๊ฒƒ์„ ํ•ญ์ƒ ์—ผ๋‘ํ•ด๋‘ฌ์•ผํ•œ๋‹ค.

 

 

 

DistributedLockAop.java

@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.dnd.matchup.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 + distributedLock.key();
        RLock rLock = redissonClient.getLock(key);

        try {
            boolean available = rLock.tryLock(distributedLock.waitTime(), distributedLock.leaseTime(), distributedLock.timeUnit());
            if (!available) return false;
            return aopForTransaction.proceed(joinPoint);
        } catch (InterruptedException e) {
            throw new InterruptedException();
        } finally {
            try {
                rLock.unlock();
            } catch (IllegalMonitorStateException e) {
                log.info("Redisson Lock Already UnLock {}", key);
            }
        }
    }
}

 

์œ„ ์ฝ”๋“œ์—์„œ๋Š” ๋จผ์ € `rLock`์„ ๊ฐ€์ ธ์˜จ ํ›„ `tryLock`์„ ํ†ตํ•ด ์• ๋…ธํ…Œ์ด์…˜์— ์ง€์ •๋œ waitTime ๊นŒ์ง€ ๋ฝ ํญ๋“์„ ์‹œ๋„ํ•œ๋‹ค. ์ดํ›„ ๋ฝ์„ ํญ๋“ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด DistributedLock ์• ๋…ธํ…Œ์ด์…˜์ด ์„ ์–ธ๋˜์–ด ์žˆ๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๋ณ„๋„์˜ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ์•„๋ž˜์™€ ๊ฐ™์ด ์ „ํŒŒ ์ „๋žต์„ `REQUIRES_NEW`๋กœ ์„ค์ •ํ•œ ํŠธ๋žœ์žญ์…˜์ด ์„ ์–ธ๋˜์–ด ์žˆ๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

 

 

 

AopForTransaction.java

@Component
public class AopForTransaction {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Object proceed(final ProceedingJoinPoint joinPoint) throws Throwable {
        return joinPoint.proceed();
    }
}

 

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

 

 


 

โ˜‘๏ธ ๊ฒฐ๋ก 

๋ ˆ๋””์Šค๋ฅผ ์ด์šฉํ•œ ๋ถ„์‚ฐ๋ฝ ๋ฐฉ์‹์€ ๋ ˆ๋””์Šค๊ฐ€ ์ด๋ฏธ ์ธํ”„๋ผ์— ํฌํ•จ๋˜์–ด ์žˆ์„ ๊ฒฝ์šฐ ์ข‹์€ ์„ ํƒ์ง€๊ฐ€ ๋  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ์‹ฑ๊ธ€ ์Šค๋ ˆ๋“œ๋กœ ์ž‘๋™ํ•˜๋Š” ๋ ˆ๋””์Šค์˜ ํŠน์„ฑ์ƒ ๋‹จ์ผ ์žฅ์•  ์ง€์ ์ด ๋  ๊ฐ€๋Šฅ์„ฑ์ด ์กด์žฌํ•œ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์ด์ „ ๊ธ€์—์„œ ์†Œ๊ฐœํ•œ Replication์ด๋‚˜ Sentinel ์•„ํ‚คํ…์ฒ˜์˜ ์‚ฌ์šฉ์ด ๊ถŒ์žฅ๋œ๋‹ค. ํ•ด๋‹น ์•„ํ‚คํ…์ฒ˜์—์„œ๋Š” ํŠน์ • ์ƒํ™ฉ์— ๋ฐ์ดํ„ฐ ๋ถˆ์ผ์น˜ ๋ฌธ์ œ๋‚˜ ๊ฒฝ์Ÿ ์ƒํƒœ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ณ  ์ด๋Š” ๋ ˆ๋“œ๋ฝ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ํ†ตํ•ด ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค

๋ฐ˜์‘ํ˜•