728x90
반응형
728x90
반응형
반응형

고려사항

ASIS 고려

  • 각 서버에서 요청 시 수집 서버를 호출하는 방식 말고 쌓인 로그 파일을 수집하여 수집 서버에서 로그를 분석하는 방식을 우선적으로 고려
  • 기존에 그라파나, 프로메테우스 설정이 되어 있으니 필요 시 이를 활용할 수 있는 방안을 고려
  • 기존 PIS 알림 방식이 가능한지 고민(특정 에러가 1분 안에 5번 이상 호출 시 알람 발생 등)

요청의 흐름에 대한 모니터링이 쉽게 되었으면 좋겠다고 생각함

  • trace id는 프론트에서 생성해서 헤더에 심어 백엔드로 전파하는게 제일 좋을 것 같음
  • 백엔드는 헤더에 trace id가 있으면 이걸 다음 컴포넌트에게 전파, 없으면 생성하여 헤더에 심어서 전파
    • 추가적인 의미있는 정보: global trace id, span id, user key...
  • 이걸 모듈화(기존 log module을 활용하여)하면 좋겠다는 생각..
  • was, ws 간 로그 포맷 정형화 필요
    • springboot 로그에서도 심지만 nginx 등 웹서버, 디비 호출, 인프라(loadbalancer) 등 에서도 trace id, 유저 구분자 등 활용 필요

Observability

로그나 실시간으로 수집되고 있는 모니터링 지표와 같은 출력을 통해 시스템의 상태를 이해할 수 있는 능력

  • 시스템/어플리케이션의 내부 상태를 이해 -> 원인/문제를 진단(디버깅) -> 성능을 최적화하는 능력

측정 데이터

  1. 메트릭 (Metrics)
    • 설명: 성능 지표. 시간에 따른 수치 데이터를 측정하여 시스템의 성능을 모니터링하기 위한 데이터
      • CPU 사용량, 메모리 소비, 요청 수 등의 지표를 포함
    • 도구 예시: Prometheus, Grafana
  2. 로그 (Logs)
    • 설명: 시간 기반 텍스트, 애플리케이션과 시스템의 이벤트에 대한 기록. 구조화된 로그 필요
    • 도구 예시: Elasticsearch, Loki
  3. 트레이스 (Traces)
    • 설명: 데이터가 흘러가는 전체적인 경로(큰그림)
      • 많은 시스템을 거쳐가는 분산 시스템에서 요청의 흐름을 추적하여 성능 병목 현상을 식별할 수 있음
      • Trace ID 기반으로 로그-트레이스 연결 가능
    • 도구 예시: Jaeger, Zipkin, Tempo

OpenTelemetry(OTel)


OpenTelemetry은 Traces, Metrics, Logs 같은 데이터를 instrumenting, generating, collecting, exporting 할 수 있는 Observability Framework

  • 오픈소스, 클라우드 네이티브 컴퓨팅 재단(CNCF, Cloud Native Computing Foundation) 프로젝트
  • 분산 추적(Distributed Tracing) 및 모니터링을 위한 표준을 제공
  • 벤더 종속적이지 않음, 큰 틀을 제공
  • OpenTelemetry는 Spring Boot와 잘 호환되는 APM(Application Performance Monitoring) 솔루션, 자동 계측 가능

위 프래임워크와 함께 선택한 기술 스택

  • LGT(M)

제안하는 아키텍쳐

OpenTelemetry Collector

설치 방식: 바이너리 다운로드 / Docker / Kubernetes(Helm Chart) 중 선택

Collector는 꼭 필요한가?

  • 애플리케이션이 많을 때 → 모든 서비스가 개별적으로 Tempo랑 연결하는 것보다 효율적
  • 샘플링, 필터링이 필요할 때 → Collector에서 간편하게 설정 가능. 오류 발생한 Trace만 Tempo로 보낼 수 있음
  • 다른 백엔드로도 보내야 할 때 → Tempo뿐만 아니라 Zipkin, Jaeger, Loki 등에도 동시에 전송 가능


