Tianqiong-gateway gateway series 2: cómo diseñar la cadena de filtros

Dirección de código abierto

https://github.com/XiaoMi/mone/tree/master/gateway-all Bienvenidos amigos que estén interesados ​​en la tecnología nativa de la nube /eficiencia de I+D a unirse a nosotros (Fork, Star).

1. ¿Qué hace la cadena de filtros en la puerta de enlace?

En el primer artículo de la serie Gateway, aprendimos que una de las funciones principales de la puerta de enlace es hacer converger las funciones públicas no comerciales de cada microservicio, como autenticación, monitoreo, equilibrio de carga, almacenamiento en caché, etc., para reducir el responsabilidades de cada servicio tanto como sea posible. Aquí, utilizamos la cadena de filtros para implementar esta función.

Cada función pública corresponde a un filtro, como filtro de autenticación, filtro de equilibrio de carga, etc. La puerta de enlace organiza estos filtros para generar un enlace de filtro secuencial. En el primer paso, después de que la solicitud ingresa a la puerta de enlace, pasará secuencialmente por cada filtro en el enlace. En este momento, podemos procesar la solicitud. El segundo paso es llegar al módulo de conversión de protocolo para la conversión de protocolo, distribuirlo a la aplicación de fondo correspondiente y obtener el resultado devuelto. En el tercer paso, el resultado de la respuesta pasa nuevamente por cada filtro en el enlace en orden inverso, aquí podemos realizar cierto procesamiento en el resultado devuelto.

2. Diseñar un enlace de filtro

1. Definir un único filtro

  • RequestFilter: la clase abstracta de filtro, que implementa filtros específicos basados ​​en esta clase abstracta. Las variables miembro de la clase pública y los métodos doFilter se definen en su interior.

  • FilterDef: utilice un conjunto de datos (id, nombre) para marcar la unicidad del filtro y, además, registre cierta información sobre el filtro (versión, autor, etc.).

  • Invoker: la clave para concatenar filtros.

  • FilterContext: se utiliza para pasar información de contexto de filtro, como ip, encabezados, 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);
}

Al definir la clase abstracta de filtro, hay un método muy importante en ella.

doFilter (contexto FilterContext, invocador Invoker, ApiInfo apiInfo, solicitud FullHttpRequest);

Junto con Invoker, es la clave para conectar todo el enlace del filtro.

2. Cargue la cadena del filtro.

  • RequestFilterChain: tiene principalmente dos funciones principales. La primera es cargar filtros y organizarlos en un enlace; la segunda es solicitar específicamente la entrada al enlace del filtro.

//省略一部分代码,可前往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. Orden de ejecución de la cadena de filtros.

FilterOrder: utilice anotaciones para controlar el orden de ejecución de los filtros en el enlace del filtro.

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

Al cargar e inicializar el enlace del filtro en RequestFilterChain , llame al método sortFilterList para ordenar todos los filtros.

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. Tome un filtro específico como ejemplo.

1. Ejecución de un único filtro

Aquí implementamos un filtro simple, cuya función principal es registrar los parámetros de entrada de la solicitud y devolver resultados. Cuando una solicitud ingresa a la puerta de enlace y llega a LogFilter, primero determinamos si el registro está habilitado para la solicitud. Si es así, registramos el registro de la solicitud y luego llamamos a invoker.doInvoker (context, apiInfo, request) para obtener el resultado devuelto y registrar el registro del resultado devuelto nuevamente.

@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. Ejecución del enlace de filtro.

También tomamos esta imagen como ejemplo. Los filtros con orden 1, 2 y 3 forman una cadena de filtros. Después de que la solicitud ingresa a la puerta de enlace, la solicitud se procesa en el orden ①, ② y ③. Después de obtener el resultado devuelto resultado, presione ④, ⑤, La secuencia de ⑥ pasa por el filtro y finalmente devuelve el resultado a la persona que llama.

Resumen de los artículos de la serie Gateway

Tianqiong-gateway gateway serie 1: introducción general de Tesla gateway

Supongo que te gusta

Origin blog.csdn.net/shanwenbang/article/details/129021697
Recomendado
Clasificación