반응형

1. ubuntu OS 가상머신 준비

 

2. docker & docker compose 설치

apt update && apt install -y docker.io docker-compose

 

3. docker-compose.yml 파일 생성

 

4. docker-compose 파일 실행

docker-compose up -d

 

5. docker container 확인

docker ps

 

 

반응형
반응형

도커 이미지 만들기: Dockerfile 작성부터 도커 허브 등록까지 완벽 가이드

안녕하세요, 개발자 여러분! 오늘은 도커(Docker)를 활용해 커스텀 이미지를 만드는 방법에 대해 자세히 알아보겠습니다. 도커 이미지를 만들기 위해 필요한 Dockerfile 작성법부터 이미지 빌드, 그리고 도커 허브(Docker Hub)에 등록하는 과정까지 예시와 함께 단계별로 설명드릴게요. 초보자도 쉽게 따라 할 수 있도록 차근차근 진행하겠습니다. 그럼 시작해볼까요? 🚀


1. Dockerfile 작성하는 방법 (+ Dockerfile 문법)

Dockerfile이란?

Dockerfile은 도커 이미지를 생성하기 위한 설계도입니다. 이 파일에는 도커 이미지를 구성하는 명령어들이 포함되어 있으며, 이를 기반으로 도커가 이미지를 빌드합니다. Dockerfile은 보통 프로젝트 루트 디렉토리에 위치하며, 파일 이름은 반드시 Dockerfile로 지정해야 합니다.

Dockerfile 기본 문법

Dockerfile은 몇 가지 주요 명령어를 사용해 작성됩니다. 각 명령어는 도커 이미지의 레이어(Layer)를 생성하며, 아래는 자주 사용되는 명령어들입니다:

  • FROM: 베이스 이미지를 지정합니다. 모든 DockerfileFROM으로 시작해야 합니다.
    FROM ubuntu:20.04
    위 예시는 Ubuntu 20.04 이미지를 베이스로 사용하겠다는 의미입니다.
  • RUN: 이미지 빌드 중 실행할 명령어를 지정합니다.
    RUN apt-get update && apt-get install -y python3
    위 명령어는 Ubuntu에서 패키지 리스트를 업데이트하고 Python3를 설치합니다.
  • COPY: 로컬 파일을 도커 이미지 내부로 복사합니다.
    COPY ./app /app
    로컬 ./app 디렉토리를 이미지 내부 /app 경로로 복사합니다.
  • WORKDIR: 작업 디렉토리를 설정합니다. 이후 명령어는 해당 디렉토리에서 실행됩니다.
    WORKDIR /app
  • CMD: 컨테이너가 시작될 때 실행할 기본 명령어를 지정합니다. 리스트 형태로 작성하는 것이 일반적입니다.
    CMD ["python3", "app.py"]
  • EXPOSE: 컨테이너가 외부로 노출할 포트를 지정합니다. (참고용으로 사용됨)
    EXPOSE 8080

예시: 간단한 Python Flask 애플리케이션 Dockerfile 작성

간단한 Flask 애플리케이션을 위한 Dockerfile을 작성해보겠습니다. 먼저 프로젝트 구조는 다음과 같다고 가정합니다:

my-flask-app/ ├── app.py └── requirements.txt

app.py는 다음과 같은 간단한 Flask 앱입니다:

from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return "Hello, Docker!" if __name__ == "__main__": app.run(host="0.0.0.0", port=8080)

requirements.txt는 필요한 패키지를 정의합니다:

Flask==2.0.1

이제 Dockerfile을 작성해보겠습니다:

# 베이스 이미지로 Python 3.9 슬림 버전 사용 FROM python:3.9-slim # 작업 디렉토리 설정 WORKDIR /app # 현재 디렉토리의 requirements.txt를 이미지 내부로 복사 COPY requirements.txt . # 필요한 패키지 설치 RUN pip install --no-cache-dir -r requirements.txt # 현재 디렉토리의 모든 파일을 이미지 내부로 복사 COPY . . # 컨테이너가 시작될 때 실행할 명령어 CMD ["python", "app.py"] # 외부로 노출할 포트 지정 EXPOSE 8080

Dockerfile은 Python 이미지를 기반으로, Flask 애플리케이션을 실행할 환경을 설정합니다. 각 명령어가 어떤 역할을 하는지 이해하셨나요?


2. Dockerfile로 이미지 만드는 방법

Dockerfile을 작성했다면, 이제 이를 기반으로 도커 이미지를 빌드(Build)할 차례입니다. 도커 이미지를 빌드하는 과정은 매우 간단합니다.

도커 이미지 빌드 명령어

도커 이미지를 빌드하려면 아래 명령어를 사용합니다:

docker build -t 이미지이름:태그 .
  • -t: 이미지 이름과 태그를 지정합니다. 예: my-flask-app:1.0
  • .: Dockerfile이 위치한 디렉토리를 지정합니다. 현재 디렉토리라면 .로 설정하면 됩니다.

예시: Flask 앱 이미지 빌드

위에서 작성한 Dockerfile을 사용해 이미지를 빌드해봅시다. 프로젝트 디렉토리에서 아래 명령어를 실행합니다:

docker build -t my-flask-app:1.0 .

 

명령어를 실행하면 도커가 Dockerfile을 읽고 각 명령어를 실행하며 이미지를 빌드합니다. 빌드 과정에서 각 단계가 콘솔에 출력되며, 완료되면 지정한 이름과 태그로 이미지가 생성됩니다.

빌드된 이미지 확인

빌드가 완료되면 아래 명령어로 생성된 이미지를 확인할 수 있습니다:

docker images

 

출력 예시:

REPOSITORY       TAG       IMAGE ID       CREATED          SIZE
my-flask-app     1.0       7b9a1f2c3d4e   2 minutes ago    123MB

컨테이너 실행 테스트

빌드한 이미지를 실행해 제대로 동작하는지 확인해봅시다:

docker run -p 8080:8080 my-flask-app:1.0
  • -p: 호스트와 컨테이너의 포트를 매핑합니다. 8080:8080은 호스트의 8080 포트를 컨테이너의 8080 포트에 연결합니다.

브라우저에서 http://localhost:8080에 접속하면 "Hello, Docker!" 메시지가 표시됩니다. 성공적으로 동작하는군요! 🎉


3. 이미지 도커 허브에 등록하는 방법

이제 만든 도커 이미지를 도커 허브(Docker Hub)에 업로드해 다른 사람과 공유하거나 다른 환경에서 사용할 수 있도록 해보겠습니다.

도커 허브란?

도커 허브는 도커 이미지를 저장하고 공유할 수 있는 클라우드 기반 레지스트리입니다. GitHub처럼 공개/비공개 리포지토리를 만들 수 있으며, 무료 계정으로도 충분히 활용 가능합니다.

도커 허브 계정 생성

먼저 도커 허브 계정이 필요합니다. 도커 허브 웹사이트에 접속해 계정을 생성하세요.

도커 CLI에서 로그인

터미널에서 도커 허브에 로그인합니다:

docker login

 

명령어를 실행하면 사용자 이름과 비밀번호를 입력하라는 메시지가 나타납니다. 도커 허브 계정 정보를 입력하면 로그인이 완료됩니다.

이미지 태그 변경

도커 허브에 업로드하려면 이미지 태그를 사용자이름/이미지이름:태그 형식으로 변경해야 합니다. 예를 들어, 제 도커 허브 사용자 이름이 johndoe라면 아래 명령어로 태그를 변경합니다:

docker tag my-flask-app:1.0 johndoe/my-flask-app:1.0

 

태그 변경 후 docker images 명령어로 확인해보면 새로운 태그가 추가된 것을 볼 수 있습니다.

도커 허브에 이미지 푸시

이제 이미지를 도커 허브로 업로드합니다:

docker push johndoe/my-flask-app:1.0

 

업로드가 완료되면 도커 허브 웹사이트에서 리포지토리가 생성되고 이미지를 확인할 수 있습니다.

다른 환경에서 이미지 사용

도커 허브에 업로드된 이미지는 다른 환경에서 쉽게 다운로드해 사용할 수 있습니다:

docker pull johndoe/my-flask-app:1.0
docker run -p 8080:8080 johndoe/my-flask-app:1.0

 


마무리

이제 여러분은 Dockerfile 작성부터 도커 이미지 빌드, 그리고 도커 허브에 등록하는 전 과정을 익혔습니다! 도커를 활용하면 애플리케이션 배포와 환경 설정이 훨씬 간편해지죠. 이번 예제는 간단한 Flask 앱을 기반으로 진행했지만, 여러분의 프로젝트에 맞게 Dockerfile을 커스터마이징해 다양한 이미지를 만들어보세요.

 
반응형
반응형