Collector는 필수가 아님, 하지만 확장성을 고려하면 강력한 도구!
대규모 MSA 환경에서는 Collector가 필수!

직접 구현해도 되나?

Java로 OpenTelemetry Collector 구현 가능

  • OpenTelemetry가 공식적으로 제공하는 proto 정의 파일을 기반으로 Java 코드를 생성해야 함

하지만… 일반적인 방식은 아니며 비효율적일 수도 있음

  • 기존 Go 기반 OpenTelemetry Collector보다 성능 저하 가능성 있음.
  • 기능 추가 및 유지보수가 어려움 (기본 OpenTelemetry Collector는 이미 다양한 Exporter 제공).
  • 프로토버프 버전 관리 및 업데이트 부담.

그래도 직접 Java로 Collector를 만들고 싶다면?

  • ProtoBuf를 이용해 OTLP 데이터 처리
  • gRPC 서버로 수신 후 필요한 백엔드로 Export
  • 필요한 Receiver, Processor 및 Exporter를 추가 개발

결론: 가능하지만 OpenTelemetry 공식 Collector를 사용하는 것이 더 현실적!

내부 데이터 흐름 (Receiver → Processor → Exporter)

  • Receiver(수집기): 외부 시스템(애플리케이션, 에이전트, 다른 Collector 등)에서 데이터를 수신예시: OTLP, Jaeger, Zipkin, Prometheus, Loki 등 다양한 수집기 지원
  • Processor(처리기): 데이터를 필터링, 배치 처리, 속성 추가 등의 변환 작업 수행예시: batch, filter, transform 등 다양한 프로세서 사용 가능
  • Exporter(전송기): 데이터를 최종 모니터링 시스템(Grafana Tempo, Prometheus, Loki 등)으로 전송
    • 예시: Tempo, Zipkin, Jaeger, Loki, Prometheus 등 다양한 Exporter 지원

 

collector 설정은 yaml로

log, trace, metric 각각은 파이프라인으로 연결

설정 예시

receivers:
  otlp:
    protocols:
      grpc: "0.0.0.0:4317"  # gRPC 기본 포트
      http: "0.0.0.0:55681"  # HTTP 기본 포트
  loki:
    endpoint: "http://loki:3100"
  prometheus:
    config:
      scrape_configs:
        - job_name: 'otel-metrics'
          static_configs:
            - targets: ['localhost:9090']

processors:
  batch:     # 배치로 전송
    timeout: 10s
  attributes:
    actions:
      - key: "http.status_code"
        value: "404"
        action: "drop"  # 404 응답 코드가 포함된 트레이스나 로그를 드롭
  filterlogs:
    match:
      log:
        severity: ERROR  # ERROR 로그만 필터링

exporters:
  otlp:
    endpoint: "http://tempo:4317"
  loki:
    endpoint: "http://loki:3100"
  prometheus:
    endpoint: "http://prometheus:9090"
  logging:
    verbosity: detailed

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [attributes, batch]
      exporters: [logging, otlp]
    logs:
      receivers: [loki]
      processors: [filterlogs, batch]
      exporters: [loki, logging]
    metrics:
      receivers: [prometheus]
      processors: [batch]
      exporters: [prometheus]
 



로그 전송기 - Promtail

중간에 Promtail 안 쓰고 바로 collector로 연결한다면?

Promtail을 사용하지 않으면, 애플리케이션이 직접 로그를 OpenTelemetry Collector로 전송해야 함.

  1. Collector가 로그 파일을 직접 읽어 Loki로 전송하는 방식
    • 로그 포맷(구조화 로그 등)을 사전에 맞춰야 함
  2. 애플리케이션에서 직접 Collector로 Push (OTLP 사용)
    • 파일 기반이 아니라 애플리케이션 내부에서 생성된 로그를 바로 전송 가능

