BackEnd/Spring Boot

[Redis] ๋ถ„์‚ฐ ๋น„๊ด€์  ๋ฝ์œผ๋กœ ๋™์‹œ์„ฑ ์ด์Šˆ ํ•ด๊ฒฐํ•˜๊ธฐ

ddonghyeo 2024. 9. 9. 23:13

0. ๋™์‹œ์„ฑ ์ด์Šˆ์™€ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

๋™์‹œ์„ฑ ์ด์Šˆ๋Š” ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๋‚˜ ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋™์‹œ์— ๊ฐ™์€ ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•˜๋ ค ํ•  ๋•Œ ๋ฐœ์ƒํ•œ๋‹ค.

 

๋™์‹œ์„ฑ ์ด์Šˆ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.

 

์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ ๋ฐœ์ƒํ•œ ๋™์‹œ์„ฑ ์ด์Šˆ๋ฅผ ํ•ด๊ฒฐํ•ด ๋ณด๋ ค๊ณ  ํ•œ๋‹ค.

 

๋จผ์ €, Spring Boot ํ™˜๊ฒฝ์—์„œ ๋™์‹œ์„ฑ ์ด์Šˆ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ช‡ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด๊ฒ ๋‹ค.

 

0-1. ๋ฝ (Lock)

๋ฝ์€ ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ ๊ณต์œ  ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ๋™์‹œ ์ ‘๊ทผ์„ ์ œ์–ดํ•˜๋Š” ๋™๊ธฐํ™” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด๋‹ค.

 

๋ฝ์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•˜๊ณ  Race Condition์„ ๋ฐฉ์ง€ํ•œ๋‹ค.

 

0-1-1. ๋‚™๊ด€์  ๋ฝ (Optimistic Lock)

๋‚™๊ด€์  ๋ฝ์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์„ ๋•Œ ๋ฝ์„ ๊ฑธ์ง€ ์•Š๋Š”๋‹ค. ๋ณดํ†ต ๋ฒ„์ „ ๋ฒˆํ˜ธ๋‚˜ ํƒ€์ž„์Šคํƒฌํ”„๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•œ๋‹ค.

 

์ถฉ๋Œ์ด ๊ฐ์ง€๋˜๋ฉด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ณ , ์žฌ์‹œ๋„๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

 

๋ณดํ†ต ์ฝ๊ธฐ ์ž‘์—…์ด ๋งŽ๊ณ  ์ถฉ๋Œ์ด ์ ์€ ํ™˜๊ฒฝ์—์„œ ์„ฑ๋Šฅ์ด ์ข‹๋‹ค.

 

๋™์‹œ์„ฑ์ด ๋†’๊ณ  Deadlock์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์ง€๋งŒ, ์žฌ์‹œ๋„ ๋งค์ปค๋‹ˆ์ฆ˜์— ์˜ํ•ด ์„ฑ๋Šฅ ์ €ํ•˜์˜ ์šฐ๋ ค๊ฐ€ ์žˆ๋‹ค.

 

0-1-2. ๋น„๊ด€์  ๋ฝ(Pessimistic Lock)

์ถฉ๋Œ์ด ์ž์ฃผ ์ผ์–ด๋‚˜๋Š” ์ƒํ™ฉ์—์„œ, ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•˜๊ธฐ ์ „์— ๋ช…์‹œ์ ์œผ๋กœ ๋ฝ์„ ๊ฑฐ๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

 

๋ฝ์„ ํš๋“ํ•˜์ง€ ๋ชปํ–ˆ์„ ๊ฒฝ์šฐ, ๋ฝ์ด ํ•ด์ œ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•œ๋‹ค.

 

๋ฐ์ดํ„ฐ์˜ ์ผ๊ด€์„ฑ์„ ๊ฐ•ํ•˜๊ฒŒ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.

 

0-2. ๋™๊ธฐํ™”(Synchronization)

Java์—๋Š” ๋™์‹œ์„ฑ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š” synchronized๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๋™๊ธฐํ™”๋œ ์˜์—ญ์— ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์˜ ์Šค๋ ˆ๋“œ๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

 

public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }

    public void incrementWithBlock() {
        synchronized(this) {
            count++;
        }
    }
}

 

synchronized๋ฅผ ํ†ตํ•ด ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์˜ ์Šค๋ ˆ๋“œ์— ์˜ํ•ด์„œ๋งŒ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

0-3. ์›์ž์  ์—ฐ์‚ฐ(Atomic Operations)

