paint-brush
OpenTelemetry 추적 및 최소 코드를 사용한 Java 백엔드 관찰성~에 의해@apanasevich
새로운 역사

OpenTelemetry 추적 및 최소 코드를 사용한 Java 백엔드 관찰성

~에 의해 Dmitriy Apanasevich10m2024/11/15
Read on Terminal Reader
Read this story w/o Javascript

너무 오래; 읽다

최소한의 코딩으로 추적을 구현하여 OpenTelemetry 프레임워크를 Java 백엔드에 통합한 방법

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - OpenTelemetry 추적 및 최소 코드를 사용한 Java 백엔드 관찰성
Dmitriy Apanasevich HackerNoon profile picture

안녕하세요 여러분! 저는 MY.GAMES의 Java 개발자인 Dmitriy Apanasevich입니다. Rush Royale 게임을 작업하고 있으며, OpenTelemetry 프레임워크를 Java 백엔드에 통합한 경험을 공유하고 싶습니다. 여기서 다룰 내용이 꽤 많습니다. 이를 구현하는 데 필요한 코드 변경 사항과 설치 및 구성해야 했던 새 구성 요소를 다루고, 물론 결과 중 일부를 공유하겠습니다.

우리의 목표: 시스템 관찰성 달성

우리의 사례에 대해 좀 더 맥락을 제공하겠습니다. 개발자로서 우리는 모니터링, 평가 및 이해하기 쉬운 소프트웨어를 만들고 싶습니다(그리고 이것이 바로 OpenTelemetry를 구현하는 목적입니다. 시스템 성능을 극대화하기 위해 관찰 가능성 ).


애플리케이션 성능에 대한 통찰력을 수집하기 위한 기존 방법에는 종종 이벤트, 메트릭 및 오류를 수동으로 로깅하는 것이 포함됩니다.



물론, 로그를 처리할 수 있는 프레임워크는 다양하며, 이 글을 읽는 모든 분들은 로그를 수집, 저장, 분석하기 위한 시스템을 갖추고 있을 것으로 확신합니다.


로깅도 완전히 구성되었으므로 로그 작업을 위해 OpenTelemetry가 제공하는 기능을 사용하지 않았습니다.


시스템을 모니터링하는 또 다른 일반적인 방법은 메트릭을 활용하는 것입니다.


또한 우리는 지표를 수집하고 시각화하기 위한 완전히 구성된 시스템을 갖고 있었으므로 여기서도 지표 작업 측면에서 OpenTelemetry의 기능을 무시했습니다.


그러나 이러한 종류의 시스템 데이터를 얻고 분석하는 데 덜 일반적인 도구는 다음과 같습니다. 흔적 .


추적은 요청이 수명 동안 시스템을 통과하는 경로를 나타내며 일반적으로 시스템이 요청을 수신할 때 시작하여 응답으로 끝납니다. 추적은 여러 가지로 구성됩니다. 스팬 각각은 개발자 또는 선택한 라이브러리에 의해 결정된 특정 작업 단위를 나타냅니다. 이러한 스팬은 시스템이 요청을 처리하는 방식을 시각화하는 데 도움이 되는 계층적 구조를 형성합니다.


이 논의에서는 OpenTelemetry의 추적 측면에 집중하겠습니다.

OpenTelemetry에 대한 추가 배경 정보

또한 OpenTelemetry 프로젝트에 대해서도 살펴보겠습니다. 이 프로젝트는 다음 두 가지를 병합하여 만들어졌습니다. 오픈트레이싱 그리고 오픈센서스 프로젝트.


OpenTelemetry는 이제 다양한 프로그래밍 언어에 대한 API, SDK, 도구 세트를 정의하는 표준을 기반으로 포괄적인 범위의 구성 요소를 제공하며, 이 프로젝트의 주요 목표는 데이터를 생성, 수집, 관리, 내보내는 것입니다.


즉, OpenTelemetry는 데이터 저장이나 시각화 도구를 위한 백엔드를 제공하지 않습니다.