기존 로그 파일을 그대로 활용하고 싶다면 Promtail → Collector → Loki

  • Promtail에서도 로그 포매팅 가능
  • Collector에서도 로그 변환 가능
  • Loki에서도 포맷 조정 가능

 

Log 저장소 - Loki

Loki는 로그 수집, 저장, 쿼리를 위한 오픈 소스 로그 집계 시스템으로 별도의 데이터베이스 없이 파일 시스템이나 클라우드 스토리지, 객체 저장소 등을 사용하여 로그를 저장

참고: 왜 Loki?

Metric 저장소 - Prometheus

Prometheus는 수집된 메트릭 데이터를 저장하고 이를 쿼리할 수 있는 중앙 데이터베이스 역할을 하기 위한 것

꼭 있어야 하나?

springboot actuator prometheus 사용 시..
콜랙터에서 바로 그라파나와 연결하여 실시간 metric 확인 가능

다만 사용 시 아래의 혜택을 얻을 수 있음

  • 메트릭 저장을 통한 장기적인 메트릭 분석 가능
  • 고급 쿼리 기능 활용
  • 알림 시스템 지원

 

Trace 저장소 - Tempo

Tempo 없이 Loki만으로 분산 추적이 가능할까?

  • Tempo 없이 완전한 분산 추적은 어려움
  • Trace ID를 로그에 남기면 Trace ID가 포함된 로그를 검색할 수는 있지만, 서비스 간 호출 관계(Span, Parent-Child 관계)는 분석 불가
  • Tempo는 Trace 간 시간 흐름을 시각화하여, 어느 서비스가 느린지, 어디에서 지연이 발생하는지 확인 가능. Loki는 단순한 텍스트 로그 검색이라 이런 분석 불가

 

Tempo는 기본적으로 Push 방식(Polling 지원 X) 관련하여 아래 설정 가능

  • Head-based sampling: 모든 요청을 추적하지 않고 일부만 추적(확률 설정) - 기본값
  • Tail-based sampling: 모든 요청을 수집하지만 collector에서 특정 조건을 만족하는 요청만 저장하도록 설정(필터링 사용) / 어플리케이션 성능에 영향 없음
    • 정상 요청은 버리고, 오류만 저장하도록 설정할 수도 있음
    • 배치 설정: 일정량 쌓이면 한번에 Push하도록 설정
  • Always on sampling: 모든 요청을 100% 저장, 데이터 저장 비용 증가 가능


참고: 다른 trace 저장소와 비교

알람 관련

Grafana 방식

  • Grafana는 Prometheus/Loki에서 데이터를 가져와 알람(Alert)을 설정 가능(템포 x)

장점

  • UI로 알람을 설정할 수 있어 편리
  • Alertmanager 없이 바로 메일/Slack/Webhook 전송 가능

단점

  • 중앙 집중형 관리 어려움
  • 코드 기반 관리 불가능
  • 확장성 없음

 

Prometheus Ruler + Alertmanager 방식이 가장 표준적인 방식

(Loki, Premetheus, Tempo) → Prometheus Ruler → Alertmanager → Email/Slack/Webhook
 


Prometheus Ruler?

  • Prometheus Ruler는 Prometheus 서버와 함께 동작(내장됨)하며, 알림 규칙(Alerting Rules)을 관리하는 기능을 제공
  • Prometheus Ruler는 알람 규칙(Alerting Rules)을 처리하고, 이 규칙이 Trigger되면 Alertmanager에 알림을 보냄
  • yaml 설정으로 관리

Alertmanager?

  • Prometheus 및 Loki, Tempo 등에서 발생한 알람을 관리하고, 이메일, Slack, PagerDuty 등의 채널로 알림을 전송하는 역할을 하는 도구
  • 알람 수신 및 라우팅, 집계, mute, 알람 중복 방지 등 기능이 있음
  • 프로메테우스 설정에서 ruler를 사용하도록 설정 후 별도 파일(yaml)로 설정 관리