์›์ž์  ์—ฐ์‚ฐ์€ java.util์˜ atomic์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

import java.util.concurrent.atomic.AtomicInteger;

public class SimpleAtomicExample {
    private static AtomicInteger counter = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(SimpleAtomicExample::incrementCounter);
        Thread t2 = new Thread(SimpleAtomicExample::incrementCounter);

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("Final count: " + counter.get());
    }

    private static void incrementCounter() {
        for (int i = 0; i < 1000; i++) {
            counter.incrementAndGet();
        }
    }
}

 

์œ„ ์˜ˆ์‹œ์—์„œ AtomicInteger๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๊ทธ๋ƒฅ int๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค๋ฉด Race Condition์— ์˜ํ•ด ์ตœ์ข… ๊ฐ’์€ 2000์ด ๋˜์ง€ ์•Š์•˜์„ ๊ฒƒ์ด๋‹ค.

 

0-4. ํŠธ๋žœ์žญ์…˜ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€ ์กฐ์ •

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ˆ˜์ค€์—์„œ ๋™์‹œ์„ฑ์„ ์ œ์–ดํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ, ํŠธ๋žœ์žญ์…˜๋“ค์ด ์„œ๋กœ์—๊ฒŒ ์–ด๋Š ์ •๋„์˜ ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ์ •์˜ํ•œ๋‹ค.

 

๊ฒฉ๋ฆฌ ์ˆ˜์ค€์ด ๋†’์„์ˆ˜๋ก ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์€ ํ–ฅ์ƒ๋˜์ง€๋งŒ, ๋™์‹œ์„ฑ์€ ๋‚ฎ์•„์ง„๋‹ค.

 

  • READ UNCOMMITTED: ๊ฐ€์žฅ ๋‚ฎ์€ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€. ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์˜ ์ปค๋ฐ‹๋˜์ง€ ์•Š์€ ๋ณ€๊ฒฝ์‚ฌํ•ญ๋„ ์ฝ์„ ์ˆ˜ ์žˆ๋‹ค.
  • READ COMMITTED: ์ปค๋ฐ‹๋œ ๋ฐ์ดํ„ฐ๋งŒ ์ฝ์„ ์ˆ˜ ์žˆ๋‹ค.
  • REPEATABLE READ: ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ๊ฐ™์€ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ํ•ญ์ƒ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์žฅํ•œ๋‹ค.
  • SERIALIZABLE: ๊ฐ€์žฅ ๋†’์€ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€. ํŠธ๋žœ์žญ์…˜์„ ์™„์ „ํžˆ ๋ถ„๋ฆฌํ•˜์—ฌ ์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰ํ•œ ๊ฒƒ๊ณผ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์žฅํ•œ๋‹ค.

 

1. WAS ํ™˜๊ฒฝ์—์„œ ๊ฐ€์žฅ ํšจ์œจ์ ์ธ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

WASํ™˜๊ฒฝ, ํŠนํžˆ ์—ฌ๋Ÿฌ ์„œ๋ฒ„๊ฐ€ ๋™์ž‘ํ•˜๋Š” ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ถ„์‚ฐ ๋ฝ(Distributed Lock)์ด ํšจ๊ณผ์ ์ด๋‹ค.

 

Java์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ํ•œ๊ณ„๊ฐ€ ๋งŽ๊ณ , ํŠธ๋žœ์žญ์…˜ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€ ์„ค์ •์€ ๋†’์€ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€์— ์˜ํ•ด ๋™์‹œ์„ฑ์ด ํฌ๊ฒŒ ์ €ํ•˜๋  ์ˆ˜ ์žˆ๋‹ค.

 

๋”ฐ๋ผ์„œ ๋ถ„์‚ฐ ๋น„๊ด€์  ๋ฝ์ด ํšจ์œจ์ ์ด๋‹ค.

 

2. Redis ํด๋ผ์ด์–ธํŠธ ์„ ํƒ

๋น„๊ด€์  ๋ฝ์€ Redis๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•  ๊ณ„ํš์ธ๋ฐ, Redis๋Š” ์—ฌ๋Ÿฌ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์žˆ๋‹ค.

 

2-1. Lettuce

  • Spring Boot 2.0 ์ดํ›„ ๊ธฐ๋ณธ Redis ํด๋ผ์ด์–ธํŠธ
  • ๋น„๋™๊ธฐ ์ง€์›, ๋›ฐ์–ด๋‚œ ์„ฑ๋Šฅ
  • ์Šค๋ ˆ๋“œ ์•ˆ์ „

