λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°

Spring & Spring Boot

[Redis] 이컀머슀 μ„œλΉ„μŠ€ - Redisson을 μ΄μš©ν•œ λΆ„μ‚° 락 κ΅¬ν˜„μœΌλ‘œ λ™μ‹œμ„± 이슈 ν•΄κ²°

πŸ“– λͺ©μ°¨

 

 

 


Spring Bootλ₯Ό 기반으둜 이컀머슀 μ„œλΉ„μŠ€λ₯Ό κ΅¬ν˜„ν•˜λ©΄μ„œ λ™μ‹œμ„± μ΄μŠˆκ°€ λ°œμƒν–ˆλ‹€.

Redis의 Redisson라이브러리λ₯Ό μ΄μš©ν•΄ λ™μ‹œμ„± 이슈λ₯Ό ν•΄κ²°ν•˜λŠ” 방법에 λŒ€ν•΄ μ•Œμ•„λ³΄μž.                                                        

 

 

 

 

 

 

πŸ“Œ  λ™μ‹œμ„± μ œμ–΄λž€? 

  λ™μ‹œμ„± μ œμ–΄(Concurrency Control)λž€, μ—¬λŸ¬ μŠ€λ ˆλ“œλ‚˜ ν”„λ‘œμ„ΈμŠ€κ°€ 데이터λ₯Ό κ³΅μœ ν•˜λŠ” μž‘μ—…μ΄ λ™μ‹œμ— 싀행될 λ•Œ κ·Έ μ‹€ν–‰ μˆœμ„œλ‚˜ 쑰건을 μ œμ–΄ν•˜μ—¬ λ°μ΄ν„°μ˜ 무결성을 μœ μ§€ν•˜κ³  μ˜ˆμƒμΉ˜ λͺ»ν•œ λ™μž‘μ„ λ°©μ§€ν•˜λŠ” 것을 λ§ν•œλ‹€. μ—¬λŸ¬ μž‘μ—… κ°„μ˜ μƒν˜Έμž‘μš©, μžμ› 곡유, 경쟁 쑰건 등을 κ΄€λ¦¬ν•˜μ—¬ ν”„λ‘œκ·Έλž¨μ˜ μ•ˆμ •μ„±κ³Ό μ„±λŠ₯을 보μž₯ν•˜λŠ” 데 μ€‘μš”ν•œ 역할을 ν•œλ‹€.

 

 

 

 

 

πŸ“Œ  λ™μ‹œμ„± μ œμ–΄μ˜ λͺ©μ  

  • μ—¬λŸ¬ μ‚¬μš©μžκ°€ DB에 μ ‘κ·Όν•˜λ”λΌλ„ λ°μ΄ν„°μ˜ 일관성을 보μž₯ν•˜κ³  λ°μ΄ν„°μ˜ 무결성을 μœ μ§€
  • μœ„λ₯Ό λ§Œμ‘±ν•˜λ©° λ°μ΄ν„°λ² μ΄μŠ€ μ‹œμŠ€ν…œμ˜ μ„±λŠ₯κ³Ό νš¨μœ¨μ„±μ„ μœ μ§€ν•˜λŠ” 것

 

 

 

 