Loki에서 직접 알람 가능?

  • Loki 자체적으로는 알람을 트리거할 기능이 제한적
  • logql 쿼리를 사용하여 Prometheus Ruler에서 감지 후 Alertmanager로 전송하는 방식이 일반적

Prometheus에서 직접 알람 가능?

  • Prometheus는 자체적으로 Prometheus Ruler를 통해 알람을 감지 가능
  • 하지만 Alertmanager 없이 직접 알람을 보낼 수 없음

Tempo에서 직접 알람 가능?

  • Tempo는 직접적인 알람 기능이 없음
  • Trace 기반으로 메트릭을 생성한 후 Prometheus Ruler를 통해 감지하는 방식 사용


Prometheus Ruler + Alertmanager를 사용 시 장단점

장점

1. 유연한 알림 라우팅

  • Alertmanager는 알림을 '라벨' 기반으로 라우팅할 수 있어서, 다양한 알림 조건에 대해 수신자를 유연하게 지정할 수 있음.
  • 예를 들어, severity, project, team과 같은 라벨을 기반으로 알림을 각기 다른 수신자 그룹(메일, 슬랙, 웹훅 등)으로 전달할 수 있음.
  • 라벨을 이용하여 프로젝트별로 혹은 환경 별로 다양한 알림 조건을 설정할 수 있음

2. 알림 집계 및 수집

  • Alertmanager는 동일한 경고에 대해 여러 번 알림을 보내지 않도록 알림을 집계하고 알림 그룹화 기능을 제공
  • 예를 들어, 여러 번 발생하는 동일한 경고를 하나의 알림으로 묶어서 처리할 수 있음

3. 알림 수신 채널 다채로움

  • 알림을 다양한 채널(이메일, 슬랙, 페이지듀티, SMS 등)로 전송할 수 있음.
  • Alertmanager는 알림을 설정한 대로 다양한 형식으로 전송할 수 있는 기능을 제공함

4. 정밀한 알림 조건 설정

  • Prometheus Ruler에서 제공하는 고급 알림 규칙 설정을 통해, 알림 조건을 세밀하게 정의할 수 있음.
  • 예를 들어, 특정 메트릭이 1분 동안 특정 값을 초과하거나, 특정 상황이 반복되는 경우에만 알림을 보내는 식으로 알림의 발생 조건을 세밀하게 조정할 수 있음.


단점

1. 설정이 복잡함

  • 설정하는 화면이 없고 yaml  파일을 작성하는 방식
  • 여러 팀이나 프로젝트별로 맞춤형 알림을 설정하는 경우, 설정 파일이 방대해질 수 있으며, 이를 관리하기 어려울 수 있다.

2. 리소스 요구사항

  • Prometheus Ruler와 Alertmanager는 각각 다른 시스템과 연동되어야 하므로, 시스템 자원의 관리가 필요함. Prometheus의 수집 데이터 양이 많아지면 알림 평가에 드는 시간과 리소스가 커질 수 있다.
  • 알림을 너무 많이 생성하거나 복잡한 계산을 수행하면 시스템에 부하를 줄 수 있음

도입 시 각 어플리케이션 수정 양은?

1. 아래 의존성 추가

// 애플리케이션에서 메트릭, 트레이스, 로그와 같은 관측 데이터를 수집하는 데 필요한 인터페이스를 제공; trace id 생성
implementation 'io.opentelemetry:opentelemetry-api:${version}'

// OpenTelemetry API의 구현체로, 실제 데이터를 수집하고 처리하는 기능을 제공; 데이터 수집
implementation 'io.opentelemetry:opentelemetry-sdk:${version}'