2-2. Jedis

  • ๊ฐ„๋‹จํ•˜๊ณ  ์ง๊ด€์ ์ธ API๋กœ ์‚ฌ์šฉ์ด ์‰ฌ์›€
  • ๋™๊ธฐ์‹ ์ž‘์—…๋งŒ ์ง€์›

2-3. Redisson

  • ๋ถ„์‚ฐ ๋ฝ, ์„ธ๋งˆํฌ์–ด, ๋ฆฌ๋” ์„ ์ถœ ๋“ฑ ๋‹ค์–‘ํ•œ ๋ถ„์‚ฐ ๊ฐ์ฒด ๋ฐ ์„œ๋น„์Šค ์ œ๊ณต
  • ๋น„๋™๊ธฐ ๋ฐ ๋ฐ˜์‘ํ˜• ์ธํ„ฐํŽ˜์ด์Šค ์ง€์›

 

๊ธฐ๋ณธ์ ์œผ๋กœ Jedis๋ณด๋‹ค Lettuce๊ฐ€ ๋น„๋™๊ธฐ๋ฅผ ์ง€์›ํ•˜๋ฉด์„œ ์„ฑ๋Šฅ์ด ๋” ์šฐ์ˆ˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, Jedis ๋ณด๋‹ค Lettuce๊ฐ€ ๋” ์ข‹์€ ์„ ํƒ์ด๋‹ค.

 

2-4. Lettuce vs Redisson

Lettuce

Lettuce๋Š” setnx(SET if Not eXist) ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด ์Šคํ•€ ๋ฝ์„ ๊ตฌํ˜„ํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, ๋งŒ์•ฝ timeout์„ ์ง€์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ๋ฌดํ•œ ๋ฃจํ”„์— ๋Œ ์ˆ˜ ์žˆ๋‹ค.

 

timeout์„ ์„ค์ •ํ•˜๋”๋ผ๋„ ๋ฝ ํš๋“์„ ๋ฌดํ•œ์ •์œผ๋กœ ์‹œ๋„ํ•˜๋‹ค ๋ณด๋ฉด Redis์— ๋งŽ์€ ๋ถ€ํ•˜๊ฐ€ ๊ฑธ๋ฆฌ๊ฒŒ ๋˜๊ณ , ์ด๋Š” ์„ฑ๋Šฅ ์ €ํ•˜๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค.

 

Redisson

 

Redisson์€ Non-Blocking I/O ์ž‘์—…์„ ์‚ฌ์šฉํ•œ๋‹ค.

 

Redisson์˜ ํŠน์ง•์€ ์ง์ ‘ Redis์˜ ๋ช…๋ น์–ด๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ, Bucket, Map, Lock๊ณผ ๊ฐ™์€ ๊ตฌํ˜„์ฒด๋ฅผ ํ†ตํ•ด์„œ ์‚ฌ์šฉํ•œ๋‹ค.

 

๋˜ํ•œ, Redisson์˜ ๊ตฌํ˜„์ฒด์—๋Š” ํƒ€์ž„์•„์›ƒ์ด ๊ตฌํ˜„๋˜์–ด ์žˆ๋‹ค.

package org.redisson.api;

Redisson์˜ ๊ตฌํ˜„์ฒด์ธ RLock์—์„œ ํƒ€์ž„์•„์›ƒ ์‹œ๊ฐ„์„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๋ฝ ํš๋“์— ์‹คํŒจํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด false๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋œ๋‹ค.

 

์ถ”๊ฐ€๋กœ, Redisson์€ Lettuce์™€ ๋‹ค๋ฅด๊ฒŒ ์Šคํ•€ ๋ฝ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  Pub/Sub ๊ตฌ์กฐ๋กœ ๋ฝ์˜ ํš๋“ ๊ฐ€๋Šฅ ์—ฌ๋ถ€๋ฅผ ์ฒดํฌํ•œ๋‹ค.

 

๋ฝ์ด ํ•ด์ œ๋˜๋Š” ์‹œ์ ์— Subscriber๋“ค์—๊ฒŒ ๋ฝ ํš๋“ ์‹œ๋„ ๊ฐ€๋Šฅ์Œ ์•Œ๋ ค์คŒ์œผ๋กœ์จ, Redis์— ์š”์ฒญ๋˜๋Š” ๋ถ€ํ•˜๊ฐ€ ์ค„์–ด๋“ค๊ฒŒ ๋œ๋‹ค.

 