πŸ“Œ  λ™μ‹œμ„± μ œμ–΄ 기법

  λ™μ‹œμ„± 이슈λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ λ‹€μ–‘ν•œ 방법을 생각해 λ³Ό 수 μžˆλ‹€. λ™μ‹œμ„± μ œμ–΄ κΈ°λ²•μ—λŠ” μ—¬λŸ¬ κ°€μ§€κ°€ μžˆλŠ”λ° 그쀑, Locking기법을 μ μš©ν•΄λ³΄λ €κ³  ν•œλ‹€. Locking기법은 데이터에 잠금(Lock)을 μ„€μ •ν•˜λ©΄ λ‹€λ₯Έ νŠΈλžœμž­μ…˜μ€ ν•΄λ‹Ή 데이터에 잠금이 ν•΄μ œ(UnLock)될 λ•ŒκΉŒμ§€ μ ‘κ·Ό, μˆ˜μ •, μ‚­μ œκ°€ λΆˆκ°€λŠ₯ν•˜κ²Œ 해놓은 방법이닀.

  • synchronized
    • ν˜„μž¬ μ ‘κ·Όν•˜κ³  μžˆλŠ” λ©”μ†Œλ“œμ— ν•˜λ‚˜μ˜ μŠ€λ ˆλ“œλ§Œ μ ‘κ·Όν•  수 μžˆλ„λ‘ 보μž₯ 및 λ™μž‘ν•œλ‹€. μžλ°”μ—μ„œ μ§€μ›ν•œλ‹€.
    • μŠ€λ ˆλ“œ ν•˜λ‚˜μ”© 순차적으둜 데이터에 μ ‘κ·Όν•  수 μžˆλ„λ‘ ν•΄μ€€λ‹€.
    • μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ ν•˜λ‚˜λ§Œ λ„μš°λŠ” κ²½μš°λŠ” λ¬΄κ΄€ν•˜μ§€λ§Œ, μ„œλ²„κ°€ μ—¬λŸ¬ λŒ€μΌ κ²½μš°μ—λŠ” μ—¬λŸ¬ κ°œμ˜ μΈμŠ€ν„΄μŠ€κ°€ μ‘΄μž¬ν•˜λŠ” κ²ƒκ³Ό λ™μΌν•˜κΈ° λ•Œλ¬Έμ— μ‹€μ§ˆμ μΈ μš΄μ˜ ν™˜κ²½μ—μ„œλŠ” λ°μ΄ν„°μ˜ μ •합성을 λ³΄μž₯ν•  μˆ˜ μ—†λ‹€.

 

  • Pessimistic Lock(비관적 락)
    • 데이터에 Lock을 κ±Έμ–΄μ„œ 정합성을 λ§žμΆ”λŠ” 방법이닀. 데이터λ₯Ό λ³€κ²½ν•˜κΈ° 전에 락을 νšλ“ν•œλ‹€.
    • ν•œ λ²ˆμ—μ—¬λŸ¬ 개의 ν…Œμ΄λΈ”μ„ μˆ˜μ •ν•˜λ €κ³  ν–ˆμ„ λ•Œ, ν•˜λ‚˜μ˜ νŠΈλžœμž­μ…˜μœΌλ‘œ λ¬Άμ—¬μžˆκΈ° λ•Œλ¬Έμ— μˆ˜μ • ν•˜λ‚˜κ°€ μ‹€νŒ¨ν•˜λ©΄ λ°μ΄ν„°λ² μ΄μŠ€λ‹¨μ—μ„œ 전체 μžλ™ λ‘€λ°±μ΄ μΌμ–΄λ‚˜κ²Œ λœλ‹€.
    • λ™μ‹œμ— μ—¬λŸ¬ μ‚¬μš©μžκ°€ 데이터에 μ ‘κ·Όν•  λ•Œ μ„±λŠ₯ 문제λ₯Ό λ°œμƒμ‹œν‚¬ 수 μžˆλ‹€.
    • 특히 데이터 λ² μ΄μŠ€λ‚˜ λΆ„μ‚° μ‹œμŠ€ν…œμ—μ„œ μ‚¬μš©λ  경우, 락이 λ°œμƒν•  λ•Œ λ‹€λ₯Έ μž‘μ—…μ΄ λŒ€κΈ°ν•΄μ•Ό ν•˜λ―€λ‘œ μ²˜λ¦¬λŸ‰μ΄ κ°μ†Œν•  수 μžˆλ‹€.

 

  • Optimistic Lock(낙관적 락) 
    • 비관적 락과 λ‹€λ₯΄κ²Œ νŠΈλžœμž­μ…˜μ„ μž‘μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— 좩돌 감지λ₯Ό ν•  수 μžˆλ‹€. μ„±λŠ₯μ μœΌλ‘œλŠ” 비관적 락보닀 μ’‹λ‹€.
    • 좩돌이 λ°œμƒν•˜μ—¬ μˆ˜μ •μ„ λͺ»ν•œ 뢀뢄에 λŒ€ν•΄μ„œλŠ” 둀백에 λŒ€ν•œ μ±…μž„μ„ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ‹¨μ—μ„œ μ§€λ©°, μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ μˆ˜λ™μœΌλ‘œ 둀백을 ν•΄μ€˜μ•Ό ν•œλ‹€.
    • 좩돌이 많이 μ˜ˆμƒλ˜κ±°λ‚˜ 좩돌이 λ°œμƒν–ˆμ„ λ•Œ, λΉ„μš©μ΄ 많이 λ“€ 것이라고 νŒλ‹¨λ˜λŠ” κ³³μ—μ„œλŠ” μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” 것이 μ’‹λ‹€.
    • 엔티티에 @Version을 μΆ”κ°€ν•΄μ£Όμ–΄μ•Ό ν•œλ‹€. μ‘°νšŒμ™€ κ°±μ‹  μ‹œ 버전 비ꡐλ₯Ό ν†΅ν•΄μ„œ 정합성을 λ§žμΆ˜λ‹€.

 

    • Named Lock
      • 말 κ·ΈλŒ€λ‘œ 이름을 κ°€μ§„ Lock이닀. λ°μ΄ν„°λ² μ΄μŠ€μ˜ λ³„λ„μ˜ μ €μž₯μ†Œμ— 이름을 κ°€μ§„ 락을 μ €μž₯ν•˜κ³ , ν•΄λ‹Ή 이름에 λŒ€ν•œ 락을 ν•΄μ œλ˜κΈ° μ „ κΉŒμ§€λŠ” λ‹€λ₯Έ νŠΈλžœμž­μ…˜μ—μ„œ 락을 νšλ“ν•  수 μ—†λ‹€.
      • 보톡 λΆ„μ‚°λ½μ—μ„œ μ‚¬μš©ν•˜λŠ” 방법이며, MySQLμ—μ„œλ§Œ μ‚¬μš©μ΄ κ°€λŠ₯ν•˜λ‹€.
      • 락을 μ‚¬μš©ν•˜κΈ° μœ„ν•΄ λ³„λ„μ˜ 컀λ„₯μ…˜ 풀을 관리해야 ν•˜κ³ , 락에 κ΄€λ ¨λœ λΆ€ν•˜λ₯Ό RDSμ—μ„œ λ°›λŠ”λ‹€λŠ” 단점이 μžˆλ‹€.

 

  • Lettuce
    • redisλ₯Ό dependency에 μΆ”κ°€ν•˜λ©΄ λ³„λ„μ˜ 라이브러리 μ„€μΉ˜ 없이 μ‚¬μš©ν•  수 μžˆλ‹€.
    • μŠ€ν•€ 락 λ°©μ‹μœΌλ‘œ λ™μ‹œμ— λ§Žμ€ μŠ€λ ˆλ“œκ°€ 락 νšλ“μ„ λŒ€κΈ° 쀑이라면, redis μ„œλ²„μ— λΆ€ν•˜λ₯Ό λ°œμƒμ‹œν‚¬ 수 μžˆλ‹€.
    • redis에 값이 μ‘΄μž¬ν•˜μ§€ μ•ŠμœΌλ©΄ μ„ΈνŒ…ν•˜κ²Œ ν•˜κ³ , 값이 μ„ΈνŒ…λ˜μ—ˆλŠ”μ§€ μ—¬λΆ€λ₯Ό 리턴 κ°’μœΌλ‘œ λ°›μ•„ 락을 νšλ“ν•˜λŠ” 데 μ„±κ³΅ν•œλ‹€. 락을 μ μœ ν•˜λŠ” μ‹œκ°„μ΄ 짧으면 μœ λ¦¬ν•˜μ§€λ§Œ μŠ€λ ˆλ“œκ°€ 락을 였래 μ μœ ν•˜κ³  μžˆλŠ” 경우 CPU에 뢀담을 쀄 수 μžˆλ‹€.
    • 락의 만료 μ‹œκ°„μ„ μ§€μ •ν•  수 μ—†κΈ° λ•Œλ¬Έμ— 락 νšλ“μ— λŒ€ν•œ μž¬μ‹œλ„κ°€ ν•„μš” μ—†λŠ” κ²½μš°μ— μ‚¬μš©ν•˜λ©΄ μ’‹λ‹€.

 

  • Redisson
    • λ³„λ„μ˜ 라이브러리λ₯Ό μΆ”κ°€ν•΄μ•Ό μ‚¬μš©ν•  수 μžˆλ‹€.
    • Pub/SubκΈ°λŠ₯을 μ‚¬μš©ν•˜μ—¬ μŠ€ν•€ 락이 redis에 μ£ΌλŠ” μ—„μ²­λ‚œ νŠΈλž˜ν”½μ„ μ€„μ˜€λ‹€.
    • 락의 만료 μ‹œκ°„μ„ μ§€μ •ν•  수 μžˆμ–΄, 락 νšλ“μ„ μž¬μ‹œλ„ν•΄μ•Ό ν•˜λŠ” κ²½μš°μ— μ‚¬μš©ν•˜λ©΄ μ’‹λ‹€.

 

 

 

 

 