// Traces, Metrics, Logs 데이터를 OTLP(HTTP/gRPC) 프로토콜을 통해 Collector로 전송; 외부로 전송
implementation 'io.opentelemetry:opentelemetry-exporter-otlp:${version}'
 

2. tracer 빈 등록 - 콜렉터 정보 등록
3. 로그백 설정

  • logback.xml 파일에서 MDC(Mapped Diagnostic Context)를 사용하여 트래스 아이디와 스팬 아이디를 자동으로 포함시킬 수 있음
  • 받은 요청에 트래스(Trace) 아이디가 있으면, 이미 존재하는 트래스 아이디를 그대로 사용하고, 새로운 스팬(Span)을 생성한다. 만약 요청에 트래스 아이디가 없다면, 새로운 트래스 아이디를 생성하고 이를 기반으로 스팬을 생성한다.
  • 소스에서 수동으로도 트래이싱 정보 추가 가능(마킹 가능)

참고

오텔 설명
https://medium.com/@dudwls96/opentelemetry-%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-18b6e4fe6e36
https://www.anyflow.net/sw-engineer/opensource-observability
위에서 제안한 아키텍쳐와 비슷한 구조로 세팅하는 과정 설명
https://blog.nashtechglobal.com/setup-observability-with-open-telemetry-prometheus-loki-tempo-grafana-on-kubernetes/

[토스] observability 시스템 구축 시 고려해야하는 사항들
https://youtu.be/Ifz0LsfAG94?si=cYAPtvm8eRy0Srk- 
[nhn forward] nhn cloud가 구축한 사용 예시
https://youtu.be/EZmUxMtx5Fc?si=_YtHU2mDayS2uxKr

728x90
반응형

'architecture > sw architecture' 카테고리의 다른 글

람다 아키텍처 vs 카파 아키텍처  (0) 2023.03.22
[design] proxy pattern 프록시 패턴  (0) 2022.04.25
반응형

람다 아키텍처

 대규모 데이터 처리 시스템을 위한 아키텍처 패턴

: 실시간 분석, 추천, 모니터링 등에 사용

  • 과거 데이터: summary data 있고
  • 신규 데이터: 최신 데이터
    • 두 개를 조합

실시간으로 들어오는 데이터와 배치 처리가 필요한 대용량 데이터를 동시에 처리하는 구조를 제공하여, 빠른 응답성과 정확성을 모두 확보하는 데 중점을 둡니다. 데이터의 수집, 처리, 분석을 효과적으로 수행하기 위해 다음과 같은 3가지 계층으로 나뉩니다.

1. 배치 레이어 (Batch Layer)

  • 역할: 대량의 데이터에서 정확한 집계와 분석을 위해 주기적인 배치 처리를 수행합니다.
  • 특징: 배치 레이어에서는 데이터가 정해진 주기에 따라 한 번에 대량으로 처리됩니다. 이때 데이터의 불변성을 유지하여 전체 데이터를 매번 다시 계산해 정확도를 보장합니다.
  • 사용 예: Hadoop, Apache Spark 등 분산 배치 처리 시스템.
  • 장점: 모든 데이터를 기반으로 계산하기 때문에 데이터 손실이 없고 최종적으로 신뢰할 수 있는 결과를 제공합니다.

2. 실시간 레이어 (Speed Layer)

  • 역할: 실시간으로 들어오는 데이터를 빠르게 처리하여 최신의 데이터에 대한 결과를 제공합니다.
  • 특징: 배치 레이어가 대규모 데이터에 대해 전체적으로 정확한 처리를 수행하는 반면, 실시간 레이어는 최신 데이터에 대한 빠른 처리와 분석을 제공합니다. 이 레이어에서는 배치 레이어에서 처리된 데이터와 별도로 결과를 제공하여 빠르게 대응할 수 있도록 합니다.
  • 사용 예: Apache Storm, Apache Kafka, Apache Flink.
  • 단점: 실시간 처리는 모든 데이터를 종합하지 않기 때문에 정확도에 제한이 있을 수 있습니다.