Lua Script

Redisson์€ Lua ์Šคํฌ๋ฆฝํŠธ๋กœ atomic ์—ฐ์‚ฐ์„ ์ง€์›ํ•˜๋ฉด์„œ, ๋ฝ์˜ ํš๋“ ๊ฐ€๋Šฅ ์—ฌ๋ถ€์™€ ํ™•์ธ์˜ ์›์ž์„ฑ์„ ๋ณด์žฅํ•˜๊ฒŒ ๋œ๋‹ค.

 

ํŠธ๋žœ์žญ์…˜์€ ๋ช…๋ น์–ด๋ฅผ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋ฌถ๋Š” ๊ธฐ๋Šฅ์ด๋ฏ€๋กœ, ๋ช…๋ น์–ด์˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„์„œ ๋‹ค๋ฅธ ์—ฐ์‚ฐ์— ํ™œ์šฉํ•˜๋Š” atomicํ•œ ์—ฐ์‚ฐ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์–ด๋ ค์šด๋ฐ, 

 

Lua ์Šคํฌ๋ฆฝํŠธ๋ฅผ ํ†ตํ•ด ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

 

 

3. ๊ตฌํ˜„

๊ตฌํ˜„์€ ์Šคํ”„๋ง AOP์™€ SpEL์„ ํ™œ์šฉํ–ˆ๋‹ค.

 

3-1. ์˜์กด์„ฑ

implementation 'org.redisson:redisson-spring-boot-starter:3.16.4'

 

 

 

3-2. RedissonConfig

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        return Redisson.create(config);
    }
}

 

Redis ์„œ๋ฒ„๋ฅผ ์—ฐ๊ฒฐํ•ด์ฃผ๋Š” ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•ด๋‘”๋‹ค.

 

3-3. @RedissonLock

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedissonLock {

    String value();

    long waitTime() default 5000L;

    long leaseTime() default 3000L;
}

 

๋ฝ์ด ํ•„์š”ํ•œ ํ•จ์ˆ˜๋‚˜ ํŠธ๋žœ์žญ์…˜์— ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ ์‚ฌ์šฉํ•  ์šฉ๋„์˜ ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค.

 

3-4. RedisLockSpELParser

public final class RedisLockSpELParser {

    public static Object getLockKey(String[] parameterNames, Object[] args, String key) {
        SpelExpressionParser 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);
    }

}

 

ํ•ด๋‹น ํด๋ž˜์Šค๋Š” SpEL(Spring Expression Language) ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋™์ ์œผ๋กœ ๋ฝ ํ‚ค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ–๊ณ  ์žˆ๋‹ค.

 

3-5. RedissonLockAspect

