inflearn logo
강의

Course

Instructor

How to solve simultaneity issues through inventory system

Create inventory reduction logic by writing Lettuce

LettureLockStockFacadeTest에서 오류가 발생합니다.

264

lykim04180289

6 asked

1

package com.example.stock.repository;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.time.Duration;

@Component
public class RedisLockRepository {

    private RedisTemplate<String, String> redisTemplate;

    public RedisLockRepository(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public Boolean lock(Long key) {
        return redisTemplate
                .opsForValue()
                .setIfAbsent(generateKey(key), "lock", Duration.ofMillis(3_000));
    }

    public void unlock(Long key) {
        redisTemplate.delete(generateKey(key));
    }

    private String generateKey(Long key) {
        return key.toString();
    }
}
package com.example.stock.facade;

import com.example.stock.repository.RedisLockRepository;
import com.example.stock.service.StockService;
import org.springframework.stereotype.Component;

@Component
public class LettuceLockStockFacade {

    private final RedisLockRepository redisLockRepository;

    private final StockService stockService;

    public LettuceLockStockFacade(RedisLockRepository redisLockRepository, StockService stockService) {
        this.redisLockRepository = redisLockRepository;
        this.stockService = stockService;
    }

    public void decrease(Long key, Long quantity) throws InterruptedException {
        while(!redisLockRepository.lock(key)){
            Thread.sleep(100);
        }
        try{
            stockService.decrease(key, quantity);
        }finally {
            redisLockRepository.unlock(key);
        }
    }
}
package com.example.stock.facade;

import com.example.stock.domain.Stock;
import com.example.stock.repository.StockRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class LettuceLockStockFacadeTest {
    @Autowired
    private LettuceLockStockFacade lettuceLockStockFacade;

    @Autowired
    private StockRepository stockRepository;

    @BeforeEach
    public void before(){
        stockRepository.saveAndFlush(new Stock(1L, 100L));
    }

    @AfterEach
    public void after(){
        stockRepository.deleteAll();
    }

    @Test
    public void 동시에_100개의_요청() throws InterruptedException {
        int threadCount = 100;
        ExecutorService executorService = Executors.newFixedThreadPool(32);
        CountDownLatch latch = new CountDownLatch(threadCount);

        for(int i = 0; i < threadCount; i++){
            executorService.submit(() -> {
                try{
                    lettuceLockStockFacade.decrease(1L, 1L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    latch.countDown();
                }
            });
        }
        latch.await();

        Stock stock = stockRepository.findById(1L).orElseThrow();
        assertEquals(0, stock.getQuantity());
    }
}

이렇게 코딩했고 dockre에 redis를 설치하고 window Shell에서 데이터가 남아 있을까봐 1번 key 값을 삭제해서 돌렸는데도 오류가 발생했습니다.

또한 레디스가 실행이 되지 않은 채 했을까봐 ping을 입력했는데 pong이라는 응답을 받았습니다.

Hibernate: drop table if exists stock
2024-11-03T22:08:40.414+09:00 DEBUG 17108 --- [    Test worker] org.hibernate.SQL                        : create table stock (id bigint not null auto_increment, product_id bigint, quantity bigint, version bigint, primary key (id)) engine=InnoDB
Hibernate: create table stock (id bigint not null auto_increment, product_id bigint, quantity bigint, version bigint, primary key (id)) engine=InnoDB
2024-11-03T22:08:40.439+09:00  INFO 17108 --- [    Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2024-11-03T22:08:41.136+09:00  INFO 17108 --- [    Test worker] o.s.d.j.r.query.QueryEnhancerFactory     : Hibernate is in classpath; If applicable, HQL parser will be used.
2024-11-03T22:08:42.092+09:00  WARN 17108 --- [    Test worker] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2024-11-03T22:08:42.990+09:00  INFO 17108 --- [    Test worker] c.e.s.facade.LettuceLockStockFacadeTest  : Started LettuceLockStockFacadeTest in 6.21 seconds (process running for 7.345)
2024-11-03T22:08:43.504+09:00 DEBUG 17108 --- [    Test worker] org.hibernate.SQL                        : insert into stock (product_id,quantity,version) values (?,?,?)
Hibernate: insert into stock (product_id,quantity,version) values (?,?,?)
2024-11-03T22:08:44.203+09:00 DEBUG 17108 --- [    Test worker] org.hibernate.SQL                        : select s1_0.id,s1_0.product_id,s1_0.quantity,s1_0.version from stock s1_0 where s1_0.id=?
Hibernate: select s1_0.id,s1_0.product_id,s1_0.quantity,s1_0.version from stock s1_0 where s1_0.id=?
2024-11-03T22:08:44.245+09:00 DEBUG 17108 --- [    Test worker] org.hibernate.SQL                        : select s1_0.id,s1_0.product_id,s1_0.quantity,s1_0.version from stock s1_0
Hibernate: select s1_0.id,s1_0.product_id,s1_0.quantity,s1_0.version from stock s1_0
2024-11-03T22:08:44.252+09:00 DEBUG 17108 --- [    Test worker] org.hibernate.SQL                        : delete from stock where id=? and version=?
Hibernate: delete from stock where id=? and version=?

Expected :0
Actual   :100
<Click to see difference>

org.opentest4j.AssertionFailedError: expected: <0> but was: <100>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:177)
	at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:639)
	at com.example.stock.facade.LettuceLockStockFacadeTest.동시에_100개의_요청(LettuceLockStockFacadeTest.java:55)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)