MSA에서 서비스 통신 방법: 동기, 비동기, 그리고 그 너머

안녕하세요! 오늘은 마이크로서비스 아키텍처(MSA)에서 서비스 간 통신을 구현하는 다양한 방법을 살펴보고, 각각의 장단점을 정리해보려고 합니다. MSA에서는 서비스가 독립적으로 동작하기 때문에 통신 방식이 성능, 유연성, 유지보수성에 큰 영향을 미치죠. 이번 글에서는 동기 방식(HTTP/OpenFeign), 비동기 방식(메시지큐/Kafka), 그리고 제로 페이로드, GraphQL, gRPC까지 다뤄보겠습니다. 그럼 시작해볼까요?

1. 동기 통신: HTTP (OpenFeign)

설명:
HTTP는 MSA에서 가장 널리 사용되는 동기 통신 방식입니다. RESTful API를 통해 서비스 간 요청과 응답을 주고받죠. Spring Boot에서는 OpenFeign 같은 선언적 HTTP 클라이언트를 사용하면 코드가 간결해지고 외부 API 호출이 쉬워집니다. 예를 들어, 주문 서비스가 상품 서비스에 "재고 확인" 요청을 보내고 즉시 응답을 받는 식이에요.

장점:

  • 직관적: 요청과 응답이 바로 이어져 로직이 단순하고 이해하기 쉬움.
  • 즉시성: 결과를 실시간으로 받아야 하는 경우 적합 (예: 결제 처리).
  • 생태계: REST API는 표준화되어 있어 도구와 라이브러리가 풍부함.

단점:

  • 강한 결합: 호출하는 서비스가 응답을 기다려야 하므로 의존성이 강해질 수 있음.
  • 지연: 응답이 느리거나 호출받는 서비스가 다운되면 전체 흐름이 멈춤.
  • 확장성 한계: 트래픽이 많아지면 동기 호출이 병목현상을 일으킬 가능성 있음.

2. 비동기 통신: 메시지큐 (Kafka)

설명:
비동기 통신은 메시지큐를 통해 서비스 간 데이터를 전달합니다. Apache Kafka는 대표적인 메시지 브로커로, 프로듀서가 토픽에 메시지를 보내고 컨슈머가 이를 비동기적으로 처리하죠. 예를 들어, 주문 서비스가 "주문 생성" 이벤트를 Kafka에 보내고, 배송 서비스가 나중에 이를 받아 처리하는 방식입니다.

장점:

  • 느슨한 결합: 서비스가 서로 직접 호출하지 않아 독립성이 높아짐.
  • 확장성: 높은 처리량과 대규모 이벤트 처리가 가능 (Kafka의 강점!).
  • 장애 내성: 한 서비스가 실패해도 메시지가 큐에 남아 있어 복구 후 처리 가능.

단점:

  • 복잡성: 메시지 브로커 설정, 토픽 관리 등 추가 인프라가 필요.
  • 지연 가능성: 실시간 응답이 필요한 경우 부적합.
  • 추적 어려움: 메시지 흐름이 분산되어 디버깅이 까다로울 수 있음.

3. 제로 페이로드 (Zero Payload)

설명:
제로 페이로드는 데이터 자체를 주고받지 않고, 참조(예: ID나 URL)만 전달하는 방식입니다. 수신 서비스가 필요한 데이터를 직접 조회하죠. 예를 들어, 주문 서비스가 "상품 ID"만 보내고, 재고 서비스가 DB에서 해당 데이터를 가져오는 식이에요.

장점:

  • 경량화: 네트워크 부하가 줄어 빠른 통신 가능.
  • 유연성: 수신 측에서 필요한 데이터만 조회해 처리 가능.
  • 단순성: 페이로드 구조를 신경 쓸 필요 없음.

단점:

  • 추가 요청: 데이터를 조회하려면 별도 호출이 필요해 지연 발생 가능.
  • 의존성: 수신 서비스가 데이터 소스에 접근할 수 있어야 함.
  • 일관성 문제: 참조 데이터가 변경되면 불일치 위험이 있음.

4. GraphQL

설명:
GraphQL은 클라이언트가 필요한 데이터만 요청할 수 있는 쿼리 언어 기반 API입니다. MSA에서는 여러 서비스의 데이터를 하나의 GraphQL 엔드포인트로 통합해 제공하거나, 서비스 간 통신에 사용할 수 있어요. 예를 들어, 주문 서비스가 상품과 사용자 데이터를 한 번에 요청할 수 있죠.

장점:

  • 효율성: 필요한 데이터만 받아 과다/과소 페칭 문제를 해결.
  • 유연성: 클라이언트가 요청 형식을 자유롭게 정의 가능.
  • 단일 엔드포인트: 여러 서비스 호출을 하나로 통합 가능.

단점:

  • 복잡성: 서버 측에서 스키마 정의와 리졸버 구현이 필요.
  • 캐싱 어려움: HTTP GET 기반 REST에 비해 캐싱이 까다로움.
  • 오버헤드: 쿼리 파싱과 실행에 추가 리소스 소요.

5. gRPC

설명:
gRPC는 Google에서 개발한 고성능 RPC(Remote Procedure Call) 프레임워크로, HTTP/2와 Protocol Buffers(Protobuf)를 사용합니다. MSA에서 서비스 간 빠르고 효율적인 통신을 위해 사용되죠. 예를 들어, 주문 서비스가 재고 서비스의 함수를 직접 호출하듯 사용할 수 있습니다.

장점:

  • 고성능: 이진 데이터(Protobuf)와 HTTP/2로 속도가 빠름.
  • 타입 안정성: Protobuf로 인터페이스 정의 시 컴파일 타임에 오류 체크 가능.
  • 스트리밍 지원: 양방향 스트리밍으로 실시간 통신 가능.

단점:

  • 학습 곡선: Protobuf와 gRPC 설정이 처음에는 낯설 수 있음.
  • 호환성: REST에 비해 브라우저 지원이 제한적.
  • 복잡성: 설정과 디버깅이 HTTP보다 까다로울 수 있음.

어떤 방식을 선택할까?

  • 실시간 응답 필요: HTTP(OpenFeign) 또는 gRPC.
  • 대규모 이벤트 처리: Kafka.
  • 최소 데이터 전송: 제로 페이로드.
  • 클라이언트 유연성: GraphQL.
  • 최고 성능: gRPC.

각 방법은 사용 사례와 요구사항에 따라 빛을 발합니다. 예를 들어, 결제처럼 즉시 응답이 필요한 경우는 OpenFeign, 주문 이벤트 전파처럼 비동기 처리가 적합한 경우는 Kafka를 선택하면 좋겠죠. 여러 방식을 조합해 사용하는 것도 좋은 전략이에요!

마무리

MSA에서 서비스 통신은 시스템의 성공을 좌우하는 핵심 요소입니다. 이번 글에서 다룬 다섯 가지 방법의 장단점을 이해하고, 프로젝트에 맞는 방식을 골라보세요.

반응형
반응형

Kafka와 Spring Boot로 메시지 보내고 받기

안녕하세요! 오늘은 분산 메시징 시스템인 Apache Kafka를 Spring Boot와 함께 사용해 메시지를 보내고 받는 방법을 알아보려고 합니다. Kafka는 대규모 데이터 처리나 실시간 스트리밍에 강력한 도구인데요, Spring Boot와 통합하면 설정도 간단하고 코드도 깔끔해집니다. 바로 시작해볼까요?

Kafka란?

Kafka는 LinkedIn에서 개발된 오픈소스 메시징 시스템으로, 높은 처리량과 확장성을 자랑합니다. **프로듀서(Producer)**가 메시지를 보내고, **컨슈머(Consumer)**가 이를 받아 처리하는 방식으로 동작하죠. 메시지는 **토픽(Topic)**이라는 카테고리에 저장되며, 필요에 따라 여러 컨슈머가 동일한 토픽을 구독할 수 있습니다.

이제 Spring Boot에서 Kafka를 활용하는 방법을 단계별로 살펴보겠습니다.

Spring Boot에서 Kafka 사용하기

Kafka를 사용하려면 먼저 Kafka 브로커가 실행 중이어야 합니다. 로컬에서 테스트하려면 Kafka 공식 사이트에서 다운로드해 실행하거나, Docker를 사용하는 것도 추천드려요. 여기서는 기본 설정을 기준으로 진행하겠습니다.

<카프카 실행 실습>

1. 의존성 추가

pom.xml에 Spring Kafka 의존성을 추가합니다.

 
<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>

 

