天穹-gateway网关系列2:如何设计filter链

开源地址

https://github.com/XiaoMi/mone/tree/master/gateway-all 欢迎对云原生技术/研发效能感兴趣的小伙伴加入(Fork、Star)我们。

一、网关里的filter链是做什么的

在gateway网关系列文章的第一篇里,我们知道网关的一个核心职能是收敛各个微服务公共非业务功能,如身份验证、监控、负载均衡、缓存等,以尽可能减少各服务的职责。在这里,我们用filter链来实现这个功能。

每个公共功能对应一个filter,比如身份验证filter、负载均衡filter等。网关组织这些filter生成一个有顺序的filter链路。第一步,request请求进入网关后,会顺序经过链路中的每个filter,这个时候我们可以对请求做一些处理。第二步,到达协议转换模块进行协议转换,分发到对应的后端应用并拿到返回结果。第三步,response结果逆序再次经过链路中的每个filter,这里我们可以对返回结果进行一定的处理。

二、设计一个filter链路

1、定义单个filter

  • RequestFilter: filter的抽象类,基于此抽象类实现具体的filter。里面定义了公共类成员变量和doFilter方法等。

  • FilterDef: 用一组数据(id、name)标记filter的唯一性,并额外记录filter的一些信息(version、author等)。

  • Invoker: 串联filter的关键。

  • FilterContext: 用于传递filter的上下文信息,比如ip,headers等

//省略一部分代码,可前往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);
}

在定义filter抽象类时,里面有一个很重要的方法

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

它和Invoker一起是串联起整个filter链路的关键。

2、加载filter链

  • RequestFilterChain: 主要有两个核心功能。一是加载filter并将它们组织成一条链路;二是具体请求进入filter链路的入口。

//省略一部分代码,可前往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、filter链的执行顺序

FilterOrder: 使用注解来控制filter链路里filter的执行顺序。

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

RequestFilterChain里加载并初始化filter链路时,调用sortFilterList方法对有所有的filter进行排序

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());
}

三、以具体的filter举例

1、单个filter的执行

这里我们实现了一个简单的filter,他的主要作用是记录请求的入参和返回结果。请求进入网关到达LogFilter,我们首先判断该请求有没有开启日志记录,如果开启记录下请求日志,然后调用invoker.doInvoker(context, apiInfo, request)获得返回结果,并再次记录下返回结果的日志。

@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、filter链路的执行

我们还以此图举例,order为1,2,3的filter组成一个filter链,请求进入网关后,request请求按按①、②、③的顺序被处理、获取到返回结果后按④、⑤、⑥的顺序经过filter,最终返回结果给调用方。

网关系列文章汇总

天穹-gateway网关系列1:Tesla网关整体介绍

猜你喜欢

转载自blog.csdn.net/shanwenbang/article/details/129021697
今日推荐