βœ”οΈ μœ„μ˜ 방법듀 쀑, λ‚˜λŠ” Redisson 라이브러리λ₯Ό μ‚¬μš©ν•˜μ—¬ λ™μ‹œμ„± μ œμ–΄λ₯Ό κ΅¬ν˜„ν–ˆλ‹€.

  νŒ€ ν”„λ‘œμ νŠΈλ‘œ 이컀머슀 μ„œλΉ„μŠ€λ₯Ό κ΅¬ν˜„ν•˜κ²Œ λ˜μ—ˆλŠ”λ°, 이컀머슀의 νŠΉμ„±μƒ μ—¬λŸ¬ μ‚¬μš©μžκ°€ λ™μ‹œμ— ν•œ κ°€μ§€ μƒν’ˆμ„ κ΅¬λ§€ν•˜κΈ° μœ„ν•œ μš”μ²­μ΄ λ§Žμ„ 것이고, μ—¬λŸ¬ μ‚¬μš©μžκ°€ λ™μ‹œμ— ν•œ κ°€μ§€μ˜ μƒν’ˆμ— λŒ€ν•΄ 주문을 생성을 μš”μ²­ν•˜λ©΄ 잘λͺ»λœ νŠΈλžœμž­μ…˜μ΄ 생성될 수 μžˆλ‹€κ³  νŒλ‹¨ν•˜μ˜€λ‹€. μ΄λŸ¬ν•œ λ™μ‹œμ„± 이슈λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ 방법을 μ°Ύμ•„λ³΄λ˜ 쀑, 마침 인프라에 redisκ°€ κ΅¬μΆ•λ˜μ–΄ μžˆμ—ˆκ³  redis의 μ—¬λŸ¬ κΈ°λŠ₯ 쀑에  λΆ„μ‚° 락을 μ μš©ν•˜κ²Œ λ˜μ—ˆλ‹€.

 

 

 

 

 

 

