Tianqiong-gateway gateway series 2: how to design the filter chain

Open source address

https://github.com/XiaoMi/mone/tree/master/gateway-all Welcome friends who are interested in cloud native technology/R&D efficiency to join (Fork, Star) us.

1. What does the filter chain in the gateway do?

In the first article of the gateway series, we learned that one of the core functions of the gateway is to converge the public non-business functions of each microservice, such as authentication, monitoring, load balancing, caching, etc., to reduce the responsibilities of each service as much as possible. Here, we use filter chain to implement this function.

Each public function corresponds to a filter, such as authentication filter, load balancing filter, etc. The gateway organizes these filters to generate a sequential filter link. In the first step, after the request enters the gateway, it will pass through each filter in the link sequentially. At this time, we can do some processing on the request. The second step is to reach the protocol conversion module for protocol conversion, distribute it to the corresponding back-end application and get the return result. In the third step, the response result passes through each filter in the link again in reverse order. Here we can perform certain processing on the returned result.

2. Design a filter link

1. Define a single filter

  • RequestFilter: The abstract class of filter, which implements specific filters based on this abstract class. Public class member variables and doFilter methods are defined inside.

  • FilterDef: Use a set of data (id, name) to mark the uniqueness of the filter, and additionally record some information about the filter (version, author, etc.).

  • Invoker: The key to concatenating filters.

  • FilterContext: used to pass filter context information, such as ip, headers, etc.

//省略一部分代码,可前往https://github.com/XiaoMi/mone/tree/master/gateway-all查看
@Slf4j
public abstract class RequestFilter {
    //唯一定义某个filter
    protected FilterDef def = new FilterDef(0, "", "", "");

    public abstract FullHttpResponse doFilter(FilterContext context, Invoker invoker, ApiInfo apiInfo, FullHttpRequest request);

    //上下线某个filter的开关
    @Getter
    @Setter
    private volatile boolean offline = false;
    
    //查看具体api是否开启某个filter
    public boolean allow(ApiInfo apiInfo) {
        if (null == apiInfo) {
            log.info("filter error filter id:{} name:{}", this.def.getId(), this.def.getName());
            return false;
        }

        if (null == apiInfo.getFilterInfoMap()) {
            return false;
        }
        return apiInfo.getFilterInfoMap().containsKey(this.getDef().getName());
    }
    
    //停止filter
    public void stop() {
        String info = StringUtils.isEmpty(this.def.getName()) ? this.getClass().getSimpleName() : this.def.toString();
        log.info("filter stop:{}", info);
    }

    //filter 初始化public void init() {
        String info = StringUtils.isEmpty(this.def.getName()) ? this.getClass().getSimpleName() : this.def.toString();
        log.info("filter init:{}", info);
    }
}
public interface Invoker {

    FullHttpResponse doInvoker(FilterContext context, ApiInfo apiInfo, FullHttpRequest request);
}

When defining the filter abstract class, there is a very important method in it

doFilter(FilterContext context, Invoker invoker, ApiInfo apiInfo, FullHttpRequest request);

Together with Invoker, it is the key to connecting the entire filter link.

2. Load the filter chain

  • RequestFilterChain: Mainly has two core functions. The first is to load filters and organize them into a link; the second is to specifically request the entrance to the filter link.

//省略一部分代码,可前往https://github.com/XiaoMi/mone/tree/master/gateway-all查看
@Component
public class RequestFilterChain implements IRequestFilterChain {

    @Autowired
    private ApplicationContext ac;

    private volatile Invoker lastInvoker;

    private final CopyOnWriteArrayList<RequestFilter> filterList = new CopyOnWriteArrayList<>();

    //只初始化一次
    private AtomicBoolean init = new AtomicBoolean(false);

    @PostConstruct
    public void init() {
        reload("init", Lists.newArrayList());
    }

//加载filterpublic void reload(String type, List<String> names) {
        log.info("reload filter");
        //获取所有系统定义的filter
        Map<String, RequestFilter> map = ac.getBeansOfType(RequestFilter.class);
        List<RequestFilter> list = new ArrayList<>(map.values());
        log.info("system filter size:{}", list.size());
        
        //对filter进行排序
        list = sortFilterList(list);

        //停止所有filter
        stopFilter();
        filterList.clear();
        filterList.addAll(list);

        init.set(false);
        long begin = System.currentTimeMillis();
        getLock();

