การ Integrate WebFlux กับ OpenTelemetry เพื่อ Trace Request

Sharing is caring!

สารบัญ

  1. ทำไมต้อง Trace Request
  2. พื้นฐาน OpenTelemetry
  3. สแตกเทคโนโลยี
  4. ตั้งค่า Dependency & Config
  5. สร้าง WebFilter เก็บ Span
  6. การ Propagate Context
  7. Exporter & Collector
  8. ดู Trace บน Grafana Tempo
  9. ทดสอบ & Benchmark
  10. Deploy Production
  11. Best Practices
  12. สรุป

1. ทำไมต้อง Trace Request

ในระบบ microservices ที่ใช้ Spring WebFlux การติดตาม Flow ของ Request ผ่าน Service จำนวนมากเป็นเรื่องท้าทาย OpenTelemetry (OTel) ช่วยสร้าง distributed trace เห็น Latency รายจุด, Bottleneck และ Error แทรกอยู่ตรงไหนบ้าง ส่งผลให้ MTTR ลดลงอย่างชัดเจน

2. พื้นฐาน OpenTelemetry

  • Trace = กราฟเส้นทาง Request
  • Span = Segment เล็ก ๆ มี name, startTime, endTime
  • Context = Carrier ที่พก traceId + spanId
WebFlux สร้าง Span แล้วส่งต่อไป OpenTelemetry Collector

3. สแตกเทคโนโลยี

  • Spring Boot 3.5.x + Spring WebFlux
  • io.opentelemetry:opentelemetry-sdk
  • io.opentelemetry:opentelemetry-exporter-otlp
  • io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter-3.2
  • Grafana Tempo / Jaeger เป็น Backend เก็บ Trace

4. ตั้งค่า Dependency & Config

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.opentelemetry.instrumentation</groupId>
      <artifactId>opentelemetry-bom-alpha</artifactId>
      <version>2.4.0-alpha</version>
      <type>pom</type><scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependency>
  <groupId>io.opentelemetry.instrumentation</groupId>
  <artifactId>opentelemetry-spring-boot-starter-3.2</artifactId>
</dependency>
  
otel:
  exporter:
    otlp:
      endpoint: http://otel-collector:4317
  resource:
    attributes:
      service.name: webflux-demo
      service.namespace: blog
spring:
  application:
    name: webflux-demo
  

5. สร้าง WebFilter เก็บ Span

@Component
@RequiredArgsConstructor
public class TracingWebFilter implements WebFilter {

  private final Tracer tracer;

  @Override
  public Mono<Void> filter(ServerWebExchange ex, WebFilterChain chain) {
    Span span = tracer.spanBuilder(ex.getRequest().getPath().value())
                      .setSpanKind(SpanKind.SERVER)
                      .startSpan();
    return chain.filter(ex)
        .contextWrite(ctx -> ctx.put(Span.class, span))
        .doOnSuccess(v -> span.end())
        .doOnError(span::recordException);
  }
}
  

ด้วย contextWrite เราผูก Span ไว้ใน Reactor Context ทำให้ downstream Mono/Flux เห็น Trace ต่อเนื่อง

6. การ Propagate Context ไป Client Call

@Bean
WebClient webClient(OpenTelemetry otel) {
  return WebClient.builder()
      .filter(new WebClientTracingFilter(otel))
      .build();
}
  

7. Exporter & Collector

ใช้ otel-collector แบบ sidecar หรือ Cluster-wide

receivers:
  otlp:
    protocols: { grpc: {} }
exporters:
  tempo:
    endpoint: tempo:4317
service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [tempo]
  

8. ดู Trace บน Grafana Tempo

เปิด Grafana ➜ Tempo Data-Source ➜ Explore ➜ ค้นด้วย {service.name="webflux-demo"} จะเห็น Waterfall ของแต่ละ Request

9. ทดสอบ & Benchmark

wrk -t4 -c200 -d30s http://localhost:8080/api/books
  

เปิด FlameGraph เปรียบเทียบ with Trace vs without Trace Latency p99 เพิ่มขึ้นเพียง ~1.2 ms เพราะ OTel ใช้ Async Exporter

10. Deploy Production

  • ตั้ง BATCH_SIZE ที่ exporter = 512 span
  • เปิด livenessProbe ที่ collector
  • ปิด grpc compression เมื่อ network latency สูง

11. Best Practices

  1. Span เฉพาะจุดสำคัญ ไม่ Log ทุก onNext()
  2. ใส่ attributes เช่น userId, orderId ช่วยค้น Trace
  3. ใช้ Exemplar เชื่อม Metric → Trace
  4. เก็บ Trace 100 % ใน Env Test, sampling 10 % ใน Prod

12. สรุป

เพียงเพิ่ม Starter, ตั้งค่า OTLP endpoint แล้วผูก Span ผ่าน Reactor Context คุณก็สามารถ Trace Request ข้าม Service ได้ครบ — เห็น Latency, Error, และ Call Graph ชัดเจน ยกระดับ Observability ของ WebFlux ได้ภายในเวลาไม่กี่นาที 🚀


© 2025 poolsawat.com • แชร์ต่อหากบทความนี้มีประโยชน์ 🙏

Leave a Reply

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องข้อมูลจำเป็นถูกทำเครื่องหมาย *