Spring Boot 버전에 따라 자동으로 적합한 Kafka 클라이언트가 포함되니, 별도로 버전을 명시하지 않아도 됩니다.

2. Kafka 설정 (application.yml)

Kafka 브로커와 연결 설정을 application.yml에 추가합니다.

spring:
  kafka:
    bootstrap-servers: localhost:9092  # Kafka 브로커 주소
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
    consumer:
      group-id: my-group  # 컨슈머 그룹 ID
      auto-offset-reset: earliest  # 처음부터 메시지 읽기
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
  • bootstrap-servers: Kafka 브로커가 실행 중인 주소.
  • serializers/deserializers: 메시지의 키와 값을 문자열로 직렬화/역직렬화.
3. 메시지 보내기 (Producer)

Spring Kafka는 KafkaTemplate을 제공해 메시지 전송을 쉽게 처리할 수 있습니다. 토픽에 메시지를 보내는 서비스를 만들어보죠.

import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
public class KafkaProducerService {

    private static final String TOPIC = "my-topic";
    private final KafkaTemplate<String, String> kafkaTemplate;

    public KafkaProducerService(KafkaTemplate<String, String> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void sendMessage(String message) {
        kafkaTemplate.send(TOPIC, message);
        System.out.println("Message sent: " + message);
    }
}
  • KafkaTemplate: Spring이 자동으로 주입해주는 템플릿.
  • send: 토픽 이름과 메시지를 인자로 전달해 전송.
4. 컨트롤러에서 Producer 호출

간단한 REST 엔드포인트를 만들어 메시지를 보내보겠습니다.

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class KafkaController {

    private final KafkaProducerService producerService;

    public KafkaController(KafkaProducerService producerService) {
        this.producerService = producerService;
    }

    @PostMapping("/send")
    public String sendMessage(@RequestParam String message) {
        producerService.sendMessage(message);
        return "Message sent to Kafka: " + message;
    }
}

 

5. 메시지 받기 (Consumer)

이제 토픽에서 메시지를 받아오는 컨슈머를 작성합니다. @KafkaListener 어노테이션을 사용하면 매우 간단하죠.

import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;

@Service
public class KafkaConsumerService {

    @KafkaListener(topics = "my-topic", groupId = "my-group")
    public void listen(String message) {
        System.out.println("Received message: " + message);
    }
}
  • @KafkaListener: 지정한 토픽의 메시지를 감지해 메서드를 실행.
  • groupId: 컨슈머 그룹을 식별. 같은 그룹 내 컨슈머는 메시지를 나누어 처리.
6. 실행 및 테스트
  1. Kafka 브로커를 실행합니다 (예: bin/kafka-server-start.sh config/server.properties).
  2. Spring Boot 애플리케이션을 실행합니다.
  3. Postman이나 curlPOST http://localhost:8080/send?message=Hello Kafka! 요청을 보냅니다.

그러면 콘솔에서:

  • Producer: "Message sent: Hello Kafka!"
  • Consumer: "Received message: Hello Kafka!"

이렇게 출력되는 걸 확인할 수 있습니다!

추가 팁: JSON 메시지 처리

문자열 대신 JSON 객체를 주고받고 싶다면, JsonSerializerJsonDeserializer를 사용하세요. 의존성에 jackson-databind를 추가하고 설정을 변경하면 됩니다.

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

 

application.yml 수정:

spring:
  kafka:
    producer:
      value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
    consumer:
      value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
      properties:
        spring.json.trusted.packages: "*"  # 역직렬화 시 신뢰할 패키지

 

그리고 DTO 클래스를 만들어 사용하면 됩니다 (예: Message 클래스).

마무리

Spring Boot와 Kafka를 함께 사용하면 메시지 기반 시스템을 정말 쉽게 구축할 수 있습니다. 이번 예제는 기본적인 Producer와 Consumer 설정을 다뤘는데요, 토픽 파티션 관리나 에러 처리 등 더 깊은 주제도 탐구해보시면 좋을 거예요. Kafka의 세계에 입문해보고 싶었다면, 지금이 시작할 완벽한 타이밍입니다!

반응형
반응형

OpenFeign이란? Spring Boot에서 활용하는 방법까지 알아보자

안녕하세요! 오늘은 마이크로서비스 개발에서 자주 활용되는 OpenFeign에 대해 알아보고, Spring Boot에서 이를 어떻게 사용할 수 있는지 예시와 함께 설명해보려고 합니다. REST API 호출을 단순화하고 코드 가독성을 높이고 싶은 분들에게 특히 유용한 도구인데요, 그럼 바로 시작해볼까요?

OpenFeign이란?

OpenFeign은 Netflix에서 개발한 오픈소스 프로젝트로, HTTP 클라이언트 라이브러리입니다. Spring Boot와 같은 프레임워크에서 외부 API를 호출할 때, 복잡한 HTTP 요청 코드를 작성하지 않고 간단한 인터페이스 정의만으로 호출을 처리할 수 있게 해줍니다. 특히 마이크로서비스 아키텍처에서 서비스 간 통신을 쉽게 만들어주는 도구로 사랑받고 있죠.

Feign의 특징을 간단히 정리하면:

  • 선언적 방식: 인터페이스에 어노테이션을 붙여 API 호출을 정의.
  • 통합 용이성: Spring Boot와 결합하면 @FeignClient로 쉽게 설정 가능.
  • 확장성: 로드 밸런싱, 로깅, 에러 처리 등을 커스터마이징할 수 있음.

Spring Cloud에서 OpenFeign을 지원하면서 더욱 강력해졌는데, 이제 실제로 어떻게 사용하는지 예제를 통해 살펴보겠습니다.

Spring Boot에서 OpenFeign 사용하기

Spring Boot 프로젝트에서 OpenFeign을 설정하고 사용하는 과정을 단계별로 정리해볼게요.

1. 의존성 추가

먼저 pom.xml에 OpenFeign 의존성을 추가해야 합니다. Spring Cloud를 사용하므로 아래와 같이 설정합니다.

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

 

Spring Cloud 의존성 버전을 관리하려면 <dependencyManagement>에 Spring Cloud BOM(Bill of Materials)을 추가하는 것도 잊지 마세요.

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2023.0.0</version> <!-- 최신 버전 확인 필요 -->
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

 

2. Feign 활성화

Spring Boot 애플리케이션 클래스에 @EnableFeignClients 어노테이션을 추가해 Feign 클라이언트를 활성화합니다.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

 

3. Feign 클라이언트 인터페이스 정의

이제 외부 API를 호출할 인터페이스를 작성합니다. 예를 들어, JSONPlaceholder라는 무료 API에서 게시글 데이터를 가져오는 클라이언트를 만들어볼게요.

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(name = "jsonplaceholder", url = "https://jsonplaceholder.typicode.com")
public interface PostClient {

    @GetMapping("/posts/{id}")
    Post getPostById(@PathVariable("id") Long id);
}
  • @FeignClient: 클라이언트의 이름과 호출할 기본 URL을 지정.
  • @GetMapping: HTTP 메서드와 엔드포인트를 정의. Spring의 REST 어노테이션을 사용.
  • Post: 응답 데이터를 매핑할 DTO 클래스 (아래에 정의).

 

4. DTO 클래스 작성

API 응답을 받을 데이터 객체를 정의합니다.

public class Post {
    private Long id;
    private Long userId;
    private String title;
    private String body;

    // 기본 생성자, getter, setter 생략 (Lombok 사용 가능)
}

 

5. 서비스에서 클라이언트 사용

이제 Feign 클라이언트를 서비스 계층에서 주입받아 사용해봅시다.

import org.springframework.stereotype.Service;

@Service
public class PostService {

    private final PostClient postClient;

    public PostService(PostClient postClient) {
        this.postClient = postClient;
    }

    public Post fetchPost(Long id) {
        return postClient.getPostById(id);
    }
}

 

6. 컨트롤러에서 호출

마지막으로 컨트롤러에서 서비스를 호출해 결과를 확인합니다.

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PostController {

    private final PostService postService;

    public PostController(PostService postService) {
        this.postService = postService;
    }

    @GetMapping("/posts/{id}")
    public Post getPost(@PathVariable Long id) {
        return postService.fetchPost(id);
    }
}

 

7. 실행 및 테스트

애플리케이션을 실행한 뒤, 브라우저나 Postman으로 http://localhost:8080/posts/1에 접속하면 JSONPlaceholder에서 ID가 1인 게시글 데이터를 JSON 형태로 받아볼 수 있습니다!

추가 팁: 로깅과 에러 처리

