场景
最近在做zipkin链路追踪,遇到了一系列的问题,特此总结一下
需求
遇到的主要问题
1.zipkin设置自定义参数,怎么设置定义参数?
2.设置的自定义参数在链路上传递问题(就是在有的服务中获取不到自定义的参数值)
3.http请求进行包装
分布式系统中两种使用情况
一种是springMvc中使用
一种是springCloud中使用
两种方式共同的思想都是通过过滤器,拦截到请求,然后在拦截的请求中处理请求头(http请求进行包装包装)中添加自定义参数
springCloud中使用
第一步:在每个服务中添加zipkin依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
第二步:配置让其生效
spring.sleuth.enabled=true
spring.zipkin.base-url=http://192.168.100.88:9411
spring.sleuth.sampler.percentage=1.0
如果不需要做额外处理上面两步就可以,烦就烦在实际中要和业务结合。
如果要将业务和链路结合则须要进行一下处理
第三步:通过过滤器,在请求头上添加自定义参数。
设置拦截过滤器 ------------》然后将过滤器注册
package com.gisquest.gateway.filter;
import java.io.IOException;
import java.util.UUID;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.web.filter.GenericFilterBean;
import com.gisquest.gateway.utils.ZipkinFilterWrapper;
public class GisqZipKinFilter extends GenericFilterBean {
private Tracer tracer;
public GisqZipKinFilter(Tracer tracer){
this.tracer = tracer;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
ZipkinFilterWrapper zipkinFilterWrapper = new ZipkinFilterWrapper(httpRequest);
String uuid = httpRequest.getHeader("gisq-platform-id");
if (uuid == null) {
uuid = UUID.randomUUID().toString();
zipkinFilterWrapper.putHeader("gisq-platform-id", uuid);
tracer.addTag("gisq-platform-id", uuid);
filterChain.doFilter(zipkinFilterWrapper, response);
} else {
tracer.addTag("gisq-platform-id", uuid);
filterChain.doFilter(request, response);
}
}
}
http请求进行封装
package com.gisquest.gateway.utils;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class ZipkinFilterWrapper extends HttpServletRequestWrapper {
private final Map<String, String> customHeaders;
public ZipkinFilterWrapper(HttpServletRequest request) {
super(request);
this.customHeaders = new HashMap<>(10);
}
public void putHeader(String name, String value) {
this.customHeaders.put(name, value);
}
//
@Override
public Enumeration<String> getHeaderNames() {
Set<String> set = new HashSet<>(customHeaders.keySet());
Enumeration<String> e = ((HttpServletRequest) getRequest()).getHeaderNames();
while (e.hasMoreElements()) {
String n = e.nextElement();
set.add(n);
}
return Collections.enumeration(set);
}
@Override
public String getHeader(String name){
String headerValue = customHeaders.get(name);
if (headerValue !=null){
return headerValue;
}
return ((HttpServletRequest) getRequest()).getHeader(name);
}
}
需要注意的问题:
1.不同版本Tracer获取问题(两种情况都让我给遇到了,真是服了!!!)
在2.0一下版本中是没有Brave的,是通过sleuth获取的tracer
import org.springframework.cloud.sleuth.Tracer
在span中添加参数
tracer.addTag("gisq-platform-id", uuid);
在2.0及以上版本中是通过Brave获取到的Tracer
import brave.Tracer;
在span中添加参数
tracer.currentSpan().tag("gisq-platform-id", uuid);
2.Tracer有时null,导致系统启动不来问题
如果在过滤器中直接注入可能Tracer使用时报异常(tracer.addTag()方法报异常),导致系统起不来了(就是如下情况,过滤器中也没有添加@Component注解)
解决方法,就是通过在过滤器注册时,将tracer对象注入进入就可以。可能是原因是注入对象(Tracer对象在容器中生成的时机有关)
private GisqZipKinFilter gisqZipKinFilter() {
return new GisqZipKinFilter(tracer);
}
实例流程说明
(以登录流程进行链路追踪说明):
在分布式系统中分登陆前和登陆后两种说法:
在登陆前,首先前端客户端发送登陆请求,直接先经过网关服务(转发auth/auth/oauth/login请求),再经过认证服务生成token,最后到具体的业务服务。
package com.gisquest.gateway.filter;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.stereotype.Component;
import com.gisquest.gateway.utils.ZipkinFilterWrapper;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
@Component
public class CustomTokenFilter extends ZuulFilter {
@Autowired
Tracer tracer;
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
if (request.getRequestURL().toString().contains("getUserByNameJoinDeptByPost")||request.getRequestURL().toString().contains("getPlatformDesignerOrExplorerUserOnlyById")){
return false;
}
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest httpRequest = ctx.getRequest();
ZipkinFilterWrapper zipkinFilterWrapper = new ZipkinFilterWrapper(httpRequest);
String uuid = httpRequest.getHeader("gisq-platform-id");
if (uuid == null) {
uuid = UUID.randomUUID().toString();
tracer.addTag("gisq-platform-id", uuid);
} else {
tracer.addTag("gisq-platform-id", uuid);
}
ctx.addZuulRequestHeader("gisq-platform-id", uuid);
return null;
}
}
网关我是这样处理的
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest httpRequest = ctx.getRequest();
ZipkinFilterWrapper zipkinFilterWrapper = new ZipkinFilterWrapper(httpRequest);
String uuid = httpRequest.getHeader("gisq-platform-id");
if (uuid == null) {
uuid = UUID.randomUUID().toString();
tracer.addTag("gisq-platform-id", uuid);
} else {
tracer.addTag("gisq-platform-id", uuid);
}
ctx.addZuulRequestHeader("gisq-platform-id", uuid);
return null;
}
注意点:1.这里网关过滤器不是通过注册生效的,而是直接添加@Component注解让其生效的,同时Tracer通过@Autowired注入依赖的。
2. ctx.addZuulRequestHeader("gisq-platform-id", uuid),在网关过滤器请求头添加自定义参数 。
登陆系统后,再次请求接口时候,此时会先通过一个网关认证过滤器判断token是否失效,如果生效则进入网关过滤器,然后进行路由转发,如果不生效就直接让其登陆。
代码写三个的过滤器分别用在、网关服务、 认证服务、业务服务 (Aop处理处理)这样同一个链路上,会生成一个相同值得业务id