3. 서빙 레이어 (Serving Layer)

  • 역할: 배치 레이어와 실시간 레이어의 결과를 결합하여 사용자에게 최종적인 분석 결과를 제공합니다.
  • 특징: 서빙 레이어는 배치 및 실시간 레이어에서 계산된 결과를 사용자에게 빠르게 응답할 수 있도록 구성됩니다. 두 레이어의 결과를 결합하여 정확하고 최신화된 정보를 제공하는 역할을 합니다.
  • 사용 예: Apache HBase, Cassandra와 같은 NoSQL 데이터베이스.

람다 아키텍처의 장단점

  • 장점
    • 확장성: 대규모 데이터와 실시간 데이터를 모두 효율적으로 처리할 수 있어 시스템 확장이 용이합니다.
    • 내결함성: 실시간 처리가 실패해도 배치 처리가 보완할 수 있는 구조입니다.
    • 정확성: 실시간 데이터는 빠르게 처리하고, 배치 처리는 전체 데이터를 기준으로 정확도를 보장합니다.
  • 단점
    • 복잡성: 배치 레이어와 실시간 레이어를 동시에 관리하고 결합해야 하므로 시스템이 복잡해질 수 있습니다.
    • 유지보수 부담: 실시간과 배치 두 가지 흐름을 동시에 관리해야 하므로 유지보수가 어려울 수 있습니다.
  • 고민
    • 배치로 중간 데이터를 만들었는데.. 지나고 이전 데이터가 들어온다면  지난 데이터를 버릴 건지 다시 배치를 돌릴 건지??

 

-> 카파 아키텍처(kappa)

카파 아키텍처(Kappa Architecture)는 대용량의 실시간 데이터 처리를 위해 설계된 아키텍처로, 람다 아키텍처의 복잡성을 줄이기 위해 제안되었습니다. 카파 아키텍처는 실시간 스트리밍 데이터의 분석과 처리를 중점으로 하며, 배치 레이어 없이 단일 데이터 처리 경로만을 사용하는 점이 특징입니다. 이는 시스템의 단순성과 유지보수를 고려한 접근 방식으로, 변화가 빠른 환경에서도 효율적으로 동작합니다.

카파 아키텍처의 주요 특징

  1. 단일 데이터 경로 (Single Pathway)
    • 카파 아키텍처에서는 데이터를 오직 하나의 경로로 처리합니다. 이 데이터 경로는 실시간 스트리밍 처리를 위한 스트림 처리 엔진을 사용하며, 람다 아키텍처의 배치 레이어가 없는 구조입니다.
    • 데이터가 들어오면 스트림 처리 시스템을 통해 처리되며, 필요한 경우 결과를 실시간으로 업데이트합니다.
  2. 실시간 데이터 처리
    • 카파 아키텍처는 실시간 처리를 핵심으로 하여, 들어오는 데이터를 신속하게 처리하고 분석하는 데 집중합니다.
    • 지속적인 스트림 데이터와 그 결과를 실시간으로 쌓아 두기 때문에 새로운 데이터에 빠르게 대응할 수 있습니다.
  3. 데이터의 불변성 유지
    • 카파 아키텍처에서도 데이터는 원본 형태로 저장되고, 필요에 따라 재처리할 수 있도록 불변성을 유지합니다.
    • 스트림 처리 엔진을 통해 재처리가 필요할 경우 저장된 원본 데이터를 다시 처리할 수 있습니다.
  4. 간단한 구조
    • 배치 처리를 제거하고 스트림 처리를 중심으로 설계해 단순한 아키텍처를 유지합니다.
    • 이는 개발 및 유지보수의 복잡성을 크게 줄여 주며, 특히 운영과 관리 측면에서 효율적입니다.

