Monolithic과 Micro Service Architecture

과거에는 UI, Business, Data Access 모두를 하나의 프로젝트에 구성하는 Monolithic 방식을 많이 사용했다. MSA는 어플리케이션에서 제공하는 기능들을 도메인 단위로 쪼개서 개발과 배포가 가능한 작은 서비스 단위로 구성하는 방식이다. 서비스 간에는 Resful 같이 가벼운 통신 프로토콜을 이용해 구성하면 API 인터페이스 외에는 각 마이크로 서비스가 서버를 어떻게 구성했는지 어떤 프레임워크를 썼는지 어떤 DB를 사용하는지 이런 정보를 감추도록 구성한다. 서로 필요한 정보는 API를 통해서 호출하도록 한다.

image

  • Monolithic
    • 장점: 소규모 서비스에서 개발, 테스트, 운영(및 모니터링)이 용이함.
    • 단점:
      • 대규모 서비스에서 조그만한 수정에도 전체 빌드가 필요(빌드시간 증가).
      • 수정과 무관한 모듈의 재배포 불가피, scale-out 불리
  • MSA
    • 장점:
      • 각 서비스 독립적으로 개발, 테스트, 배포, scale-out 가능함. CI/CD 달성 가능.
      • 서비스별로 각기 다른 언어 / DB 사용 가능(각 서비스는 api로 추상화)
    • 단점:
      • 개발 복잡도와 통신 오버헤드 증가, 통합테스트 불리, 트랜잭션 처리

Event Driven Architecture

Event는 시스템의 내부나 외부에서 유발된 시스템 상태의 중요한 변화 또는 의미있는 사건으로 마우스/키보드 입출력이 그 예이다. Event Driven Architecture는 분산 시스템에서 비동기 통신 방식으로 이벤트를 발행/구독하는 아키텍쳐이다.

  • 동기, 비동기 통신
    • 동기 통신: (RESTful API를 비롯한) API를 통한 요청-응답 방식(peer to peer)
    • 비동기 통신: Event Channel(Message Broker, Kafka)를 통한 pub/sub 방식
      • Event Channel이 장애가 나면 치명적임
  • EDM = Event Driven Architecture를 적용한 MicroService
    • 비동기 통신 사용 - 각 MicroService간 느슨한 결합도(Loosely Coupled) 유지 가능
    • EDM에서 발생한 이벤트는 이벤트 스토어에 저장(이벤트 로그)
    • Transaction Management: Retry, Rollbac

EDM 서비스

아래는 쇼핑몰 EDM서비스 구축 예시이다. 각 서비스들은 자신의 이벤트 채널에 publish 된 이벤트를 consume해서 적절한 처리를 하게 된다. 사용자가 어떤 상품을 주문하면 order 서비스가 요청을 받아서 “ORDER_CREATE” event를 Inventory Topic 에 발행한다. Inventory service가 “ORDER_CREATE” event를 consume해서 재고를 차감하게 되고, 재고가 있을때 “ITEM_RESERVED” event를 Payment Topic에 보낸다. Payment service는 payment topic을 구독하고 있다가 “ITEM_RESERVED” event가 들어온 것을 확인하고 결제를 하게 된다. Payment service는 DB를 사용하지 않고 외부의 payment gateway를 호출하는 식으로 구성되어 있다. PG 호출이 성공하면 Payment service는 “PAYMENT_COMPLETE” event를 order topic에 발행한다. order service는 order topic을 구독하고 있기 때문에 “PAYMENT_COMPLETE” event를 확인하고 order DB 에 결제까지 완료되었다는 마킹을 하게 된다. 사용자는 transactionid 를 가지고 나의 주문이 제대로 처리되었는지 확인할 수 있다.

image

MSA에서는 보통 각 서비스별로 DB를 별도로 가지고 있는다. DB는 핸들링 해야하는 데이터의 특성에 맞게 선택한다. 이것이 바로 Polyglot Persistence 이다. DynamoDB는 AWS 에서 제공하는 NoSQL DB이다.

image

MSA에서는 각 서비스 별로 DB를 가지고 있고, DB의 종류도 다른데 트랜잭션을 어떻게 처리할까? 단일 DB에서는 DBMS가 제공하는 transaction으로 트랙잯션을 쉽게 할 수있다. 하지만 MSA에서는 각 마이크로 서비스간 API로만 처리하기 때문에 트랜잭션 처리 commit과 rollback은 어플리케이션 레벨에서 구현해줘야 한다.

MSA 각 서비스들은 로컬 트랜잭션만 가능하다. 그래서 SAGA pattern 이 등장했고, SAGA pattern는 MSA의 각 로컬 트랜잭션을 순차적으로 진행해주는 역할을 한다. 서비스들은 자신의 로컬 트랜잭션을 성공적으로 마치면 다음 서비스의 로컬 트랜잭션이 띄워질 수 있도록 이벤트를 이벤틀 채녈에 등록한다.

image

중간에 트랜잭션이 실패하면 이 이벤트를 등록해서 트랜잭션을 수행햇던 역순으로 rollback을 시킨다.

image

SAGA pattern는 두가지 방식으로 나뉘는데, 코레오그래피와 오케스트레이션 방식이 있다. 코레오그래피는 서비스간 이벤트 채널을 통해서 이벤트를 주고 받는 방식으로 비동기로 작동하는 방식이다. 오케스트레이션은 커맨드 방식으로 분산 트랙잭션을 관리하기 위한 별도의 오케스트레이터가 존재하고, 이 오케스트레이터가 모든 트랜잭션을 관리한다. 코레오그래피는 해당 어플리케이션에서 롤백 이벤트를 발행하지만, 오케스트레이션은 오케스트레이터에서 롤백 이벤트를 발행한다.

EDM 서비스 구축

https://github.com/jingene/fastcampus_kafka_handson/tree/main/edm 참고

version: '3'
services:
  zookeeper-1:
    hostname: zookeeper1
    image: confluentinc/cp-zookeeper:6.2.0
    environment:
      ZOOKEEPER_SERVER_ID: 1
      ZOOKEEPER_CLIENT_PORT: 12181
      ZOOKEEPER_DATA_DIR: /zookeeper/data
    ports:
      - 12181:12181
    volumes:
      - ./zookeeper/data/1:/zookeeper/data

  kafka-1:
    hostname: kafka1
    image: confluentinc/cp-kafka:6.2.0
    depends_on:
      - zookeeper-1
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper1:12181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka1:19092
      KAFKA_LOG_DIRS: /kafka
      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    ports:
      - 19092:19092
    volumes:
      - ./kafka/logs/1:/kafka

  localstack-1:
    hostname: localstack1
    image: localstack/localstack:latest
    environment:
      AWS_DEFAULT_REGION: us-east-2
      EDGE_PORT: 4566
      SERVICES: dynamodb
      DATA_DIR: /tmp/localstack/dynamodb/data
    ports:
      - 4566:4566
    volumes:
      - ./localstack:/tmp/localstack

  mysql-1:
    hostname: mysql1
    image: mysql/mysql-server:5.7
    ports:
      - 3306:3306
    environment:
      MYSQL_USER: root
      MYSQL_ROOT_HOST: "%%"
      MYSQL_DATABASE: inventory
      MYSQL_ROOT_PASSWORD: inventorypw
    command: mysqld
      --server-id=1234
      --max-binlog-size=4096
      --binlog-format=ROW
      --log-bin=bin-log
      --sync-binlog=1
      --binlog-rows-query-log-events=ON
    volumes:
      - ./mysql:/var/lib/mysql