우선 코드를 변경하기 전에 전체적인 적용할려는 시스템의 로직을 파악하자
초록을 프로듀서 연파랑을 컨슈머로 하면 되지 않을까?
전체적으로 적용을 위한 구조를 작성해 보았다.
현재는 로컬에서 테스트를 하기에 localhost로 컨슈머와 프로듀서를 설정하였습니다.
하지만 추후에 서버를 나누게 된다면 설정 파일도 변경할것입니다.
먼저 클라이언트에서 요청을 보내는 것을 kafka 서버로 보낸는 것부터 구현하겠습니다.
원본 코드
@GetMapping("/admin/users/v2")
/**
* @RequestParam(defaultValue = "0") int page : page 파라미터가 없으면 0으로 설정
*/
public String adminViewV2(@RequestParam(defaultValue = "0") int page, Model model, @RequestParam(defaultValue = "") String userName
,@RequestParam(defaultValue = "") String userRole, @RequestParam(defaultValue = "5") int pageSize ) {
Page<User> users = userService.findUsersByUsernameAndRoleV1(userName, userRole, PageRequest.of(page, pageSize));
List<UserResponseDto> userResponseDtos = users.stream().map(UserResponseDto::new).collect(Collectors.toList());
model.addAttribute("currentPage", page); // 현재 페이지 번호 추가
model.addAttribute("totalPages", users.getTotalPages());
model.addAttribute("users", userResponseDtos);
return "adminV2";
}
빨간 상자는 Back의 서버에서 관리하고 노란 상자는 front의 컨슈머에서 던져주는 형태로 하면 어떨까?
bookDonationEventApplyOutput
토픽으로부터 메시지를 받아 처리하는 도중 List<UserResponseDto>
타입으로의 변환 과정에서 ListenerExecutionFailedException
오류가 발생처음 신청한 딱 하나의 책 만이 신청에 성공하고 나머지는 신청이 거부되었습니다.
동시에 100명 정도가 신청하였지만 가장 먼저 신청한 한 사람만이 신청을 성공하였습니다.
-> 즉 동시성 문제가 카프카를 통해 구조적으로 해결 되었습니다.
초기에 Kafka를 도입하면서 그 구조와 특성 덕분에 동시성 문제를 해결할 수 있을 것으로 기대했습니다.
Kafka는 메시지를 파티션 단위로 관리하고, 각 파티션은 순서가 있는 로그로 구성되어 있어 단일 파티션 내에서는 프로듀서와 컨슈머 사이에 동시성 문제가 거의 발생하지 않습니다.
그러나 책나눔 서비스에 마이크로서비스 아키텍처를 적용하면서 다수의 파티션을 관리해야 하는 상황이 되었고, 여기서 파티션을 병렬로 처리할 필요성이 생기면서 기존에 생각했던 구조적인 해결책 만으로는 동시성 문제를 해결할 수 없음을 인식하게 되었습니다.
각 파티션에서는 메시지 처리가 독립적으로 이루어지지만, 전체 시스템에서는 다수의 작업이 동시에 발생하므로 동시성 관련 문제가 여전히 발생할 수 있습니다.
이를 해결하기 위해 정보를 수집하는 과정 내에서 분산 락의 개념을 알게 되었고, redis나 ZooKeeper와 같은 분산 락 서비스를 통해 리소스 접근을 제어하면 동시성 문제를 해결할 수 있을 것으로 보입니다.
특히 Redis를 활용해 분산 락을 구현함으로써 여러 컨슈머가 동일한 리소스에 접근할 때 한 번에 한 컨슈머만이 리소스를 사용할 수 있도록 제어할 계획입니다.
이와 같은 방법을 통해 Kafka의 분산 처리 능력과 분산 락을 조합함으로써 데이터베이스 락에서 발생할 수 있는 데드락을 줄이고, 동시성 문제를 효과적으로 해결할 방향으로 진행해야 될 것 같다는 결론에 도달하게 되었습니다.