우리는 추적에만 관심이 있었기 때문에 추적을 저장하고 시각화하기 위한 가장 인기 있는 오픈 소스 솔루션을 살펴보았습니다.

  • 저격병
  • 지킨
  • 그라파나 템포


궁극적으로 우리는 인상적인 시각화 기능, 빠른 개발 속도, 메트릭 시각화를 위한 기존 Grafana 설정과의 통합으로 인해 Grafana Tempo를 선택했습니다. 단일 통합 도구를 갖는 것도 상당한 이점이었습니다.

OpenTelemetry 구성 요소

OpenTelemetry의 구성 요소에 대해서도 간략히 살펴보겠습니다.


사양:

  • API - 데이터 유형, 작업, 열거형

  • SDK — 사양 구현, 다양한 프로그래밍 언어의 API. 언어가 다르면 알파에서 안정까지 다른 SDK 상태가 됩니다.

  • 데이터 프로토콜(OTLP) 및 의미적 규칙


Java API SDK:

  • 코드 계측 라이브러리
  • Exporters - 생성된 추적을 백엔드로 내보내기 위한 도구
  • 크로스 서비스 전파기 - 프로세스(JVM) 외부로 실행 컨텍스트를 전송하기 위한 도구


OpenTelemetry Collector는 데이터를 수신하고 처리하고 전달하는 프록시로서 중요한 구성 요소입니다. 자세히 살펴보겠습니다.

오픈 텔레메트리 수집기

초당 수천 개의 요청을 처리하는 고부하 시스템의 경우 데이터 볼륨을 관리하는 것이 중요합니다. 추적 데이터는 종종 볼륨 면에서 비즈니스 데이터를 능가하므로 수집하고 저장할 데이터의 우선순위를 정하는 것이 필수적입니다. 여기서 데이터 처리 및 필터링 도구가 등장하여 어떤 데이터를 저장할 가치가 있는지 결정할 수 있습니다. 일반적으로 팀은 다음과 같은 특정 기준을 충족하는 추적을 저장하려고 합니다.


  • 응답 시간이 특정 임계값을 초과하는 추적입니다.
  • 처리 중 오류가 발생한 추적입니다.
  • 특정 마이크로서비스를 통과했거나 코드에서 의심스러운 것으로 표시된 것과 같이 구체적인 속성이 포함된 추적입니다.
  • 시스템의 정상적인 작동에 대한 통계적 스냅샷을 제공하는 정기적인 추적을 무작위로 선택하여 일반적인 동작을 이해하고 추세를 파악하는 데 도움이 됩니다.

어떤 추적 정보를 저장하고 어떤 정보를 삭제할지 결정하는 데 사용되는 두 가지 주요 샘플링 방법은 다음과 같습니다.

  • 헤드 샘플링 - 추적을 시작할 때 추적을 유지할지 여부를 결정합니다.
  • 테일 샘플링 - 전체 추적이 사용 가능해진 후에만 결정합니다. 이는 추적에서 나중에 나타나는 데이터에 따라 결정이 달라질 때 필요합니다. 예를 들어, 오류 범위를 포함한 데이터입니다. 이러한 경우는 전체 추적을 먼저 분석해야 하기 때문에 헤드 샘플링으로 처리할 수 없습니다.


OpenTelemetry Collector는 데이터 수집 시스템을 구성하여 필요한 데이터만 저장하도록 돕습니다. 나중에 구성에 대해 논의하겠지만, 지금은 추적을 생성하기 위해 코드에서 무엇을 변경해야 하는지에 대한 질문으로 넘어가겠습니다.

제로코드 계측

추적 생성을 위해서는 최소한의 코딩만 필요했습니다. Java 에이전트로 애플리케이션을 시작하고 다음을 지정하기만 하면 됩니다. 구성 :


-javaagent:/opentelemetry-javaagent-1.29.0.jar

-Dotel.javaagent.configuration-file=/otel-config.properties


OpenTelemetry는 엄청난 수의 라이브러리 및 프레임워크 따라서 에이전트로 애플리케이션을 실행한 후, 서비스 간 요청 처리 단계, DBMS 등에 대한 데이터가 포함된 추적 정보를 즉시 받았습니다.


