SpringCloudGateWay耗时统计

753 阅读3分钟

blog.csdn.net/qq_18661793…

先说背景:压测发现耗时很久,使用postman偶发性的耗时久,怀疑是网络不稳定,但是又拿不出证据。 为了验证到底是网络问题还是程序性能问题,所以就有了在网关层面进行耗时统计的需求。

思路:网关的功能有路由分发、请求发起等功能。

只要在网关的所有过滤器之前再放入1个过滤器TakeTimeLogBeginFilter,在TakeTimeLogBeginFilter中在request的attrbute里放入1个属性comsuming-start-time。 在网关的所有过滤器之后再放入1个过滤器TakeTimeLogEndFilter,取出第一个过滤器放入的comsuming-start-time。然后用当前时间减去comsuming-start-time即可。

都知道过滤器是有顺序的只要实现Ordered接口重写getOrder方法即可。

int HIGHEST_PRECEDENCE = -2147483648;
int LOWEST_PRECEDENCE = 2147483647;

getOrder方法返回值越小过滤器的优先级越高。 getOrder方法返回值越大过滤器的优先级越低。

基于这种猜想

我们把TakeTimeLogBeginFilter的优先级改为HIGHEST_PRECEDENCE

把TakeTimeLogBeginFilter的优先级LOWEST_PRECEDENCE。

理想很丰满,现实很骨感。

what?

TakeTimeLogBeginFilter确实排在了第一位。

TakeTimeLogBeginFilter为什么跑到了最后一位?

看了下FilteringWebHandler#loadFilters的源码

private static List<GatewayFilter> loadFilters(List<GlobalFilter> filters) {
   return filters.stream().map(filter -> {
      GatewayFilterAdapter gatewayFilter = new GatewayFilterAdapter(filter);
      if (filter instanceof Ordered) {
         int order = ((Ordered) filter).getOrder();
         return new OrderedGatewayFilter(gatewayFilter, order);
      }
      return gatewayFilter;
   }).collect(Collectors.toList());
}

发现好像并没有地方可以对filter的排序进行扩展或者干预,后面仔细看了下还是有的!

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.cloud.gateway.filter.*;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;

 
@Component
@Slf4j
public class TakeTimeLogBeginFilter implements GlobalFilter, Ordered {

    /***
     * 放在request中的属性,用于记录调用filter链开始的时间。
     */
    public static final String START_TIME = "consuming-time-start";

    /****
     * FilteringWebHandler的静态内部类DefaultGatewayFilterChain的class全限定名
     */
    public static final String DEFAULT_GATEWAY_FILTER_CHAIN_CLASS_NAME = "org.springframework.cloud.gateway.handler.FilteringWebHandler$DefaultGatewayFilterChain";

    /***
     * FilteringWebHandler的静态内部类DefaultGatewayFilterChain的index字段
     */
    public static final String FIELD_INDEX = "index";

    /***
     * FilteringWebHandler的静态内部类DefaultGatewayFilterChain的filters字段
     */
    public static final String METHOD_GET_FILTERS = "getFilters";

    /****
     * FilteringWebHandler的静态内部类GatewayFilterAdapter的class全限定名
     */
    public static final String GATEWAY_FILTER_ADAPTER_CLASS_NAME = "org.springframework.cloud.gateway.handler.FilteringWebHandler$GatewayFilterAdapter";

    /***
     * FilteringWebHandler的静态内部类DefaultGatewayFilterChain的index字段
     */
    public static final String FIELD_DELEGATE = "delegate";

    public static Class<?> DEFAULT_GATEWAY_FILTER_CHAIN_CLASS = null;

    public static Class<?> GATEWAY_FILTER_ADAPTER_CLASS = null;

