Istio最佳实践系列:如何实现方法级调用跟踪? (3)

下面我们来测试一下eshop实例程序。我们可以自己搭建一个Kubernetes集群并安装Istio以用于测试。这里为了方便,直接使用腾讯云上提供的全托管的服务网格 TCM,并在创建的 Mesh 中加入了一个容器服务TKE 集群来进行测试。

在 TKE 集群中部署该程序,查看Istio分布式调用跟踪的效果。

git clone git@github.com:aeraki-framework/method-level-tracing-with-istio.git cd method-level-tracing-with-istio git checkout without-opentracing kubectl apply -f k8s/eshop.yaml

在浏览器中打开地址:${INGRESS_EXTERNAL_IP}/checkout ,以触发调用eshop示例程序的REST接口。

在浏览器中打开 TCM 的界面,查看生成的分布式调用跟踪信息。

TCM 图形界面直观地展示了这次调用的详细信息,可以看到客户端请求从Ingressgateway进入到系统中,然后调用了eshop微服务的checkout接口,checkout调用有三个child span,分别对应到inventory,billing和delivery三个微服务的REST接口。

Istio最佳实践系列:如何实现方法级调用跟踪?

使用OpenTracing来传递分布式跟踪上下文

OpenTracing提供了基于Spring的代码埋点,因此我们可以使用OpenTracing Spring框架来提供HTTP header的传递,以避免这部分硬编码工作。在Spring中采用OpenTracing来传递分布式跟踪上下文非常简单,只需要下述两个步骤:

在Maven POM文件中声明相关的依赖,一是对OpenTracing SPring Cloud Starter的依赖;另外由于Istio 采用了Zipkin的上报接口,我们也需要引入Zipkin的相关依赖。

在Spring Application中声明一个Tracer bean。如下所示,注意我们需要把 Istio 中的zpkin上报地址设置到OKHttpSernder中。

@Bean public io.opentracing.Tracer zipkinTracer() { String zipkinEndpoint = System.getenv("ZIPKIN_ENDPOINT"); if (zipkinEndpoint == null || zipkinEndpoint == ""){ zipkinEndpoint = "http://zipkin.istio-system:9411/api/v2/spans"; } OkHttpSender sender = OkHttpSender.create(zipkinEndpoint); Reporter spanReporter = AsyncReporter.create(sender); Tracing braveTracing = Tracing.newBuilder() .localServiceName("my-service") .propagationFactory(B3Propagation.FACTORY) .spanReporter(spanReporter) .build(); Tracing braveTracer = Tracing.newBuilder() .localServiceName("spring-boot") .spanReporter(spanReporter) .propagationFactory(B3Propagation.FACTORY) .traceId128Bit(true) .sampler(Sampler.ALWAYS_SAMPLE) .build(); return BraveTracer.create(braveTracer); }

部署采用OpenTracing进行HTTP header传递的程序版本,其调用跟踪信息如下所示:

Istio最佳实践系列:如何实现方法级调用跟踪?


从上图中可以看到,相比在应用代码中直接传递HTTP header的方式,采用OpenTracing进行代码埋点后,相同的调用增加了7个名称前缀为spring-boot的Span,这7个Span是由OpenTracing的tracer生成的。虽然我们并没有在代码中显示创建这些Span,但OpenTracing的代码埋点会自动为每一个REST请求生成一个Span,并根据调用关系关联起来。

OpenTracing生成的这些Span为我们提供了更详细的分布式调用跟踪信息,从这些信息中可以分析出一个HTTP调用从客户端应用代码发起请求,到经过客户端的Envoy,再到服务端的Envoy,最后到服务端接受到请求各个步骤的耗时情况。从图中可以看到,Envoy转发的耗时在1毫秒左右,相对于业务代码的处理时长非常短,对这个应用而言,Envoy的处理和转发对于业务请求的处理效率基本没有影响。

在Istio调用跟踪链中加入方法级的调用跟踪信息

Istio/Envoy提供了跨服务边界的调用链信息,在大部分情况下,服务粒度的调用链信息对于系统性能和故障分析已经足够。但对于某些服务,需要采用更细粒度的调用信息来进行分析,例如一个REST请求内部的业务逻辑和数据库访问分别的耗时情况。在这种情况下,我们需要在服务代码中进行埋点,并将服务代码中上报的调用跟踪数据和Envoy生成的调用跟踪数据进行关联,以统一呈现Envoy和服务代码中生成的调用数据。

在方法中增加调用跟踪的代码是类似的,因此我们用AOP + Annotation的方式实现,以简化代码。
首先定义一个Traced注解和对应的AOP实现逻辑:

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface Traced { } @Aspect @Component public class TracingAspect { @Autowired Tracer tracer; @Around("@annotation(com.zhaohuabing.demo.instrument.Traced)") public Object aroundAdvice(ProceedingJoinPoint jp) throws Throwable { String class_name = jp.getTarget().getClass().getName(); String method_name = jp.getSignature().getName(); Span span = tracer.buildSpan(class_name + "." + method_name).withTag("class", class_name) .withTag("method", method_name).start(); Object result = jp.proceed(); span.finish(); return result; } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpjgxd.html