에이전트 구성에서 추적에서 보고 싶지 않은 스팬이 있는 라이브러리를 비활성화하고 코드가 작동하는 방식에 대한 데이터를 얻기 위해 이를 표시했습니다. 주석 :


 @WithSpan("acquire locks") public CompletableFuture<Lock> acquire(SortedSet<Object> source) { var traceLocks = source.stream().map(Object::toString).collect(joining(", ")); Span.current().setAttribute("locks", traceLocks); return CompletableFuture.supplyAsync(() -> /* async job */); }


이 예제에서 @WithSpan 주석은 메서드에 사용되었으며, 이는 " acquire locks "라는 이름의 새 span을 생성해야 한다는 신호를 보내고, " locks " 속성은 메서드 본문에서 생성된 span에 추가됩니다.


메서드가 작업을 마치면 span이 닫히고 비동기 코드의 경우 이 세부 사항에 주의를 기울이는 것이 중요합니다. 주석이 달린 메서드에서 호출된 람다 함수에서 비동기 코드의 작업과 관련된 데이터를 가져와야 하는 경우 이러한 람다를 별도의 메서드로 분리하고 추가 주석으로 표시해야 합니다.

우리의 추적 수집 설정

이제 전체 추적 수집 시스템을 구성하는 방법에 대해 이야기해 보겠습니다. 모든 JVM 애플리케이션은 OpenTelemetry 수집기에 데이터를 보내는 Java 에이전트로 시작됩니다.


그러나 단일 수집기는 대량의 데이터 흐름을 처리할 수 없으며 시스템의 이 부분은 확장되어야 합니다. 각 JVM 애플리케이션에 대해 별도의 수집기를 시작하면 꼬리 샘플링이 중단됩니다. 추적 분석은 하나의 수집기에서 이루어져야 하며 요청이 여러 JVM을 거치는 경우 하나의 추적 범위가 다른 수집기에 도달하여 분석이 불가능하기 때문입니다.


여기서, 수집기 구성됨 균형자가 구조하러 온다.


결과적으로, 우리는 다음과 같은 시스템을 얻습니다. 각 JVM 애플리케이션은 동일한 밸런서 컬렉터로 데이터를 전송하고, 이 컬렉터의 유일한 작업은 다른 애플리케이션에서 수신한 데이터를 주어진 트레이스와 관련된 동일한 컬렉터-프로세서로 분배하는 것입니다. 그런 다음 컬렉터-프로세서는 데이터를 Grafana Tempo로 전송합니다.



이 시스템의 구성 요소의 구성을 자세히 살펴보겠습니다.

로드 밸런싱 수집기