  • 로깅: application.ymlfeign.client.config.jsonplaceholder.logging-level: FULL을 추가하면 요청/응답을 자세히 로그로 확인할 수 있어요.
  • 에러 처리: FeignException을 캐치하거나, @FeignClientfallback 속성을 추가해 예외 발생 시 대체 로직을 설정할 수도 있습니다.

마무리

OpenFeign은 복잡한 HTTP 호출 코드를 인터페이스 하나로 깔끔하게 정리할 수 있게 해주는 강력한 도구입니다. Spring Boot와 함께 사용하면 설정도 간단하고, 마이크로서비스 환경에서 특히 빛을 발하죠. 이번 예제를 참고해 직접 적용해보시며, OpenFeign을 사용해보세요!

반응형
반응형

동시성 문제를 해결하는 4가지 방법

안녕하세요, 오늘은 멀티스레드 환경에서 발생하는 동시성 문제를 해결하는 네 가지 방법에 대해 이야기해보려고 합니다. 동시성 문제는 여러 스레드가 동시에 같은 자원에 접근하면서 데이터 무결성이 깨질 수 있는 상황을 말하는데요. 이를 해결하기 위해 자바와 Spring Boot에서 사용할 수 있는 다양한 접근법을 예시와 함께 알아보겠습니다.

1. 자바 synchronized 키워드

가장 기본적인 동시성 제어 방법 중 하나는 자바의 synchronized 키워드를 사용하는 것입니다. 이 방식은 특정 코드 블록이나 메서드에 대해 한 번에 하나의 스레드만 접근할 수 있도록 잠금을 걸어줍니다.

언제 사용하나요?

  • 간단한 동기화가 필요한 경우
  • 객체 단위로 락을 걸어 데이터 무결성을 지키고 싶을 때

Spring Boot 예시

재고 관리 시스템에서 동시에 재고를 감소시키는 상황을 생각해봅시다.

@Service
public class StockService {
    private int stock = 100;

    public synchronized void decreaseStock(int quantity) {
        if (stock >= quantity) {
            stock -= quantity;
            System.out.println("재고 감소! 남은 재고: " + stock);
        } else {
            throw new RuntimeException("재고 부족!");
        }
    }
}
 

위 코드에서 synchronized를 사용하면 여러 스레드가 동시에 decreaseStock 메서드에 접근하더라도 순차적으로 실행됩니다. 단점은 성능이 중요한 대규모 시스템에서는 병목 현상이 발생할 수 있다는 점입니다.


2. 낙관적 락 (Optimistic Lock)

낙관적 락은 "충돌이 별로 없을 거야!"라는 낙관적인 가정을 기반으로 동시성을 제어합니다. 데이터를 수정할 때 충돌 여부를 버전 번호나 타임스탬프를 통해 체크하고, 충돌이 발생하면 예외를 던져서 처리합니다.

언제 사용하나요?

  • 충돌 가능성이 낮은 환경
  • 성능 최적화가 중요한 경우

Spring Boot 예시

JPA를 사용해 낙관적 락을 구현해보겠습니다. @Version 어노테이션을 활용하면 됩니다.

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private int stock;

    @Version
    private int version; // 버전 필드 추가
}

@Service
public class ProductService {
    @Autowired
    private ProductRepository productRepository;

    @Transactional
    public void decreaseStock(Long productId, int quantity) {
        Product product = productRepository.findById(productId)
                .orElseThrow(() -> new RuntimeException("상품 없음"));
        if (product.getStock() >= quantity) {
            product.setStock(product.getStock() - quantity);
            productRepository.save(product);
        } else {
            throw new RuntimeException("재고 부족!");
        }
    }
}
 

위 코드에서 트랜잭션이 끝날 때 JPA가 버전을 체크합니다. 다른 스레드가 먼저 수정해서 버전이 달라지면 OptimisticLockException이 발생하고, 이를 통해 충돌을 감지합니다. 성능이 비관적 락보다 좋지만, 충돌이 자주 발생하면 롤백 처리가 늘어날 수 있어요.


3. 비관적 락 (Pessimistic Lock)

비관적 락은 "충돌이 자주 발생할 거야!"라는 비관적인 가정을 하고, 아예 트랜잭션 시작 시점에 데이터베이스에 락을 겁니다. 다른 스레드는 락이 해제될 때까지 기다려야 합니다.

언제 사용하나요?

  • 충돌 가능성이 높은 환경
  • 데이터 무결성이 매우 중요한 경우 (예: 금융 시스템)

Spring Boot 예시

JPA에서 @Lock 어노테이션을 사용해 비관적 락을 설정할 수 있습니다.

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT p FROM Product p WHERE p.id = :id")
    Optional<Product> findByIdWithPessimisticLock(@Param("id") Long id);
}

@Service
public class ProductService {
    @Autowired
    private ProductRepository productRepository;

    @Transactional
    public void decreaseStock(Long productId, int quantity) {
        Product product = productRepository.findByIdWithPessimisticLock(productId)
                .orElseThrow(() -> new RuntimeException("상품 없음"));
        if (product.getStock() >= quantity) {
            product.setStock(product.getStock() - quantity);
            productRepository.save(product);
        } else {
            throw new RuntimeException("재고 부족!");
        }
    }
}

 

PESSIMISTIC_WRITE 락을 걸면 해당 레코드는 트랜잭션이 끝날 때까지 다른 스레드가 수정할 수 없습니다. 성능은 낙관적 락보다 떨어지지만, 중요한 데이터의 정확성을 보장할 수 있어요.


4. 캐시 서버 도입

마지막으로, 캐시 서버(예: Redis)를 활용하는 방법입니다. 이건 돈이 좀 들 수 있는 솔루션이지만, 동시성 문제를 근본적으로 줄이고 성능을 크게 향상시킬 수 있습니다. 캐시는 자주 조회되는 데이터를 메모리에 저장해 DB 부하를 줄여줍니다.

언제 사용하나요?

  • 읽기 요청이 많은 시스템
  • DB 부하를 줄이고 응답 속도를 높이고 싶을 때

Spring Boot 예시

Redis를 사용해 재고를 캐싱하고 동시성을 제어해보겠습니다.

@Service
public class StockService {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private static final String STOCK_KEY = "stock:product:1";

    public void decreaseStock(int quantity) {
        String stockStr = redisTemplate.opsForValue().get(STOCK_KEY);
        int stock = stockStr != null ? Integer.parseInt(stockStr) : 100;

        if (stock >= quantity) {
            redisTemplate.opsForValue().set(STOCK_KEY, String.valueOf(stock - quantity));
            System.out.println("재고 감소! 남은 재고: " + (stock - quantity));
        } else {
            throw new RuntimeException("재고 부족!");
        }
    }
}
 

여기서는 단순히 Redis에 재고를 저장했지만, 실제로는 Redis의 SETNX(Set if Not Exists) 같은 명령어나 분산 락(Distributed Lock)을 활용해 동시성을 더 철저히 제어할 수 있습니다. 비용이 들지만 확장성과 성능 면에서 큰 이점이 있어요.


정리: 어떤 방법을 선택할까?

  • synchronized: 간단하고 비용 없음, 소규모 시스템에 적합
  • 낙관적 락: 충돌이 적을 때 성능 좋음, 무료
  • 비관적 락: 충돌이 많고 데이터 무결성이 중요할 때, 무료지만 성능 저하 주의
  • 캐시 서버: 돈은 들지만 성능과 확장성 극대화

프로젝트의 요구사항과 예산에 따라 적절한 방법을 선택하면 됩니다. 예를 들어, 간단한 앱이라면 synchronized나 낙관적 락으로 충분할 수 있고, 대규모 트래픽을 감당해야 한다면 Redis 같은 캐시 서버를 고려해볼 만해요.

이 글이 동시성 문제 해결에 도움이 되셨길 바랍니다.

반응형

'개발 부트캠프 > 백엔드' 카테고리의 다른 글

[Kafka] Kafka 이용하여 Spring Boot로 메시지 보내고 받기  (0) 2025.02.27
[MSA] OpenFeign  (0) 2025.02.27
[Trace] Jaeger  (0) 2025.02.21
[Log] Log 중앙화  (0) 2025.02.21
[Trace] 핀포인트(Pinpoint)  (0) 2025.02.21
반응형

Jaeger란 무엇인가?


Jaeger는 분산 추적 시스템(Distributed Tracing System)을 위한 오픈소스 도구입니다. Uber에서 개발되었으며, 현재는 Cloud Native Computing Foundation(CNCF)의 프로젝트로 관리되고 있습니다. Jaeger는 마이크로서비스 아키텍처에서 애플리케이션의 요청 흐름을 추적하고 성능 문제를 분석하는 데 주로 사용됩니다.


  • 이름의 유래: "Jaeger"는 독일어로 "사냥꾼"을 의미하며, 시스템 내에서 복잡한 요청 경로를 "추적"한다는 의미를 담고 있습니다.
  • 핵심 기능: 분산 시스템에서 발생하는 트랜잭션의 시작부터 끝까지의 흐름을 시각화하고, 지연 시간(latency), 오류, 병목 지점을 파악합니다.

