6、spring cloud zuul使用

使用zuul生成关联traceID

这里我们使用zuul的过虑器,完成一个trace日志的功能,创建一个traceID,关联整个链路,打印在日志中。

从一个请求的开始和结束,这整个链路traceID唯一,这在生产开发中也是很常见的功能,不仅仅可以将整个链路的日志关联起来,方便排查问题,还为后期日志的收集奠定基础。

接下来,我们进入代码环节

  1. 首先搭建eureka-serverzuul-serverconsumer-serverprovider-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"
}

这样基础工作完成。

  1. 来创建保存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

  1. 接下来我们编写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里面

  1. 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保存到上下文中

  1. 上面完成后,只是保证从路由到应用,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里面。

  1. 配置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  : ========================================
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,563评论 18 139
  • why: 1,微服务架构微服务增多,一个客户端请求形成一个复杂的分布式服务调用链路,如果任何一个服务延迟过高或...
    xiaoyang08阅读 3,842评论 0 5
  • 为什么要使用微服务网关 不同的微服务一般会有不同的网络地址,而客户端可能需要调用多个服务接口才能完成一个业务需求 ...
    聪明的奇瑞阅读 11,840评论 0 19
  • (谁家灶头无烟火~厨手) 手起刀落分山河, 灶房炉旺紫烟薰。 一番厨手岀美食, 烟台深处味香纯。 翻江倒海五味穷,...
    甘朝武阅读 84评论 0 0
  • string number boolean Null undefined 以上五种类型属于基本数据,以后我们看到的...
    hi武林高手阅读 181评论 0 2