수집기-밸런서 구성에서는 다음과 같은 주요 부분을 구성했습니다.


 receivers:  otlp:    protocols:      grpc: exporters:  loadbalancing:    protocol:      otlp:        tls:          insecure: true    resolver:      static:        hostnames:          - collector-1.example.com:4317          - collector-2.example.com:4317          - collector-3.example.com:4317 service:  pipelines:    traces:      receivers: [otlp]      exporters: [loadbalancing]


  • 수신기 - 수집기가 데이터를 수신할 수 있는 방법(방법)이 구성된 곳입니다. OTLP 형식으로만 데이터 수신을 구성했습니다. (데이터 수신을 구성할 수 있습니다. 다른 많은 프로토콜 (예를 들어 Zipkin, Jaeger.)
  • Exporters — 데이터 밸런싱이 구성되는 구성의 일부입니다. 이 섹션에 지정된 수집기-프로세서 중에서 데이터는 추적 식별자에서 계산된 해시에 따라 분산됩니다.
  • 서비스 섹션에서는 서비스가 작동하는 방법에 대한 구성을 지정합니다. 추적만 사용하고, 맨 위에 구성된 OTLP 수신기를 사용하고, 밸런서로 데이터를 전송합니다(즉, 처리하지 않음).

데이터 처리를 하는 수집기

수집기-프로세서의 구성은 더 복잡하므로 이를 살펴보겠습니다.


 receivers:  otlp:    protocols:      grpc:        endpoint: 0.0.0.0:14317 processors:  tail_sampling:    decision_wait: 10s    num_traces: 100    expected_new_traces_per_sec: 10    policies:      [          {            name: latency500-policy,            type: latency,            latency: {threshold_ms: 500}          },          {            name: error-policy,            type: string_attribute,            string_attribute: {key: error, values: [true, True]}          },          {            name: probabilistic10-policy,            type: probabilistic,            probabilistic: {sampling_percentage: 10}          }      ]  resource/delete:    attributes:      - key: process.command_line        action: delete      - key: process.executable.path        action: delete      - key: process.pid        action: delete      - key: process.runtime.description        action: delete      - key: process.runtime.name        action: delete      - key: process.runtime.version        action: delete exporters:  otlp:    endpoint: tempo:4317    tls:      insecure: true service:  pipelines:    traces:      receivers: [otlp]      exporters: [otlp]


컬렉터-밸런서 구성과 유사하게 처리 구성은 수신기, 내보내기, 서비스 섹션으로 구성됩니다. 그러나 데이터가 처리되는 방식을 설명하는 프로세서 섹션에 초점을 맞출 것입니다.


첫째, tail_sampling 섹션은 다음을 보여줍니다. 구성 저장 및 분석에 필요한 데이터를 필터링할 수 있습니다.


  • delay500-policy : 이 규칙은 지연 시간이 500밀리초를 초과하는 추적을 선택합니다.

  • error-policy : 이 규칙은 처리 중에 오류가 발생한 추적을 선택합니다. 추적 범위에서 "true" 또는 "True" 값을 가진 "error"라는 문자열 속성을 검색합니다.

  • probabilistic10-policy : 이 규칙은 모든 추적의 10%를 무작위로 선택하여 일반적인 애플리케이션 작동, 오류 및 긴 요청 처리에 대한 통찰력을 제공합니다.


이 예제에서는 tail_sampling 외에도 데이터 분석 및 저장에 필요하지 않은 속성을 삭제하는 resource/delete 섹션을 보여줍니다.

결과

그 결과 Grafana 추적 검색 창을 사용하면 다양한 기준으로 데이터를 필터링할 수 있습니다. 이 예에서는 게임 메타데이터를 처리하는 로비 서비스에서 수신한 추적 목록을 간단히 표시합니다. 이 구성을 사용하면 지연, 오류 및 무작위 샘플링과 같은 속성으로 나중에 필터링할 수 있습니다.


추적 보기 창에는 요청을 구성하는 다양한 스팬을 포함하여 로비 서비스의 실행 타임라인이 표시됩니다.


그림에서 볼 수 있듯이 이벤트 순서는 다음과 같습니다. 잠금이 획득된 후 캐시에서 객체가 검색되고, 요청을 처리하는 트랜잭션이 실행된 후 객체가 다시 캐시에 저장되고 잠금이 해제됩니다.


데이터베이스 요청과 관련된 스팬은 표준 라이브러리의 계측으로 인해 자동으로 생성되었습니다. 반면, 잠금 관리, 캐시 작업 및 트랜잭션 시작과 관련된 스팬은 앞서 언급한 주석을 사용하여 비즈니스 코드에 수동으로 추가되었습니다.



스팬을 볼 때, 처리 중에 발생한 일을 더 잘 이해할 수 있게 해주는 속성을 볼 수 있습니다. 예를 들어, 데이터베이스의 쿼리를 볼 수 있습니다.



Grafana Tempo의 흥미로운 기능 중 하나는 다음과 같습니다. 서비스 그래프 모든 서비스를 내보내는 추적, 이들 간의 연결, 요청의 속도 및 대기 시간을 그래픽으로 표시합니다.


마무리하기

우리가 보았듯이, OpenTelemetry 추적을 사용하면 관찰 능력이 꽤 훌륭하게 향상되었습니다. 최소한의 코드 변경과 잘 구성된 수집기 설정으로 심층적인 통찰력을 얻었고, Grafana Tempo의 시각화 기능이 어떻게 우리의 설정을 더욱 보완하는지 보았습니다. 읽어주셔서 감사합니다!