Jaeger의 주요 구성 요소


  1. Jaeger Agent:
    • 애플리케이션에 가까이 배치되어 추적 데이터를 수집.
    • UDP를 통해 데이터를 Jaeger Collector로 전송.
  2. Jaeger Collector:
    • 추적 데이터(스팬)를 수집하고 저장소(예: Elasticsearch, Cassandra)에 저장.
    • 데이터 처리 및 집계 역할.
  3. Jaeger Query & UI:
    • 저장된 추적 데이터를 조회하고 웹 UI로 시각화.
    • 사용자가 트레이스와 스팬을 분석할 수 있는 인터페이스 제공.
  4. 저장소(Storage Backend):
    • 추적 데이터를 영구 저장 (예: Elasticsearch, Cassandra, 메모리).
  5. Instrumentation:
    • 애플리케이션에 추가되는 코드(예: OpenTracing/OpenTelemetry API)로, 추적 데이터를 생성.

Jaeger가 하는 일


Jaeger는 **스팬(Span)**과 **트레이스(Trace)**라는 개념을 기반으로 동작합니다:


  • 스팬(Span): 요청의 단일 작업 단위(예: API 호출, DB 쿼리).
  • 트레이스(Trace): 여러 스팬으로 구성된 요청의 전체 흐름.

예를 들어, 사용자가 웹사이트에서 주문을 하면:


  1. 프론트엔드 → 주문 API → 결제 서비스 → DB 호출로 이어지는 요청 흐름을 하나의 트레이스로 기록.
  2. 각 단계(스팬)의 소요 시간, 성공/실패 여부 등을 추적.

Jaeger를 사용하는 이유


1. 분산 시스템의 복잡성 관리

  • 문제: 마이크로서비스 환경에서는 단일 요청이 여러 서비스를 거치며, 어디서 문제가 발생했는지 파악하기 어려움.
  • 해결: Jaeger는 요청의 전체 경로를 시각화해 병목 지점이나 오류를 빠르게 식별.
  • 예시: 주문 처리 중 결제 서비스에서 2초 지연 발생 시 Jaeger UI에서 해당 스팬 확인.

2. 성능 최적화

  • 문제: 특정 API 호출이 느리거나 응답 시간이 길어지는 이유를 찾기 어려움.
  • 해결: Jaeger는 각 스팬의 지연 시간을 분석해 성능 개선 포인트를 제공.
  • 예시: DB 쿼리 스팬이 500ms 걸린다면 쿼리 튜닝 필요.

3. 장애 분석

  • 문제: 시스템 장애 발생 시 원인을 추적하기 위해 여러 서버 로그를 뒤져야 함.
  • 해결: Jaeger는 오류가 발생한 트레이스와 스팬을 상세히 보여줘 디버깅 시간 단축.
  • 예시: "결제 실패" 오류가 특정 서비스에서 발생한 경우 해당 트레이스에서 확인.

4. 서비스 간 의존성 파악

  • 문제: 복잡한 마이크로서비스 환경에서 서비스 호출 관계를 문서화 없이 알기 어려움.
  • 해결: Jaeger는 의존성 그래프를 생성해 서비스 간 상호작용을 시각화.
  • 예시: 프론트엔드가 주문, 결제, 배송 서비스와 어떻게 연결되는지 한눈에 확인.

5. 실시간 모니터링

  • 문제: 시스템 상태를 실시간으로 파악하려면 별도 모니터링 도구 필요.
  • 해결: Jaeger는 실시간으로 트레이스를 수집하고 UI로 보여줌.
  • 예시: 트래픽 급증 시 요청 처리 속도 변화 모니터링.

6. 오픈소스와 표준 지원

  • 이유: Jaeger는 OpenTracing과 OpenTelemetry를 지원하며, 무료로 사용 가능.
  • 장점: 다양한 언어(Java, Python, Go 등)와 프레임워크(Spring Boot 등)에 쉽게 통합.

 

Jaeger 실습

1) Jaeger 서버

- jaeger 다운로드 및 압축 해제

wget https://github.com/jaegertracing/jaeger/releases/download/v1.66.0/jaeger-2.3.0-linux-amd64.tar.gz

tar zxvf jaeger-2.3.0-linux-amd64.tar.gz

 

- 폴더 이동 후 실행

cd jaeger-2.3.0-linux-amd64

./jaeger

 

- 클라이언트에서 접속 가능하도록 bind address 환경 변수 변경

export JAEGER_LISTEN_HOST=0.0.0.0

 

- 16686포트로 접속



2) 백엔드(Spring) 서버

- Spring Boot 애플리케이션에서 Jaeger 분산 추적 시스템을 설정하는 코드 작성

 

JaegerConfig.java

package com.example.jaeger.config;

import io.jaegertracing.Configuration;
import io.jaegertracing.internal.JaegerTracer;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class JaegerConfig {
    @Bean
    public JaegerTracer jaegerTracer() {
        Configuration.SamplerConfiguration samplerConfig = Configuration.SamplerConfiguration.fromEnv().withType("const").withParam(1);

        Configuration.SenderConfiguration udpSender = Configuration.SenderConfiguration.fromEnv().withAgentHost("[Jaeger Server IP]");

        Configuration.ReporterConfiguration reporterConfig= Configuration.ReporterConfiguration.fromEnv().withLogSpans(true).withSender(udpSender);

        Configuration config = new Configuration("My Project").withSampler(samplerConfig).withReporter(reporterConfig);

        return config.getTracer();
    }
}
Jaeger의 JaegerTracer 객체를 Spring 빈으로 등록하여 애플리케이션에서 추적 데이터를 생성하고 Jaeger 서버로 전송할 수 있도록 합니다.
SamplerConfiguration:
  • 샘플링은 추적할 요청의 비율을 결정합니다. 모든 요청을 추적하면 성능에 부담을 줄 수 있으므로 샘플링 전략이 중요합니다.
SenderConfiguration:
  • 추적 데이터를 Jaeger로 전송하는 방법을 정의합니다.
ReporterConfiguration:
  • 추적 데이터를 어떻게 보고(report)할지 설정합니다.

3) 전체 동작

  1. Spring 초기화:
    • 애플리케이션 시작 시 JaegerConfig가 스캔되고, JaegerTracer 빈이 생성.
  2. 추적 설정:
    • 모든 요청을 추적(const 샘플러).
    • 추적 데이터를 192.0.2.45:6831(Jaeger Agent)로 전송하고 로그에도 기록.
  3. 사용:
    • 컨트롤러나 서비스에서 JaegerTracer를 주입받아 추적 시작.

 

요약 

Jaeger는 분산 시스템에서 요청 흐름을 추적하고, 성능 문제를 빠르게 파악하며, 시스템의 투명성을 확보하기 위해 사용됩니다. Jaeger를 사용하면 문제 진단 시간을 단축하고, 서비스 간 상호작용을 이해할 수 있으며 성능 최적화 및 안정성 향상에 도움이 됩니다. 또한, 마이크로서비스, 클라우드 네이티브 애플리케이션 등의 환경에서 사용하는 것이 적합합니다.

 

 

반응형

'개발 부트캠프 > 백엔드' 카테고리의 다른 글

[MSA] OpenFeign  (0) 2025.02.27
[Java] 동시성 제어하는 네가지 방법  (1) 2025.02.25
[Log] Log 중앙화  (0) 2025.02.21
[Trace] 핀포인트(Pinpoint)  (0) 2025.02.21
[Kafka] Kafka 실행 실습  (0) 2025.02.17
반응형

로그 서버를 따로 두는 이유는 시스템 운영, 성능, 보안, 그리고 유지 관리 측면에서 여러 가지 이점을 얻기 위함입니다. 특히 분산 시스템이나 대규모 애플리케이션 환경에서 로그를 중앙화하는 것은 필수적인 전략으로 여겨집니다. 아래에 주요 이유와 그에 따른 장점을 자세히 설명드리겠습니다.



1. 중앙화된 로그 관리


  • 이유: 여러 서버나 애플리케이션에서 발생하는 로그를 각 시스템에 개별적으로 저장하면 관리가 어려워집니다. 로그 서버를 따로 두면 모든 로그를 한 곳에 모아 분석하고 모니터링할 수 있습니다.
  • 장점:
    • 로그 검색과 분석이 쉬워짐 (예: ELK 스택의 Kibana로 시각화).
    • 문제 발생 시 단일 지점에서 전체 시스템 로그 확인 가능.
    • 예: Log 서버에 로그 데이터를 저장하고 확인.

