6、spring cloud zuul使用

使用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é)

  1. 首先搭建eureka-serverzuul-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ǔ)工作完成。

  1. 來創(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

  1. 接下來我們編寫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里面

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

  1. 上面完成后,只是保證從路由到應(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里面。

  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 被調(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  : ========================================
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,661評論 19 139
  • why: 1,微服務(wù)架構(gòu)微服務(wù)增多,一個客戶端請求形成一個復(fù)雜的分布式服務(wù)調(diào)用鏈路,如果任何一個服務(wù)延遲過高或...
    xiaoyang08閱讀 3,955評論 0 5
  • 為什么要使用微服務(wù)網(wǎng)關(guān) 不同的微服務(wù)一般會有不同的網(wǎng)絡(luò)地址,而客戶端可能需要調(diào)用多個服務(wù)接口才能完成一個業(yè)務(wù)需求 ...
    聰明的奇瑞閱讀 12,050評論 0 19
  • (誰家灶頭無煙火~廚手) 手起刀落分山河, 灶房爐旺紫煙薰。 一番廚手岀美食, 煙臺深處味香純。 翻江倒海五味窮,...
    甘朝武閱讀 94評論 0 0
  • string number boolean Null undefined 以上五種類型屬于基本數(shù)據(jù),以后我們看到的...
    hi武林高手閱讀 247評論 0 2

友情鏈接更多精彩內(nèi)容