πŸ“Œ  λ™μ‹œμ„± μ œμ–΄ 적용 μ „

  • ν…ŒμŠ€νŠΈ μ‹œλ‚˜λ¦¬μ˜€: μ΄ˆλ‹Ή 100번의 μš”μ²­μ„ ν–ˆμ„ λ•Œ, μƒν’ˆμ˜ μž¬κ³ λŠ” 100개 -> 0개, 주문은 100개 μƒμ„±λ˜μ–΄μ•Ό ν•œλ‹€.
  • ν…ŒμŠ€νŠΈ κ²°κ³Ό: 3초 λ™μ•ˆ 총 300번의 μš”μ²­μ„ ν•œ κ²°κ³Ό, μƒν’ˆμ˜ μž¬κ³ λŠ” 100개-> 0개둜 μ€„μ—ˆκ³ , 주문은 107κ°œκ°€ μƒμ„±λ˜μ—ˆλ‹€.

<7번 μƒν’ˆμ˜ 재고λ₯Ό 100개둜 μ„€μ •ν•œλ‹€.>

 

<μž¬κ³ λŠ” 0κ°œκ°€ λ˜μ—ˆμ§€λ§Œ, 주문은 107κ°œκ°€ μƒμ„±λ˜μ—ˆλ‹€.>

 

 

 

  • ν…ŒμŠ€νŠΈ μ‹œλ‚˜λ¦¬μ˜€: μ΄ˆλ‹Ή 100번의 μš”μ²­μ„ ν–ˆμ„ λ•Œ, μƒν’ˆμ˜ μž¬κ³ λŠ” 1000개 -> 900개, 주문은 100개 μƒμ„±λ˜μ–΄μ•Ό ν•œλ‹€.
  • ν…ŒμŠ€νŠΈ κ²°κ³Ό: 3초 λ™μ•ˆ 총 300번의 μš”μ²­μ„ ν•œ κ²°κ³Ό, μƒν’ˆμ˜ μž¬κ³ λŠ” 1000개-> 727개둜 μ€„μ—ˆκ³ , 주문은 281κ°œκ°€ μƒμ„±λ˜μ—ˆλ‹€.

