alibaba / Sentinel

A powerful flow control component enabling reliability, resilience and monitoring for microservices. (面向云原生微服务的高可用流控防护组件)
https://sentinelguard.io/
Apache License 2.0
22.34k stars 8.01k forks source link

Gateway SentinelGatewayFilter throws FlowException after all the GlobalFilter excuted #821

Open cui1100 opened 5 years ago

cui1100 commented 5 years ago

My SpringCloud Gateway has lots of globla filters , the sentinel filter is first . when the try number is larger than setting number, i hope that the sentinel throws the exception and finish, but because of code chain.filter(exchange) ,all the filter would be excuted ,then throws my exception .

i want to ask that ,how to throw exception immediately if over the setting number , don't excute the other filters

sczyh30 commented 5 years ago

Hi, could you please give a code snippet to demonstrate your problem?

cui1100 commented 5 years ago

First Filter:

@Component
public class FirstFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("First Filter ------------");
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

Copy of SentinelGatewayFilter:

@Override
    public int getOrder() {
        return 2;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
        System.out.println("-------------Copy of SentinelGatewayFilter----------");
        Mono<Void> asyncResult = chain.filter(exchange);
        if (route != null) {
            String routeId = route.getId();
            Object[] params = paramParser.parseParameterFor(routeId, exchange,
                    r -> r.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID);
            String origin = Optional.ofNullable(GatewayCallbackManager.getRequestOriginParser())
                    .map(f -> f.apply(exchange))
                    .orElse("");
            asyncResult = asyncResult.transform(
                    new SentinelReactorTransformer<>(new EntryConfig(routeId, EntryType.IN,
                            1, params, new ContextConfig(contextName(routeId), origin)))
            );
        }

        Set<String> matchingApis = pickMatchingApiDefinitions(exchange);
        for (String apiName : matchingApis) {
            Object[] params = paramParser.parseParameterFor(apiName, exchange,
                    r -> r.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME);
            asyncResult = asyncResult.transform(
                    new SentinelReactorTransformer<>(new EntryConfig(apiName, EntryType.IN, 1, params))
            );
        }

        return asyncResult;
    }

Last Filter:

@Component
public class LastFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("Last Filter ------------");
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 3;
    }
}

I thought the demo would stop on the SentinelFilter(Order 2) and throws Exception, but the LastFilter(Order 3) printed and throwed exception then.

sczyh30 commented 5 years ago

I'll take a view these days.

shxz130 commented 5 years ago

i think you need to adjust the filter's excution order,when the sentinel‘s filter is first execute Filter and throws exception, other’s filter has no time to execute。

cui1100 commented 5 years ago

i think you need to adjust the filter's excution order,when the sentinel‘s filter is first execute Filter and throws exception, other’s filter has no time to execute。

I Had try what u said, I deleted the first Filter, Sentinel Filter is first, but the last Filter still excuted and printed,then throws the FlowException...

cui1100 commented 5 years ago

I'll take a view these days.

Big god , pay attention to my question please! We Need U

jasonjoo2010 commented 5 years ago

May the problem locates on this line?

Mono<Void> asyncResult = chain.filter(exchange);

chain.filter at the beginning invoke the next filter first in future.

sczyh30 commented 5 years ago

The filters in Spring Cloud Gateway is reactive (i.e. asynchronous), so chain.filter(exchange) does not actually invoke the next filter. The filters are invoked on subscription (i.e. when requests are coming).

The Sentinel Reactor operator wraps the subscribe operation with the Sentinel subscriber. For example:

public class MonoSentinelOperator<T> extends MonoOperator<T, T> {

    @Override
    public void subscribe(CoreSubscriber<? super T> actual) {
        source.subscribe(new SentinelReactorSubscriber<>(entryConfig, actual, true));
    }
}

This will trigger the onSubscribe event for downstream. The filter chain is wrapped with Mono.defer(), which will be triggered on subscription. See FilterWebHandler of Spring Cloud Gateway:

    private static class DefaultGatewayFilterChain implements GatewayFilterChain {

        private final int index;

        private final List<GatewayFilter> filters;

        DefaultGatewayFilterChain(List<GatewayFilter> filters) {
            this.filters = filters;
            this.index = 0;
        }

        private DefaultGatewayFilterChain(DefaultGatewayFilterChain parent, int index) {
            this.filters = parent.getFilters();
            this.index = index;
        }

        public List<GatewayFilter> getFilters() {
            return filters;
        }

        @Override
        public Mono<Void> filter(ServerWebExchange exchange) {
            return Mono.defer(() -> {
                if (this.index < filters.size()) {
                    GatewayFilter filter = filters.get(this.index);
                    DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this,
                            this.index + 1);
                    return filter.filter(exchange, chain);
                }
                else {
                    return Mono.empty(); // complete
                }
            });
        }

    }

The asynchronous part of the proceeding filters won't be called actually (the Sentinel reactor subscriber will cut down the event if blocked). But this should be resolved, as some operations (not in the reactive stream) are indeed performed during subscription from upstream (this might be difficult to understand).

Nirvana2047 commented 3 years ago

I also encountered the same problem. There are some custom filters in my gateway to process services, such as authorization authentication. When I put SentinelGatewayFilter first in the Filter chain, due to the line of Mono asyncResult = chain.filter(exchange), when ParamFlowException is thrown, SpringCloudGateway returns Mono.empty(), which is not expected of. The reason is because other business Filter has also been executed and committed Response. My expectation is that when the current limit occurs, it will not enter other filters. But now, I can only put SentinelGatewayFilter at the end. When other Filters are committed, I no longer enter SentinelGatewayFilter and return Response directly.