한 APi server에 많은 유저가 읽기 요청을 보내고, 한 유저가 아주 큰 동영상을 업로드(쓰기)한다고 가정하면 이러한 요청들에 대해 Api 서버가 대응을 하느라 나머지 조회 요청을 잘 처리하지 못할 것
-> 이를 해결 하기 위해 upload server
, api server
처럼 upload에 대한 별도의 모듈을 만들어서 upload server가 이 일을 할수 있게 처리
-> 조회 요청은 api server가 처리(항목에 따라 나누기)
MSA에서는 역할(용도)에 맞게 server 군들을 나눔
upload server 같은 경우 요청 자체는 잘읽어도, 무거운 처리만 하기에 응답이 느려짐
-> 이때 관심사는 파일 처리가 언제 끝날지
-> upload server에게 요청 다 처리했니? 계속 물어보는 것이 폴링
-> 따로 물어 보지 않아도 요청에 대한 이벤트가 오는 것은(ex : 배달원이 음식을 픽업했습니다, 음식이 거의 도착했습니다) Callback event
polling과 callback event는 두 가지 다른 방법으로 일어나는 이벤트를 처리하는 방식
Polling은 일정 시간 간격을 가지고 일어나는 이벤트를 검사하는 방법이고, callback event는 이벤트가 일어나면 정해진 콜백 함수를 실행하는 방법
public enum EventType {
COMPLETE, ERROR
}
@Getter
@Builder
public class FileEvent {
private String eventId;
private String type;
private Map<String, Object> data;
public static FileEvent toCompleteEvent(Map data){
return FileEvent.builder()
.eventId(UUID.randomUUID().toString())
.type(EventType.COMPLETE.name())
.data(data)
.build();
}
public static FileEvent toErrorEvent(Map data){
return FileEvent.builder()
.eventId(UUID.randomUUID().toString())
.type(EventType.ERROR.name())
.data(data)
.build();
}
}
@Slf4j
@Component
public class FileEventListener {
@EventListener // Event Listener를 통해 파일 처리가 완료된 시점에 데이터를 받아 다른 api server에 전달가능
// 또는 다른 Db server에 insert
public void onfileEventHandler(FileEvent fileEvent){
log.info("file event receive type: {} data: {} ", fileEvent.getType(), fileEvent.getData());
//// TODO: 2022/12/12
if(fileEvent.getType().equals("COMPLETE")){
// 사용자에게 파일 업로드 완료 메세지를 전송한다.
}
}
}
@Component
public class FileEventPublisher {
@Autowired
private ApplicationEventPublisher applicationEventPublisher; // 이벤트 전달
public void notifyComplete(FileEvent fileEvent){
applicationEventPublisher.publishEvent(fileEvent); // 이때 Listener가 감지
}
public void notifyError(FileEvent fileEvent){
applicationEventPublisher.publishEvent(fileEvent);
}
}
@RestController
public class FileController {
@Autowired
FileService fileService;
@GetMapping("/upload/Image")
public ResponseEntity fileupload(){
// 원래 라면 파라미터를 통해 데이터 받겠지
Map<String, Object> data = new HashMap<>();
data.put("userId", "홍길동");
data.put("type", "webp");
data.put("FileSize", "5");
fileService.fileUpload(data);
return ResponseEntity.ok("success");
}
}
@Slf4j
@Service
public class FileService {
@Autowired
FileEventPublisher fileEventPublisher;
public void fileUpload(Map<String, Object> data){
try{
log.info("파일 복사 완료");
log.info("DB 파일 메타 정보 저장 완료");
FileEvent completeEvent = FileEvent.toCompleteEvent(data);
fileEventPublisher.notifyComplete(completeEvent);
}catch(Exception e){
log.error("file upload fail", e);
FileEvent.toErrorEvent(data);
FileEvent errorEvent = FileEvent.toCompleteEvent(data);
fileEventPublisher.notifyError(errorEvent);
}
}
}
위 코드는 Callback Event 방식
메세지큐
란 프로세스 또는 프로그램 간에 데이터를 교환할때 사용하는 통신 방법 중 하나로, 메세지 지향 미들웨어(MOM)를 구현한 시스템
MOM : 비동기 메세지를 사용하는 응용 프로그램들 사이에서 데이터를 송수신
-> 여기서 메세지란 요청,응답,오류메세지, 혹은 단순한 정보 등의 작은 데이터
메세지큐란 메세지를 임시로 저장하는 간단한 버퍼와 비슷
메세지 전송 시 생산자(Producer)로 취급되는 컴포넌트가 메세지를 메세지 큐에 추가하면, 해당 메세지는 소비자(Consumer)로 취급되는 또 다른 컴포넌트가 메세지를 검색하고 이를 사용해 어떤 작업을 수행할때까지 메세지 큐에 저장됨
메세지큐는 Consumer가 실제로 메세지를 어느 시점에 가져가서 처리하는 지느 보장하지 않음
-> 언젠가는 큐에 넣어둔 메세지가 소비되어 처리될것이라 믿음
-> 이러한 비동기적 특성 때문에 이는 실패하면 치명적인 핵심 작업(당장 실행해서 값을 얻어야하는 서비스들)보다는 부가적인 기능에 사용하는 것이 적합
예를 들면
1) 이메일보내기
2) 블로그에 이미지를 올려 포스팅할때 사진을 최적화시키는 서비스
3) 대용량 배치 작업
4) 채팅 서비스
다 당장 실행되어야 하는 것은 아님
-> 조금의 시간이 걸려도 사용자들에게 큰 영향을 미치지 않는다
중간에 큐를 두고 데이터를 처리해서 많은 데이터 통신으로 병목현상이 생기거나 서버의 성능이 저하되는 것을 막기 위함
-> 미들웨어에 부가적인 서비스를 위임하여 순차적으로 처리되게끔 한다
서버가 한대일 경우에는 위 코드 예제와 비슷하게
서버가 여러대 인 경우에는 별도의 캐시 서버를 둔 것처럼 메세지 큐도 별도의 서버를 두게 됨
FileEvent 객체 같은 것을 Queue 에다가 저장(DB의 테이블 같은 느낌)
queue들이 Broker라는 서버에 적재되어 있음
분산환경에서 메세지를 효율적으로 처리하기위해 메세지 큐 사용