[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規範愈來愈完善,它的使用也愈來愈流行。在設計本身的軟件時,能夠提早考慮預留接口(一般給予重要功能攔截器的工呢功能),以便在擴展時,可以方面的將本身加入到調用鏈鏈路上來。

剩下的,就是工做量了。

相關文章
相關標籤/搜索