대규모 분산 처리를 하는 과정에 대해 관심이 생겨서 찾아보는 도충 메시지 큐 방식이 있다는 것을 알았다.
많이 들어본 기술인데 잘 모르겠다는 생각이 들어서 한번 찾아보면서 쭉 정리를 해본다.
메시지 큐 방식이란?
'큐'라는 말이 들어가는 만큼 작업을 큐에 넣은 이후, 작업 로직을 큐에서 하나씩 빼내어 처리하는 작업이다.
보통 우리가 restAPI를 활용하고 myBatis 든 JPA든 활용한다고 가정했을 때
controller -> service -> dao 이렇게 하나의 스텝으로 묶인다. 하지만, 메시지 큐로 처리하게 되면 producer, cunsumer 즉 두개의 분기로 나누어진다.
그래서 만약 "재고 확인" 이라는 작업을 진행한다고 가정하면,
1. controller("재고확인") -> service("재고확인") 이렇게 들어가게 된다.
2. 그리고 해당 service에서 "재고확인" 이라는 메세지를 메세지큐를 생성하여 넣어준다. '큐' 상태임으로 first in first out이다.
3. 위 과정까지가 producer
4. 이후 해당 큐에 작업이 들어갔는지 감지하는 listener 가 있다.
5. 해당 리스너에서 "재고확인" 으로 큐에 요청이 왔는지 감지하게 되고
6. 감지하게 되면 service를 호출하면서 재고확인 작업쿼리를 실행하게 된다.
가장 간단한 예시로 들어보았다.
예시코드는 다음과 같다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/inventory")
public class InventoryController {
@Autowired
private InventoryService inventoryService;
// 재고 확인 요청 API
@PostMapping("/check")
public String checkInventory(@RequestParam("itemId") String itemId) {
// 재고 확인 요청을 메시지 큐로 전달
inventoryService.requestInventoryCheck(itemId);
return "재고 확인 요청이 접수되었습니다.";
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Service;
@Service
public class InventoryService {
@Autowired
private JmsTemplate jmsTemplate;
// 메시지 큐로 재고 확인 요청을 전달하는 메서드
public void requestInventoryCheck(String itemId) {
// 메시지 큐로 아이템 ID를 전송
jmsTemplate.convertAndSend("INVENTORY_QUEUE", itemId);
}
// 실제 재고 확인을 처리하는 메서드
public void processInventoryCheck(String itemId) {
// 데이터베이스에서 해당 아이템의 재고를 확인
int stock = inventoryDao.getInventory(itemId);
System.out.println("Item ID: " + itemId + "의 재고: " + stock);
}
}
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface InventoryDao {
// 아이템 ID로 재고를 조회하는 SQL
@Select("SELECT stock FROM inventory WHERE item_id = #{itemId}")
int getInventory(String itemId);
}
consumer 부분
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component
public class InventoryConsumer {
@Autowired
private InventoryService inventoryService;
// 메시지 큐에서 재고 확인 요청을 처리하는 리스너
@JmsListener(destination = "INVENTORY_QUEUE")
public void onMessage(String itemId) {
// 메시지 큐에서 아이템 ID를 받아 재고 확인을 처리
inventoryService.processInventoryCheck(itemId);
}
}
위 코드에서 메시지 큐를 계속 감지하고 있음.
그러면 왜 이렇게 만든 것일까?
예를 들어 사용자가 배달어플에서 주문 버튼을 눌렀을때,
'주문이 완료되었습니다' 알림이 바로 노출되지 1분 있다가 노출되지는 않는다.
이런 경우에 사용한다. 먼저 사용자에게 메세지를 노출해주고 그 뒤 서버 작업을 진행 시키는 것이다.
그리고 메시지 큐 방식을 사용함으로 써 중간에 서버를 하나 더 두어서 중개 서버로 활용이 가능하다. 이러면 훨씬 서버 부하가 줄어든다.
결합도도 낮아진다.
즉 producer 서버, 메세지큐 서버, consumer 서버. 이렇게 처리할 수 있다.
이 방식에서 메세지 큐 서버로 많이 사용하는 환경이 래빗엠큐, 카프카등이 있다.
시스템 장애 시 메세지들이 큐에 남아있기에 작업을 보존할 수 있다.
그리고 대표적으로 사용하는 예시가 스트리밍인데 왜 스트리밍에서 메세지큐 방식을 사용할까?
스트리밍 데이터는 한번에 받을 수 없는 용량이고,
분산하여 조각조각 가져와서 데이터를 받을때, 순서가 중요하다.
순서를 보장해주는 메세지 큐 방식이 적절함.
결국 내가 생각한 쓰레드와의 차이점은 다음과 같다.
1. 동기화 문제가 발생하지 않는다. 왜냐하면 큐에 넣고 순차적으로 처리하기 때문.
2. 메세지 서버는 producer 서버, 메시지 서버, consumer서버 이렇게 분산처리가 가능하지만, 쓰레드는 단일서버에서만 처리가능하다.
여기서 추가적으로 고민해보아야 할 것.
rabbit MQ vs Redis
이 두가지의 차이점과 무엇을 선택할 것인지...??????
'CS' 카테고리의 다른 글
자바 메시징 서비스 (JMS) 에 관하여 (1) | 2025.01.02 |
---|---|
인덱스를 사용할 때 주의 할 점, 인덱스 탐구 (0) | 2024.10.28 |
로그인 기능 작동 시 쿠키와 세션은 어떻게 작동하며 JWT 토큰은 왜 사용하는가? (4) | 2024.10.20 |
자바 클래스와 객체의 차이. 생성자가 왜 필요할까? (1) | 2024.10.13 |
쓰레드와 프로세스에 대한 자세한 설명 (0) | 2024.10.02 |