2. 성능 최적화


  • 이유: 애플리케이션 서버가 로그를 직접 기록하면 디스크 I/O, CPU, 메모리 등의 리소스를 소모합니다. 로그 서버를 분리하면 애플리케이션 서버의 부하를 줄일 수 있습니다.
  • 장점:
    • 애플리케이션 서버는 핵심 비즈니스 로직에 집중 가능.
    • 로그 쓰기 작업이 병목 지점이 되지 않음.
    • 예: 애플리케이션 로그를 수집해 로그 서버의 HBase로 전달하면 애플리케이션 자체는 로그 처리 부담 없음.

3. 보안 강화


  • 이유: 로그에는 민감한 정보(사용자 데이터, 요청 IP 등)가 포함될 수 있습니다. 애플리케이션 서버에 로그를 두면 해킹 시 로그 데이터도 노출될 위험이 큽니다.
  • 장점:
    • 로그 서버를 별도로 보호하고 접근 제어 가능 (예: 방화벽, VPN).
    • 로그 데이터 암호화 및 백업 용이.
    • 예: HBase와 ZooKeeper를 별도 서버에 두고 인증 설정으로 접근 제한.

4. 확장성과 고가용성


  • 이유: 분산 시스템에서는 서버 수가 증가하며 로그 양도 급격히 늘어납니다. 로그 서버를 따로 두면 로그 처리 시스템을 독립적으로 확장할 수 있습니다.
  • 장점:
    • 로그 서버를 클러스터로 구성해 고가용성(HA) 확보 (예: ZooKeeper 앙상블).
    • 대규모 트래픽에도 안정적 로그 처리 가능.
    • 예: Kafka를 로그 스트리밍 서버로 사용해 여러 애플리케이션 로그를 수집.

5. 장애 분석 및 복구


  • 이유: 애플리케이션 서버가 다운되면 로컬 로그에 접근하기 어려울 수 있습니다. 별도의 로그 서버는 애플리케이션 장애와 독립적으로 로그를 보존합니다.
  • 장점:
    • 장애 원인 분석 시 최신 로그 즉시 확인.
    • 로그 백업으로 데이터 손실 방지.
    • 예: 로그 서버에 저장된 로그 데이터로 장애 시점 추적.

6. 실시간 모니터링 및 알림


  • 이유: 중앙화된 로그 서버는 실시간 로그 처리 및 알림 시스템과 통합하기 용이합니다.
  • 장점:
    • 오류 로그 발생 시 즉시 알림 (예: ELK + 슬랙 연동).
    • 실시간 대시보드 제공 (예: Pinpoint Web, Kibana).
    • 예: Pinpoint Web이 Collector와 HBase에서 데이터를 가져와 실시간 UI 제공.

7. 법적/감사 요구사항


  • 이유: 일부 산업(금융, 의료 등)에서는 로그를 장기 보관하고 감사 추적을 해야 할 법적 요구사항이 있습니다.
  • 장점:
    • 별도 로그 서버에서 로그를 체계적으로 보관 및 관리.
    • 규제 준수 용이.

 

log 서버에 다른 백엔드 서버 로그 저장 실습

1) Log 서버

- rsyslog 설치

apt install rsyslog

 

- 환경 설정

vi /etc/rsyslog.conf

------------------------------------------

# provides UDP syslog reception 주석 해제
module(load="imudp") 
input(type="imudp" port="514") 

# provides TCP syslog reception 주석 해제
module(load="imtcp") 
input(type="imtcp" port="514")

 

- rsyslog 재시작

systemctl restart rsyslog

 

- 514 포트 확인

netstat -anlp | grep :514

 

- log 저장할 파일 생성 및 권한 설정

touch  /var/log/spring-application.log

chmod 777 /var/log/spring-application.log

 

- facility와 Log 저장 파일 경로 설정 추가

vi /etc/rsyslog.d/50-default.conf

------------------------------------------
# 마지막 줄에 추가
user.*    /var/log/spring-application.log
  • user facility에서 발생한 로그를 /var/log/spring-application.log 파일에 기록하도록 추가

 

- 재시작

systemctl restart rsyslog

 

- 로그 확인

tail -f /var/log/spring-application.log

 

2) 백엔드 서버(Spring)

- logback 설정파일(xml) 생성 및 애플리케이션 로그를 원격 Syslog 서버로 전송하도록 구성

<configuration>
    <property name="SYSLOG_HOST" value="[LogServer IP]"/>
    <property name="SYSLOG_PORT" value="[LogServer rsyslog PORT]"/>
    <appender name="SYSLOG" class="ch.qos.logback.classic.net.SyslogAppender">
        <syslogHost>${SYSLOG_HOST}</syslogHost>
        <port>${SYSLOG_PORT}</port>
        <facility>USER</facility>
        <suffixPattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %m%n</suffixPattern>
    </appender>
</configuration>
  • SYSLOG_HOST: 로그를 전송할 Syslog 서버의 IP 주소 (예: 192.168.1.100).
  • SYSLOG_PORT: Syslog 서버가 수신하는 포트 번호 (예: 514, rsyslog의 기본 포트).
  • name="SYSLOG": 이 Appender의 이름, 로거에서 참조 시 사용.
  • class="ch.qos.logback.classic.net.SyslogAppender": Syslog 프로토콜을 통해 로그를 전송하는 Logback의 클래스.
  • facility="USER": Syslog 메시지의 출처를 USER로 설정 (Syslog 표준 필드, 애플리케이션 로그 의미).
  • suffixPattern: 로그 메시지의 출력 형식:
    • %d{yyyy-MM-dd HH:mm:ss}: 날짜와 시간 (예: 2025-02-21 12:34:56).
    • [%thread]: 스레드 이름.
    • %-5level: 로그 레벨 (INFO, ERROR 등, 5자리로 정렬).
    • %logger{36}: 로거 이름 (최대 36자).
    • %m: 로그 메시지.
    • %n: 줄 바꿈.

위의 설정을 통해 여러 백엔드 서버의 로그를 하나의 로그 서버에 저장할 수 있습니다.

 

반응형

'개발 부트캠프 > 백엔드' 카테고리의 다른 글

[Java] 동시성 제어하는 네가지 방법  (1) 2025.02.25
[Trace] Jaeger  (0) 2025.02.21
[Trace] 핀포인트(Pinpoint)  (0) 2025.02.21
[Kafka] Kafka 실행 실습  (0) 2025.02.17
[Spring] 환경 변수 설정  (0) 2025.02.15
반응형

핀포인트(Pinpoint)란?

Pinpoint는 애플리케이션 성능 모니터링(APM, Application Performance Monitoring)을 위한 오픈소스 도구입니다. 주로 분산 시스템에서 애플리케이션의 성능 문제를 추적하고 분석하는 데 사용됩니다. Pinpoint는 특히 요청 흐름(request tracing)을 시각화하고, 병목 지점을 식별하며, 실시간 모니터링을 제공하는 데 강점을 가지고 있습니다. 한국의 네이버(Naver)에서 개발했으며, 현재는 오픈소스 프로젝트로 GitHub에서 관리되고 있습니다.
 

핀포인트의 주요 기능

  • 분산 추적(Distributed Tracing):
    • 마이크로서비스 환경에서 하나의 요청이 여러 서비스를 거치는 경로를 추적합니다.
    • 예: 사용자 요청 → 프론트엔드 → 백엔드 API → 데이터베이스 호출 과정을 시각화.

  • 실시간 모니터링:
    • 애플리케이션의 응답 시간, 처리량, 오류율 등을 실시간으로 확인.

  • 성능 분석:
    • 느린 요청이나 오류가 발생한 지점을 정확히 파악.
    • 메서드 레벨까지 세부적으로 성능 데이터 제공.

  • 시각화:
    • 웹 기반 대시보드로 트랜잭션 흐름도, 서버 상태, 통계 등을 표시.

  • 경량 에이전트:
    • 애플리케이션에 추가되는 Pinpoint 에이전트가 성능에 미치는 영향을 최소화.

 