    static {
        //反射获取DefaultGatewayFilterChain
        try {
            DEFAULT_GATEWAY_FILTER_CHAIN_CLASS = Class.forName(DEFAULT_GATEWAY_FILTER_CHAIN_CLASS_NAME, true, TakeTimeLogBeginFilter.class.getClassLoader());
        } catch (Exception e) {
            log.info("反射加载DefaultGatewayFilterChainClass失败");
        }

        //反射获取GatewayFilterAdapter
        try {
            GATEWAY_FILTER_ADAPTER_CLASS = Class.forName(GATEWAY_FILTER_ADAPTER_CLASS_NAME, true, TakeTimeLogBeginFilter.class.getClassLoader());
        } catch (Exception e) {
            log.info("反射加载GatewayFilterAdapterClass失败");
        }
    }


    public TakeTimeLogBeginFilter() {
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //反射加载类失败 直接放行
        if (Objects.isNull(GATEWAY_FILTER_ADAPTER_CLASS) || Objects.isNull(DEFAULT_GATEWAY_FILTER_CHAIN_CLASS)) {
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            }));
        }

        //获取DefaultGatewayFilterChain的index字段的值
        int indexValue = 0;
        try {
            Field index = DEFAULT_GATEWAY_FILTER_CHAIN_CLASS.getDeclaredField(FIELD_INDEX);
            index.setAccessible(true);
            indexValue = index.getInt(chain);
        } catch (Exception e) {
            log.info("获取DefaultGatewayFilterChainClass的字段index失败");
            //失败直接放行
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            }));
        }
        //用于存放DefaultGatewayFilterChain中的所有filter
        List<GatewayFilter> filters;
        try {
            //反射调用getFilters 获取DefaultGatewayFilterChain中的所有filter
            Method getFiltersMethod = DEFAULT_GATEWAY_FILTER_CHAIN_CLASS.getDeclaredMethod(METHOD_GET_FILTERS);
            getFiltersMethod.setAccessible(true);
            Object chainFilters = getFiltersMethod.invoke(chain);

            if (Objects.nonNull(chainFilters) && chainFilters instanceof List) {
                filters = (List<GatewayFilter>) chainFilters;
                //TakeTimeLogEndFilter 在filters中对应的下标
                int endFilterIndex = 0;
                //NettyRoutingFilter 在filters中对应的下标
                int nettyFilterIndex = 0;
                //ForwardRoutingFilter 在filters中对应的下标
                int forwardFilterIndex = 0;
                //获取适配器类GatewayFilterAdapter 为什么要获取?
                //因为FilteringWebHandler.loadFilters把GlobalFilter都用GatewayFilterAdapter做了一层适配,GatewayFilterAdapter的delegate才是真正的GlobalFilter。
                //并且做了1个判断 如果GlobalFilter instanceof Ordered 那么需要包装成 new OrderedGatewayFilter(gatewayFilterAdapter, order);
                //下面需要的判断是真正的GlobalFilter的类型 因此需要一层一层剥开

                Field adapterDeleteField = GATEWAY_FILTER_ADAPTER_CLASS.getDeclaredField(FIELD_DELEGATE);
                adapterDeleteField.setAccessible(true);

                if (CollectionUtils.isNotEmpty(filters)) {
                    for (int i = 0; i < filters.size(); i++) {
                        GatewayFilter gatewayFilter = filters.get(i);
                        if (gatewayFilter instanceof OrderedGatewayFilter) {
                            OrderedGatewayFilter orderedGatewayFilter = (OrderedGatewayFilter) gatewayFilter;
                            GatewayFilter gatewayFilterAdapter = orderedGatewayFilter.getDelegate();
                            if (gatewayFilterAdapter.getClass() == GATEWAY_FILTER_ADAPTER_CLASS){
                                Object globalFilter = null;
                                try {
                                    globalFilter = adapterDeleteField.get(gatewayFilterAdapter);
                                } catch (Exception e) {
                                    log.info("反射获取globalFilter失败");
                                    globalFilter = null;
                                }
                                //耗时统计结束过滤器
                                if (globalFilter instanceof TakeTimeLogEndFilter) {
                                    endFilterIndex = i;
                                }
                                //路由分发与发起请求过滤器
                                if (globalFilter instanceof NettyRoutingFilter) {
                                    nettyFilterIndex = i;
                                }
                                //转发过滤器
                                if (globalFilter instanceof ForwardRoutingFilter) {
                                    forwardFilterIndex = i;
                                }
                            }
                        }
                    }
                    //调换顺序 将耗时统计的Filter放到 NettyRoutingFilter 和  ForwardRoutingFilter 之前
                    GatewayFilter endFilter = filters.get(endFilterIndex);
                    GatewayFilter nettyFilter = filters.get(nettyFilterIndex);
                    GatewayFilter forwardFilter = filters.get(forwardFilterIndex);
                    if (Objects.nonNull(endFilter)
                            && Objects.nonNull(nettyFilter)
                            && Objects.nonNull(forwardFilter)
                            && endFilterIndex != 0
                            && nettyFilterIndex != 0
                            && forwardFilterIndex != 0) {
                        filters.set(endFilterIndex, nettyFilter);
                        filters.set(nettyFilterIndex, forwardFilter);
                        filters.set(forwardFilterIndex, endFilter);
                    }
                }
            }
        } catch (Exception e) {
            log.info("调整filter执行顺序失败!");
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            }));
        }
        //放入耗时统计的开始时间
        exchange.getAttributes().put(TakeTimeLogBeginFilter.START_TIME, System.currentTimeMillis());
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
        }));
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;


