프로메테우스를 사용하여 워터밀의 실시간 모니터링

메트릭

워터밀은 발행자/구독자에 대한 데코레이터 및 핸들러에 대한 미들웨어를 사용하여 모니터링할 수 있습니다. 공식 고 프로메테우스 클라이언트를 이용한 기본 구현을 제공합니다.

components/metrics 패키지는 발행자, 구독자, 핸들러를 감싸 관련 프로메테우스 레지스트리를 업데이트하기 위한 편리한 함수를 제공하는 PrometheusMetricsBuilder를 노출합니다:

전체 소스 코드: github.com/ThreeDotsLabs/watermill/components/metrics/builder.go

// ...
// The PrometheusMetricsBuilder provides methods for decorating publishers, subscribers, and handlers.
type PrometheusMetricsBuilder struct {
    // PrometheusRegistry can fill in an existing Prometheus registry or be empty to use the default registry.
    PrometheusRegistry prometheus.Registerer

    Namespace string
    Subsystem string
}

// AddPrometheusRouterMetrics는 메시지 라우터의 모든 핸들러에 메트릭 미들웨어를 추가하는 편리한 함수이며, 해당 핸들러의 발행자 및 구독자를 장식합니다.
func (b PrometheusMetricsBuilder) AddPrometheusRouterMetrics(r *message.Router) {
// ...

발행자, 구독자, 핸들러 래핑

대부분의 경우 권장되는 워터밀 라우터를 사용하는 경우, 이 라우터에 추가된 모든 핸들러가 프로메테우스 레지스트리를 업데이트하기 위해 감싸지도록 하며, 이러한 핸들러의 발행자와 구독자도 함께 감싸도록 하기 위해 편리한 함수인 AddPrometheusRouterMetrics를 사용할 수 있습니다:

전체 소스 코드: github.com/ThreeDotsLabs/watermill/components/metrics/builder.go

// ...
// AddPrometheusRouterMetrics는 메시지 라우터의 모든 핸들러에 메트릭 미들웨어를 추가하는 편리한 함수이며, 해당 핸들러의 발행자 및 구독자를 장식합니다.
func (b PrometheusMetricsBuilder) AddPrometheusRouterMetrics(r *message.Router) {
    r.AddPublisherDecorators(b.DecoratePublisher)
    r.AddSubscriberDecorators(b.DecorateSubscriber)
    r.AddMiddleware(b.NewRouterMiddleware().Middleware)
}
// ...

AddPrometheusRouterMetrics의 사용 예:

전체 소스 코드: github.com/ThreeDotsLabs/watermill/_examples/basic/4-metrics/main.go

// ...
    // 우리는 네임스페이스와 서브시스템을 비워 두었습니다
    metricsBuilder := metrics.NewPrometheusMetricsBuilder(prometheusRegistry, "", "")
    metricsBuilder.AddPrometheusRouterMetrics(router)
// ...

위의 코드 스니펫에서는 namespacesubsystem 매개변수를 비워 두었습니다. 프로메테우스 클라이언트 라이브러리는 이러한 매개변수를 사용하여 메트릭 이름에 접두사를 붙입니다. 네임스페이스나 서브시스템을 사용하고 싶을 수 있지만, 이러한 매개변수를 사용하는 경우 메트릭 이름에 영향을 줄 수 있으므로 Grafana 대시보드를 조정해야 합니다.

독립적인 발행자와 구독자도 PrometheusMetricsBuilder의 전용 메서드를 사용하여 장식할 수 있습니다:

전체 소스 코드: github.com/ThreeDotsLabs/watermill/_examples/basic/4-metrics/main.go

// ...
    subWithMetrics, err := metricsBuilder.DecorateSubscriber(pubSub)
    if err != nil {
        panic(err)
    }
    pubWithMetrics, err := metricsBuilder.DecoratePublisher(pub)
    if err != nil {
        panic(err)
    }
// ...

/metrics 엔드포인트 노출

Prometheus의 작동 원리에 따르면, 서비스는 데이터 수집을 위한 HTTP 엔드포인트를 노출해야 합니다. 전통적으로 이는 GET 엔드포인트이며, 일반적으로 경로는 /metrics입니다.

이 엔드포인트를 제공하기 위해 두 가지 편리한 함수가 있습니다. 하나는 이전에 생성한 Prometheus 레지스트리를 사용하고, 다른 하나는 새 레지스트리를 동시에 생성합니다.

완전한 소스 코드: github.com/ThreeDotsLabs/watermill/components/metrics/http.go

// ...
// CreateRegistryAndServeHTTP는 /metrics 엔드포인트를 노출하기 위해 주어진 주소에 HTTP 서버를 설정합니다.
// 이는 새로운 Prometheus 레지스트리(메트릭 등록을 위해)와 서버를 종료하기 위한 취소 함수를 반환합니다.
func CreateRegistryAndServeHTTP(addr string) (registry *prometheus.Registry, cancel func()) {
	registry = prometheus.NewRegistry()
	return registry, ServeHTTP(addr, registry)
}

// ServeHTTP는 /metrics 엔드포인트를 노출하기 위해 주어진 주소에 HTTP 서버를 설정합니다.
// 이는 기존의 Prometheus 레지스트리를 받아들이고 서버를 종료하기 위한 취소 함수를 반환합니다.
func ServeHTTP(addr string, registry *prometheus.Registry) (cancel func()) {
// ...

다음은 사용 예시입니다:

완전한 소스 코드: github.com/ThreeDotsLabs/watermill/_examples/basic/4-metrics/main.go

// ...
	prometheusRegistry, closeMetricsServer := metrics.CreateRegistryAndServeHTTP(*metricsAddr)
	defer closeMetricsServer()

	// 우리는 네임스페이스와 서브시스템을 비워둡니다.
	metricsBuilder := metrics.NewPrometheusMetricsBuilder(prometheusRegistry, "", "")
	metricsBuilder.AddPrometheusRouterMetrics(router)
// ...

예시 어플리케이션

대시보드가 실제로 작동하는 방법을 이해하기 위해서는 메트릭 예시를 참고할 수 있습니다.

예시의 README에 있는 지침을 따라 실행하고, Grafana에 Prometheus 데이터 소스를 추가하세요.

Grafana 대시보드

위에서 언급한 메트릭 구현과 함께 사용할 Grafana 대시보드가 준비되어 있습니다. 이는 처리량, 실패율, 및 게시/처리 기간에 대한 기본 정보를 제공합니다.

이 대시보드를 지역에서 보고 싶으면, 예시 어플리케이션을 사용할 수 있습니다.

Prometheus로 내보낸 메트릭에 대한 자세한 정보는 "내보낸 메트릭"을 참고하세요.

대시보드 가져오기

그래프나 대시보드를 가져오려면 왼쪽 메뉴에서 대시보드/관리를 선택한 후 +가져오기를 클릭하세요.

대시보드 URL https://grafana.com/dashboards/9777 (또는 ID인 9777)를 입력한 후, 불러오기를 클릭하세요.

대시보드 가져오기

그런 다음 /metrics 엔드포인트를 가져오기 위해 사용된 Prometheus 데이터 소스를 선택하세요. 가져오기를 클릭하면 끝났습니다!

내보낸 메트릭

PrometheusMetricsBuilder에 의해 Prometheus 레지스트리에 등록된 모든 메트릭을 나열합니다.

Prometheus 메트릭 유형에 대한 자세한 정보는 Prometheus 문서를 참조하십시오.

객체 메트릭 설명 라벨/값
Subscriber subscriber_messages_received_total Prometheus 카운터. 구독자가 수신한 메시지 수를 계산합니다. acked는 "acked" 또는 "nacked"입니다.
만약 구독자가 핸들러 내에서 작동하는 경우, handler_name을 설정하십시오. 그렇지 않으면 ""로 설정하십시오.
subscriber_name은 구독자를 식별합니다. fmt.Stringer 인터페이스를 구현하는 경우 String()의 결과가 됩니다. 그렇지 않으면 package.structName이 됩니다.
Handler handler_execution_time_seconds Prometheus 히스토그램. 미들웨어에 의해 감싸인 핸들러 함수의 실행 시간을 기록합니다. handler_name은 핸들러의 이름입니다.
success는 래핑된 핸들러 함수가 오류를 반환하는지에 따라 "true" 또는 "false"입니다.
Publisher publish_time_seconds Prometheus 히스토그램. 발행자의 데코레이트된 발행 함수의 실행 시간을 기록합니다. success는 데코레이트된 발행자가 오류를 반환하는지에 따라 "true" 또는 "false"입니다.
만약 발행자가 핸들러 내에서 작동하는 경우, handler_name을 설정하십시오. 그렇지 않으면 ""로 설정하십시오.
publisher_name은 발행자를 식별합니다. fmt.Stringer 인터페이스를 구현하는 경우 String()의 결과가 됩니다. 그렇지 않으면 package.structName이 됩니다.

또한, 각 메트릭은 프로메테우스에 의해 제공되는 node 라벨을 가지고 있으며, 해당 값은 메트릭 소스의 인스턴스에 해당하며, job프로메테우스 구성 파일에서의 작업 이름으로 지정됩니다.

참고: 앞에서 언급한대로, 비어 있지 않은 namespace 또는 subsystem을 사용하면 메트릭 이름 접두사가 생깁니다. 따라서 Grafana 대시보드의 패널 정의와 같은 해당 조정이 필요할 수 있습니다.

사용자 정의

특정 메트릭이 누락된 것으로 판단된다면, 이 기본 구현을 쉽게 확장할 수 있습니다. 최상의 방법은 ServeHTTP 메서드를 사용하여 Prometheus 레지스트리를 사용하고, 여기의 Prometheus 클라이언트 문서에 따라 메트릭을 등록하는 것입니다.

이러한 메트릭을 업데이트하는 간결한 방법은 데코레이터를 사용하는 것입니다. 전체 소스 코드는 github.com/ThreeDotsLabs/watermill/message/decorator.go에서 찾을 수 있습니다.

// ...
// MessageTransformSubscriberDecorator는 구독자를 생성하는 데코레이터를 만들어서 구독자를 통해 전달된 각 메시지에 대해 transform 함수를 호출합니다.
func MessageTransformSubscriberDecorator(transform func(*Message)) SubscriberDecorator {
	if transform == nil {
		panic("transform function is nil")
	}
	return func(sub Subscriber) (Subscriber, error) {
		return &messageTransformSubscriberDecorator{
			sub:       sub,
			transform: transform,
		}, nil
	}
}

// MessageTransformPublisherDecorator는 발행자를 생성하는 데코레이터를 만들어서 발행자를 통해 전달된 각 메시지에 대해 transform 함수를 호출합니다.
func MessageTransformPublisherDecorator(transform func(*Message)) PublisherDecorator {
	if transform == nil {
		panic("transform function is nil")
	}
	return func(pub Publisher) (Publisher, error) {
		return &messageTransformPublisherDecorator{
			Publisher: pub,
			transform: transform,
		}, nil
	}
}

type messageTransformSubscriberDecorator struct {
// ...

또는 라우터의 미들웨어를 사용할 수 있습니다.

또 다른 간단한 방법은 처리기 함수에서 필요한 메트릭들만 업데이트하는 것입니다.