[jaeger] 4、微服务之调用链(Feign+SpringCloud)

终于到了咱们的重点,微服务了。java

与使用OkHttp3来实现的客户端相似,Feign接口原本也就是一个Http调用,依然可使用Http头传值的方式,将Trace往下传。node

本文更多的是关于SpringCloud的一些知识,你须要了解一些基本的Spring相关的知识。git

更多系列,请关注公众号小姐姐味道,本文相关代码的github地址,见:github

https://github.com/sayhiai/example-jaeger-opentracing-tutorial-004
复制代码

安装Consul

SpringCloud的注册中心,咱们选用Consul。golang

consul也是用golang开发的。从consul官网下载二进制包之后,解压。web

./consul agent   -bind 127.0.0.1 -data-dir . -node my-register-center -bootstrap-expect 1 -ui -dev
复制代码

使用以上脚本快速启动,便可使用。spring

访问 http://localhost:8500/ui/ 能够看到Consul的web页面。sql

构建微服务服务端和客户端

maven依赖

以bom方式引入springboot和springcloud的组件。apache

spring-boot-dependencies 2.1.3.RELEASE
spring-cloud-dependencies Greenwich.SR1
复制代码

都是热乎乎的新鲜版本。bootstrap

接下来下,引入其余必须的包

opentracing-util 0.32.0
jaeger-client 0.35.0
logback-classic 1.2.3
opentracing-spring-jaeger-cloud-starter 2.0.0

spring-boot-starter-web
spring-boot-starter-aop
spring-boot-starter-actuator
spring-cloud-starter-consul-discovery
spring-cloud-starter-openfeign
复制代码

构建服务端

服务端App的端口是8888

@SpringBootApplication
@EnableAutoConfiguration
@EnableDiscoveryClient
@ComponentScan(basePackages = {
        "com.sayhiai.example.jaeger.totorial04.controller",
})
public class App extends SpringBootServletInitializer {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}
复制代码

在application.yml中,配置Consul做为配置中心。

 cloud:
 consul:
 host: 127.0.0.1
 port: 8500
 discovery:
 register: true
 tags: version=1.0,author=xjjdog
 healthCheckPath: /actuator/health
 healthCheckInterval: 5s
复制代码

建立Rest服务/hello

@PostMapping("/hello")
@ResponseBody
public String hello(@RequestBody String name) {
        return "hello " + name;
}
复制代码

构建Feign客户端

Feign客户端的App端口是9999,一样是一个SpringCloud服务。

建立FeignClient

@FeignClient("love-you-application")
public interface LoveYouClient {
    @PostMapping("/hello")
    @ResponseBody
    public String hello(@RequestBody String name);
}
复制代码

建立调用入口/test

@GetMapping("/test")
@ResponseBody
public String hello() {
    String rs = loveYouClient.hello("小姐姐味道");
    return rs;
}
复制代码

集成jaeger

目前,已经有相关SpringCloud的轮子了,咱们就不重复制造了。

首先,咱们看一下使用方法,而后,说明一下背后的原理。了解原理以后,你将很容易的给本身开发的中间件加入Trace功能。


轮子在这里,引入相应maven包便可使用:

https://github.com/opentracing-contrib/java-spring-jaeger
复制代码
<dependency>
  <groupId>io.opentracing.contrib</groupId>
  <artifactId>opentracing-spring-jaeger-cloud-starter</artifactId>
</dependency>
复制代码

加入配置生效

application.yml中,加入如下配置,就能够获得调用链功能了。

配置指明了trace的存放地址,并将本地log打开。

opentracing.jaeger.http-sender.url: http://10.30.94.8:14268/api/traces
opentracing.jaeger.log-spans: true
复制代码

访问 localhost:9999/test,会获得如下调用链。

能够看到。Feign的整个调用过程都被记录下来了。

原理

Feign的调用

Feign经过Header传递参数

首先看下Feign的Request构造函数。

public static Request create( String method, String url, Map<String, Collection<String>> headers, byte[] body, Charset charset) {
    return new Request(method, url, headers, body, charset);
}
复制代码

如代码,彻底能够经过在headers参数中追加咱们须要的信息进行传递。