@Component
@Slf4j
public class TakeTimeLogEndFilter implements GlobalFilter, Ordered {

    public TakeTimeLogEndFilter() {
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        //反射加载类失败 直接放行
        if (Objects.isNull(TakeTimeLogBeginFilter.GATEWAY_FILTER_ADAPTER_CLASS) || Objects.isNull(TakeTimeLogBeginFilter.DEFAULT_GATEWAY_FILTER_CHAIN_CLASS)) {
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            }));
        }

        int indexValue = 0;
        try {
            Field index = TakeTimeLogBeginFilter.DEFAULT_GATEWAY_FILTER_CHAIN_CLASS.getDeclaredField(TakeTimeLogBeginFilter.FIELD_INDEX);
            index.setAccessible(true);
            indexValue = index.getInt(chain);
        } catch (Exception e) {
            log.info("获取DefaultGatewayFilterChainClass的字段index失败");
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            }));
        }

        int finalIndexValue = indexValue;
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            List<GatewayFilter> filters;
            try {
                Method method = TakeTimeLogBeginFilter.DEFAULT_GATEWAY_FILTER_CHAIN_CLASS.getDeclaredMethod(TakeTimeLogBeginFilter.METHOD_GET_FILTERS);
                method.setAccessible(true);
                Object invoke = method.invoke(chain);
                Long startTime = exchange.getAttribute(TakeTimeLogBeginFilter.START_TIME);
                if (Objects.nonNull(invoke) && invoke instanceof List) {
                    filters = (List<GatewayFilter>) invoke;
                    if (CollectionUtils.isNotEmpty(filters)) {
                        if (startTime != null) {
                            Long executeTime = (System.currentTimeMillis() - startTime);
                            Integer rawStatusCode = null;
                            if (Objects.nonNull(exchange.getResponse())) {
                                rawStatusCode = exchange.getResponse().getRawStatusCode();
                            }
                            log.info("==================请求路径:{},过滤器数量:{},当前下标:{},累计耗时:{},响应编码信息:{}", exchange.getRequest().getURI().getRawPath(), filters.size(), finalIndexValue, executeTime, rawStatusCode);
                        }
                    }
                }
            } catch (Exception e) {
                log.info("TakeTimeLogEndFilter#filter耗时统计失败");
            }
        }));
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

虽然这样也可以但是总感觉不优雅,又重新翻了一下GatewayAutoConfiguration的源码。

源码如下。

@Bean
public FilteringWebHandler filteringWebHandler(List<GlobalFilter> globalFilters) {
   return new FilteringWebHandler(globalFilters);
}

拍大腿啊!

我们只需要在注入FilteringWebHandler的时候,拿到globalFilters

globalFilters前后加入自定义统计耗时的filter即可。

具体点:从容器获取FilteringWebHandler获取globalFiltersglobalFilters前后加入自定义统计耗时的filter即可。