BackEnd Developer, Love Crossfit, Welcome to Jay's blog

프로그래머스 웹백엔드 스터디 7기 / 5주차 - Kafka를 이용한 이벤트 시스템과 웹 노티피케이션

|

프로그래머스 웹백엔드 스터디 7기 활동을 하면서 공부한 내용과 기능 구현시 했던 고민들을 정리한 내용입니다.


Index

  • 스터디 5주차
    • 느낀점
    • 고민
    • 공부한 내용
    • TODO 기능 구현
  • References

스터디 5주차

카프카를 이용한 이벤트 전파 시스템과 Web Notification

느낀점

이번 주차는 MSA란 무엇이고 이벤트 시스템 그리고 아파치 카프카의 개념에 대해 알아보았다. 이전에는 HTTP API를 이용해서 동기적인 통신만 해왔는데 - 물론 비동기 처리 방식의 외부 라이브러리를 사용해본적은 있지만 극히 일부분에만 적용되었음 - 메시징 시스템을 이용해 비동기적으로 통신하고 다뤄보는 건 처음이라서 신기했다.

학습한 개념을 코드로 직접 구현해보니 Event Driven 방식이 무엇인지, Pub/sub 구조에서 메시징 처리 방식은 어떻게 동작하는지 확실히 더 잘 와닿았다. 개인적으로 2주차에 했었던 Spring Security보다 이번 이벤트 시스템이 훨씬 재미있었다. 메시징 방식이라고 들어는 많이 봤지만 애매하고 추상적이기만 해서 의문의 대상이었는데, 기능 구현을 통해 그동안 궁금했던 곳을 시원하게 싹 긁어주는 느낌이었기 때문이다. (물론 시간은 더 걸렸지만..)

특히 이번 미션인 구글에서 제공해주는 EventBus 라이브러리를 이용해 유저가 회원가입을 하면 웹 푸시를 보내는 것과, API 서버와 PUSH 서버 두 개의 서비스로 분리해서 카프카 이벤트 전파 시스템을 이용해 웹 노티피케이션을 해보는 기능 구현을 통해 많은 것을 배웠고 사고의 범위가 새로운 영역으로 확장되는 느낌이 들었다.

찾아보니 이외 더 많은 것들이 있었다. 좀 더 공부가 필요할 것 같다. 그래도 이벤트 시스템에 대한 구조를 익히고 기본 구현을 경험해 본 것에 만족한다.

고민했던 점

  1. Event Bus로 이벤트 발생시키는 방법. 어디서 이벤트를 발생시켜야 하고, 이벤트 버스는 그 이벤트와 연결된 리스너를 어떻게 찾는걸까?
  1. 카프카는 이벤트를 어떻게 받아오지? 이벤트 전파 프로세스

공부한 내용

  • 이벤트 시스템
    • 서비스를 만들 때, 이벤트를 정의하고 이 이벤트에 의해서 엔티티 상태들이 변경 되도록 작성하는 프로그래밍 방식
    • 상태가 변결될 때마다 이벤트가 전파되는 구조
    • 도메인 이벤트란?
      • 특정 도메인에서 일어난 일을 도메인 이벤트라 하며 이 이벤트에 의해서 도메인 상태가 변경된다.
        • Post(도메인)가 작성되면 ‘작성되었다’라는 created 값이 생기면서 포스트의 상태가 바뀜
        • Post(도메인)을 누가 클릭하면, 포스트 클릭 횟수(count)가 변함
      • Event란 일어난 일, 특정한 도메인에서 일어난 이벤트를 Domain Event라 한다.
        • 이벤트란 fact(일어난 사실)이어야 한다.
        • 예) Post(도메인)가 작성되면 created라는 이벤트가 발생한다. 주문이 생성/거절/취소됐다. 댓글이 달렸다.

alt text

