使用zuul生成关联traceID
这里我们使用zuul的过虑器,完成一个trace日志的功能,创建一个traceID,关联整个链路,打印在日志中。
从一个请求的开始和结束,这整个链路traceID唯一,这在生产开发中也是很常见的功能,不仅仅可以将整个链路的日志关联起来,方便排查问题,还为后期日志的收集奠定基础。
接下来,我们进入代码环节
- 首先搭建
eureka-server
,zuul-server
,consumer-server
,provider-server
服务,这在之前都说过,这里不再详细说明,简单把controller代码和配置贴一下
provider-server
的controller,仅仅提供了一个服务
@RestController
public class ProviderController {
private final Logger LOGGER = Logger.getLogger(ProviderController.class);
/**
* get方式接口
* @param request 请求参数
*/
@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请求传参数1
*/
@RequestMapping("/consumer/v1")
public String consumerV1() {
return restTemplate.getForEntity("http://zuul-server/api/provider?request={1}", String.class, "test").getBody();
}
/**
* GET请求传参数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 #注册服务的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
现在分别启动服务,测试调用没有问题,路由信息如下:
{
"/a/**": "consumer-server",
"/api/**": "provider-server",
"/consumer-server/**": "consumer-server",
"/provider-server/**": "provider-server"
}
这样基础工作完成。
- 来创建保存traceID的上下文,这里我新创建了一个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();
}
}
上面创建了trace model和trace context使用threadLocal保存上下文中的trace
- 接下来我们编写zuul的filter,zuul的filter分为前置过虑器,后置过虑器和路由过虑器,type类型如下:
-
pre
:在请求被路由之前调用。 -
routing
:在路由请求时候被调用。 -
post
:在routing和error过滤器之后被调用。 -
error
:处理请求时发生错误时被调用。
首先写了前置过虑器TrackingFilter.java
/**
* zuul 前置过虑器
* 设置关联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;
}
/**
* 返回一个整数值,表示filter执行顺序
*/
@Override
public int filterOrder() {
return FILTER_ORDER;
}
/**
* 返回一个boolean,表示该过虑器是否执行
*/
@Override
public boolean shouldFilter() {
return SHOULD_FILTER;
}
/**
* 每次filter执行的代码
*/
@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里面进行传递的。
这里使用到了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);
}
/**
* 设置traceId
*/
public static void setTraceId() {
RequestContext requestContext = RequestContext.getCurrentContext();
requestContext.addZuulRequestHeader(TRACE_ID, generateTraceId());
}
}
这里已经把前置zuul过滤器已经编写完成,有这个过滤器是不够的,还需要在请求完成后,将trace_id
设置到response header里面进行传递。编写后置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
设置到response header里面
- zuul filter编写完成后,只是保证
trace_id
可以在网关路由阶段可以传递,但当请求打到了具体的应用,如果在应用内不作处理,这个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
保存到上下文中
- 上面完成后,只是保证从路由到应用,
trace_id
不会丢失,但是如果consumer-server
调用provider-server
的服务时候,如果trace_id
没有被传播,此时调用到provider-server
的服务时候,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
然后设置到请求的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 被调用,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 : ========================================
网友评论