Spring Cloud Sleuth + Zipkin使用教程

Sleuth官方文档

Sleuth提供了一套完整的服务跟踪的解决方案并兼容Zipkin

Sleuth做链路追踪,Zipkin做数据搜集/存储/可视化

在分布应用中,存在服务之间的相互调用,在这里相互调用之间就形成了一条调用链路

一条链路对应一个完整的服务功能,每一个微服务对应链路中的一个节点

链路中的任何一环出现高延时或错误都会引起整个请求最后的失败,因此对整个服务的调用进行链路追踪和分析就非常的重要

本文将基于 Sleuth + Zipkin搭建一个链路追踪案例

基本原理

Sleuth中有两个核心概念,Trace和Span

trace表示一个链路,由一个traceId唯一标识

span表示链路中的一个节点,由traceId标识属于哪个链路,由parentId标识节点的前后依赖关系

span中存储服务调用的来源和去向信息和耗时信息,等等

安装Zipkin

  1. 下载Zipkin

  2. 进入对应的下载目录,使用java -jar zipkin-server-2.12.9-exec.jar命令运行,出现下图的标志则说明运行成功

  3. 默认的运行端口是9411,使用浏览器访问http://localhost:9411/zipkin/

  4. 分别在微服务模块中加入Sleuth和Zipkin的依赖

            <!--引入zipkin依赖,其中已经包含了sleuth-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-zipkin</artifactId>
                <version>2.2.8.RELEASE</version>
            </dependency>
    
  5. 分别配置Sleuth的采样率和Zipkin的服务地址

    • 通过"spring.zipkin.base-url"配置zipkin的服务地址
    • 通过"spring.sleuth.sampler.probability"配置sleuth的采样率
    spring:
      application:
        name: member-gateway
      zipkin:
        #配置zipkin服务器的地址
        base-url: http://localhost:9411
      sleuth:
        sampler:
          #probability标识采样率,取值范围[0.0,1.0],1.0表示全部采集
          probability: 1.0
    
  6. 访问服务接口,然后查看浏览器中zipkin的仪表盘验证链路追踪是否成功

记录一个奇怪的问题

在gateway中集成sleuth和zipkin出现推送链路数据到zipkin失败,报错显示找不到对应的地址

完整的报错信息,该报错仅仅在第一次访问网关时出现,后续没有出现,但推送链路信息到zipkin依然失败

其他服务模块是可以正常推送,并且能zipkin仪表盘查询到数据

2025-09-13 14:30:20.668  WARN [member-gateway,,] 22908 --- [/api/v2/spans}}] z.r.AsyncReporter$BoundedAsyncReporter   : Spans were dropped due to exceptions. All subsequent errors will be logged at FINE level.
2025-09-13 14:30:20.671  WARN [member-gateway,,] 22908 --- [/api/v2/spans}}] z.r.AsyncReporter$BoundedAsyncReporter   : Dropped 2 spans due to NotFound(404 Not Found from POST http://localhost:9411/api/v2/spans)

org.springframework.web.reactive.function.client.WebClientResponseException$NotFound: 404 Not Found from POST http://localhost:9411/api/v2/spans
	at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:223) ~[spring-webflux-5.3.30.jar:5.3.30]
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	*__checkpoint ⇢ 404 from POST http://localhost:9411/api/v2/spans [DefaultWebClient]
Original Stack Trace:
		at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:223) ~[spring-webflux-5.3.30.jar:5.3.30]
		at org.springframework.web.reactive.function.client.DefaultClientResponse.lambda$createException$1(DefaultClientResponse.java:207) ~[spring-webflux-5.3.30.jar:5.3.30]
		at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:106) ~[reactor-core-3.4.33.jar:3.4.33]
		at reactor.core.publisher.FluxOnErrorReturn$ReturnSubscriber.onNext(FluxOnErrorReturn.java:162) ~[reactor-core-3.4.33.jar:3.4.33]
		at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:101) ~[reactor-core-3.4.33.jar:3.4.33]
		at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129) ~[reactor-core-3.4.33.jar:3.4.33]
		at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107) ~[reactor-core-3.4.33.jar:3.4.33]
		at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:299) ~[reactor-core-3.4.33.jar:3.4.33]
		at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:337) ~[reactor-core-3.4.33.jar:3.4.33]
		at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1839) ~[reactor-core-3.4.33.jar:3.4.33]
		at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:160) ~[reactor-core-3.4.33.jar:3.4.33]
		at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) ~[reactor-core-3.4.33.jar:3.4.33]
		at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:260) ~[reactor-core-3.4.33.jar:3.4.33]
		at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) ~[reactor-core-3.4.33.jar:3.4.33]
		at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:413) ~[reactor-netty-core-1.0.38.jar:1.0.38]
		at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:437) ~[reactor-netty-core-1.0.38.jar:1.0.38]
		at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:491) ~[reactor-netty-core-1.0.38.jar:1.0.38]
		at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:753) ~[reactor-netty-http-1.0.38.jar:1.0.38]
		at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:114) ~[reactor-netty-core-1.0.38.jar:1.0.38]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103) ~[netty-codec-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346) ~[netty-codec-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318) ~[netty-codec-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) ~[netty-transport-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) ~[netty-common-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.100.Final.jar:4.1.100.Final]
		at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.100.Final.jar:4.1.100.Final]
		at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
	Suppressed: java.lang.Exception: #block terminated with an error
		at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:100) ~[reactor-core-3.4.33.jar:3.4.33]
		at reactor.core.publisher.Mono.block(Mono.java:1742) ~[reactor-core-3.4.33.jar:3.4.33]
		at org.springframework.cloud.sleuth.zipkin2.WebClientSender.lambda$new$0(WebClientSender.java:66) ~[spring-cloud-sleuth-zipkin-3.1.7.jar:3.1.7]
		at org.springframework.cloud.sleuth.zipkin2.HttpSender.post(HttpSender.java:137) ~[spring-cloud-sleuth-zipkin-3.1.7.jar:3.1.7]
		at org.springframework.cloud.sleuth.zipkin2.HttpSender$HttpPostCall.doExecute(HttpSender.java:150) ~[spring-cloud-sleuth-zipkin-3.1.7.jar:3.1.7]
		at org.springframework.cloud.sleuth.zipkin2.HttpSender$HttpPostCall.doExecute(HttpSender.java:140) ~[spring-cloud-sleuth-zipkin-3.1.7.jar:3.1.7]
		at zipkin2.Call$Base.execute(Call.java:391) ~[zipkin-2.23.2.jar:na]
		at zipkin2.reporter.AsyncReporter$BoundedAsyncReporter.flush(AsyncReporter.java:299) ~[zipkin-reporter-2.16.3.jar:na]
		at zipkin2.reporter.AsyncReporter$Flusher.run(AsyncReporter.java:378) ~[zipkin-reporter-2.16.3.jar:na]
		at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]