@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class RedissonLockAspect {

    private final RedissonClient redissonClient;

    @Around("@annotation(com.ddonghyeo.example.global.annotation.RedissonLock)")
    public Object redissonLock(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        RedissonLock redissonLock = method.getAnnotation(RedissonLock.class);

        //๋ฝ ํ‚ค ์ƒ์„ฑ
        String lockKey =
                method.getName() + ":" + RedisLockSpELParser.getLockKey(signature.getParameterNames(),
                        joinPoint.getArgs(), redissonLock.value());

        long waitTime = redissonLock.waitTime();
        long leaseTime = redissonLock.leaseTime();

        RLock lock = redissonClient.getLock(lockKey);
        boolean isLocked = false;

        try {
            isLocked = lock.tryLock(waitTime, leaseTime, MILLISECONDS);
            if (isLocked) {
                log.info("[Redisson Lock] ๋ฝ ํš๋“ ์„ฑ๊ณต ---> {}", lockKey);
                return joinPoint.proceed(); //๋ฝ ํš๋“์— ์„ฑ๊ณตํ–ˆ๋‹ค๋ฉด ์›๋ž˜ ๋กœ์ง ์‹คํ–‰
            } else {
                log.error("[Redisson Lock] ๋ฝ ํš๋“ ์‹คํŒจ ---> {}", lockKey);
                throw new CustomException(CommonErrorCode.FAILED_TO_ACQUIRE_LOCK);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("[Redisson Lock] ๋ฝ ํš๋“ ์ค‘ ์ธํ„ฐ๋ŸฝํŠธ ๋ฐœ์ƒ ---> {}", lockKey);
            throw new CustomException(CommonErrorCode.FAILED_TO_ACQUIRE_LOCK);
        } finally {
            if (isLocked) {
                lock.unlock(); //๋ฝ ํ•ด์ œ
                log.info("[Redisson Lock] ๋ฝ์„ ํ•ด์ œํ•˜๋Š”๋ฐ ์„ฑ๊ณต ---> {}", lockKey);
            }
        }
    }
}

 

๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด๋‘์—ˆ๋˜ RedisLockSpELParser๋ฅผ ํ†ตํ•ด ๋ฝ ํ‚ค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํš๋“์„ ์‹œ๋„ํ•œ๋‹ค.

 

Redisson์˜ RLock ๋ฉ”์„œ๋“œ tryLock์€ ๋ฝ ํš๋“ ์‹คํŒจ ์‹œ false๋ฅผ ๋ฆฌํ„ดํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฝ ํ‚ค ํš๋“ ์„ฑ๊ณต ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•œ๋‹ค.

 

๋งŒ์•ฝ ํ‚ค ํš๋“์— ์„ฑ๊ณตํ•œ๋‹ค๋ฉด joinPoint.proceed() ๋ฅผ ํ†ตํ•ด ์›๋ž˜ ์‹คํ–‰ํ•˜๋ ค ํ–ˆ๋˜ ๋กœ์ง์„ ์‹คํ–‰ํ•œ๋‹ค.

 

3-6. ์‚ฌ์šฉ

์ด๋ ‡๊ฒŒ ๊ตฌํ˜„๋œ ๋ฝ์€ ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

@RedissonLock(value = "#productId", waitTime = 5000L, leaseTime = 3000L)
public OrderResult createOrder(Long productId, Long userId, int quantity) {
    Product product = productRepository.findById(productId)
        .orElseThrow(() -> new ProductNotFoundException("Product not found"));

    if (product.getStock() < quantity) {
        throw new InsufficientStockException("Not enough stock");
    }

    product.decreaseStock(quantity);
    productRepository.save(product);

    Order order = Order.builder()
        .userId(userId)
        .productId(productId)
        .quantity(quantity)
        .status(OrderStatus.CREATED)
        .build();

    orderRepository.save(order);

    return new OrderResult(order.getId(), "Order created successfully");
}

 

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉ์ž๊ฐ€ ์ฃผ๋ฌธ์„ ์‹œ๋„ํ•  ๋•Œ์™€ ๊ฐ™์€ ๋™์‹œ์„ฑ ์ด์Šˆ๊ฐ€ ์žˆ๋Š” ๋ฉ”์„œ๋“œ์— @RedissonLock์„ ์‚ฌ์šฉํ•œ๋‹ค.

 

 

4. ์ฃผ์˜ํ•  ์ 

  • leaseTime์„ ๋„ˆ๋ฌด ์งง๊ฒŒ ์„ค์ •ํ•œ๋‹ค๋ฉด, ์ž‘์—…์ด ์™„๋ฃŒ๋˜๊ธฐ ์ „๊นŒ์ง€ ํ•ด์ œ๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ถฉ๋ถ„ํžˆ ๊ธธ๊ฒŒ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.
    • ๋ฐ˜๋Œ€๋กœ ๋„ˆ๋ฌด ๊ธธ๊ฒŒ ์„ค์ •ํ•œ๋‹ค๋ฉด ์‹œ์Šคํ…œ ์žฅ์•  ์‹œ ๋ฝ์ด ์˜ค๋žซ๋™์•ˆ ํ•ด์ œ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค.
  • AOP๋ฅผ ์‚ฌ์šฉํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ถ”๊ฐ€์ ์ธ ํ”„๋ก์‹œ ๋กœ์ง์ด ์‹คํ–‰๋œ๋‹ค. ๋™์‹œ์„ฑ์ด ๋†’์„์ˆ˜๋ก ๊ณผ๋ถ€ํ•˜๊ฐ€ ์ผ์–ด๋‚  ์ˆ˜ ์žˆ๋‹ค. ์–ด๋–ค ์ƒํ™ฉ์ด๋“ ์ง€ ์„œ๋ฒ„ ์ƒํ™ฉ์— ๋งž๊ฒŒ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.

 

 

์ฐธ๊ณ 
https://javadoc.io/doc/org.redisson/redisson/latest/index.html
https://github.com/redisson/redisson/wiki/Table-of-Content
https://hyperconnect.github.io/2019/11/15/redis-distributed-lock-1.html