Spring Cloud with Apache Kafka - 1

최혜성·2024년 1월 17일
0

msa

목록 보기
7/7

스프링은 이제 마이크로서비스가 지배한다.

첫글은 Spring Cloud를 이용한 MSA (Micro Service Architecture)를 구현해보고자 한다.

갑자기 왜?

원래 해보고 싶었는데, 졸프 팀 선정에서 빠꾸먹어서 혼자라도 해보고 싶어서 진행..

개론

기존 서비스는 2가지로 나눌 수 있다.

  • 모놀리식
  • 마이크로 서비스

모놀리식은 그냥 레거시 시스템이다. 아무리 모듈화를 잘해놓고 패키지를 잘 나눠놔도 하나의 jar안에서 모든 기능을 다 처리하고 작동한다.

이는 어느 기능을 수정할땐 하면 어김없이 다른 기능에서 에러가 터지니 수정하다 시간 다 날려먹기 좋다.

물론 SOLID원칙을 준수하고, TDD를 지켜 테스트를 다 작성해두고 하면 이 문제는 줄어들지만
아예 밑바닥 설계부터 변경되는 경우에는 답이 없었다.

만약 Response가 반환해야 하는 데이터가 1개 더 추가된다고 하면 이 Response를 파싱하는 클래스 전부가 영향을 받을 수 있는게 아닐까
-> 물론 Builder나 그런걸로 해결할 수 있겠지만, 이런걸로도 안되는 문제는 꼭 1개씩은 있다.

마이크로 서비스

그냥 작은 서비스끼리 서로 상호작용을 하며 데이터를 주고 받으며 돌아간다.
만약 유저 서비스에서 오타내서 서버가 죽더라도 다른 서비스는 살아 있기 때문에 메인 서비스가 다운되는 일은 잘 없다. 안돌아갈뿐

모놀리식 방식에서는 Critical한 Error 발생시 바로 서비스 전체가 죽기 때문에 규모가 큰 서비스에서는 신중하게 개발해야 한다.

그래서?

기존 WebMVC기반으로 개발을 진행한다.
이때 추가로 로드 밸런싱, 서킷 브레이커등을 지원하는 Spring Cloud를 곁들인다.
이후, 서버간 통신을 위한 Message Queue인 Apache Kafka를 사용하기로 했다.

근데 진짜 대량의 서비스가 아니면 굳이 마이크로 서비스를 구현할 소요는 없을듯 하다. (개인적)
이는 Reactive하게 모든 요청을 처리하는 Spring WebFlux를 이용해 해결할 수 있는 부분이라 각 팀의 목표나 개발 역량에 따라 선택하는게 좋을것 같다.

너는 왜 WebFlux안쓰고 이걸로 하냐

해보고 싶었음

  • JDD 어록 모음
    • 비동기 흐름 내에서 동기함수를 몰래 써도 된다. 나중에 병목 찾는 작업 + 비동기로 개선하는 추가 작업 기한까지 함께 받을 수 있다.
    • 기술 스택을 공부하지 말고, 해당 기술 스택의 단점 리스트만 공부해라. 누가 해당 기술 스택을 물어보면, 단점을 들먹이자

WebFlux의 경우 Netty위에서 작동되므로 Tomcat과 달리 적은 쓰레드(보통 8개)로 돌아간다.
단, 비동기 방식으로 처리되기 때문에 cpu에 가해지는 부하가 적은 방식이라 기존 WebMVC보다 더 나은 성능을 보여준다.

하지만 다음과 같은 점에서 도입하기 망설어졌다.

  • JPA 미지원
  • 모든 로직을 Reactive하게

JPA가 WebFlux를 미지원한다. 이는 JDBC가 동기적으로 돌아가기 때문에 비동기 위주의 WebFlux와는 맞지 않다는 점이다. 물론 Hibernate Reactive로 개발된 라이브러리가 있긴 하다.

또한 모든 로직을 비동기화 처리해야 한다는 점이였다.
만약 비동기 함수에서 해당 쓰레드를 블로킹하게 된다면 다음으로 처리될 이벤트를 처리하지 못하게 되고 응답시간이 기하급수적으로 늘어나게 된다.

핑계.

이러한 단점은 핑계일뿐, 추후에 시간이 된다면 WebFlux또한 개발해보고자 한다.
JPA가 안되면 직접 Mapping하면 되고, Coroutine을 사용하면 suspend를 이용해서 Reactive한 방식도 쉽게 적용할 수 있다.

  • Before
@GetMapping("/search")
fun search(@RequestParam request: SearchRequest): Mono<String> {
    // 휴무일여부 + 배달가능센터 동시 조회
    return searchService.find(request).map { search.blahblah(it).map{ result -> {}} ...
      
}
  • After
@GetMapping("/search")
suspend fun search(@RequestParam request: SearchRequest): String {
 	val findResponse = searchService.find(request)
    val mappedResponse = findResponse.blahblash()
    return mappedResponse
}

WebFlux는 어떻게 강팀이 되었는가

참고 코드 : https://techblog.woowahan.com/7349/

하지만 Coroutine으로 작성하더라도 WebFlux의 flow를 알지 못한다면 적용하기 어렵다.
만약 search를 할때 동시에 지도의 정보를 받아와야 한다면 어떻게 해야할까?

val findResponse = searchService().find(request)
val mapResponse = mapService.find(request)

무심코 이렇게 짜기 쉽다. 하지만 suspend 함수 내라는것을 생각해야 한다.
suspend내에서 호출된 suspend함수는 blocking method처럼 동작한다. 따라서 검색이 끝난 이후 map data를 찾는 과정을 거치게 되어 병렬적으로 동작하지 못한다.

	coroutineScope {
    	val deferredFind = async { searchService.find(request) }
        val deferredMap = async { mapService.find(request) }
    }
    return "${deferredFind.await()} ${deferredMap.await()}"

따라서 이러한 형태로 async으로 각 코루틴을 독립적으로 호출하고, 최종 결과에서 await를 하는 방식으로 merge의 형태로 작성해야 한다.

따라서 WebFlux를 좀더 공부하고 추후에 한번 도전해보는걸로..

profile
KRW 채굴기

0개의 댓글