- Aggregate: 여러 도메인 관리해서 트랜잭션을 보장시키며 특정 이벤트 만드는 친구
- Event Publisher: 발생한 이벤트를 subscriber들에게 알리는 친구 ex) Post가 작성됐어
- Subscriber: 이벤트 리스너, reative하게 반응하고 동작하는 애 ex) Post가 작성됐으니까 DB에 기록해야지. 다른 서비스한테 알려서 통계처리를 하게 해야지
  • 도메인 이벤트 처리 장점

    • 특정 이벤트 발생시, 해당 이벤트에 반응하는 핸들러들을 중간에 추가해도 이벤트 발생시키는 애랑 듣는애 간에 코드 결합도가 없어서 원하는 서비스를 추가해도 복잡도에 영향 없다.

    • 스프링에서 A 서비스가 B 서비스 사용을 위해 주입 받아 B 서비스의 메소드를 런타임때 호출하는 구조였다. 메소드를 다이렉트하게 호출하는 구조이므로 메소드 시그니처가 바뀌면 A도 변경해줘야 한다. 다른 C 서비스를 사용하게 된다면 C 서비스도 주입받고 호출해야하는 구조이다. (다른 서비스의 특정 행동을 호출하는 개념)

    • 이벤트 기반으로 한다면 B 서비스에 특정 이벤트가 발생시, A 서비스는 듣다가 반응만 하는 구조. A 서비스는 다른 서비스의 존재를 알 필요가 없다. 대신 서비스 간에 이벤트를 전달해주는 EventBus가 필요. 각 서비스는 event가 오면 반응하고 그에 따른 행동을 하면 된다. 따라서 서비스간 결합도 훨씬 떨어진다.

    • Guava EventBus, Spring의 ApplicationEventPublisher 사용

    • 새로운 기능 추가시 특정한 이벤트(Post read/write)를 만들어서 Guava EventBus에 publish 하면 Guava EventBus를 subscribe하는 애가 동작하게 할 수 있다.

    • Event Bus를 하나의 인스턴스/서비스가 아니라 여러개의 서비스로 확장시키면 MSA 환경에서 Event Driven하게 MSA를 구성할 수 있다.

  • MSA(마이크로서비스) alt text [사진 출처] https://bcho.tistory.com/948

    • Monolithic 아키텍쳐
      • 1개의 서버(WAS) 안에 모든 비즈니스 로직이 들어가 있는 형태
      • controller service 1:1 맵핑 구조
      • 각 컴포넌트 간의 상호 호출은 함수를 이용한 call-by-reference 구조를 취한다.
    • Microservices 아키텍처

      • 물리적으로 / 코드 베이스로 / 서비스별로 또는 아예 팀으로 나눈것. 빌드/배포 또한 독립적. 이럴 경우 어느정도 규모가 필요.
      • 서비스마다 독립적으로 수행 가능
      • 기술 스택의 독립 처리
        • 각 서비스별로 적합한 언어로 비즈니스 로직 구현과 DB 선택
        • 관계가 중요한 경우 RDBMS, 양이 많은 고속 데이터는 NoSQL 등
      • 서비스별로 별도의 DB 존재
        • Mono(통짜구조)에서는 모든 서비스가 동일한 DB를 공유하는 구조
      • API 게이트웨이를 앞단에서 활용 (프록시처럼)
        • 각 서비스의 공통 기능 처리
          • 인증, 로깅, 메시지변환(xml -> json), 프로토콜 변환(http ->https)
        • EndPoint 통합과 라우팅
      • 물리적으로 분리되어 있는 서비스들. 서로 통신은 어떻게 하지?
        • grpc 또는 HTTP Rest API 이용(synchronize하게 통신)
        • 카프카 같은 메시지 브로커 이용한 EventDriven 방식
          • 특정 이벤트를 구독해서 이벤트가 발생되면 그에 맞는 행동을 함
      • 통신 방식 예시
        • 결제를 위해 사용자 정보가 필요한 결제 서비스가 있다. MSA 구조에서 결제를 위해 어떻게 처리해야 할까? 결제가 완료되면 알림 이메일도 발송한다.
        • Rest API 방식
          • 결제할 때마다 사용자 정보를 API로 찔러 처리한다. 결제가 완료되면 이메일 API를 찔러 이메일을 발송한다.
        • Event Driven 방식
          • 사용자 가입/탈퇴를 이벤트로 만들어서 이 이벤트를 메시지 브로커(카프카)/Event Bus에 publish(전파)한다.
          • 그럼 해당 이벤트에 관심있는 아이(결제 서비스)가 subscribe하다가 반응해서 처리한다.
          • 즉, 결제할때 마다 사용자 정보를 API로 찔러서 가져오는게 아니라 사용자가 가입을 할때, subscribe해서 필요한 사용자 정보를 결제 서비스의 DB에 저장해둔다.
          • 결제 서비스 안에서 필요한 사용자 정보를 들고 있게 되므로 실결제 발생시 자체 DB에서 사용자 정보를 가져와 결제를 완료시키고 publish한다.
          • 그럼 이메일 서비스가 결제 완료 이벤트에 반응해 이메일 발송
          • 가입 후 유저 정보가 변경되었다면? => 변경된 것도 이벤트로 만들어 결제 서비스에 전파해줘야 한다.
          • 하지만, 결제 서비스가 유저의 마지막 상태를 반드시 알고 있어야 한다면 할 수 없이 api 호출을 통해 가져와야 한다.
        • 모노리틱 구조라면, 결제 발생 -> 사용자 DB Repository 조회 -> 필요한 정보 합쳐서 응답해주고 -> email 발송 api 호출. Synchronize하게 처리됨.
        • Event Driven 방식은 매번 API를 호출해야하는 Rest API 방식보다 부하가 훨씬 적다. 이벤트 통해서 전파하면 이미 사용자 db를 해당 서비스에서 들고 있기 때문에 api 호출없이 자체적으로 해결 가능하기 때문이다.
      • MSA 장점

        • 확장성
          • 부하가 많은 서비스만 확장이 가능(많은 자원 요구하거나 트래픽이 몰리는 곳). 필요한 부분만 확장 가능
          • 이메일 발송에만 트래픽이 몰릴 경우 이메일 서비스만 서버 수를 늘리고, 상대적으로 트래픽이 적은 서비스(결제는 한달에 1~2번)는 서버 수를 줄일 수 있음
          • 모노리틱 하다면, 특정 서비스에만 부하가 생기면 그 서비스 때문에 전체 서버 수를 늘리거나 CPU 수를 늘려줘야 하지만, MSA 경우 부하를 많이 받는 서비스 컴포넌트만 확장해주면 된다.

    MSA에서는 시스템 간의 호출이 많기 때문에 서비스간 결합도를 낮추기 위해서, 비동기 요청, 성능, 안정성 등 여러가지 이점이 있어서 메시징 시스템을 많이 사용

    • MSA 구성시 Spring MVC가 없는 모듈이 있을 수 있나?

      • MSA 구성시 REST API가 없는 서비스가 있을 수 있다. 이 경우 mvc 모듈이 필요없다 대신 카프카 통해 subscriber를 띄어야 한다
    • 이벤트 드리븐 방식 언제 사용?

      • 외부 시스템과 연동해야하는 상황, 언제 변할지 모를 때, 동기적이지 않을 때, 주로 사용.
      • ex.주문 시스템, 여행사 예약 시스템
  • 아파치 카프카 등장 배경

    • HTTP API 요청으로 서비스 A가 B로 요청을 하기 때문에 A에서 B로 메시지가 흘러간다 즉 하나의 파이프라인이 생성된다로 표현
    • 새로운 공급자(서비스 B api 호출하는 애들)가 생기면 여러개의 파이프라인이 생김
    • 새로운 소비자도 생기면 (빅데이터 분석을 위한 서비스)
    • 엄청난 복잡한 형태의 데이터 파이프라인이 만들어 지게된다.
    • 카프카를 사용하게 되면 이러한 복잡한 형태를 좀 더 단순하게 사용할 수 있게 된다.
  • 카프카의 주요 용어

    • Producer: 메시지/데이터를 생성하고 제공. ex) 회원 가입시 회원 정보를 생성
    • Brokers: 생성된 메시지를 Consumer들에게 전달해주는 역할
    • Consumers: 필요한 데이터를 소비 ex) 생성된 회원 정보를 DB에 저장, 결제 처리를 위해 필요한 회원 정보를 따로 보관, 이메일 발송
  • 카프카

    • 모든 메시지를 카프카로 쏘기 때문에 고가용성 필요.
    • 서버를 여러대로 나눠 분산 처리. (대량신속처리 분산 메시지 시스템)
    • 분산 커밋 로그.
    • 메시지 -> 카프카 -> 카프카 내부에서 각 메시지를 로그로 보고 파일 시스템에 기록(commit). 메시지간 순서가 보장됨.
    • 그런데 카프카 내부 로그에 write되는 속도와 읽어가는 속도(소비 속도)는 달라질 수 있다.

    • 모든것은 분산되어 있다. (프로듀서, 컨슈머, 카프카)

    • 카프카 클러스터링
      • 카프카는 여러개의 서버로 클러스터링 되어 있다.
      • M-S 구조로 관리하기 위해서 이를 관리해주는 주키퍼 서버가 필요하다.
  • 토픽과 파티션

    • 데이터가 저장되는 곳이 토픽이며, 토픽 내부는 파티션으로 분할되어 있다.
    • 토픽으로 데이터를 저장하고 읽어온다.
    • subscriber는 토픽 기준으로 subscribe함. 코드상에서 나는 이 event.join 토픽을 subscribe하겠다.
    • 예시
      • 스프링부트로 만들어진 인사 관리 서버/시스템/서비스, 고객 서비스 - 고객의 정보만 관리하는 서비스와 가입/탈퇴 처리 전용 서비스가 따로 분리되어있다. 가입 발생 -> 카프카 특정 토픽을 향해 customer created 메시지 전달 -> 커슈머들은 해당 이벤트를 리슨하고 해당 토픽에 있는 메시지들을 컨슘.
  • 토픽 구조
    • 토픽은 파티션으로 구성되며, 파티션 단위로 분산처리가 된다
    • 메시지는 특정 토픽 안에 특정 파티션 안에 저장된다.
    • 파티션이란 데이터를 나누는 개념. 무슨 기준으로 나눌까?
      • 월단위로 파티셔닝, 모듈러 연산 (3으로 쪼개서 1,2,3에 넣기), 도단위로 파티셔닝
      • 이런식으로 데이터 분산처리 (좀더 찾아보기 파티셔닝방법)
    • 토픽의 파티션은 불변. 한 번 커밋(기록)되면 변경 불가
    • 주문을 되돌리고 싶으면 주문을 취소하는 커밋을 해야함. 토픽, 파티션 자체를 지울순 있어도 파티션 사용중 내용을 바꿀순 없음
  • 컨슈머 Offset 포인팅

    • 컨슈머는 어디까지 읽었는지 카프카에 offset으로 기록
    • 컨슈머를 재시동하면 처음부터가 아닌 이전에 읽어왔던 지점부터 읽어오게됨
    • offset, partition, topic으로 메시지를 추적함. 어느 토픽의 어느 파티션을 어디 까지 읽었냐
  • 컨슈머와 컨슈머 그룹

    • 컨슈머 그룹 개념을 통해 큐와 pub/sub 모델 지원
    • 파티셔닝은 하나의 컨슈머 접근만 허용. 3개의 파티셔닝. 메시지가 특정 파티션에 들어가면, 새 메시지는 모든 커슈머 그룹에 브로드 캐스트 된다. 커슈머 그룹은 0번 파티션에 써진 메시지를 모두 읽어간다. 하지만, 하나의 그룹 내에서는 하나의 컨슈머만 해당 메시지를 읽어간다.

    위 그림에서 만약 컨슈머 그룹 A에 A4가 추가되었다. A4는 어느 파티션을 읽을까? => 아무것도 안 읽는다. 하나가 죽으면 대신 처리하는 역할만하게 됨. 안 죽으면 가만히 있음 => groupB처럼 컨슈머가 1개만 있는 경우 3개를 모두 다 읽어간다. 죽으면 해당 그룹에서는 아무것도 안한다. 어떻게 분산을 하고 하나의 컨슈머 그룹안에서 몇개의 인스턴스를 들고 갈지 고민이 필요한다.

    • A3가 죽으면 컨슈머 스스로 파티션 재분배가 일어난다.
    • 서버 3개, 또는 서버 1개 내 쓰레드 3개로 구성 등 다양한 방식으로 구성 가능
  • Replication(복제)

    • 데이터 손실 방지를 위해 파티션 단위로 복제를 한다.
    • 복제를 안하면 데이터 날라가면 끝.
    • –replication-factor 옵션을 이용해 설정
    • 파티션 3, replicafactor 2 주면 총 6개 파티션이 생성됨.
  • lag(렉)

    • 소비속도가 공급 속도보다 느리면 렉이 발생하게된다.
  • 추가 학습 / 정리한 내용

