์ด๋ฒ ํฌ์คํ ์ ์ฌ์ ์ง์์ผ๋ก ์ด์์ฒด์ ์ ๋๊ธฐํ ์ด๋ก ์ ๋ํด ์๊ณ ์์ด์ผ ์์ฝ๊ฒ ์ดํดํ ์ ์์ผ๋ฏ๋ก, ํท๊ฐ๋ฆฌ์๋ ๋ถ๋ค์ ์๋ ํฌ์คํ ์ ๋จผ์ ์ฝ๊ณ ์ด๋ฒ ํฌ์คํ ์ ์ฝ์ด์ฃผ์๋ฉด ๊ฐ์ฌํ๊ฒ ์ต๋๋ค.
๊ณต์ ์์์ ๋ํด ๋์์ ์ฌ๋ฌ ๊ฐ์ ํ๋ก์ธ์ค๊ฐ ์ ๊ทผํ์ฌ ์๊ธฐ๋ ๊ฒฝ์ ์ํฉ(race condition)์ ์ฐ๋ฆฌ๋ ๋์์ฑ ๋ฌธ์ ๋ผ๊ณ ๋ ํ๋ฉฐ, ๋ ์์ธํ๋ ๋์ผํ ํ๋์ ๋ฐ์ดํฐ์ ๋ ๊ฐ ์ด์์ ์ค๋ ๋, ํน์ ์ธ์ ์์ ๊ฐ๋ณ ๋ฐ์ดํฐ๋ฅผ ๋์์ ์ ์ดํ ๋ ๋ํ๋ ๋ฌธ์ ๋ก, ํ๋์ ์ธ์ ์ด ๋ฐ์ดํฐ๋ฅผ ์์ ์ค์ผ ๋, ๋ค๋ฅธ ์ธ์ ์์ ์์ ์ ์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํด ๋ก์ง์ ์ฒ๋ฆฌํจ์ผ๋ก์จ ๋ฐ์ดํฐ์ ์ ํฉ์ฑ์ด ๊นจ์ง๋ ๋ฌธ์ ๋ฅผ ๋งํฉ๋๋ค
๋ฐฑ์๋ ์ฆ ์๋ฒ๊ฐ๋ฐ์์๊ฒ ์ด๋ฌํ ๋์์ฑ ๋ฌธ์ ๋ ๋งค์ฐ ์ค์ํ๋ฐ, ์์ ํฌ์คํธ ์๋ฌธ์ ๋ ๋์์๋ฏ์ด ๋์์ฑ ๋ฌธ์ ๋ฅผ ์ฒ๋ฆฌํ์ง ์์ผ๋ฉด ์๋์ ๊ฐ์ ์ํฉ์ด ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
- ๊ณต์ ์์์ธ ์ ์ญ ๋ณ์ ์๊ธ 10๋ง ์์ด ์๋ค๊ณ ๊ฐ์ ํ๋ค.
- ํ๋ก์ธ์ค P1์ ์๊ธ 10๋ง ์์ ํ์ธํ ์ํฉ์์ ํ๋ก์ธ์ค P2๊ฐ ์๊ธ 5๋ง ์์ ์ ๊ธํ์ฌ ์ด 15๋ง ์์ ์๊ธ์ ์ ์ฅํ๋ค.
- ํ์ง๋ง ํ๋ก์ธ์ค P1์ ์ฅ์์๋ ์์ง ์๊ธ์ด 10๋ง ์ ์ด๊ธฐ์ 10๋ง ์์ ์ถ๊ฐํ๋๋ผ๋ 15 + 10 = 25๋ผ๋ ๊ฒฐ๊ณผ๊ฐ ์๋ 10 + 10 = 20์ด๋ผ๋ ์ด์๊ธ์ด ์ ์ฅ๋๊ฒ ๋๋ค.
๊ทธ๋ ๋ค๋ฉด ์ด์ ์ด๋ป๊ฒ ์ด๋ฌํ ๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์์ง ํ๋ฒ ์ฌ๊ณ ์์คํ ์ด๋ผ๋ ๊ฐ๋จํ ๋ก์ง์ ์์ฑํ์ฌ ์ ๋ฆฌํด ๋ณด๊ฒ ์ต๋๋ค.
ํ ์คํธ ํ๊ฒฝ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- Apple Silicon (M1)
- Java 11, Spring, JPA, Lombok
- JUnit5
- MySQL, Redis
์ฝ๋๋ ๊นํ๋ธ์์ ํ์ธ ๊ฐ๋ฅํฉ๋๋ค.
๋ชฉ์ฐจ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ์ฌ๊ณ ์์คํ ๊ธฐ์ด ๋ก์ง
- ๋์์ฑ์ ๊ณ ๋ คํ์ง ์์ ๋ก์ง
- Java Synchronized ํค์๋๋ฅผ ํ์ฉํ ๋๊ธฐํ ๋ก์ง
- DB์์ ์ ์ดํ๋ ๋ฐฉ๋ฒ
- Pessimistic Lock์ ํ์ฉํ ๋๊ธฐํ ๋ก์ง
- Optimistic Lock์ ํ์ฉํ ๋๊ธฐํ ๋ก์ง
- Named Lock์ ํ์ฉํ ๋๊ธฐํ ๋ก์ง
- Redis๋ฅผ ํ์ฉํ๋ ๋ฐฉ๋ฒ
- Redis Lettuce์ ํ์ฉํ ๋ถ์ฐ ๋ฝ ๊ตฌํ
- Redis Redission์ ํ์ฉํ ๋ถ์ฐ ๋ฝ ๊ตฌํ
1. ์ฌ๊ณ ์์คํ ๊ธฐ์ด ๋ก์ง
๋จผ์ Entity, Service, Repository๋ ๋ค์๊ณผ ๊ฐ์ ์์๋ก ๊ฐ๋จํ๊ฒ ๊ตฌ์ฑํ์ต๋๋ค.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Stock {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Long productId;
private Long quantity;
public Stock(final Long id, final Long quantity) {
this.id = id;
this.quantity = quantity;
}
public void decrease(final Long quantity) {
if (this.quantity - quantity < 0) {
throw new RuntimeException("์ฌ๊ณ ๋ถ์กฑ");
}
this.quantity -= quantity;
}
}
@Service
@RequiredArgsConstructor
public class StockService {
private final StockRepository stockRepository;
@Transactional
public void decrease(Long id, Long quantity) {
Stock stock = stockRepository.findById(id).orElseThrow();
stock.decrease(quantity);
stockRepository.saveAndFlush(stock);
}
}
public interface StockRepository extends JpaRepository<Stock, Long> {
}
2. ๋์์ฑ์ ๊ณ ๋ คํ์ง ์์ ๋ก์ง
๋จผ์ ๋์์ฑ์ ๊ณ ๋ คํ์ง ์์ ๋ก์ง ๋จผ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
@SpringBootTest
class StockServiceTest {
@Autowired
private StockService stockService;
@Autowired
private StockRepository stockRepository;
@BeforeEach
public void before() {
Stock stock = new Stock(1L, 100L);
stockRepository.saveAndFlush(stock);
}
@AfterEach
public void after() {
stockRepository.deleteAll();
}
@Test
@DisplayName("race condition ์ผ์ด๋๋ ํ
์คํธ")
public void ๋์์_100๊ฐ_์์ฒญ() throws InterruptedException {
int threadCount = 100;
//๋ฉํฐ์ค๋ ๋ ์ด์ฉ ExecutorService : ๋น๋๊ธฐ๋ฅผ ๋จ์ํ๊ฒ ์ฒ๋ฆฌํ ์ ์๋๋ก ํด์ฃผ๋ java api
ExecutorService executorService = Executors.newFixedThreadPool(32);
//๋ค๋ฅธ ์ค๋ ๋์์ ์ํ์ด ์๋ฃ๋ ๋ ๊น์ง ๋๊ธฐํ ์ ์๋๋ก ๋์์ฃผ๋ API - ์์ฒญ์ด ๋๋ ๋ ๊น์ง ๊ธฐ๋ค๋ฆผ
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
executorService.submit(() -> {
try {
stockService.decrease(1L, 1L);
} finally {
latch.countDown();
}
});
}
latch.await();
Stock stock = stockRepository.findById(1L).orElseThrow();
assertEquals(0L, stock.getQuantity());
}
}
์ด๊ธฐ์ ProductId๊ฐ 1์ธ ์ฌํ์ ์๋์ 100๊ฐ๋ก ์ธํ ํ์ฌ DB์ ์ธํ ์์ผฐ๊ณ , ExecutorService๋ฅผ ํตํด 32๊ฐ์ ๊ณ ์ ๋ ์ค๋ ๋ํ์ ์์ฑํ์ฌ submit๋ฉ์๋๋ฅผ ํตํด ๋ฉํฐ์ค๋ ๋๋ก ๋์ํ์ฌ ์ฌ๊ณ ๋ฅผ ๊ฐ์์ํค๋ ๋ก์ง์ ์ํํ์ต๋๋ค.
ExecutorService์ CountDownLatch์ ๋ํด ์์ํ์ ๋ถ๋ค์ ์ํด ๊ฐ๋ตํ๊ฒ ์ ๋ฆฌํ๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
ExecutorService
- ExecutorService๋, ๋ณ๋ ฌ ์์ ์ ์ฌ๋ฌ ๊ฐ์ ์์ ์ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ๊ธฐ ์ํด ์ ๊ณต๋๋ JAVA API์ด๋ค.
- ExecutorService๋ ์์ฝ๊ฒ ThreadPool์ ๊ตฌ์ฑํ๊ณ Task๋ฅผ ์คํํ๊ณ ๊ด๋ฆฌํ ์ ์๋ ์ญํ ์ ํ๋ค.
- Executors๋ฅผ ์ฌ์ฉํ์ฌ ExecutorService ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ฉฐ, ์ค๋ ๋ ํ์ ๊ฐ์ ๋ฐ ์ข ๋ฅ๋ฅผ ์ง์ ํ ์ ์๋ ๋ฉ์๋๋ฅผ ์ ๊ณตํ๋ค.
CountDownLatch
- CountDownLatch๋, ์ด๋ค ์ค๋ ๋๊ฐ ๋ค๋ฅธ ์ค๋ ๋์์ ์์ ์ด ์๋ฃ๋ ๋ ๊ฐ์ง ๊ธฐ๋ค๋ฆด ์ ์๋๋ก ํด์ฃผ๋ ํด๋์ค์ด๋ค.
- CountDownLatch๋ฅผ ์ด์ฉํ์ฌ, ๋ฉํฐ์ค๋ ๋๊ฐ ๋จ์ ํ์์ ์์ ์ ๋ชจ๋ ์๋ฃํ ํ, ํ ์คํธ๋ฅผ ํ๋๋ก ๊ธฐ๋ค๋ฆฌ๊ฒ ํฉ๋๋ค.
- CountDownLatch ์๋์๋ฆฌ
- new CountDownLatch(10); ์ ํ์์ผ๋ก Latch ํ ๊ฐ์๋ฅผ ์ง์ ํ๋ค.
- countDown()์ ํธ์ถํ๋ฉด Latch์ ์นด์ดํฐ๊ฐ 1๊ฐ์ฉ ๊ฐ์ํ๋ค.
- await()์ Latch์ ์นด์ดํฐ๊ฐ 0์ด ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฐ๋ค.
์ ๋ก์ง์ ์ํ ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ์ด ์คํจ๋ก ๋์ต๋๋ค.
100๊ฐ์ ์ฌ๊ณ ๋ฅผ 1๊ฐ์ฉ 100๋ฒ ๊ฐ์์์ผฐ๋ ๋งํผ ์ํ๊ฒฐ๊ณผ 0์ด๋ผ๋ ์ฌ๊ณ ์ ์๋์ ๊ธฐ๋ํ์ผ๋ 96์ด๋ผ๋ ํฐ๋ฌด๋์๋ ์๋์ด ๋จ์์์ต๋๋ค. ์ด์ ๋ ์๋์ ๋ช ์ํ๋ ์ํ ์ ์ถ๊ธ ์์์ธ race condition(๊ฒฝ์์ํฉ)์ด ์ผ์ด๋ฌ๊ธฐ ๋๋ฌธ์ธ๋ฐ, ๋ ๊ฐ ์ด์์ ์ค๋ ๋๊ฐ ๊ณต์ ์์์ ๋ํด์ ๋์์ ๋ณ๊ฒฝํ๋ ค๊ณ ํ๊ธฐ ๋๋ฌธ์ ์ ๋๋ค.
race condition์ ํด๊ฒฐํ๊ธฐ ์ํด์๋ ๊ทผ๋ณธ์ ์ผ๋ก ๊ณต์ ์์์ ๋ํด ํ๋์ ์ค๋ ๋๊ฐ ์์ ์ ์๋ฃํ ํ์ ๋ค๋ฅธ ์ค๋ ๋๊ฐ ์ ๊ทผํ ์ ์๋๋ก ์ค์ ํด์ผ ํฉ๋๋ค.
3. Synchronized ํค์๋๋ฅผ ํ์ฉํ ๋๊ธฐํ ๋ก์ง
๋จผ์ Synchronizedํค์๋๋ฅผ ์ด์ฉํ์ฌ ๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
@Service
@RequiredArgsConstructor
public class StockService {
private final StockRepository stockRepository;
/**
* synchronized ์ฌ์ฉ์ @Transactional๊ณผ ๋์์ ์ฌ์ฉํ๋ฉด ์๋๋ค.
* Synchronized๋ฅผ ์ฌ์ฉํ๋ ์ด์ ๋ ํด๋น ๋ฉ์๋๋ฅผ ํ ์ฐ๋ ๋์์๋ง ๋๋ฆฌ๊ธฐ ์ํด์๋ค.
* ํ์ง๋ง, ํธ๋์ญ์
์ด ๊ฐ์ด ์ ์๊ฐ ๋์ด์๋ค๋ฉด ์ฒซ ๋ฒ์งธ ์ฐ๋ ๋๊ฐ ๋๋๊ธฐ ์ ๋ ๋ฒ์งธ ์ฐ๋ ๋๊ฐ ๋ฐ๋ํ ์๋ ์๋ค.
*/
public synchronized void decrease(Long id, Long quantity) {
Stock stock = stockRepository.findById(id).orElseThrow();
stock.decrease(quantity);
stockRepository.saveAndFlush(stock);
}
}
@Test
@DisplayName("synchronized ์ฌ์ฉ")
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 {
stockService.decrease(1L, 1L);
} finally {
latch.countDown();
}
});
}
latch.await();
Stock stock = stockRepository.findById(1L).orElseThrow();
assertEquals(0L, stock.getQuantity());
}
์์ ๊ฐ์ด ๊ฐ๋จํ๊ฒ Service๋ก์ง์์ ๋ฉ์๋์ synchronized ํค์๋๋ฅผ ์ถ๊ฐํ๊ณ , @Transactional์ ์ ๊ฑฐํจ์ผ๋ก์จ ํด๊ฒฐํ ์ ์์ต๋๋ค.
- synchronized๋ฅผ ์ด์ฉํ๋ฉด ํ์ฌ ์ ๊ทผํ๊ณ ์๋ ๋ฉ์๋์ ํ๋์ ์ค๋ ๋๋ง ์ ๊ทผํ ์ ์๋๋ก ์๋ํฉ๋๋ค.
- ์๋ฐ์์ ์ง์ํ๋ synchronized๋, ํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๊ณ ์๋ ํด๋น ์ค๋ ๋๋ฅผ ์ ์ธํ๊ณ ๋๋จธ์ง ์ค๋ ๋๋ค์ ๋ฐ์ดํฐ ์ ๊ทผ์ ๋ง์ ์์ฐจ์ ์ผ๋ก ๋ฐ์ดํฐ์ ์ ๊ทผํ ์ ์๋๋ก ํด์ค๋๋ค.
synchronized๋ฅผ ์ฌ์ฉํ ๋ @Transactional์ ์ ๊ฑฐํด์ฃผ์ด์ผ๋ง ํ๋ ์ด์ ๋ @Transactional์ ํตํ ์ ์ธ์ ํธ๋์ญ์ ๊ด๋ฆฌ ๋ฐฉ์์ ๊ธฐ๋ณธ์ ์ผ๋ก ํ๋ก์ ๋ฐฉ์์ AOP๊ฐ ์ ์ฉ๋๊ธฐ ๋๋ฌธ์ ๋๋ค.
TransactionStatus status = transactionManager.getTransaction(..);
try {
target.logic(); // public synchronized void decrease ๋ฉ์๋ ์ํ
// logic ์ํ ์ดํ ํธ๋์ญ์
์ข
๋ฃ ์ ์ ๋ค๋ฅธ ์ฐ๋ ๋๊ฐ decrease์ ์ ๊ทผ!
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw new IllegalStateException(e);
}
์ ์ฝ๋๋ ํธ๋์ญ์ ํ๋ก์์ ๊ฐ๋จํ ์์ ์ฝ๋์ ๋๋ค. ํธ๋์ญ์ ํ๋ก์๊ฐ ํธ๋์ญ์ ์ฒ๋ฆฌ ๋ก์ง์ ์ํํ ๋ ์ ํด๋์ค๋ฅผ ์๋ก ๋ง๋ค์ด locic์ ์ํํ๊ฒ ๋ฉ๋๋ค. synchronized๋ฅผ ํตํด ์๋น์ค ๋ก์ง์ decrease() ๋ฉ์๋๋ฅผ ํ ์ค๋ ๋๋ง ์ ๊ทผํ ์ ์๋๋ก ๋ง๋๋ผ๋ Transactional์ ์ํด ์๋ก์ด ํ๋ก์๋ก ์ํ๋๊ฒ ๋์ด๋ฒ๋ฆฌ๋ฉด ํ๋ก์ ๋ก์ง์ synchronized๊ฐ ๊ฑธ๋ฆฐ ์ํ๊ฐ ์๋๊ธฐ ๋๋ฌธ์ ์ฐ๋ฆฌ๊ฐ ์ํ๋ ๊ฒฐ๊ณผ๋ฅผ ์ป์ง ๋ชปํ๊ฒ ๋ฉ๋๋ค.
JAVA Sychronized์ ๋ฌธ์ ์
synchronized์ ์ฌ์ฉ์ ๊ฐ๋จํ์ง๋ง ๋ค์๊ณผ ๊ฐ์ ๋ฌธ์ ์ ์ ๊ฐ์ง๊ณ ์์ต๋๋ค.
- ์๋ฐ์ Sychronized๋ ํ๋์ ํ๋ก์ธ์ค ์์์๋ง ๋ณด์ฅ์ด ๋ฉ๋๋ค.
- ์ฆ, ์๋ฒ๊ฐ 1๋์ผ ๋๋ ๋ฌธ์ ๊ฐ ์์ง๋ง ์๋ฒ๊ฐ ์ฌ๋ฌ ๋ ์ผ๊ฒฝ์ฐ ์ฌ๋ฌ ๊ฐ์ ์ธ์คํด์ค๊ฐ ์กด์ฌํ๋ ๊ฒ๊ณผ ๋์ผํ๊ธฐ ๋๋ฌธ์ ์ค์ง์ ์ธ ์ด์ ํ๊ฒฝ์์๋ ๋ฐ์ดํฐ์ ์ ํฉ์ฑ์ ๋ณด์ฅํ ์ ์์ต๋๋ค.
4. DB์์ ์ ์ดํ๋ ๋ฐฉ๋ฒ
์ด๋ฒ ๋ฐฉ๋ฒ์ DateBase Lock์ ์ด์ฉํ์ฌ ์์ฐจ์ ์ธ ์ ๊ทผ์ผ๋ก ์ ์ดํ๋ ๋ฐฉ๋ฒ์ ๋๋ค. ํฌ๊ฒ ๋ค์๊ณผ ๊ฐ์ ๋ฐฉ๋ฒ์ด ์์ผ๋ฉฐ, ์กฐ๊ธ ๋ ์์ธํ๊ณ DB๋จ์์ ์ง์ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ๋ค์ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ๋ฉด ์ข์ ๊ฒ ๊ฐ์ต๋๋ค. ๋งํฌ: MySQL์์์ Lock
- Pessimistic Lock(๋น๊ด์ ๋ฝ)
- Optimistic Lock(๋๊ด์ ๋ฝ)
- Named Lock(๋ค์๋ ๋ฝ)
1. Pessimistic Lock
- Pessimistic Lock์ด๋ ์ค์ ๋ก ๋ฐ์ดํฐ์ Lock์ ๊ฑธ์ด์ ์ ํฉ์ฑ์ ๋ง์ถ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
- Exclusive Lock(๋ฐฐํ์ ์ ๊ธ)์ ๊ฑธ๊ฒ ๋๋ฉด ๋ค๋ฅธ ํธ๋์ญ์ ์์๋ Lock ์ด ํด์ ๋๊ธฐ ์ ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ๊ฐ ์ ์๊ฒ ๋ฉ๋๋ค.
- ์์ ์์ฒญ์ ๋ฐ๋ฅธ ๋์์ฑ๋ฌธ์ ๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ผ๊ณ ์์ํ๊ณ ๋ฝ์ ๊ฑธ์ด๋ฒ๋ฆฌ๋ ๋น๊ด์ ๋ฝ ๋ฐฉ์์ ๋๋ค.
- ํ์ง๋ง, Dead Lock(๊ต์ฐฉ์ํ)์ ๋น ์ง ์ํ์ฑ์ด ์์ผ๋ฏ๋ก ์ ์ํด์ผ ํฉ๋๋ค.
Dead Lock(๊ต์ฐฉ์ํ)๋ ์ด์ OS ์ ๋ฆฌ ๋ ํฌ์คํ ํ ์ ์์ผ๋ ์ฐธ๊ณ ํ์๋ฉด ๊ฐ์ฌํ๊ฒ ์ต๋๋ค.
์์ ๋์๋์ ์ํ ๊ณผ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- Transaction_1์์ table์ Id 2๋ฒ์ ์ฝ์ ( name = Karol )
- Transaction_2์์ table์ Id 2๋ฒ์ ์ฝ์ ( name = Karol )
- Transaction_2์์ table์ Id 2๋ฒ์ name์ Karol2๋ก ๋ณ๊ฒฝ ์์ฒญ ( name = Karol )
- ํ์ง๋ง Transaction 1์์ ์ด๋ฏธ shared Lock์ ์ก๊ณ ์๊ธฐ ๋๋ฌธ์ Blocking
- Transaction_1์์ ํธ๋์ญ์ ํด์ (commit)
- Blocking ๋์ด์์๋ Transaction_2์ update ์์ฒญ ์ ์ ์ฒ๋ฆฌ
์ฝ๋
public interface StockRepository extends JpaRepository<Stock, Long> {
@Lock(value = LockModeType.PESSIMISTIC_WRITE)
Optional<Stock> findById(Long id);
}
@Service
@RequiredArgsConstructor
public class StockService {
private final StockRepository stockRepository;
@Transactional
public void decrease(Long id, Long quantity) {
Stock stock = stockRepository.findById(id).orElseThrow();
stock.decrease(quantity);
stockRepository.saveAndFlush(stock);
}
}
@Test
@DisplayName("Pessimistic Lock(๋น๊ด์ ๋ฝ) ์ฌ์ฉ")
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 {
stockService.decrease(1L, 1L);
} finally {
latch.countDown();
}
});
}
latch.await();
Stock stock = stockRepository.findById(1L).orElseThrow();
assertEquals(0L, stock.getQuantity());
}
์์ ๊ฐ์ด Repository์์ DB ์กฐํ ์์ @Lock(value = LockModeType.PESSIMISTIC_WRITE)์ ์ ์ฉํ์ฌ ํธ๋์ญ์ ์ด ์์ํ ๋ Shared/Exclusive Lock์ ์ ์ฉํ๊ฒ ํ์ฌ ์ฌ์ฉํ ์ ์์ต๋๋ค.
๊ฒฐ๊ตญ Pesimistic Lock์ด๋, ๋ฐ์ดํฐ์๋ Lock์ ๊ฐ์ง ์ค๋ ๋๋ง ์ ๊ทผ์ด ๊ฐ๋ฅํ๋๋ก ์ ์ดํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
์ฅ์
- Pessimistic Lock์ ๋์์ฑ ์ถฉ๋์ด ์ฆ์ ๊ฒ์ผ๋ก ์์๋์ด ๋์์ฑ์ ๊ฐ๋ ฅํ๊ฒ ์ง์ผ์ผ ํ ๋ ์ฌ์ฉํฉ๋๋ค.
- ์ถฉ๋์ด ๋น๋ฒํ๊ฒ ์ผ์ด๋๋ค๋ฉด ๋กค๋ฐฑ์ ํ์๋ฅผ ์ค์ผ ์ ์๊ธฐ ๋๋ฌธ์, Optimistic Lock๋ณด๋ค๋ ์ฑ๋ฅ์ด ์ข์ ์ ์๊ณ , ๊ฐ์ฅ ๊ฐ๋ ฅํ ๋ฐ์ดํฐ ์ ํฉ์ฑ ๋ณด์ฅ ๋ฐฉ๋ฒ์ ๋๋ค.
๋จ์
- ๋ฐ์ดํฐ ์์ฒด์ Lock์ ๊ฑธ๊ธฐ ๋๋ฌธ์ ์๋๊ฐ ์๋์ ์ผ๋ก ๋๋ฆฐ ํธ์ ๋๋ค.
- ์๋ก ์์์ด ํ์ํ ๊ฒฝ์ฐ, ๋ฝ์ด ๊ฑธ๋ ค์์ผ๋ฏ๋ก Dead Lock(๊ต์ฐฉ์ํ)์ ๋น ์ง ๊ฐ๋ฅ์ฑ์ด ์์ต๋๋ค.
2. Optimistic Lock
- Optimistic Lock์ ์ค์ Lock์ ์ฌ์ฉํ์ง ์๊ณ , ๋ฐ์ดํฐ์ Version์ ์ด์ฉํ์ฌ ๋ฐ์ดํฐ์ ์ ํฉ์ฑ์ ์ค์ํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
- ๋จผ์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ ํ์ update๋ฅผ ์ํํ ๋ ํ์ฌ ๋ด๊ฐ ์กฐํํ ๋ฒ์ ์ด ๋ง๋์ง ํ์ธํ๋ฉฐ ์ ๋ฐ์ดํธํฉ๋๋ค.
- ์์์ Lock์ ๊ฑธ์ด์ ์ ์ ํ์ง ์๊ณ , ๋์์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ฉด ๊ทธ๋ ๊ฐ์ ์ฒ๋ฆฌํ๋ ๋๊ด์ ๋ฝ ๋ฐฉ์์ ๋๋ค.
- ๋ด๊ฐ ์กฐํํ ๋ฒ์ ์์ ์์ ์ฌํญ์ด ์๊ฒผ์ ๊ฒฝ์ฐ์๋ application์์ ๋ค์ ์กฐํ ํ์ ์์ ์ ์ํํ๋ ๋กค๋ฐฑ ์์ ์ ์ํํด์ผ ํฉ๋๋ค.
์์ ๋์๋์ ์ํ ๊ณผ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- A๊ฐ table์ Id 2๋ฒ์ ์ฝ์ ( name = Karol, version = 1 )
- B๊ฐ table์ Id 2๋ฒ์ ์ฝ์ ( name = Karol, version = 1 )
- B๊ฐ table์ Id 2๋ฒ, version 1์ธ row์ ๊ฐ ๊ฐฑ์ ( name = Karol2, version = 2 ) ์ฑ๊ณต
- A๊ฐ table์ Id 2๋ฒ, version 1์ธ row์ ๊ฐ ๊ฐฑ์ ( name = Karol1, version = 2 ) ์คํจ
- Id 2๋ฒ์ ์ด๋ฏธ version์ด 2๋ก ์ ๋ฐ์ดํธ๋์๊ธฐ ๋๋ฌธ์ A๋ ํด๋น row๋ฅผ ๊ฐฑ์ ํ์ง ๋ชปํจ
- ์ฟผ๋ฆฌ๊ฐ ์คํจํ๋ฉด ๋ค์ ์กฐํํ์ฌ ๋ฒ์ ์ ๋ง์ถ ํ ์ ๋ฐ์ดํธ ์ฟผ๋ฆฌ๋ฅผ ๋ ๋ฆฌ๋ ๊ณผ์ ์ ๋ฐ๋ณต
์ flow๋ฅผ ํตํด์ ๊ฐ์ row์ ๋ํด์ ๊ฐ๊ธฐ ๋ค๋ฅธ 2๊ฐ์ ์์ ์์ฒญ์ด ์์์ง๋ง 1๊ฐ๊ฐ ์ ๋ฐ์ดํธ๋จ์ ๋ฐ๋ผ version์ด ๋ณ๊ฒฝ๋์๊ธฐ ๋๋ฌธ์ ๋ค์ ์์ ์์ฒญ์ ๋ฐ์๋์ง ์๊ฒ ๋์์ต๋๋ค. ์ด๋ ๊ฒ ๋๊ด์ ๋ฝ์ version๊ณผ ๊ฐ์ ๋ณ๋์ ์ปฌ๋ผ์ ์ถ๊ฐํ์ฌ ์ถฉ๋์ ์ธ ์ ๋ฐ์ดํธ๋ฅผ ๋ง์ต๋๋ค. version ๋ฟ๋ง ์๋๋ผ hashcode ๋๋ timestamp๋ฅผ ์ด์ฉํ๊ธฐ๋ ํฉ๋๋ค.
์ฝ๋
Pessimistic Lock๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก DB ์กฐํ ์์ @Lock(value = LockModeType.OPTIMISTIC)์ ๋ช ์ํ์ฌ ์ ์ฉํ ์ ์์ต๋๋ค.
public interface StockRepository extends JpaRepository<Stock, Long> {
@Lock(value = LockModeType.OPTIMISTIC)
Optional<Stock> findById(Long id);
}
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Stock {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Long productId;
private Long quantity;
// ๋ฒ์ ์ปฌ๋ผ ์ถ๊ฐ!!
@Version
private Long version;
public Stock(final Long id, final Long quantity) {
this.id = id;
this.quantity = quantity;
}
public void decrease(final Long quantity) {
if (this.quantity - quantity < 0) {
throw new RuntimeException("์ฌ๊ณ ๋ถ์กฑ");
}
this.quantity -= quantity;
}
}
Entity์๋ @Version์ ํตํด version ์ปฌ๋ผ์ ์ถ๊ฐํ์ต๋๋ค.
@Service
@RequiredArgsConstructor
public class StockService {
private final StockRepository stockRepository;
@Transactional
public void decrease(Long id, Long quantity) {
Stock stock = stockRepository.findById(id).orElseThrow();
stock.decrease(quantity);
stockRepository.saveAndFlush(stock);
}
}
Service๋ก์ง์ ๊ฒฝ์ฐ์๋ ๊ธฐ์กด์ ๋ก์ง๊ณผ ์ฐจ์ด๊ฐ ์๊ณ , ์ง์์ ์ผ๋ก DB ๋ณ๊ฒฝ์ ์ฌ์๋ํ๋ ๋ก์ง์ ๊ตฌํํด์ผ ํ๊ธฐ ๋๋ฌธ์ ๋ณ๋์ Helper ์ ํธ๋ฆฌํฐ์ ๊ฐ์ Facade๋ฅผ ์๋์ ๊ฐ์ด ๋ง๋ค์์ต๋๋ค.
@Component
@RequiredArgsConstructor
public class OptimisticLockStockFacade {
private final StockService stockService;
/**
* Lock์ ์ก์ง ์์ผ๋ฏ๋ก, Pessimistic Lock(๋น๊ด์ ๋ฝ)๋ณด๋ค ์ฑ๋ฅ์ ์ด์ ์ด ์์ ์ ์๋ค.
* ํ์ง๋ง, ์
๋ฐ์ดํธ๊ฐ ์คํจํ์ ๊ฒฝ์ฐ ์ฌ์๋ ๋ก์ง์ด ๊ฐ๋ฐ์๊ฐ ์ง์ ์์ฑ์ ํด์ฃผ์ด์ผ ํ๋ค.
* ๋ํ ์ถฉ๋์ด ๋น๋ฒํ๊ฒ ์ผ์ด๋๋ค๋ฉด, Pessimistic Lock์ด ์ฑ๋ฅ์ ์ด์ ์ด ๋ ์์ ์ ์๋ค.
*/
public void decrease(Long id, Long quantity) throws InterruptedException {
while (true) {
try {
stockService.decrease(id, quantity);
break;
} catch (Exception e) {
Thread.sleep(1);
}
}
}
}
Optimistic Lock์์ Version์ ํ์ธํ๋๋ฐ, ๋ง์ฝ DB ๋ณ๊ฒฝ ํธ๋์ญ์ ์ ๋ง๋ค๊ณ ์ ํ๋ ํ์ฌ ์ค๋ ๋๊ฐ ์ด์ Version์ ๊ฐ์ง๊ณ ์๋ค๋ฉด ํธ๋์ญ์ ์ ๋ณด๋ด์ง ๋ชปํ๊ณ 1ms๋์ ๊ธฐ๋ค๋ฆฌ๋๋ก ์ค์ ํ์ต๋๋ค.
@Test
@DisplayName("Optimistic Lock(๋๊ด์ ๋ฝ) ์ฌ์ฉ")
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 {
optimisticLockStockFacade.decrease(1L, 1L);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
});
}
latch.await();
Stock stock = stockRepository.findById(1L).orElseThrow();
assertEquals(0L, stock.getQuantity());
}
์ฅ์
- ์ถฉ๋์ด ์์ฃผ ์ผ์ด๋์ง ์๋๋ค๋ ๊ฐ์ ํ์, ๋ณ๋์ Lock์ ๊ฑธ์ง ์์ผ๋ฏ๋ก Pessimistic Lock๋ณด๋ค๋ ์ฑ๋ฅ์ ์ด์ ์ ๊ฐ์ง ์ ์์ต๋๋ค.
๋จ์
- ์ ๋ฐ์ดํธ๊ฐ ์คํจํ์ ์, ์ฌ์๋ ๋ก์ง์ ๊ฐ๋ฐ์๊ฐ ์ง์ ์์ฑํด ์ฃผ์ด์ผ ํฉ๋๋ค.
- ์ถฉ๋์ด ๋น๋ฒํ๊ฒ ์ผ์ด๋๋ค๋ฉด, Roll Back ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ์ด์ผ ํ๊ธฐ ๋๋ฌธ์, Pessimistic Lock์ด ๋ ์ฑ๋ฅ์ด ์ข์ ์ ์์ต๋๋ค.
3. Named Lock
- Named Lock์ ์ด๋ฆ์ ๊ฐ์ง Metadata Lock์ ๋๋ค.
- ์ด๋ฆ์ ๊ฐ์ง Lock์ ํ๋ํ ํ, ํด์ง๋ ๋๊น์ง ๋ค๋ฅธ ์ธ์ ์ ์ด Lock์ ํ๋ํ ์ ์๊ฒ ๋ฉ๋๋ค.
- ์ฃผ์ํ ์ ์, ํธ๋์ญ์ ์ด ์ข ๋ฃ๋ ๋ Lock์ด ์๋์ผ๋ก ํด์ง๋์ง ์๊ธฐ ๋๋ฌธ์, ๋ณ๋๋ก ํด์งํด์ฃผ๊ฑฐ๋ ์ ์ ์๊ฐ์ด ๋๋์ผ ํด์ง๋ฉ๋๋ค.
- Mysql์์๋ getLock( )์ ํตํด ํ๋ค / releaseLock()์ผ๋ก ํด์งํ ์ ์์ต๋๋ค.
์์ ๋์๋์ ์ํ ๊ณผ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- Named Lock์ Stock์ ๋ฝ์ ๊ฑธ์ง ์๊ณ , ๋ณ๋์ ๊ณต๊ฐ์ Lock์ ๊ฑด๋ค.
- session-1 ์ด 1์ด๋ผ๋ ์ด๋ฆ์ผ๋ก Lock์ ๊ฑด๋ค๋ฉด, session 1 ์ด 1์ ํด์งํ ํ์ Lock์ ์ป์ ์ ์๋ค.
์ฃผ์ํ ์ ์ Named Lock์ ํ์ฉํ ๋ ๋ฐ์ดํฐ์์ค๋ฅผ ๋ถ๋ฆฌํ์ง ์๊ณ , ํ๋๋ก ์ฌ์ฉํ๊ฒ ๋๋ฉด connection pool์ด ๋ถ์กฑํด์ง๋ ํ์์ด ๋ฐ์ํด Lock์ ์ฌ์ฉํ์ง ์๋ ๋ค๋ฅธ ์๋น์ค๊น์ง ์ํฅ์ ๋ผ์น ์ ์๋ค๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
Named Lock์ ํ์ฉํ๋ฉด ๋ถ์ฐ ๋ฝ์ ๊ตฌํํ ์ ์๊ณ , Pessmistic Lock์ ๊ฒฝ์ฐ ํ์์์์ ๊ตฌํํ๊ธฐ ๊น๋ค๋กญ์ง๋ง, Named Lock์ ์์ฝ๊ฒ ๊ตฌํํ ์ ์์ต๋๋ค. ํ์ง๋ง ํธ๋์ญ์ ์ข ๋ฃ ์์ Lock ํด์ ์ ๋ฐ์ดํฐ ์์ค ๋ถ๋ฆฌ ์ ์ธ์ ๊ด๋ฆฌ๋ฅผ ์๋์ผ๋ก ์งํ๋์ด์ผ ํ๋ค๋ ๋ถํธํ ์ ์ด ์์ต๋๋ค.
์ฝ๋
public interface LockRepository extends JpaRepository<Stock, Long> {
@Query(value = "select get_lock(:key, 3000)", nativeQuery = true)
void getLock(String key);
@Query(value = "select release_lock(:key)", nativeQuery = true)
void releaseLock(String key);
}
@Component
@RequiredArgsConstructor
public class NamedLockFacade {
private final LockRepository lockRepository;
private final StockService stockService;
/**
* Named Lock์ ์ฃผ๋ก ๋ถ์ฐ ๋ฝ์ ๊ตฌํํ ๋ ์ฌ์ฉํ๋ค.
* Pessimistic Lock์ ํ์์์์ ๊ตฌํํ๊ธฐ ๊ต์ฅํ ๊น๋ค๋กญ์ง๋ง, Named Lock์ ์์ฝ๊ฒ ๊ตฌํํ ์ ์๋ค.
* ์ด์ธ์๋ ๋ฐ์ดํฐ ์ฝ์
์์ ์ ํฉ์ฑ์ ๋ง์ถฐ์ผ ํ๋ ๊ฒฝ์ฐ์๋ ์ฌ์ฉํ ์ ์๋ค.
* ํ์ง๋ง ์ด ๋ฐฉ๋ฒ์ ํธ๋์ญ์
์ข
๋ฃ์์ ๋ฝ ํด์ ์ ์ธ์
๊ด๋ฆฌ๋ฅผ ์ง์ ์ ํด์ค์ผํ๋ฏ๋ก ์ฃผ์ํด์ ์ฌ์ฉํด์ผํ๊ณ , ์ฌ์ฉํ ๋๋ ๊ตฌํ๋ฐฉ๋ฒ์ด ๋ณต์กํ ์ ์๋ค.
*/
@Transactional
public void decrease(Long id, Long quantity) {
try {
lockRepository.getLock(id.toString());
stockService.decrease(id, quantity);
}finally {
// Lock ํด์
lockRepository.releaseLock(id.toString());
}
}
}
@Service
@RequiredArgsConstructor
public class StockService {
private final StockRepository stockRepository;
// ๋ถ๋ชจ์ ํธ๋์ญ์
๊ณผ ๋ณ๋๋ก ์คํ๋์ด์ผ ํจ
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void decrease(Long id, Long quantity) {
Stock stock = stockRepository.findById(id).orElseThrow();
stock.decrease(quantity);
stockRepository.saveAndFlush(stock);
}
}
์ฌ๊ธฐ์ ์ค์ ๋ก์ง์ ์ค๋ช ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- StockService๋ ๋ถ๋ชจ์ ํธ๋์ญ์ ๊ณผ ๋ณ๋๋ก ์คํ๋์ด์ผ ํ๊ธฐ ๋๋ฌธ์ propergation์ ๋ณ๋๋ก ์์ฑํด ์ค๋๋ค
- ๋ถ๋ชจ์ ํธ๋์ญ์ ๊ณผ ๋์ผํ ๋ฒ์๋ก ๋ฌถ์ธ๋ค๋ฉด Synchronized์ ๊ฐ์ ๋ฌธ์ ์ธ DataBase์ Commit ๋๊ธฐ ์ ์ Lock์ด ํ๋ฆฌ๋ ํ์์ด ๋ฐ์ํฉ๋๋ค.
- ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ๋ณ๋์ ํธ๋์ญ์ ์ผ๋ก ๋ถ๋ฆฌํด์ DataBase์ ์ ์์ ์ผ๋ก Commit์ด ๋ ํ์ ๋ฝ์ ํด์ ํด ์ฃผ๋๋ก ํฉ๋๋ค.
- ํต์ฌ์ Lock์ ํด์ ํ๊ธฐ ์ ์ DataBase์ Commit์ด ๋๋๋ก ํ๋ ๊ฒ์ ๋๋ค.
๊ทธ๋ฆฌ๊ณ Connection Pool์ ์๋ฅผ ๋๋ ค์ค๋๋ค.
spring:
jpa:
hibernate:
ddl-auto: create
show-sql: true
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:tcp://localhost/~/test
username: sa
password:
hikari:
maximum-pool-size: 40 // here
@Test
@DisplayName("Named Lock ์ฌ์ฉ")
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 {
namedLockStockFacade.decrease(1L, 1L);
} finally {
latch.countDown();
}
});
}
latch.await();
Stock stock = stockRepository.findById(1L).orElseThrow();
assertEquals(0L, stock.getQuantity());
}
์ฅ์
- Named Lock ์ ์ฃผ๋ก ๋ถ์ฐ๋ฝ์ ๊ตฌํํ ๋ ์ฌ์ฉํฉ๋๋ค.
- Pessimistic ๋ฝ์ timeout์ ๊ตฌํํ๊ธฐ ๊ต์ฅํ ํ๋ค์ง๋ง, Named Lock์ ๋น๊ต์ ์์ฝ๊ฒ ๊ตฌํํ ์ ์์ต๋๋ค.
๋จ์
- Named Lock ์ ํธ๋์ญ์ ์ข ๋ฃ ์์, Lock ํด์ ์ ์ธ์ ๊ด๋ฆฌ๋ฅผ ์ํด์ฃผ์ด์ผ ํ๋ฏ๋ก ์ฃผ์ํด์ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
- ์ค์ ์ ๋ฌด ํ๊ฒฝ์์๋ ๊ตฌํ๋ฐฉ๋ฒ์ด ๋ณต์กํ ์ ์์ต๋๋ค.
5. Redis๋ฅผ ํ์ฉํ๋ ๋ฐฉ๋ฒ
Redis๋ key-value ๊ตฌ์กฐ์ ๋น์ ํ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ ๊ด๋ฆฌํ๊ธฐ ์ํ ์คํ ์์ค ๊ธฐ๋ฐ์ ๋น ๊ด๊ณํ ์ธ๋ฉ๋ชจ๋ฆฌ DBMS์ ๋๋ค. Redis์ ๋ค์ํ ํน์ง ์ค์์๋ Single Threaded ํ ํน์ง ์ฆ, ํ ๋ฒ์ ํ๋์ ๋ช ๋ น๋ง ์ฒ๋ฆฌํ ์ ์๋ ํน์ง ๋๋ฌธ์ ๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋๋ฐ ๋ง์ด ์ฌ์ฉ๋ฉ๋๋ค.
Java์ Redis Client๋ ์๋์ ๊ฐ์ด ํฌ๊ฒ ์ธ ๊ฐ์ง๊ฐ ์์ต๋๋ค.
์ด ์ค์์ Jedis๋ ์ฑ๋ฅ์ด ๋ง์ด ์ข์ง ์๊ธฐ ๋๋ฌธ์ Lettuce์ Redisson์ ๋น๊ตํด์ ์ ๋ฆฌํ๊ฒ ์ต๋๋ค. Jedis์์ ์ฑ๋ฅ ๋น๊ต๋ ์กฐ์กธ๋๋์ ๋ธ๋ก๊ทธ์ ์์ธํ ์ค๋ช ๋์ด ์์ต๋๋ค. ๋งํฌ: https://jojoldu.tistory.com/418
1. Lettuce
- Setnx ๋ช ๋ น์ด๋ฅผ ํ์ฉํ์ฌ ๋ถ์ฐ๋ฝ์ ๊ตฌํ (Set if not Exist - key:value๋ฅผ Set ํ ๋. ๊ธฐ์กด์ ๊ฐ์ด ์์ ๋๋ง Set ํ๋ ๋ช ๋ น์ด)
- Setnx๋ Spin Lock๋ฐฉ์์ด๋ฏ๋ก retry ๋ก์ง์ ๊ฐ๋ฐ์๊ฐ ์์ฑํด ์ฃผ์ด์ผ ํฉ๋๋ค.
- Spin Lock ์ด๋, Lock์ ํ๋ํ๋ ค๋ ์ค๋ ๋๊ฐ Lock์ ํ๋ํ ์ ์๋์ง ํ์ธํ๋ฉด์ ๋ฐ๋ณต์ ์ผ๋ก ์๋ํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
๋ฝ์ ํ๋ํ๋ค๋ ๊ฒ์ “๋ฝ์ด ์กด์ฌํ๋์ง ํ์ธํ๋ค”, “์กด์ฌํ์ง ์๋๋ค๋ฉด ๋ฝ์ ํ๋ํ๋ค” ๋ ์ฐ์ฐ์ด atomic ํ๊ฒ ์ด๋ฃจ์ด์ ธ์ผ ํฉ๋๋ค. Redis์ ๊ธฐ๋ฐํ Lettuce ๋ฐฉ์์ “๊ฐ์ด ์กด์ฌํ์ง ์์ผ๋ฉด ์ธํ ํ๋ค”๋ผ๋ setnx(set when not exists) ๋ช ๋ น์ด๋ฅผ ์ง์ํฉ๋๋ค. ์ด setnx๋ฅผ ์ด์ฉํ์ฌ ๋ ๋์ค์ ๊ฐ์ด ์กด์ฌํ์ง ์์ผ๋ฉด ์ธํ ํ๊ฒ ํ๊ณ , ๊ฐ์ด ์ธํ ๋์๋์ง ์ฌ๋ถ๋ฅผ ๋ฆฌํด ๊ฐ์ผ๋ก ๋ฐ์ ๋ฝ์ ํ๋ํ๋ ๋ฐ์ ์ฑ๊ณตํ๋์ง ํ์ธํฉ๋๋ค.
Named Lock๊ณผ ๋ฌ๋ฆฌ Redis๋ฅผ ์ฌ์ฉํ๋ฉด ํธ๋์ญ์ ์ ๋ฐ๋ผ ๋์๋๋ ํ์ฌ ํธ๋์ญ์ ํ ์ธ์ ๊ด๋ฆฌ๋ฅผ ํ์ง ์์๋ ๋๋ฏ๋ก ๊ตฌํ์ด ํธ๋ฆฌํฉ๋๋ค. ์์ ๋งํ๋ฏ Spin Lock ๋ฐฉ์์ด๋ฏ๋ก Sleep Time์ด ์ ์์๋ก Redis์ ๋ถํ๋ฅผ ์ค ์ ์์ด์ thread busy waiting์ ์์ฒญ ๊ฐ์ ์๊ฐ์ ์ ์ ํ ์ฃผ์ด์ผ ํฉ๋๋ค.
์ฝ๋
dependencies {
//redis ์์กด์ฑ ์ถ๊ฐ
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}
@Component
@RequiredArgsConstructor
public class RedisLockRepository {
private final RedisTemplate<String, String> redisTemplate;
public Boolean lock(final Long key) {
return redisTemplate
.opsForValue()
.setIfAbsent(generateKey(key), "lock", Duration.ofMillis(3_000));
}
public Boolean unlock(final Long key) {
return redisTemplate.delete(generateKey(key));
}
private String generateKey(final Long key) {
return key.toString();
}
}
@Component
@RequiredArgsConstructor
public class LettuceLockStockFacade {
private final RedisLockRepository redisLockRepository;
private final StockService stockService;
/**
* ๊ตฌํ์ด ๊ฐ๋จํ๋ค.
* Spring Data Redis๋ฅผ ์ด์ฉํ๋ฉด Lettuce๊ฐ ๊ธฐ๋ณธ์ด๊ธฐ ๋๋ฌธ์ ๋ณ๋์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ง ์์๋ ๋๋ค.
* Spin Lock ๋ฐฉ์์ด๊ธฐ ๋๋ฌธ์ ๋์์ ๋ง์ ์ค๋ ๋๊ฐ Lock ํ๋ ๋๊ธฐ ์ํ๋ผ๋ฉด Redis์ ๋ถํ๊ฐ ๊ฐ ์ ์๋ค.
* ์ค๋ฌด์์๋ ์ฌ์๋๊ฐ ํ์ํ Lock์ ๊ฒฝ์ฐ์๋ Redission์ ํ์ฉํ๊ณ , ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ์๋ Lettuce์ ํ์ฉํ๋ค.
* ์ฌ์๋๊ฐ ํ์ํ ๊ฒฝ์ฐ?: ์ ์ฐฉ์ 100๋ช
๊น์ง ๋ฌผํ์ ๊ตฌ๋งคํ ์ ์์ ๊ฒฝ์ฐ
* ์ฌ์๋๊ฐ ํ์ํ์ง ์์ ๊ฒฝ์ฐ?: ์ ์ฐฉ์ ํ๋ช
๋ง ๊ฐ๋ฅ, Lock ํ๋ ์ฌ์๋ ํ ํ์๊ฐ ์์
*/
public void decrease(final Long key, final Long quantity) throws InterruptedException {
while (!redisLockRepository.lock(key)) {
Thread.sleep(50);
}
//lock ํ๋ ์ฑ๊ณต์
try{
stockService.decrease(key,quantity);
}finally {
//๋ฝ ํด์
redisLockRepository.unlock(key);
}
}
}
Spin Lock๋ฐฉ์์ผ๋ก Lock ์ป๊ธฐ๋ฅผ ์๋ํ๊ณ , Lock์ ์ป์ ํ, ์ฌ๊ณ ๊ฐ์ ๋น์ฆ๋์ค ๋ก์ง์ ์ฒ๋ฆฌํฉ๋๋ค. ๊ทธ ํ Lock์ ํด์ ํ๋ ๋ฐฉ๋ฒ์ผ๋ก ๊ตฌํํฉ๋๋ค.
@Test
@DisplayName("Lettuce Lock ์ฌ์ฉ")
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(0L, stock.getQuantity());
}
Spin Lock ๋ฐฉ์์๋ ๋ค์๊ณผ ๊ฐ์ ์ฌ๋ฌ ๊ฐ์ง ๋ฌธ์ ์ ์ด ์์ต๋๋ค.(https://hyperconnect.github.io/2019/11/15/redis-distributed-lock-1.html)
1. Lock์ ํ์์์์ด ์ง์ ๋์ด ์์ง ์์ต๋๋ค.
์์ ์ฝ๋์ ๊ฐ์ด ์คํ ๋ฝ์ ๊ตฌํํ์์์ ๋ฝ์ ํ๋ํ์ง ๋ชปํ๋ฉด ๋ฌดํ ๋ฃจํ๋ฅผ ๋๊ฒ ๋ฉ๋๋ค. ๋ง์ฝ ํน์ ํ ์ ํ๋ฆฌ์ผ์ด์ ์์ tryLock์ ์ฑ๊ณตํ๋๋ฐ ๋ถ์ดํ๊ฒ๋ ์ด๋ค ์ค๋ฅ ๋๋ฌธ์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ข ๋ฃ๋์ด๋ฒ๋ฆฌ๋ฉด ์ด๋ป๊ฒ ๋ ๊น์? ๋ค๋ฅธ ๋ชจ๋ ์ ํ๋ฆฌ์ผ์ด์ ๊น์ง ์์ํ ๋ฝ์ ํ๋ํ์ง ๋ชปํ ์ฑ ๋ฝ์ด ํด์ ๋๊ธฐ๋ง์ ๊ธฐ๋ค๋ฆฌ๋ ๋ฌดํ์ ๋๊ธฐ์ํ๊ฐ ๋์ด ์ ์ฒด ์๋น์ค์ ์ฅ์ ๊ฐ ๋ฐ์ํ๊ฒ ๋ ๊ฒ์ ๋๋ค.
๊ทธ๋์ ์ผ๋ฐ์ ์ธ ๋ก์ปฌ ์คํ ๋ฝ๊ณผ๋ ๋ค๋ฅด๊ฒ ์ผ์ ์๊ฐ์ด ์ง๋๋ฉด ๋ฝ์ด ๋ง๋ฃ๋๋๋ก ๊ตฌํํด์ผ ํฉ๋๋ค. ๊ทธ๋ฌ๋ ค๋ฉด expire time์ ์ค์ ํด์ฃผ์ด์ผ ํฉ๋๋ค. ํ์ง๋ง ์์ ์ฝ๋์์๋ “๋ฝ์ ์ฌ์ฉ ์ค์ธ์ง ํ์ธ”, “๋ฝ์ ํ๋” ์ฐ์ฐ์ ํ๋๋ก ๋ฌถ๊ธฐ ์ํด setnx ๋ช ๋ น์ด๋ฅผ ์ฌ์ฉํ์ต๋๋ค. ์ด ๋ช ๋ น์ด๋ expire time์ ์ง์ ํ ์ ์๊ธฐ์ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ๊ฐ ํ๋ญ๋๋ค.
๋ํ ๋ฌดํ์ ์ผ๋ก ๋ฝ์ ํ๋์ ์๋ํ๋ค๋ฉด ๋ฌธ์ ๊ฐ ๋ ์ ์์ต๋๋ค. ๋ง์ฝ ์ฐ์ฐ์ด ์ค๋ ๊ฑธ๋ฆด ๊ฒฝ์ฐ ๋๋ถ๋ถ์ ์ค๋ ๋๊ฐ ๋ฝ์ ๋๊ธฐํ๋ ์ํ๊ฐ ๋์ด ํด๋ผ์ด์ธํธ์ ์๋ตํ๋ ์๋๊ฐ ๋ฆ์ด์ง๊ณ , ๋์์ ๋ ๋์ค์ ์์ฒญ๋ ํธ๋ํฝ์ ๋ณด๋ผ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค. ๊ทธ๋์ ๋ฝ์ ํ๋ํ๋ ์ต๋ ํ์ฉ์๊ฐ์ ์ ํด์ฃผ๊ฑฐ๋, ์ต๋ ํ์ฉ ํ์๋ฅผ ์ ํด์ฃผ๋ ๊ฒ์ด ์ข์ต๋๋ค. ๋ง์ฝ ๋ฝ์ ํ๋ํ๋ ๋ฐ์ ์คํจํ๋ค๋ฉด ์ฐ์ฐ์ ์ํํ ์ ์๋ ์ํ์ด๊ธฐ์ Exception์ ๋์ง๋๋ค.
2. Redis์ ๋ง์ ๋ถํ๋ฅผ ๊ฐํ๊ฒ ๋ฉ๋๋ค.
์์ ์ฝ๋๋ ์คํ ๋ฝ์ ์ฌ์ฉํ์ง๋ง ์ฌ์ค ์คํ ๋ฝ์ ์ฌ์ฉํ๋ฉด ๋ ๋์ค์ ์์ฒญ๋ ๋ถ๋ด์ ์ฃผ๊ฒ ๋ฉ๋๋ค. ์คํ ๋ฝ์ ์ง์์ ์ผ๋ก ๋ฝ์ ํ๋์ ์๋ํ๋ ์์ ์ด๊ธฐ ๋๋ฌธ์ ๋ ๋์ค์ ๊ณ์ ์์ฒญ์ ๋ณด๋ด๊ฒ ๋๊ณ ๋ ๋์ค๋ ์ด๋ฐ ํธ๋ํฝ์ ์ฒ๋ฆฌํ๋๋ผ ๋ถ๋ด์ ๋ฐ๊ฒ ๋ฉ๋๋ค.
์คํ ๋ฝ์ ์ฌ์ฉํ๋ฉด์ ๋ ๋์ค์ ๋ถ๋ด์ ๋ ์ฃผ๊ธฐ ์ํด 50ms๋งํผ sleep ํ๋ฉด์ tryLock์ ์ํํ๋๋ก ํ์ง๋ง, ์ด ๋ํ 50ms๋ง๋ค ๊ณ์ ๋ ๋์ค์ ์์ฒญ์ ๋ณด๋ด๋ ๊ฒ์ด๋ฏ๋ก ์์ ์ด ์ค๋ ๊ฑธ๋ฆด์๋ก, ์์ฒญ ์๊ฐ ๋ง์์๋ก ๋ ํฐ ๋ถํ๋ฅผ ๊ฐํ๊ฒ ๋ฉ๋๋ค.
๋ง์ฝ 300ms๊ฐ ๊ฑธ๋ฆฌ๋ ๋๊ธฐํ๋ ์์ ์ ๋์์ 100๊ฐ์ ์์ฒญ์ด ์๋ค๊ณ ๊ฐ์ ํด ๋ณด๊ฒ ์ต๋๋ค. (๋ถ์ฐ ๋ฝ์ด๋ฏ๋ก ์๋ฒ์ ๋์๋ ๋ฌด๊ดํฉ๋๋ค.)
์ฒ์์ผ๋ก ๋ฝ์ ํ๋ํ๋ ๋ฐ ์ฑ๊ณตํ 1๊ฐ์ ์์ฒญ์ ์ ์ธํ๊ณ , ๋๋จธ์ง 99๊ฐ์ ์์ฒญ์ ์์ ์ด ์๋ฃ๋๋ 300ms ๋์ ๋ฌด๋ ค ๋ ๋์ค์ 594ํ์ ๋ฝ ํ๋ ์์ฒญ์ ํ๊ฒ ๋ฉ๋๋ค. ์ฆ 1์ด ๋์ ์ฝ 2000ํ๋ผ๋ ๋ง์ ์์ฒญ์ ๋ ๋์ค์ ๋ณด๋ด๊ฒ ๋ฉ๋๋ค.
๋ํ ์ผํ์ฑ์ด ์๋๋ผ ๋ชจ๋ ์์ ์ด ์๋ฃ๋ ๋๊น์ง ์ง์์ ์ผ๋ก ๋ ๋์ค์ ๋ถํ๋ฅผ ๊ฐํ๊ธฐ ๋๋ฌธ์ ์์ฒญ์ด ์ง์์ ์ผ๋ก ๋ค์ด์ค๋ ํ๊ฒฝ์ด๋ผ๋ฉด ์ด๋ฌํ ๋นํจ์จ์ฑ์ ๋์ฑ ์ปค์ง๋๋ค.
๋ง์ฝ ๋ ๋์ค์ ๋ถ๋ด์ ๋ ์ฃผ๊ธฐ ์ํด sleep ์๊ฐ์ 300ms๋ก ๋๋ฆฐ๋ค๋ฉด ์ด๋จ๊น์? 50ms๊ฐ ๊ฑธ๋ฆฌ๋ ์์ ์ ์ด ๋๊ธฐํ๋ฅผ ์ ์ฉํ๋ฉด ๋ฝ์ ํ๋ํ์ง ๋ชปํ ๊ฒฝ์ฐ 50ms ๊ฑธ๋ฆฌ๋ ์์ ์ ํ๊ธฐ ์ํด 300ms๋ฅผ ๋๊ธฐํด์ผ ํ๋ ๋ค๋ฅธ ๋นํจ์จ์ ์ธ ์ํฉ์ด ์๊ธฐ๊ฒ ๋ฉ๋๋ค.
2. Redisson
- Pub-sub ๊ธฐ๋ฐ์ผ๋ก Lock ๊ตฌํ ์ ๊ณต
- Pub-Sub ๋ฐฉ์์ด๋, ์ฑ๋์ ํ๋ ๋ง๋ค๊ณ , ๋ฝ์ ์ ์ ์ค์ธ ์ค๋ ๋๊ฐ, Lock์ ํด์ ํ์์, ๋๊ธฐ ์ค์ธ ์ค๋ ๋์๊ฒ ์๋ ค์ฃผ๋ฉด ๋๊ธฐ ์ค์ธ ์ค๋ ๋๊ฐ Lock ์ ์ ๋ฅผ ์๋ํ๋ ๋ฐฉ์์ ๋๋ค.
- ์ด ๋ฐฉ์์, Lettuce์ ๋ค๋ฅด๊ฒ ๋๋ถ๋ถ ๋ณ๋์ Retry ๋ฐฉ์์ ์์ฑํ์ง ์์๋ ๋ฉ๋๋ค.
์ฝ๋
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
// redisson ์์กด์ฑ ์ถ๊ฐ
implementation group: 'org.redisson', name: 'redisson-spring-boot-starter', version: '3.19.0'
}
@Component
@RequiredArgsConstructor
public class RedissonLockStockFacade {
private final RedissonClient redissonClient;
private final StockService stockService;
/**
* Lock ํ๋ ์ฌ์๋๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณตํ๋ค.
* pub-sub ๋ฐฉ์์ผ๋ก ๊ตฌํ์ด ๋์ด์๊ธฐ ๋๋ฌธ์ Lettuce์ ๋น๊ตํ์ ๋ Redis์ ๋ถํ๊ฐ ๋ ๊ฐ๋ค.
* ๋ณ๋์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
* Lock์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฐจ์์์ ์ ๊ณตํด์ฃผ๊ธฐ ๋๋ฌธ์ ์ฌ์ฉ๋ฒ์ ๊ณต๋ถํด์ผ ํ๋ค.
* ์ค๋ฌด์์๋ ์ฌ์๋๊ฐ ํ์ํ Lock์ ๊ฒฝ์ฐ์๋ Redission์ ํ์ฉํ๊ณ , ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ์๋ Lettuce์ ํ์ฉํ๋ค.
* ์ฌ์๋๊ฐ ํ์ํ ๊ฒฝ์ฐ?: ์ ์ฐฉ์ 100๋ช
๊น์ง ๋ฌผํ์ ๊ตฌ๋งคํ ์ ์์ ๊ฒฝ์ฐ
* ์ฌ์๋๊ฐ ํ์ํ์ง ์์ ๊ฒฝ์ฐ?: ์ ์ฐฉ์ ํ๋ช
๋ง ๊ฐ๋ฅ, Lock ํ๋ ์ฌ์๋ ํ ํ์๊ฐ ์์
*/
public void decrease(final Long key, final Long quantity) {
RLock lock = redissonClient.getLock(key.toString());
try {
// ํ๋์๋ ์๊ฐ, ๋ฝ ์ ์ ์๊ฐ
boolean available = lock.tryLock(5, 1, TimeUnit.SECONDS);
if (!available) {
System.out.println("lock ํ๋ ์คํจ");
return;
}
stockService.decrease(key, quantity);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
lock.unlock();
}
}
}
Redisson์ Lettuce์ ๋ฌ๋ฆฌ ๋ณ๋์ ์ธํฐํ์ด์ค์ด๊ธฐ ๋๋ฌธ์ gradle ์์กด ํจํค์ง ์ค์น ๋ฐ ๋ณ๋์ Facade ์์ฑ์ด ํ์ํฉ๋๋ค.
@Test
@DisplayName("Redisson Lock ์ฌ์ฉ")
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 {
redissonLockStockFacade.decrease(1L, 1L);
} finally {
latch.countDown();
}
});
}
latch.await();
Stock stock = stockRepository.findById(1L).orElseThrow();
assertEquals(0L, stock.getQuantity());
}
์ด์ ๋ ์คํ์์ค ๋ ๋์ค ํด๋ผ์ด์ธํธ์ธ Redisson์ด ๋ถ์ฐ ๋ฝ์ ์ด๋ป๊ฒ ์ค๊ณํ๋์ง ์๊ฐํ๋ฉฐ ์ด๋ป๊ฒ ์ด ๋ฌธ์ ์ ๋ค์ ํด๊ฒฐํ๊ณ , ๋ณด๋ค ๋น ๋ฅธ ์ฑ๋ฅ์ ๋ด๊ฒ ๋์๋์ง ์ค๋ช ํ๊ฒ ์ต๋๋ค.
Redisson์ Lettuce์ ๋น์ทํ๊ฒ Netty๋ฅผ ์ฌ์ฉํ์ฌ non-blocking I/O๋ฅผ ์ฌ์ฉํฉ๋๋ค. Redisson์ ํน์ดํ ์ ์ ์ง์ ๋ ๋์ค์ ๋ช ๋ น์ด๋ฅผ ์ ๊ณตํ์ง ์๊ณ , Bucket์ด๋ Map๊ฐ์ ์๋ฃ๊ตฌ์กฐ๋ Lock ๊ฐ์ ํน์ ํ ๊ตฌํ์ฒด์ ํํ๋ก ์ ๊ณตํ๋ค๋ ๊ฒ์ ๋๋ค.
1. Lock์ ํ์์์์ด ๊ตฌํ๋์ด ์์ต๋๋ค.
Redisson์ tryLock ๋ฉ์๋์ ํ์์์์ ๋ช ์ํ๋๋ก ๋์ด์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ ๋ฝ ํ๋์ ๋๊ธฐํ ํ์์์์ด๊ณ , ๋ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ ๋ฝ์ด ๋ง๋ฃ๋๋ ์๊ฐ์ ๋๋ค.
์ฒซ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋งํผ์ ์๊ฐ์ด ์ง๋๋ฉด false๊ฐ ๋ฐํ๋๋ฉฐ ๋ฝ ํ๋์ ์คํจํ๋ค๊ณ ์๋ ค์ค๋๋ค. ๊ทธ๋ฆฌ๊ณ ๋ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋งํผ์ ์๊ฐ์ด ์ง๋๋ฉด ๋ฝ์ด ๋ง๋ฃ๋์ด ์ฌ๋ผ์ง๊ธฐ ๋๋ฌธ์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ฝ์ ํด์ ํด์ฃผ์ง ์๋๋ผ๋ ๋ค๋ฅธ ์ค๋ ๋ ํน์ ์ดํ๋ฆฌ์ผ์ด์ ์์ ๋ฝ์ ํ๋ํ ์ ์์ต๋๋ค.
์ด๋ก ์ธํด ๋ฝ์ด ํด์ ๋์ง ์๋ ๋ฌธ์ ๋ก ๋ฌดํ ๋ฃจํ์ ๋น ์ง ์ํ์ด ์ฌ๋ผ์ง๋๋ค.
// RedissonLock์ tryLock ๋ฉ์๋ ์๊ทธ๋์ณ
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException
2. ์คํ ๋ฝ์ ์ฌ์ฉํ์ง ์์ต๋๋ค.
Redisson์ ๊ธฐ๋ณธ์ ์ผ๋ก ์คํ ๋ฝ์ ์ฌ์ฉํ์ง ์๊ธฐ ๋๋ฌธ์ ๋ ๋์ค์ ๋ถ๋ด์ ์ฃผ์ง ์์ต๋๋ค. ๊ทธ๋ผ ์ด๋ป๊ฒ ๋ฝ์ ํ๋ ๊ฐ๋ฅ์ฌ๋ถ๋ฅผ ํ๋จํ ๊น์?
Redisson์ pubsub ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ฌ ์คํ ๋ฝ์ด ๋ ๋์ค์ ์ฃผ๋ ์์ฒญ๋ ํธ๋ํฝ์ ์ค์์ต๋๋ค. ๋ฝ์ด ํด์ ๋ ๋๋ง๋ค subscribe ํ๋ ํด๋ผ์ด์ธํธ๋ค์๊ฒ “๋๋ค๋ ์ด์ ๋ฝ ํ๋์ ์๋ํด๋ ๋๋ค”๋ผ๋ ์๋ฆผ์ ์ฃผ์ด์ ์ผ์ผ์ด ๋ ๋์ค์ ์์ฒญ์ ๋ณด๋ด ๋ฝ์ ํ๋๊ฐ๋ฅ์ฌ๋ถ๋ฅผ ์ฒดํฌํ์ง ์์๋ ๋๋๋ก ๊ฐ์ ํ์ต๋๋ค.
๋ํ Redisson์ ์ต๋ํ ๋ ๋์ค์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ถํ๋ฅผ ์ฃผ์ง ์๋๋ก ์ ๊ฒฝ ์ด ๋ชจ์ต์ด ๋ณด์ ๋๋ค. ์๋๋ Redisson์ Lock ํ๋ ํ๋ก์ธ์ค์ ๋๋ค.
- ๋๊ธฐ ์๋tryLock ์คํผ๋ ์ด์ ์ ํ์ฌ ๋ฝ ํ๋์ ์ฑ๊ณตํ๋ฉด true๋ฅผ ๋ฐํํฉ๋๋ค. ์ด๋ ๊ฒฝํฉ์ด ์์ ๋ ์๋ฌด๋ฐ ์ค๋ฒํค๋ ์์ด ๋ฝ์ ํ๋ํ ์ ์๋๋ก ํด์ค๋๋ค.
- pubsub์ ์ด์ฉํ์ฌ ๋ฉ์์ง๊ฐ ์ฌ ๋๊น์ง ๋๊ธฐํ๋ค๊ฐ ๋ฝ์ด ํด์ ๋์๋ค๋ ๋ฉ์ธ์ง๊ฐ ์ค๋ฉด ๋๊ธฐ๋ฅผ ํ๊ณ ๋ค์ ๋ฝ ํ๋์ ์๋ํฉ๋๋ค. ๋ฝ ํ๋์ ์คํจํ๋ฉด ๋ค์ ๋ฝ ํด์ ๋ฉ์์ง๋ฅผ ๊ธฐ๋ค๋ฆฝ๋๋ค. ์ด ํ๋ก์ธ์ค๋ฅผ ํ์์์ ์๊น์ง ๋ฐ๋ณตํฉ๋๋ค.
- ํ์์์์ด ์ง๋๋ฉด ์ต์ข ์ ์ผ๋ก false๋ฅผ ๋ฐํํ๊ณ ๋ฝ ํ๋์ ์คํจํ์์ ์๋ฆฝ๋๋ค. ๋๊ธฐ๊ฐ ํ๋ฆด ๋ ํ์์์ ์ฌ๋ถ๋ฅผ ์ฒดํฌํ๋ฏ๋ก ํ์์์์ด ๋ฐ์ํ๋ ์๊ฐ์ ํ๋ผ๋ฏธํฐ๋ก ๋๊ธด ํ์์์์๊ฐ๊ณผ ์ฝ๊ฐ์ ์ฐจ์ด๊ฐ ์์ ์ ์์ต๋๋ค.
์ ๋ฆฌ
Lettuce
- ๊ตฌํ์ด ๊ฐ๋จํ๋ค
- Spring data redis๋ฅผ ์ด์ฉํ๋ฉด lettuce๊ฐ ๊ธฐ๋ณธ์ด๊ธฐ ๋๋ฌธ์ ๋ณ๋์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ง ์์๋ ๋๋ค.
- Spin Lock ๋ฐฉ์์ด๊ธฐ ๋๋ฌธ์ ๋์์ ๋ง์ ์ค๋ ๋๊ฐ lock ํ๋ ๋๊ธฐ ์ํ๋ผ๋ฉด redis์ ๋ถํ๊ฐ ๊ฐ ์ ์๋ค.
Redisson
- ๋ฝ ํ๋ ์ฌ์๋๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณตํ๋ค.
- pub-sub ๋ฐฉ์์ผ๋ก ๊ตฌํ์ด ๋์ด์๊ธฐ ๋๋ฌธ์ lettuce์ ๋น๊ตํ์ ๋ redis์ ๋ถํ๊ฐ ๋ ๊ฐ๋ค.
- ๋ณ๋์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
- lock์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฐจ์์์ ์ ๊ณตํด์ฃผ๊ธฐ ๋๋ฌธ์ ์ฌ์ฉ๋ฒ์ ๊ณต๋ถํด์ผ ํ๋ค.
์ฐธ๊ณ
- https://www.inflearn.com/course/๋์์ฑ์ด์-์ฌ๊ณ ์์คํ
- https://hyperconnect.github.io/2019/11/15/redis-distributed-lock-1.html
- https://jypthemiracle.medium.com/weekly-java-%EA%B0%84%EB%8B%A8%ED%95%9C-%EC%9E%AC%EA%B3%A0-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9C%BC%EB%A1%9C-%ED%95%99%EC%8A%B5%ED%95%98%EB%8A%94-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-9daa85155f66
- https://velog.io/@minnseong/Spring-synchronized%EB%A1%9C-%ED%95%B4%EA%B2%B0%ED%95%A0-%EC%88%98-%EC%9E%88%EC%9D%84%EA%B9%8C
- https://thalals.tistory.com/370
'BackEnd๐ฑ > Java' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Java์ ๊ธฐ๋ณธ ํจ์ํ ์ธํฐํ์ด์ค (0) | 2023.03.20 |
---|---|
Java์ ์์ธ ์์ฑ ๋น์ฉ์ ๋น์ธ๋ค (0) | 2023.03.10 |
๋๋ฏธํฐ ๋ฒ์น (Law of Demeter)์ด๋? (2) | 2023.02.01 |
ํด์ ์๋ฃ๊ตฌ์กฐ์, ํด์ ์ถฉ๋ ๊ทธ๋ฆฌ๊ณ Java์ HashMap ๋์ ๋ฐฉ๋ฒ (1) | 2022.12.30 |
[Java] Checked Exception๊ณผ UnChecked Exception (2) | 2022.12.18 |
[Java] ์๋ฐ์์ '+' ์ฐ์ฐ์ ํตํ ๋ฌธ์์ด ํฉ์น๊ธฐ๋ฅผ ์ง์ํ๋ผ (1) | 2022.07.19 |
๋๊ธ