<7번 μƒν’ˆμ˜ 재고λ₯Ό 1000개둜 μ„€μ •ν•œλ‹€.>

 

<μž¬κ³ λŠ” 727κ°œκ°€ λ˜μ—ˆμ§€λ§Œ, 주문은 281κ°œκ°€ μƒμ„±λ˜μ—ˆλ‹€.>

 

  μ—¬λŸ¬ 번의 μš”μ²­μ„ λ³΄λƒˆμ„ λ•Œ, λ™μ‹œμ„± 이슈둜 인해 쀄어든 μƒν’ˆμ˜ 재고 μˆ˜μ™€ 주문의 생성 κ°œμˆ˜κ°€ λ§žμ§€ μ•ŠλŠ”λ‹€. λ°μ΄ν„°μ˜ 정합성이 κΉ¨μ§„ 것이닀. 이λ₯Ό λΆ„μ‚° 락을 μ΄μš©ν•œ λ™μ‹œμ„± μ œμ–΄λ₯Ό 톡해 ν•΄κ²°ν•΄ 보도둝 ν•˜μž.

 

 

 

 

 

 

πŸ“Œ  Redisson을 μ΄μš©ν•œ λΆ„μ‚° 락 κ΅¬ν˜„ν•˜κΈ° 

 

  Springμ—μ„œ Redisson을 μ‚¬μš©ν•˜λ €λ©΄ build.gradle에 λ‹€μŒκ³Ό 같이 μ˜μ‘΄μ„±μ„ μΆ”κ°€ν•΄μ£Όμ–΄μ•Ό ν•œλ‹€.

dependencies {
  // redisson
  implementation 'org.redisson:redisson-spring-boot-starter:3.23.2'
}

 

 

 

  RedissonClientλ₯Ό μ‚¬μš©ν•˜κΈ° μœ„ν•΄ Redis Configλ₯Ό μ„€μ •ν•΄ μ€€λ‹€.

@Configuration
@EnableRedisRepositories
public class RedisConfig {

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

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

    private static final String REDISSON_HOST_PREFIX = "redis://";

    @Bean
    public RedissonClient redissonClient() {
        RedissonClient redisson = null;
        Config config = new Config();
        config.useSingleServer().setAddress(REDISSON_HOST_PREFIX + redisHost + ":" + redisPort);
        redisson = Redisson.create(config);

        return redisson;
    }

