2편의 내용은 이론적으로 Redis의 동시성 문제를 해결하는 방법을 다루었습니다. 이번에는 Lua script가 실제로 잘 작동하는지 확인해보겠습니다.
INCR
을 사용하면 더 간단하게 구현할 수 있습니다. 하지만 동시성 문제를 발생시키기 위해서 GET SET
을 사용하겠습니다.
요구사항
간략한 요구사항은 다음과 같습니다.
- 동일한 주문 번호가 없음
- 주문 번호는 주문 마다 1씩 증가
예시 코드
불필요한 부분은 생략하고 중요한 부분만 살펴보겠습니다. order
엔티티 입니다.
1 |
|
기본 redis
1 | public Long getSet() { |
orderNumber
를 가져와서 1을 증가시키고 다시 저장하는 코드입니다.
테스트
100개의 스레드가 동시에
getSet
을 호출합니다.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void redis_GET_SET_thread_test() throws InterruptedException {
ConcurrentHashMap<Long, Long> orderNumbers = new ConcurrentHashMap<Long, Long>();
ExecutorService executorService = Executors.newFixedThreadPool(100);
CountDownLatch countDownLatch = new CountDownLatch(100);
for (int i = 0; i < 100; i++) {
executorService.execute(() -> {
try {
orderNumbers.put(raceService.getSet(), 0L);
} finally {
countDownLatch.countDown();
}
});
}
countDownLatch.await();
assertEquals(100, orderNumbers.size());
}
여러 스레드일 때는 중복된 주문 번호가 발생하는 것을 확인할 수 있습니다.
- 싱글 스레드 결과
1
2
3
4
5
6
7
8
9
10
11
void redis_GET_SET_single_test(){
ConcurrentHashMap<Long, Long> orderNumbers = new ConcurrentHashMap<Long, Long>();
for (int i = 0; i < 100; i++) {
orderNumbers.put(raceService.getSet(), 0L);
}
assertEquals(100, orderNumbers.size());
}
중복 주문 번호가 발생하지 않습니다.
Lua script
1 | public Long getSetWithLuaScript() { |
테스트
1 |
|
루아 스크립트를 사용하면 멀티 스레드 환경에서도 중복 주문 번호가 발생하지 않습니다.
번외
INCR
명령어를 사용하면 동시성을 고려하지 않고 더 간단하게 구현할 수 있습니다.1
2
3
4
5
6public Long incr() {
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
Long orderNumber = valueOperations.increment("orderNumber", 1);
Order order = new Order(orderNumber);
return orderRepository.save(order).getOrderNumber();
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void redis_INCR_test() throws InterruptedException {
ConcurrentHashMap<Long, Long> orderNumbers = new ConcurrentHashMap<Long, Long>();
ExecutorService executorService = Executors.newFixedThreadPool(100);
CountDownLatch countDownLatch = new CountDownLatch(100);
for (int i = 0; i < 100; i++) {
executorService.execute(() -> {
try {
orderNumbers.put(raceService.incr(), 0L);
} finally {
countDownLatch.countDown();
}
});
}
countDownLatch.await();
assertEquals(100, orderNumbers.size());
}