使用zuul生成關(guān)聯(lián)traceID
這里我們使用zuul的過慮器,完成一個trace日志的功能,創(chuàng)建一個traceID,關(guān)聯(lián)整個鏈路,打印在日志中。
從一個請求的開始和結(jié)束,這整個鏈路traceID唯一,這在生產(chǎn)開發(fā)中也是很常見的功能,不僅僅可以將整個鏈路的日志關(guān)聯(lián)起來,方便排查問題,還為后期日志的收集奠定基礎(chǔ)。
接下來,我們進(jìn)入代碼環(huán)節(jié)
- 首先搭建
eureka-server,zuul-server,consumer-server,provider-server服務(wù),這在之前都說過,這里不再詳細(xì)說明,簡單把controller代碼和配置貼一下
provider-server的controller,僅僅提供了一個服務(wù)
@RestController
public class ProviderController {
private final Logger LOGGER = Logger.getLogger(ProviderController.class);
/**
* get方式接口
* @param request 請求參數(shù)
*/
@RequestMapping(value = "/provider", method = RequestMethod.GET)
public String provider(@RequestParam String request) {
LOGGER.info("========================================");
LOGGER.info("provider service ");
LOGGER.info("========================================");
return "provider, " + request;
}
}
consumer-server的controller
@RestController
public class Controller {
private final Logger LOGGER = Logger.getLogger(Controller.class);
@Autowired
private RestTemplate restTemplate;
/**
* GET請求傳參數(shù)1
*/
@RequestMapping("/consumer/v1")
public String consumerV1() {
return restTemplate.getForEntity("http://zuul-server/api/provider?request={1}", String.class, "test").getBody();
}
/**
* GET請求傳參數(shù)2
*/
@RequestMapping("/consumer/v2")
public String consumerV2() {
Map<String, String> map = Maps.newHashMap();
map.put("request", "test");
return restTemplate.getForEntity("http://provider-server/provider?request={request}", String.class, map).getBody();
}
}
zuul-server的application配置
#整合eureka
eureka:
instance:
prefer-ip-address: true #注冊服務(wù)的IP
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka
register-with-eureka: true
fetch-registry: true
zuul:
routes:
consumer-server: #手動定義路由映射
path: /a/**
url: consumer-server
provider-server: #手動定義路由映射
path: /api/**
url: provider-server
現(xiàn)在分別啟動服務(wù),測試調(diào)用沒有問題,路由信息如下:
{
"/a/**": "consumer-server",
"/api/**": "provider-server",
"/consumer-server/**": "consumer-server",
"/provider-server/**": "provider-server"
}
這樣基礎(chǔ)工作完成。
- 來創(chuàng)建保存traceID的上下文,這里我新創(chuàng)建了一個module,將下面代碼保存到新的jar包里面。
/**
* trace model
*
* @author hui.wang
* @since 19 November 2018
*/
public class Trace {
private String traceId;
private Date createTime;
public String getTraceId() {
return traceId;
}
public void setTraceId(String traceId) {
this.traceId = traceId;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
}
/**
* @author hui.wang
* @since 19 November 2018
*/
public class TraceContext {
private static final ThreadLocal<Trace> TRACE_CONTEXT = new ThreadLocal<Trace>();
public static Trace getTraceContext() {
return TRACE_CONTEXT.get();
}
public static void setTraceContext(Trace traceContext) {
Assert.notNull(traceContext, "Only non-null traceContext instances are permitted");
TRACE_CONTEXT.set(traceContext);
}
public static void clean() {
TRACE_CONTEXT.remove();
}
}
上面創(chuàng)建了trace model和trace context使用threadLocal保存上下文中的trace
- 接下來我們編寫zuul的filter,zuul的filter分為前置過慮器,后置過慮器和路由過慮器,type類型如下:
-
pre:在請求被路由之前調(diào)用。 -
routing:在路由請求時候被調(diào)用。 -
post:在routing和error過濾器之后被調(diào)用。 -
error:處理請求時發(fā)生錯誤時被調(diào)用。
首先寫了前置過慮器TrackingFilter.java
/**
* zuul 前置過慮器
* 設(shè)置關(guān)聯(lián)ID
*
* @author hui.wang
* @since 19 November 2018
*/
@Component
public class TrackingFilter extends ZuulFilter{
private final Logger LOGGER = Logger.getLogger(TrackingFilter.class);
private static final int FILTER_ORDER = 1;
private static final boolean SHOULD_FILTER = true;
private static final String FILTER_TYPE = "pre";
/**
* filter類型,前置過慮器,后置過慮器和路由過慮器
*/
@Override
public String filterType() {
return FILTER_TYPE;
}
/**
* 返回一個整數(shù)值,表示filter執(zhí)行順序
*/
@Override
public int filterOrder() {
return FILTER_ORDER;
}
/**
* 返回一個boolean,表示該過慮器是否執(zhí)行
*/
@Override
public boolean shouldFilter() {
return SHOULD_FILTER;
}
/**
* 每次filter執(zhí)行的代碼
*/
@Override
public Object run() {
if (StringUtils.isEmpty(FilterUtils.getTraceId())) {
FilterUtils.setTraceId();
}
RequestContext requestContext = RequestContext.getCurrentContext();
String URL = requestContext.getRequest().getRequestURL().toString();
String traceId = FilterUtils.getTraceId();
LOGGER.info("======================================");
LOGGER.info("request url = " + URL + ", traceId = " + traceId);
LOGGER.info("======================================");
return null;
}
}
代碼也很簡單,先判斷header里面有沒有trace_id,如果有,不做其他操作,只是打印日志,如果沒有生成一個trace_id,放到header里面。這里需要說明一下trace_id是保存到HTTP header里面進(jìn)行傳遞的。
這里使用到了FilterUtils工具類,這里我也貼一下代碼
public class FilterUtils {
public static final String TRACE_ID = "trace_id";
/**
* 獲取隨機ID
*/
public static String generateTraceId() {
return java.util.UUID.randomUUID().toString();
}
/**
* 獲取traceId
*/
public static String getTraceId() {
RequestContext requestContext = RequestContext.getCurrentContext();
if (StringUtils.isNotEmpty(requestContext.getRequest().getHeader(TRACE_ID))) {
return requestContext.getRequest().getHeader(TRACE_ID);
}
return requestContext.getZuulRequestHeaders().get(TRACE_ID);
}
/**
* 設(shè)置traceId
*/
public static void setTraceId() {
RequestContext requestContext = RequestContext.getCurrentContext();
requestContext.addZuulRequestHeader(TRACE_ID, generateTraceId());
}
}
這里已經(jīng)把前置zuul過濾器已經(jīng)編寫完成,有這個過濾器是不夠的,還需要在請求完成后,將trace_id設(shè)置到response header里面進(jìn)行傳遞。編寫后置zuul 過濾器
public class ResponseFilter extends ZuulFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(ResponseFilter.class);
private static final int FILTER_ORDER = 1;
private static final boolean SHOULD_FILTER = true;
private static final String FILTER_TYPE = "post";
@Override
public String filterType() {
return FILTER_TYPE;
}
@Override
public int filterOrder() {
return FILTER_ORDER;
}
@Override
public boolean shouldFilter() {
return SHOULD_FILTER;
}
@Override
public Object run() {
RequestContext requestContext = RequestContext.getCurrentContext();
String URL = requestContext.getRequest().getRequestURL().toString();
LOGGER.info("======================================");
LOGGER.info("response url " + URL + "traceId = " + FilterUtils.getTraceId());
LOGGER.info("======================================");
requestContext.getResponse().addHeader(FilterUtils.TRACE_ID, FilterUtils.getTraceId());
return null;
}
}
這里代碼也是很簡單的,將上下文中的trace_id設(shè)置到response header里面
- zuul filter編寫完成后,只是保證
trace_id可以在網(wǎng)關(guān)路由階段可以傳遞,但當(dāng)請求打到了具體的應(yīng)用,如果在應(yīng)用內(nèi)不作處理,這個trace_id在后面的鏈路就丟了,因此還要編寫HTTP servlet過慮器,傳遞trace_id
public class TraceFilter implements Filter{
private static final Logger LOGGER = LoggerFactory.getLogger(TraceFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
try {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
if (StringUtils.isNotEmpty(httpServletRequest.getHeader(FilterUtils.TRACE_ID))) {
Trace trace = new Trace();
trace.setTraceId(httpServletRequest.getHeader(FilterUtils.TRACE_ID));
trace.setCreateTime(new Date());
TraceContext.setTraceContext(trace);
LOGGER.info("filter set trace success");
}
} catch (Exception e) {
LOGGER.error("filter set trace error", e);
}
filterChain.doFilter(servletRequest, servletResponse);
} finally {
TraceContext.clean();
}
}
@Override
public void destroy() {
}
}
代碼也很簡單,就是判斷header里面有沒有trace_id,如果有的話,將trace_id保存到上下文中
- 上面完成后,只是保證從路由到應(yīng)用,
trace_id不會丟失,但是如果consumer-server調(diào)用provider-server的服務(wù)時候,如果trace_id沒有被傳播,此時調(diào)用到provider-server的服務(wù)時候,trace_id就丟失了,因此要編寫spring Interceptor然后將Interceptor整合到RestTemplate中
public class TraceInterceptor implements ClientHttpRequestInterceptor{
@Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
if (TraceContext.getTraceContext() != null && StringUtils.isNotEmpty(TraceContext.getTraceContext().getTraceId())) {
HttpHeaders httpHeaders = httpRequest.getHeaders();
httpHeaders.set(FilterUtils.TRACE_ID, TraceContext.getTraceContext().getTraceId());
}
return clientHttpRequestExecution.execute(httpRequest, bytes);
}
}
代碼也很簡單,在上下文中取trace_id然后設(shè)置到請求的header里面。
- 配置filter和Interceptor
在zuul-server的啟動類上配置zuul filter
@SpringBootApplication
@EnableZuulProxy
public class ZuulAppllication {
@Bean
public TrackingFilter trackingFilter() {
return new TrackingFilter();
}
@Bean
public ResponseFilter responseFilter() {
return new ResponseFilter();
}
public static void main(String[] args) {
SpringApplication.run(ZuulAppllication.class, args);
}
}
在consumer-server的啟動類上配置filter和Interceptor
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApplication {
@LoadBalanced
@Bean
RestTemplate restTemplate() {
RestTemplate template = new RestTemplate();
List interceptors = template.getInterceptors();
if (Objects.isNull(interceptors)) {
template.setInterceptors(Lists.newArrayList(new TraceInterceptor()));
} else {
interceptors.add(new TraceInterceptor());
template.setInterceptors(interceptors);
}
return template;
}
@Bean
public TraceFilter traceFilter() {
return new TraceFilter();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
在provider-server啟動類上配置filter
@SpringBootApplication
@EnableDiscoveryClient
public class ProviderApplicatioin {
@Bean
public TraceFilter traceFilter() {
return new TraceFilter();
}
public static void main(String[] args) {
SpringApplication.run(ProviderApplicatioin.class, args);
}
}
這樣代碼基本完成,簡單測試一下,訪問一下http://localhost:8822/a/consumer/v1
zuul-server的日志:
2018-11-19 18:18:57.670 INFO 46324 --- [nio-8822-exec-9] c.h.w.s.c.t.a.zuulFilter.TrackingFilter : ======================================
2018-11-19 18:18:57.670 INFO 46324 --- [nio-8822-exec-9] c.h.w.s.c.t.a.zuulFilter.TrackingFilter : request url = http://localhost:8822/a/consumer/v1, traceId = 4d25f02d-0a7a-4002-8694-32493f7daa1b
2018-11-19 18:18:57.670 INFO 46324 --- [nio-8822-exec-9] c.h.w.s.c.t.a.zuulFilter.TrackingFilter : ======================================
2018-11-19 18:18:57.683 INFO 46324 --- [io-8822-exec-10] c.h.w.s.c.t.a.zuulFilter.TrackingFilter : ======================================
2018-11-19 18:18:57.683 INFO 46324 --- [io-8822-exec-10] c.h.w.s.c.t.a.zuulFilter.TrackingFilter : request url = http://10.1.32.187:8822/api/provider, traceId = 4d25f02d-0a7a-4002-8694-32493f7daa1b
2018-11-19 18:18:57.683 INFO 46324 --- [io-8822-exec-10] c.h.w.s.c.t.a.zuulFilter.TrackingFilter : ======================================
2018-11-19 18:18:57.700 INFO 46324 --- [io-8822-exec-10] c.h.w.s.c.t.a.zuulFilter.ResponseFilter : ======================================
2018-11-19 18:18:57.701 INFO 46324 --- [io-8822-exec-10] c.h.w.s.c.t.a.zuulFilter.ResponseFilter : response url http://10.1.32.187:8822/api/providertraceId = 4d25f02d-0a7a-4002-8694-32493f7daa1b
2018-11-19 18:18:57.701 INFO 46324 --- [io-8822-exec-10] c.h.w.s.c.t.a.zuulFilter.ResponseFilter : ======================================
2018-11-19 18:18:57.705 INFO 46324 --- [nio-8822-exec-9] c.h.w.s.c.t.a.zuulFilter.ResponseFilter : ======================================
2018-11-19 18:18:57.705 INFO 46324 --- [nio-8822-exec-9] c.h.w.s.c.t.a.zuulFilter.ResponseFilter : response url http://localhost:8822/a/consumer/v1traceId = 4d25f02d-0a7a-4002-8694-32493f7daa1b
2018-11-19 18:18:57.705 INFO 46324 --- [nio-8822-exec-9] c.h.w.s.c.t.a.zuulFilter.ResponseFilter : ======================================
從zuul-server的日志可以看出整個鏈路請求過程
consumer-server端的日志為:
2018-11-19 18:18:57.676 INFO 46320 --- [nio-8111-exec-3] c.h.w.s.c.t.a.servletFIlter.TraceFilter : filter set trace success
2018-11-19 18:18:57.679 INFO 46320 --- [nio-8111-exec-3] c.h.w.s.cloud.controller.Controller : =================================
2018-11-19 18:18:57.679 INFO 46320 --- [nio-8111-exec-3] c.h.w.s.cloud.controller.Controller : traceId = 4d25f02d-0a7a-4002-8694-32493f7daa1b
2018-11-19 18:18:57.679 INFO 46320 --- [nio-8111-exec-3] c.h.w.s.cloud.controller.Controller : =================================
provider-server端的日志為:
2018-11-19 18:18:57.690 INFO 46315 --- [nio-8081-exec-3] c.h.w.s.c.t.a.servletFIlter.TraceFilter : filter set trace success
2018-11-19 18:18:57.698 INFO 46315 --- [nio-8081-exec-3] c.h.w.s.c.controller.ProviderController : ========================================
2018-11-19 18:18:57.698 INFO 46315 --- [nio-8081-exec-3] c.h.w.s.c.controller.ProviderController : provider service 被調(diào)用,traceId = 4d25f02d-0a7a-4002-8694-32493f7daa1b
2018-11-19 18:18:57.698 INFO 46315 --- [nio-8081-exec-3] c.h.w.s.c.controller.ProviderController : ========================================