    @Bean
    public RedissonConnectionFactory redisConnectionFactory(RedissonClient redissonClient) {
        return new RedissonConnectionFactory(redissonClient);
    }
}

 

 

 

 

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

    String key(); // 락의 이름

    TimeUnit timeUnit() default TimeUnit.SECONDS; // 락의 μ‹œκ°„ λ‹¨μœ„

    long waitTime() default 5L; // 락을 κΈ°λ‹€λ¦¬λŠ” μ‹œκ°„

    long leaseTime() default 3L; // 락 μž„λŒ€ μ‹œκ°„
}

 

 

 

  Redisson을 μ΄μš©ν•œ λ‘œμ§μ€ λ‹€μŒκ³Ό κ°™λ‹€. μ–΄λ…Έν…Œμ΄μ…˜ μ„ μ–Έ μ‹œ μˆ˜ν–‰λ˜λŠ” AOPν΄λž˜μŠ€μ΄λ‹€.

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

    private static final String REDISSON_LOCK_PREFIX = "LOCK: ";
    private static final long RETRY_DELAY = 3L; // μž¬μ‹œλ„ μ‚¬μ΄μ˜ λŒ€κΈ° μ‹œκ°„
    private static final long MAX_RETRY_COUNT = 5L; // μ΅œλŒ€ μž¬μ‹œλ„ 횟수

    private final RedissonClient redissonClient;
    private final AopForTransaction aopForTransaction;

    @Around("...")
    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 + CustomSpringELParser.getDynamicValue(
            signature.getParameterNames(),
            joinPoint.getArgs(),
            distributedLock.key());

        RLock rLock = redissonClient.getLock(key);

        // 락 νšλ“ μ‹œλ„ 및 μž¬μ‹œλ„ 둜직
        int retryCount = 0;
      
        while (retryCount < MAX_RETRY_COUNT) {
            try { // 락을 νšλ“ν•˜λ €κ³  μ‹œλ„ν•˜κ³ , μ •μ˜λœ μ‹œκ°„μ΄ μ§€λ‚˜λ©΄ μž κΈˆμ„ ν•΄μ œν•œλ‹€.
                boolean available = rLock.tryLock(distributedLock.waitTime(),
                    distributedLock.leaseTime(), distributedLock.timeUnit()); 

                if (available) {
                    System.out.println("Lock acquired with key: " + key);
                    try {
                        return aopForTransaction.proceed(joinPoint);
                    } finally {
                        if (rLock.isLocked() && rLock.isHeldByCurrentThread()) {
                        // isLocked(): νŠΉμ • 락이 ν˜„μž¬ Redis μ„œλ²„μ— 락 λ˜μ–΄ μžˆλŠ”μ§€ μ—¬λΆ€λ₯Ό 확인
                        // isHeldByCurrentThread(): νŠΉμ • 락을 ν˜„μž¬ μŠ€λ ˆλ“œκ°€ λ³΄μœ ν•˜κ³  μžˆλŠ”μ§€ μ—¬λΆ€λ₯Ό 확인
                            System.out.println("Lock released with key: " + key);
                            rLock.unlock();
                        }
                    }
                } ... {
                    // 락 νšλ“ μ‹€νŒ¨ μ‹œ λŒ€κΈ°ν•˜κ³  μž¬μ‹œλ„ν•˜λŠ” 둜직
                }
            } catch (InterruptedException e) {
                log.error("DistributedLock lock interrupted");
                System.out.println("DistributedLock lock interrupted");
                throw new InterruptedException(e.getMessage());
            }
        }
        throw new IllegalStateException("DistributedLock lock failed after retries");
    }
}

 

 

 

  λ™μ‹œμ„± ν™˜κ²½μ—μ„œμ˜ 데이터 정합성을 보μž₯ν•˜κΈ° μœ„ν•΄ Propagation.REQUIRES_NEW μ˜΅μ…˜μ„ μ§€μ •ν–ˆκ³ , νŠΈλžœμž­μ…˜ 컀밋 이후에 락이 ν•΄μ œλ˜κ²Œ μ²˜λ¦¬ν–ˆλ‹€.

@Component
public class AopForTransaction {

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

 

 

 

  λΆ„μ‚° 락을 κ΅¬ν˜„ν•œ μ–΄λ…Έν…Œμ΄μ…˜μ„ μ μš©ν•˜μ—¬ ν…ŒμŠ€νŠΈλ₯Ό μ§„ν–‰ν•΄ 보자.

@Service
@RequiredArgsConstructor
@Component
public class OrderService {
...

    @DistributedLock(key = "#orderProducts.![productId].toString()")
    public OrderInfo createOrder(
        Long memberId,
        Long receiverId,
        Long totalAmount,
        PayType type,
        List<OrderProduct> orderProducts
    ) {
	...
        OrderEntity saved = orderJpaRepository.save(orderEntity);

        historyService.createHistory(saved.getId(), orderProducts);

        return ...
    }

 

 

 

 

 

 

 

πŸ“Œ  λ™μ‹œμ„± μ œμ–΄ 적용 ν›„

  • ν…ŒμŠ€νŠΈ μ‹œλ‚˜λ¦¬μ˜€: μ΄ˆλ‹Ή 100번의 μš”μ²­μ„ ν–ˆμ„ λ•Œ, μƒν’ˆμ˜ μž¬κ³ λŠ” 100개 -> 0개, 주문은 100개 μƒμ„±λ˜μ–΄μ•Ό ν•œλ‹€.
  • ν…ŒμŠ€νŠΈ κ²°κ³Ό: 3초 λ™μ•ˆ 총 300번의 μš”μ²­μ„ ν•œ κ²°κ³Ό, μƒν’ˆμ˜ μž¬κ³ λŠ” 100개-> 0개둜 μ€„μ—ˆκ³ , 주문은 100κ°œκ°€ μƒμ„±λ˜μ—ˆλ‹€.

<λ™μΌν•˜κ²Œ 7번 μƒν’ˆμ˜ 재고λ₯Ό 100개둜 μ„€μ •ν•œλ‹€.>

 

<μž¬κ³ λŠ” 0개둜 κ°μ†Œν•˜μ˜€κ³ , 그에 따라 주문은 100κ°œκ°€ μ •μƒμ μœΌλ‘œ μƒμ„±λ˜μ—ˆλ‹€.>

 

 

 

