
สารบัญ
- ทำไมต้อง Trace Request
- พื้นฐาน OpenTelemetry
- สแตกเทคโนโลยี
- ตั้งค่า Dependency & Config
- สร้าง WebFilter เก็บ Span
- การ Propagate Context
- Exporter & Collector
- ดู Trace บน Grafana Tempo
- ทดสอบ & Benchmark
- Deploy Production
- Best Practices
- สรุป
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

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
- Span เฉพาะจุดสำคัญ ไม่ Log ทุก onNext()
- ใส่
attributes
เช่น userId, orderId ช่วยค้น Trace - ใช้ Exemplar เชื่อม Metric → Trace
- เก็บ 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 • แชร์ต่อหากบทความนี้มีประโยชน์ 🙏