핀포인트의 구성 요소

  1. Pinpoint Agent:
    • 모니터링 대상 애플리케이션(Java 기반)에 설치.
    • 애플리케이션의 요청 데이터(호출 스택, 시간 등)와 같은 성능 관련 지표를 수집해 Collector로 전달.
  2. Pinpoint Collector:
    • 에이전트로부터 수집된 데이터를 받아 HBase에 저장.
    • ZooKeeper로 데이터 조정 및 클러스터 관리.
  3. Pinpoint Web:
    • 저장된 데이터를 웹 UI로 시각화.
    • 사용자 인터페이스를 통해 모니터링 및 분석 제공.
  4. HBase:
    • Pinpoint의 백엔드 데이터베이스.
    • 수집된 추적 데이터와 통계를 저장.
  5. ZooKeeper:
    • Collector와 Web 간 데이터 동기화 및 클러스터 상태 관리.

 

사용 사례 및 장 단점

 

사용 사례

  • 대규모 서비스: 네이버, 쿠팡 등 트래픽이 많은 환경에서 사용.
  • 마이크로서비스: 서비스 간 호출 분석.
  • 장애 분석: 느린 응답이나 오류 원인 파악.

장점

  • 코드 수정 최소화: 에이전트를 붙이는 것만으로 모니터링 가능.
  • 상세 추적: 메서드 단위까지 분석.
  • 오픈소스: 무료로 사용 가능, 커뮤니티 지원.

단점

  • 설정 복잡성: HBase, ZooKeeper 등 의존성 설치 필요.
  • 리소스 사용: 대규모 데이터 처리 시 서버 부하 증가.

 

실습 방법

1) 저장소 및 Web 서버 설정

 

1. 자바 설치

apt update

apt install -y openjdk-8-jdk

 

2. HBase 다운로드 및 압축 해제

wget https://archive.apache.org/dist/hbase/1.2.7/hbase-1.2.7-bin.tar.gz

tar zxvf hbase-1.2.7-bin.tar.gz

 

3. HBase 환경 변수 수정

 

vi ./hbase-1.2.7/conf/hbase-env.sh

------------------------------------------------------

# 28번 라인 수정
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64

# 44, 47, 48 라인 주석 처리 
# export HBASE_OPTS="-XX:+UseConcMarkSweepGC"
# export HBASE_MASTER_OPTS="$HBASE_MASTER_OPTS -XX:Per    mSize=128m -XX:MaxPermSize=128m -XX:ReservedCodeCacheS    ize=256m"
# export HBASE_REGIONSERVER_OPTS="$HBASE_REGIONSERVER_    OPTS -XX:PermSize=128m -XX:MaxPermSize=128m -XX:Reserv    edCodeCacheSize=256m"

 

4. HBase 실행

./hbase-1.2.7/bin/start-hbase.sh
  • HBase 마스터와 리전 서버를 시작. 이 과정에서 ZooKeeper도 함께 시작됨(기본적으로 HBase 1.2.7은 번들 ZooKeeper 포함).

5. HBase 테이블 생성 스크립트 다운로드 및 테이블 생성

wget https://raw.githubusercontent.com/pinpoint-apm/pinpoint/master/hbase/scripts/hbase-create.hbase

./hbase-1.2.7/bin/hbase shell ./hbase-create.hbase
  • Pinpoint에서 제공하는 HBase 테이블 생성 스크립트(hbase-create.hbase)를 다운로드
  • 이 스크립트는 Pinpoint가 데이터를 저장할 HBase 테이블을 정의 및 생성 (예: ApplicationTraceIndex, AgentStat 등)

6. Pinpoint Collector 다운로드 및 실행

wget https://github.com/pinpoint-apm/pinpoint/releases/download/v2.3.3/pinpoint-collector-boot-2.3.3.jar

nohup java -jar -Dpinpoint.zookeeper.address=localhost pinpoint-collector-boot-2.3.3.jar > collector.log 2> collector-error.log &
  • nohup: 프로세스가 터미널 종료 후에도 백그라운드에서 계속 실행되도록 함.
  • java -jar: JAR 파일을 실행.
  • -Dpinpoint.zookeeper.address=localhost: JVM 속성으로 ZooKeeper 주소를 localhost로 설정 (기본 포트 2181 가정).
  • > collector.log: 표준 출력을 collector.log 파일로 리다이렉트.
  • 2> collector-error.log: 표준 에러를 collector-error.log로 리다이렉트.
  • &: 백그라운드에서 실행.

7. Pinpoint Web 다운로드 및 실행

wget https://github.com/pinpoint-apm/pinpoint/releases/download/v2.3.3/pinpoint-web-boot-2.3.3.jar

nohup java -jar -Dpinpoint.zookeeper.address=localhost pinpoint-web-boot-2.3.3.jar > web.log 2> web-error.log &

 

 

8. 8080 포트로 Pinpoint Web 접속

 

 

2) Agent 설정

 

1. 자바 설치

apt update

apt install -y openjdk-17-jdk

 

2. Pinpoint Agent다운로드 및 압축해제

wget https://repo1.maven.org/maven2/com/navercorp/pinpoint/pinpoint-agent/3.0.1/pinpoint-agent-3.0.1.tar.gz

tar zxvf pinpoint-agent-3.0.1.tar.gz

 

3. Pinpoint Agent 환경 설정에서 collector IP 주소 변경

vi ./pinpoint-agent-3.0.1/profiles/release/pinpoint.config

----------------------------------------------------

#23번 라인 수정
profiler.transport.grpc.collector.ip=[Pinpoint Collector IP Adress]

 

4. Pinpoint Agent와 함께 배포한 백엔드 서버(java) 실행

java -jar \
  -javaagent:/root/pinpoint-agent-3.0.1/pinpoint-bootstrap-3.0.1.jar \
  -Dpinpoint.agentId=my-project \
  -Dpinpoint.applicationName=spring-server \
backend-ver-SNAPSHOT.jar

 

요약

Pinpoint는 분산 시스템의 성능을 모니터링하고 요청 흐름을 추적하는 도구로, HBase와 ZooKeeper를 활용해 데이터를 저장하고 시각화합니다. 애플리케이션 성능 문제를 쉽게 파악하고 싶을 때 유용하며, 특히 Java 기반 시스템에서 강력한 기능을 발휘합니다.

반응형

'개발 부트캠프 > 백엔드' 카테고리의 다른 글

[Trace] Jaeger  (0) 2025.02.21
[Log] Log 중앙화  (0) 2025.02.21
[Kafka] Kafka 실행 실습  (0) 2025.02.17
[Spring] 환경 변수 설정  (0) 2025.02.15
[Spring] @Component와 @Bean의 차이점  (0) 2025.02.15
반응형

Nginx에서 백엔드 서버에 부하 분산(load balancing)을 설정하려면 upstream 모듈을 사용하여 여러 백엔드 서버를 정의하고, proxy_pass를 통해 요청을 분배하도록 설정할 수 있습니다. 기본적인 설정 예시는 아래와 같습니다:

1. upstream 블록 정의

upstream 블록을 사용하여 백엔드 서버들을 정의합니다. 여러 서버를 등록할 수 있으며, Nginx가 요청을 분배합니다.

upstream backend {
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com;
    # 로드 밸런싱 알고리즘을 변경할 수 있습니다 (예: round-robin, least_conn, ip_hash 등)
}