  • ν…ŒμŠ€νŠΈ μ‹œλ‚˜λ¦¬μ˜€: μ΄ˆλ‹Ή 100번의 μš”μ²­μ„ ν–ˆμ„ λ•Œ, 재고 1000개 -> 900개, 주문은 100개 μƒμ„±λ˜μ–΄μ•Ό ν•œλ‹€.
  • ν…ŒμŠ€νŠΈ κ²°κ³Ό: 3초 λ™μ•ˆ 총 300번의 μš”μ²­μ„ ν•œ κ²°κ³Ό, μž¬κ³ λŠ” 1000개 -> 700개, 주문은 300κ°œκ°€ μƒμ„±λ˜μ—ˆλ‹€.

<λ™μΌν•˜κ²Œ 7번 μƒν’ˆμ˜ 재고λ₯Ό 1000개둜 λ§žμΆ°μ€€λ‹€.>

 

<μž¬κ³ λŠ” 700개둜 κ°μ†Œν•˜μ˜€κ³ , 그에 따라 주문은 300κ°œκ°€ μ •μƒμ μœΌλ‘œ μƒμ„±λ˜μ—ˆλ‹€.>

 

 

 

μ—¬κΈ°κΉŒμ§€ λ™μ‹œμ„± μ΄μŠˆκ°€ 일어났을 λ•Œ Redisson을 μ‚¬μš©ν•œ λΆ„μ‚° 락의 κ΅¬ν˜„μ— λŒ€ν•΄ μ•Œμ•„λ³΄μ•˜λ‹€.

 

 

 

 

 

πŸ“Œ  μ°Έκ³ 

 

ν’€ν•„λ¨ΌνŠΈ μž…κ³  μ„œλΉ„μŠ€νŒ€μ—μ„œ 뢄산락을 μ‚¬μš©ν•˜λŠ” 방법 - Spring Redisson

μ–΄λ…Έν…Œμ΄μ…˜ 기반으둜 뢄산락을 μ‚¬μš©ν•˜λŠ” 방법에 λŒ€ν•΄ μ†Œκ°œν•©λ‹ˆλ‹€.

helloworld.kurly.com

 

 

 

λ ˆλ””μŠ€μ™€ λΆ„μ‚° 락(1/2) - λ ˆλ””μŠ€λ₯Ό ν™œμš©ν•œ λΆ„μ‚° 락과 μ•ˆμ „ν•˜κ³  λΉ λ₯Έ 락의 κ΅¬ν˜„

λ ˆλ””μŠ€λ₯Ό ν™œμš©ν•œ λΆ„μ‚° 락에 λŒ€ν•΄ μ•Œμ•„λ΄…λ‹ˆλ‹€. 그리고 μ„±λŠ₯을 높이고 일관성을 보μž₯ν•˜λŠ” 방법에 λŒ€ν•΄ μ•Œμ•„λ΄…λ‹ˆλ‹€.

hyperconnect.github.io

 

 

[Redis] λΆ„μ‚° 락 (feat. Redisson)

λΆ„μ‚° λ½μ΄λž€? λΆ„μ‚° 락(Distributed Lock)은 λ‹€μˆ˜μ˜ μ„œλ²„(λ˜λŠ” ν”„λ‘œμ„ΈμŠ€)κ°€ λ™μ‹œμ— 같은 μžμ›μ— μ ‘κ·Όν•  λ•Œ λ°œμƒν•  수 μžˆλŠ” λ™μ‹œμ„± 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λ˜λŠ” 동기화 λ©”μ»€λ‹ˆμ¦˜μž…λ‹ˆλ‹€. λΆ„μ‚° 락의 ν•΅

inma.tistory.com

 

 

8. Distributed locks and synchronizers

Redisson - Valkey and Redis Java client. Complete Real-Time Data Platform. Sync/Async/RxJava/Reactive API. Over 50 Valkey and Redis based Java objects and services: Set, Multimap, SortedSet, Map, L...

github.com