        try {
            log.info("get write lock use time:{}", System.currentTimeMillis() - begin);
            Invoker last = new GatewayInvoker();
            int size = list.size();
            //串联filter链路
            for (int i = 0; i < size; i++) {
                log.info("init filter index:{}", i);
                RequestFilter filter = list.get(i);
                filter.init();
                Invoker next = last;
                final int ti = i;
                //Invoker的实现
                last = (context, apiInfo, request) -> {
                    long now = System.currentTimeMillis();
                    FullHttpResponse res = filter.doFilter(context, next, apiInfo, request);
                    long rpcUseTime = filter.rpcFilter() ? 0 : context.getRpcUseTime();
                    long useTime = System.currentTimeMillis() - now - rpcUseTime;
                    if (useTime > 250) {
                        context.addTraceEvent("filter_" + ti + "_" + filter.getName(), useTime);
                    }
                    return res;
                };
            }
            log.info("init filter finish");
            this.lastInvoker = last;
        } finally {
            lock.writeLock().unlock();
            log.info("reload end");
        }

        init.set(true);
    }

    //具体请求进入filter链路的入口
    public FullHttpResponse doFilter(ApiInfo apiInfo, FullHttpRequest request, RequestContext context) {
        if (!init.get()) {
            return HttpResponseUtils.create(Result.fromException(new RuntimeException("filter init")));
        }
        try {
            FilterContext ctx = this.initFilterContext(context);
            try {
                lock.readLock().lock();
                return this.lastInvoker.doInvoker(ctx, apiInfo, request);
            } finally {
                lock.readLock().unlock();
            }
        } catch (Throwable ex) {
            log.error(ex.getMessage(), ex);
            throw ex;
        }
    }
}

3. Execution order of filter chain

FilterOrder: Use annotations to control the execution order of filters in the filter link.

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Documented
public @interface FilterOrder {
    /**     * 越小的执行次序越靠前     * @return*/int value();
}

When loading and initializing the filter link in RequestFilterChain , call the sortFilterList method to sort all filters.

private List<RequestFilter> sortFilterList(List<RequestFilter> list) {
    return list.stream().sorted((a, b) -> {
        Integer x = a.getClass().getAnnotation(FilterOrder.class).value();
        Integer y = b.getClass().getAnnotation(FilterOrder.class).value();
        return y.compareTo(x);
    }).collect(Collectors.toList());
}

3. Take a specific filter as an example

1. Execution of a single filter

Here we implement a simple filter, whose main function is to record the input parameters of the request and return results. When a request enters the gateway and reaches the LogFilter, we first determine whether logging is enabled for the request. If so, record the request log, then call invoker.doInvoker(context, apiInfo, request) to obtain the return result, and record the log of the return result again.

@Component
@FilterOrder(100)
public class LogFilter extends RequestFilter {
    private static final Logger logger= LoggerFactory.getLogger(LogFilter.class);

    @Override
    public FullHttpResponse doFilter(FilterContext context, Invoker invoker, ApiInfo apiInfo, FullHttpRequest request) {
        //判断具体请求有没有开启日志记录
        if (apiInfo.isAllow(Flag.ALLOW_LOG)) {
            long begin = System.currentTimeMillis();
            String params = "";
            if (request.method().equals(HttpMethod.GET)) {
                params = HttpRequestUtils.getQueryString(request);
            }
            if (request.method().equals(HttpMethod.POST)) {
                params = new String(HttpRequestUtils.getRequestBody(request));
            }

            String traceId = context.getTraceId();
            logger.info("invoke begin : id:{} traceId:{} method:{} uri:{} params:{} headers:{}", apiInfo.getId(), traceId, request.method(), request.uri(), params, request.headers().toString());
            try {
                FullHttpResponse res = invoker.doInvoker(context, apiInfo, request);
                String content = HttpResponseUtils.getContent(res);
                logger.info("invoke end : id:{} traceId:{} method:{} uri:{} content:{} uid:{} useTime:{}", apiInfo.getId(), traceId, request.method(), request.uri(), content, context.getUid(), System.currentTimeMillis() - begin);
                return res;
            } catch (Throwable ex) {
                //捕获下,然后打印异常
                logger.warn("invoke error : id:{} traceId:{} method:{} uri:{} params:{} ex:{} useTime:{}", apiInfo.getId(), traceId, request.method(), request.uri(), params, ex.getMessage(), System.currentTimeMillis() - begin);
                throw ex;
            }
        } else {
            return invoker.doInvoker(context, apiInfo, request);
        }
    }
}

2. Execution of filter link

We also take this picture as an example. Filters with order 1, 2, and 3 form a filter chain. After the request enters the gateway, the request request is processed in the order of ①, ②, and ③. After obtaining the returned result, press ④, ⑤, The sequence of ⑥ goes through the filter and finally returns the result to the caller.

Summary of gateway series articles

Tianqiong-gateway gateway series 1: overall introduction of Tesla gateway

Guess you like

Origin blog.csdn.net/shanwenbang/article/details/129021697