카파 아키텍처의 구현 방식

  • 스트림 처리 엔진: 카파 아키텍처에서 중요한 역할을 하는 요소로, Apache Kafka, Apache Flink, Apache Samza 등이 자주 사용됩니다. 이들 엔진은 대용량의 스트리밍 데이터를 고성능으로 처리하는 데 적합합니다.
    • 카프카에서 데이터를 재정렬, 필터링 등 여러 액션을 할 수 있음
  • 데이터 저장소: 원본 데이터를 저장하는 시스템으로, Apache HBase, Cassandra, Elasticsearch와 같은 NoSQL 데이터베이스나 Kafka 같은 메시징 시스템이 사용됩니다.
    • Apache Iceberg 사용하는 추세
      • Apache Iceberg는 대규모 분석 데이터 테이블을 위한 고성능 오픈 소스 테이블 포맷입니다. Iceberg는 데이터를 대규모로 효율적으로 관리하고 처리할 수 있도록 설계되었으며, 특히 빅데이터 환경에서 기존 테이블 포맷의 문제를 해결하는 데 중점을 두고 있습니다. Iceberg는 데이터 레이크에서 다양한 저장소 및 파일 포맷을 지원하면서도 ACID 트랜잭션을 제공하고, 강력한 스키마 관리와 높은 쿼리 성능을 제공합니다.
      • 과거와 현재 데이터를 중간에 스냅샷을 찍어서 관리하는 구조

카파 아키텍처의 장단점

  • 장점
    • 단순성: 배치 레이어를 제거하고 스트림 처리만 사용하여 시스템 구조가 단순합니다.
    • 빠른 대응: 실시간 데이터를 실시간으로 처리할 수 있어 최신 데이터를 활용한 분석이 용이합니다.
    • 유지보수 용이: 단일 데이터 경로만 관리하므로 복잡한 유지보수 작업을 줄여줍니다.
    • 읽기 속도가 좋고 람다보다 유연
  • 단점
    • 재처리 문제: 배치 처리가 없기 때문에 모든 데이터를 실시간으로 처리하며, 재처리가 필요한 경우 스트림 처리 엔진에 추가적인 부담이 될 수 있습니다.
    • 적용 한계: 대용량 데이터의 정확한 분석이 필요하거나 일괄처리가 필요한 환경에서는 적합하지 않을 수 있습니다.

카파 아키텍처 vs 람다 아키텍처

카파 아키텍처는 람다 아키텍처와는 달리 실시간 스트리밍 처리만을 사용하여 시스템을 단순화합니다. 이를 통해 람다 아키텍처에서 필요했던 두 경로의 중복 코드나 복잡한 데이터 일관성 유지 문제를 해결할 수 있습니다. 그러나 재처리나 정밀한 배치 처리가 필요한 환경에서는 여전히 람다 아키텍처가 유리할 수 있습니다.

728x90
반응형

'architecture > sw architecture' 카테고리의 다른 글

[OpenTelemetry] observability 아키텍쳐  (0) 2025.02.25
[design] proxy pattern 프록시 패턴  (0) 2022.04.25
반응형

프록시 패턴

  • 인터페이스를 구현을 통해 진짜 구현체를 대신하여 부가기능을 제공하거나 리턴 값을 변경할 수 있도록 대리자(surrogate)나 자리표시자(placeholder)의 역할을 하는 객체를 제공하는 패턴

각 클래스는 자신이 해야 할 일(SRP: Single Responsibility Principle)만 해야하는데 부가적인 기증을 제공할 때 이런 패턴을 주로 사용함

하지만 부가적인 기능을 추가할 때마다 위임하는 코드가 중복해서 발생할 수 있음

코딩 예시: https://bamdule.tistory.com/154

 

[디자인 패턴] 프록시 패턴 (Proxy Pattern)

1. 프록시 패턴이란? 실제 객체를 바로 이용하는 것 대신 가상 객체에 실제 객체를 선언하여 실제 객체의 기능과 추가적인 기능을 사용함으로 써 기능의 흐름을 제어하는 디자인 패턴입니다. 2.