TODO 기능 구현

1.멀티모듈화 Maven프로젝트를 부모-자식 프로젝트로 변경

social-server를 다음 2개로 멀티 모듈화

  • social-server-api
  • social-server-push

2. 카프카를 이용해 social-server-api 프로젝트에서 Notification을 subscribe하는 API 구현

  • kafkaTemplate의 sendAndReceive를 이용해서 Subscription정보를 Push서버에서 처리하도록 한다.
    • 토픽이름: v1.event.subscription.request
    • 토픽이름: v1.event.subscription.response

3. 포스트 댓글 작성에 대한 이벤트와 리스너 생성

  • CommentCreatedEvent

4. CommentCreatedEvent 발생시 카프카로 이벤트 전파

  • 토픽 이름: v1.event.comment-created
  • CommentCreatedEvent 이벤트를 구독하여 콘솔에 다음과 같이 출력
    • ${작성자의 subscription 정보 중 endpoint} -> “${코멘트작성자의이름}가 코멘트를 달았습니다.”푸쉬발송
    • user별 subscription 정보를 처리하는 DB가 별도로 존재하여 Subscription 이벤트가 발생할 때 해당 정보를 디비에 기록한다.

References

MSA
https://bcho.tistory.com/948?category=431297

이벤트시스템
https://www.slideshare.net/koneru9999/guavas-event-bus https://coding-start.tistory.com/132

카프카
https://www.popit.kr/kafka-consumer-group/
https://www.popit.kr/kafka-%EC%9A%B4%EC%98%81%EC%9E%90%EA%B0%80-%EB%A7%90%ED%95%98%EB%8A%94-%EC%B2%98%EC%9D%8C-%EC%A0%91%ED%95%98%EB%8A%94-kafka/ https://victorydntmd.tistory.com/344?category=798367 https://www.confluent.io/blog/apache-kafka-spring-boot-application/