Java HotSpot(TM) 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended

 

어디를 더 수정해야 하는지 모르겠습니다..

java spring 동시성

Answer 2

0

169developer9973

소리쨩님 안녕하세요.
첫번째로 강의에 첨부드린 github 에 있는 소스로 해도 테스트가 통과하지 않는지 궁금합니다.
두번째로 테스트가 통과하지 않는 소스코드를 github 에 업로드 해주시고 주소를 공유해주실 수 있으실까요?

디버깅을 해보면 좋을거같습니다

 

0

communityai8509

안녕하세요, 인프런 AI 인턴입니다.

소리쨩님께서 직면하고 있는 문제는 Redis를 통한 분산 락이 예상대로 작동하지 않아서 발생하는 것으로 보입니다. Redis의 setIfAbsent 메서드가 기대한 대로 작동하지 않거나, Redis의 연결이나 설정에 문제가 있을 수 있습니다. 다음과 같은 점검 사항을 확인해 보면 좋겠습니다:

  1. Redis 연결 확인: Redis가 제대로 연결되어 있고 set 및 delete 명령이 올바르게 수행되는지 스프링 부트 애플리케이션과 Redis 간의 연결을 재차 확인해 보시기 바랍니다.

  2. 분산 락 시간 설정: setIfAbsent 메서드를 사용하여 락이 설정될 때 유효 시간을 지정하고 있습니다. 그러나 이 유효 시간이 완료되기 전에 스레드가 작업을 완료하지 못할 경우, 기존 락이 만료되어 다른 스레드가 다시 작업을 시도할 수 있습니다. 유효 시간을 늘려보는 것도 방법이 될 수 있습니다.

  3. 로그 추가: 애플리케이션에 로그를 추가하여 락이 언제 발급되고 해제되는지를 기록해 보세요. 이렇게 하면 어느 부분에서 문제가 발생하는지 더 명확히 알 수 있습니다.

  4. 트랜잭션 관리: stockService.decrease 메서드의 구현이 중요합니다. 해당 메서드가 스레드 세이프하게 작동하는지, 트랜잭션 내에서 작업이올바르게 처리되고 있는지 다시 확인하십시오.

추가적으로 로그와 코드를 확인하면서 문제를 해결해 보시길 권장드립니다.계속해서 난점이 해결되지 않는다면, 코드의 타 부분이나 잘못된 설정을 찾아내기 위한 보다 상세한 디버깅이 필요할 수 있습니다.

레퍼런스 정보에서는 Redis에 대한 정보와 Lettuce 및 Jedis 설정 방법을 제공하고 있습니다. 필요하시면 다음 URL을 참조하시기 바랍니다:

Redis Lettuce to Jedis 설정 참고

레디선 테스트코드에서 채널이름은 없어도 되는건가요?

0

37

2

낙관적 락을 사용할 떄 차이점

0

117

2

동시성 검증 코드에 관한 문의

0

88

2

단일연산

0

69

2

낙관적락vs. 레디스락

0

103

2

안녕하세요. 레디슨 질문있습니다..!!

0

65

2

@Lock(OPTIMISTIC)이 필요한 이유

0

88

2

get_lock 의 timeout이 3000초 이던데 너무 긴거 아닌가요?

0

128

2

DataSource Hikari 사용 이유

0

142

2

saveAndFlush 사용 이유 문의

0

111

3

비관적 락 VS 네임드 락

0

155

3

application.yaml 에 redis 정보

0

97

2

왜 클래스 이름에 Facade 가 붙나요?

0

180

2

@Transactional 으로 인한 동시성 문제 발생 원인이 궁금합니다.

0

215

2

@modifying 이용한 동시성 제어

0

166

2

DB락과 분산락

0

258

2

NamedLock 테스트 실패

0

186

2

테스트에서 트랜잭션 어노테이션 질문 있습니다.

0

169

2

optimistic Lock 재시도 질문입니다.

0

229

2

낙관적 락 테스트 실패

0

238

2

오류?

0

1624

4

Pessimistic Lock 전체 테스트 오류 문의

0

353

3

비관적 락 vs 레디스(Lettuce)락 비교 관련 질문

0

453

2

낙관적락 vs 네임드락

0

384

2