bamdule.tistory.com

 


매번 클래스를 만드는게 아니라 동적으로 생성하는 방식

-> java reflextion을 활용한 다이내믹 프록시 사용

  • (컴파일 타임이 아닌) 런타임에 인터페이스를 구현하는 클래스/프록시 인스턴스를 만들어 사용하는 프로그래밍 기법

프록시 인스턴스 만들기

  • Object Proxy.newProxyInstance(ClassLoader, Interfaces, InvocationHandler)
    • object로 리턴되기 때문에 type casting 필요
//BookService는 반드시 인터페이스여야 함
BookService bookService = (BookService) Proxy.newProxyInstance(BookService.class.getClassLoader(), new Class[]{BookService.class},
        new InvocationHandler() {
            BookService bookService = new DefaultBookService();
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (method.getName().equals("rent")) { //rent 라는 method에만 적용하고 싶다면
                    System.out.println("aaaa");
                    Object invoke = method.invoke(bookService, args);
                    System.out.println("bbbb");
                    return invoke;
                }
             //나머지 method는 그대로 리턴
                return method.invoke(bookService, args);
            }
        });

근데 매번 이렇게 구현하는 게 더 복잡, 유연한 구조가 아님.

그리고 클래스 기반의 (자바가 제공하는) proxy를 만들 수 없음.

-> 그래서 Spring AOP(프록시 기반의 AOP)가 등장

 


클래스의 프록시가 필요하다면?

-> subclass를 만들어서 프록시를 만드는 라이브러리를 사용

 

CGlib lib 사용

  • https://github.com/cglib/cglib/wiki
  • 스프링, 하이버네이트가 사용하는 라이브러리
  • 버전 호환성이 좋지 않아서 서로 다른 라이브러리 내부에 내장된 형태로 제공되기도 한다.
MethodInterceptor handler = new MethodInterceptor() {
    BookService bookService = new BookService(); //class
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        if(method.getName().equals("rent")){ //method 이름이 rent 일 때만 작업
            log.info("aaa");
            Objet invoke = method.invoke(bookService, args);
            log.info("bb");
            return invoke;
        }
        //나머지 method는 그대로 리턴
        return method.invoke(bookService, args);
    }
};

BookService bookService = (BookService) Enhancer.create(BookService.class, handler);

 

ByteBuddy lib 사용

  • https://bytebuddy.net/#
  • 스프링에서 버전 관리해줌
  • 바이트 코드 조작뿐 아니라 런타임(다이내믹) 프록시를 만들 때도 사용할 수 있다.
Class<? extends BookService> proxyClass = new ByteBuddy().subclass(BookService.class)
        .method(named("rent")) //method 이름이 rent면 적용
        .intercept(InvocationHanderAdaptor.of(new InvocationHander(){
            BookService bookService = new BookService();
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
                log.info("aaa");
                Object invoke = method.invoke(bookService, args);
                log.info("Bbb");
                return invoke;
            }
        }))
        .make().load(BookService.class.getClassLoader()).getLoaded();
BookService bookService = proxyClass.getConstructor(null).newInstance();

 

서브 클래스를 만드는 방법의 단점

  • 상속(extends)을 사용하지 못하는 경우 프록시를 만들 수 없음
    • private 생성자(하위 클래스에서 항상 부모의 생성자를 호출한다)
    • final 클래스
  • 인터페이스가 있을 때는 인터페이스의 프록시를 만들어 사용할 것.

 

다이내믹 프록시 기법이 사용되는 곳

  • 스프링 데이터 JPA
  • 스프링 AOP
  • Mockito
  • 하이버네이트 lazy initialization(@OneToMany 등 entity 들고 있을 때 프록시 객체로 처음에 리턴)

 

728x90
반응형

+ Recent posts