[jaeger] 四、微服務之呼叫鏈(Feign+SpringCloud)

小姐姐味道發表於2019-05-10

終於到了我們的重點,微服務了。

與使用OkHttp3來實現的客戶端類似,Feign介面本來也就是一個Http呼叫,依然可以使用Http頭傳值的方式,將Trace往下傳。

本文更多的是關於SpringCloud的一些知識,你需要了解一些基本的Spring相關的知識。

更多系列,請關注公眾號小姐姐味道,本文相關程式碼的github地址,見:

https://github.com/sayhiai/example-jaeger-opentracing-tutorial-004
複製程式碼

安裝Consul

SpringCloud的註冊中心,我們選用Consul。

consul也是用golang開發的。從consul官網下載二進位制包以後,解壓。

./consul agent   -bind 127.0.0.1 -data-dir . -node my-register-center -bootstrap-expect 1 -ui -dev
複製程式碼

使用以上指令碼快速啟動,即可使用。

訪問 http://localhost:8500/ui/ 可以看到Consul的web頁面。

構建微服務服務端和客戶端

maven依賴

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

spring-boot-dependencies 2.1.3.RELEASE
spring-cloud-dependencies Greenwich.SR1
複製程式碼

都是熱乎乎的新鮮版本。

接下來下,引入其他必須的包

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,會得到以下呼叫鏈。

[jaeger] 四、微服務之呼叫鏈(Feign+SpringCloud)
可以看到。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());
複製程式碼

就這樣,整個鏈條就穿插起來啦。

[jaeger] 四、微服務之呼叫鏈(Feign+SpringCloud)

相關文章