server {
    listen 80;

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

2. 로드 밸런싱 알고리즘

Nginx는 기본적으로 round-robin 방식으로 부하 분산을 처리합니다. 그러나 다른 알고리즘을 사용하려면 upstream 블록 내에 추가적인 옵션을 설정할 수 있습니다.

  • least_conn: 연결 수가 가장 적은 서버로 요청을 분배
  • ip_hash: 클라이언트 IP 주소에 따라 서버를 지정하여 동일한 클라이언트가 항상 같은 서버로 연결되도록 함

예시:

upstream backend {
    least_conn;  # 연결 수가 적은 서버로 분배
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com;
}

--------------------------------------

upstream backend {
    ip_hash;  # 클라이언트 IP를 기반으로 동일 서버로 요청을 보냄
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com;
}
 

3. 헬스 체크 및 서버 다운 시 처리

서버가 다운되었을 때 다른 서버로 요청을 전달하도록 설정하려면 down 옵션을 사용할 수 있습니다. 또한, 서버의 상태를 체크하여 문제가 발생한 서버를 자동으로 제외하는 설정도 가능합니다.

upstream backend {
    server backend1.example.com check;
    server backend2.example.com check;
    server backend3.example.com;
}

이렇게 설정하면 Nginx가 주기적으로 서버 상태를 점검하고 문제가 있을 경우 자동으로 제외합니다.

4. 기타 설정

추가로 필요한 설정들을 location 블록에 추가할 수 있습니다. 예를 들어, proxy_set_header를 사용하여 클라이언트의 실제 IP를

백엔드 서버에 전달하는 등의 설정을 할 수 있습니다.

 
location / {
    proxy_pass http://backend;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Connection '';
    proxy_http_version 1.1;
    proxy_cache_bypass $http_upgrade;
}

이 설정은 백엔드 서버로 요청을 전달할 때 추가적인 헤더 정보를 제공하여 클라이언트의 IP와 요청 정보를 정확히 전달하도록 돕습니다.

5. 설정 적용 후 테스트

설정을 완료한 후에는 Nginx를 재시작하거나 리로드하여 변경 사항을 적용합니다:

 
sudo systemctl restart nginx  # 또는
sudo nginx -s reload

이렇게 하면 Nginx에서 백엔드 서버에 대한 부하 분산을 설정할 수 있습니다.

 

6. 활용 예시

vi /etc/nginx/sites-enabled/default

-------------------------------------------

upstream tomcat_backend {
    server backend_server_IP1:8080;
    server backend_server_IP1:8080;
    server backend_server_IP1:8080;
} 
server { 
    listen 80 default_server;
    listen [::]:80 default_server; 
    root /var/www/html; index index.html index.htm index.nginx-debian.html;
    servername ; 
    location / { 
	    try_files $uri $uri/ =404; 
    } 
    location /api { 
        #해당 부분 추가 
        rewrite ^/api(/.*)$ $1 break; 
        proxy_pass http://tomcat_backend/; # 업스트림 그룹으로 프록시 
        proxy_set_header Host $host; 
        proxy_set_header X-Real-IP $remote_addr; 
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
        proxy_set_header X-Forwarded-Proto $scheme; 
    } 
}

 

반응형

'개발 부트캠프 > 네트워크' 카테고리의 다른 글

[HTTP] Stateful vs Stateless  (1) 2024.12.04
[IP] 고정 IP  (0) 2024.12.03
[프로토콜] TCP(Transmission Control Protocol)  (0) 2024.12.03
[IP] 바인드 주소(Bind Addresss)  (0) 2024.11.27
반응형

1. 리눅스 서버 3대 준비

- 메시지를 보낼 서버

- kafka, zookeepr 서버

- 메시지를 받을 서버

2. 세개 서버에 모두 kafka 다운로드 및 압축 풀기

- apache kafka download 검색 후 다운로드 주소 복사

- 서버에서 다운로드 및 압축 해제

wget https://dlcdn.apache.org/kafka/3.9.0/kafka_2.13-3.9.0.tgz
tar zxvf kafka_2.13-3.9.0.tgz

 

3. kafka는 java 기반 프레임 워크로 모든 서버에 java 다운로드

apt update
apt install openjdk-17-jdk

 

4. kafka 서버에 zookeeper & kafka 실행

- bin 폴더로 이동하여 zookeeper 실행

./zookeeper-server-start.sh ../config/zookeeper.properties

- config 폴더 이동하여 kafka 설정 변경

주석 처리된 listerners, advertised 주석 해제 및 advertised의 ip주소 kafka 서버로 변경

 

- bin 폴더 이동하여 kafka 실행

./kafka-server-start.sh ../config/server.properties

 

5. 메시지를 보낼 서버에서 producer 실행

- bin 폴더 이동하여 실행

./kafka-console-producer.sh --broker-list [kafka server ip]:9092 --topic abc

 

6. 메시지를 받을 서버에서 consumer 실행

- bin 폴더 이동하여 실행

./kafka-console-consumer.sh --bootstrap-server [kafka server ip]:9092 --topic abc

 

7. 메시지를 보낼 서버에서 메시지 입력 후 메시지를 받는 서버에서 메시지가 오는지 확인

메시지 전송
메시지 받기

 

반응형

'개발 부트캠프 > 백엔드' 카테고리의 다른 글

[Log] Log 중앙화  (0) 2025.02.21
[Trace] 핀포인트(Pinpoint)  (0) 2025.02.21
[Spring] 환경 변수 설정  (0) 2025.02.15
[Spring] @Component와 @Bean의 차이점  (0) 2025.02.15
[Spring] Stream  (0) 2025.02.15
반응형

Spring에서 환경 변수 설정하는 방법

Spring 애플리케이션에서 환경 변수를 설정하는 방법에는 여러 가지가 있습니다. 이번 글에서는 IntelliJ에서 직접 설정하는 방법, .env 파일을 사용하는 방법, 그리고 HashiCorp Vault 서버를 활용하는 방법을 각각 예제와 함께 설명하겠습니다.


1. IntelliJ에서 환경 변수 직접 설정하기

개발 도구(IntelliJ)에서 환경 변수를 설정하면, 실행 환경에 따라 애플리케이션에서 사용할 변수를 변경할 수 있습니다.

설정 방법

  1. IntelliJ에서 Run/Debug Configurations로 이동합니다.
  2. 해당 애플리케이션 실행 설정을 선택합니다.
  3. Environment Variables 섹션을 찾아 클릭합니다.
  4. 변수 이름과 값을 입력한 후 저장합니다.

예제

아래와 같이 환경 변수를 설정하면, Spring Boot 애플리케이션에서 해당 값을 사용할 수 있습니다.

 
# IntelliJ에서 설정한 환경 변수
db.username=myuser
db.password=mypassword

이렇게 설정한 환경 변수는 @Value 또는 System.getenv()를 통해 접근할 수 있습니다.

@Value("${db.username}")
private String dbUsername;

@Value("${db.password}")
private String dbPassword;

public void printDatabaseConfig() {
    System.out.println("DB Username: " + dbUsername);
    System.out.println("DB Password: " + dbPassword);
}

2. .env 파일을 사용한 환경 변수 설정

.env 파일은 개발 환경별로 쉽게 설정을 관리할 수 있는 방법 중 하나입니다. Spring Boot에서 .env 파일을 활용하려면 dotenv 라이브러리를 사용해야 합니다.

설정 방법

  1. 프로젝트 루트 디렉토리에 .env 파일을 생성합니다.
  2. 환경 변수를 .env 파일에 정의합니다.

예제

.env 파일:

 
DB_USERNAME=myuser
DB_PASSWORD=mypassword

Spring Boot에서 .env 파일 로드하기

  1. io.github.cdimascio:java-dotenv 라이브러리를 추가합니다.
<dependency>
    <groupId>io.github.cdimascio</groupId>
    <artifactId>java-dotenv</artifactId>
    <version>5.2.2</version>
</dependency>
  1. .env 파일의 값을 로드하여 사용합니다.
import io.github.cdimascio.dotenv.Dotenv;

public class EnvConfig {
    private static final Dotenv dotenv = Dotenv.load();

    public static String getEnv(String key) {
	    return dotenv.get(key);
    }

    public static void main(String[] args) {
    	System.out.println("DB Username: " + getEnv("DB_USERNAME"));
    	System.out.println("DB Password: " + getEnv("DB_PASSWORD"));
    }
}

3. HashiCorp Vault를 사용한 환경 변수 설정

Vault는 보안성이 중요한 환경에서 민감한 정보를 안전하게 저장하고 관리할 수 있는 강력한 솔루션입니다.

설정 방법

  1. Vault 서버 설치 및 실행
 
vault server -dev
  1. 환경 변수 저장
 
vault kv put secret/db username=myuser password=mypassword
  1. Spring Boot에서 Vault 설정 Spring Boot에서 Vault를 사용하려면 spring-cloud-vault 의존성을 추가해야 합니다.
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency>
  1. application.yml 설정
spring:
    cloud:
        vault:
            host: localhost
            port: 8200
            scheme: http
            authentication: token
            token: my-root-token
            kv:
	            enabled: true
  1. 환경 변수 가져오기
@Value("${username}")
private String dbUsername;

@Value("${password}")
private String dbPassword;

public void printVaultConfig() {
    System.out.println("Vault DB Username: " + dbUsername);
    System.out.println("Vault DB Password: " + dbPassword);
}

마무리

Spring Boot에서 환경 변수를 설정하는 방법에는 다양한 방식이 있습니다.

  • IntelliJ에서 직접 설정하면 빠르게 테스트할 수 있습니다.
  • .env 파일을 사용하면 설정을 별도로 관리할 수 있어 편리합니다.
  • Vault 서버를 활용하면 보안성이 강화됩니다.

필요에 따라 적절한 방법을 선택하여 효율적으로 환경 변수를 관리하세요!

 

반응형

'개발 부트캠프 > 백엔드' 카테고리의 다른 글

[Trace] 핀포인트(Pinpoint)  (0) 2025.02.21
[Kafka] Kafka 실행 실습  (0) 2025.02.17
[Spring] @Component와 @Bean의 차이점  (0) 2025.02.15
[Spring] Stream  (0) 2025.02.15
[Spring] DTO(Data Transfer Object)  (0) 2025.02.15

+ Recent posts