接着源代码往下找: Client**->** LoadBalancerFeignClient execute()-> executeWithLoadBalancer()-> IClient**->**

再往下,IClient实现有 OkHttpLoadBalancingClient RibbonLoadBalancingHttpClient(基于apache的包) 等,它们均可以很容易的设置其Header

最终,咱们的请求仍是由这些底层的库函数发起,默认的是HttpURLConnection。

读过Feign和Ribbon源码的人都知道,这部分代码不是通常的乱,但好在上层的Feign是一致的。

使用委托包装Client

经过实现feign.Client接口,结合委托,能够从新封装execute方法,而后将信息inject进Feign的scope中。

使用Aop自动拦截Feign调用

@Aspect
class TracingAspect {
  @Around("execution (* feign.Client.*(..)) && !within(is(FinalType))")
  public Object feignClientWasCalled(final ProceedingJoinPoint pjp) throws Throwable {
    Object bean = pjp.getTarget();
    if (!(bean instanceof TracingClient)) {
      Object[] args = pjp.getArgs();
      return new TracingClientBuilder((Client) bean, tracer)
          .withFeignSpanDecorators(spanDecorators)
          .build()
          .execute((Request) args[0], (Request.Options) args[1]);
    }
    return pjp.proceed();
  }
}
复制代码

利用spring boot starter技术,咱们不须要任何其余改动,就能够拥有trace功能了。


Web端的发送和接收

了解spring的人都知道,最适合作http头信息添加和提取的地方,就是拦截器和过滤器。

发送

对于普通的http请求客户端来讲,是经过添加一个 ClientHttpRequestInterceptor 拦截器来实现的。过程再也不表诉,依然是使用inject等函数进行头信息设置。

接收

而对于接收,则使用的是Filter进行实现的。经过实现一个普通的servlet filter。能够经过extract函数将trace信息提取出来,而后将context做为Request的attribute进行传递。

相关代码片断以下。

if (servletRequest.getAttribute(SERVER_SPAN_CONTEXT) != null) {
    chain.doFilter(servletRequest, servletResponse);
} else {
    SpanContext extractedContext = tracer.extract(Format.Builtin.HTTP_HEADERS,
            new HttpServletRequestExtractAdapter(httpRequest));

    final Span span = tracer.buildSpan(httpRequest.getMethod())
            .asChildOf(extractedContext)
            .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER)
            .start();

httpRequest.setAttribute(SERVER_SPAN_CONTEXT, span.context());
复制代码

就这样,整个链条就穿插起来啦。

使用instrumentation自动装配

相似pinpoint和SkyWalking的所实现的同样。opentracing-contrib还基于bytebuddy写了一套比较全的agent套件。

https://github.com/opentracing-contrib/java-specialagent
复制代码

已经有如下客户端被支持。

OkHttp3
JDBC API (java.sql)
Concurrent API (java.util.concurrent)
Java Web Servlet API (javax.servlet)
Mongo Driver
Apache Camel
AWS SDK
Cassandra Driver
JMS API (javax.jms v1 & v2)
Elasticsearch6 Client
RxJava 2
Kafka Client
AsyncHttpClient
RabbitMQ Client
RabbitMQ Spring
Thrift
GRPC
Jedis Client
Apache HttpClient
Lettuce Client
Spring Web
Spring Web MVC
Spring WebFlux
Redisson
Grizzly HTTP Server
Grizzly AsyncHttpClient
Reactor
Hazelcast
复制代码

可是,bytebuddy的性能是较差的,你可能须要参考这些代码,使用asm一类的api进行构建。

关于更详细的javaagent技术,能够参考小姐姐味道的另一篇文章: 冷门instrument包,功能d炸天

End

咱们后面的几篇文章,从单机到多机,从多线程到分布式,说明了OpenTracing的api使用方法。能够看到它的抽象仍是比较全面的,可以适应大部分场景。

随着OpenTracing规范愈来愈完善,它的使用也愈来愈流行。在设计本身的软件时,能够提早考虑预留接口(一般给予重要功能拦截器的工呢功能),以便在扩展时,可以方面的将本身加入到调用链链路上来。

剩下的,就是工做量了。

相关文章
相关标签/搜索