Spring-Cloud-Gateway 从升级到放弃

  • Built on Spring Framework 5, Project Reactor and Spring Boot 2.0
  • Able to match routes on any request attribute.

  • Predicates and filters are specific to routes.

  • Hystrix Circuit Breaker integration.

  • Spring Cloud DiscoveryClient integration

  • Easy to write Predicates and Filters
  • Request Rate Limiting
  • Path Rewriting

这是官方说的,spring gateway相对spring zuul要新很多,应用也更加自由,开发体验更好。但是我在测试中发现,spring-cloud-gateway相对于zuul来说,更加出众的还是其性能,当然最后让我放弃的也是因为这一点。

网上的朋友也有做一些gateway和zuul的性能比较,大多的结论也是gateway要优于zuul,同时也更加稳定。

但是我们不能轻信,所以我也做了测试,这部分测试内容若不感兴趣可以跳过,zuul就不测试了。

2.spring-cloud-gateway的初步测试

step.1:测试准备:

1.gateway版本:2.0.1

2.服务主机:10.1.4.32,16G内存,4核虚拟机

3.测试客户端:10.1.4.34,16G内存,4核虚拟机

4.测试工具wrk

step.2:建立gateway工程并写两个测试http接口,

1.http://10.1.4.32:14077/hello [POST]

2.http://10.1.4.32:14077/test [GET]

step.3:开始测试

step.4:测试结果

[wrk@localhost wrk]$ ./wrk  -t 15 -c500 -d 10 --latency  http://10.1.4.32:14077/test
Running 10s test @ http://10.1.4.32:14077/test
  15 threads and 500 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.38ms    3.26ms 113.45ms   95.76%
    Req/Sec    10.84k     1.44k   26.48k    89.50%
  Latency Distribution
     50%    2.79ms
     75%    3.51ms
     90%    4.21ms
     99%   17.23ms
  1625714 requests in 10.10s, 131.79MB read
Requests/sec: 160961.07
Transfer/sec:     13.05MB

以及:

[wrk@localhost wrk]$ ./wrk  -t 15 -c500 -d 10 --latency -s scripts/gateway.lua  http://10.1.4.32:14077/hello
Running 10s test @ http://10.1.4.32:14077/hello
  15 threads and 500 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     5.21ms    3.96ms 255.59ms   96.75%
    Req/Sec     6.62k   604.79    13.72k    88.48%
  Latency Distribution
     50%    4.78ms
     75%    5.55ms
     90%    6.32ms
     99%   14.87ms
  994374 requests in 10.10s, 539.59MB read
Requests/sec:  98471.14
Transfer/sec:     53.43MB

说明,如果测试结果差别较大可能是因为测试工具的问题。

结果显示,POST方法的性能TPS达到了10W/s,而GET方法的性能TPS达到了16W/s。

这看起来很不可思议,因为正常的微服务,能达到2W/s的性能已经是良好,达到10W实在是不可思议。但是前面说了spring-cloud-gateway引入了Spring Reactor反应式编程,应对的便是这种高并发需求。

当然,即便spring-cloud-gateway给了我们很大惊喜,但是如果因此就引入了spring-cloud-gateway,那还是会有些草率,毕竟gateway是用来干什么的?是路由和过滤。继续测试。

step.5:加上路由和过滤器,在配置文件中加入下面内容

spring:
  cloud:
    gateway:
      routes:
      - id: test
        uri: http://10.1.4.32:14077/test
        predicates:
        - Path=/tt
        filters:
        - AddRequestParameter=foo, bar

表示,给test方法加入了路由,并且加入了官方提供的过滤器:AddRequestParameter=foo, bar

step.6:测试,并附测试结果:

[wrk@localhost wrk]$ ./wrk  -t 15 -c500 -d 10 --latency  http://10.1.4.32:14077/tt
Running 10s test @ http://10.1.4.32:14077/tt
  15 threads and 500 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    18.99ms   12.15ms 122.69ms   70.84%
    Req/Sec     1.82k   155.77     2.36k    73.94%
  Latency Distribution
     50%   17.03ms
     75%   25.49ms
     90%   35.02ms
     99%   57.13ms
  274529 requests in 10.10s, 22.25MB read
Requests/sec:  27182.88
Transfer/sec:      2.20MB

性能只剩27000/s,貌似降低了很多,但是比起zuul仍然快了不少。因为在这台机器上,测试zuul或许都不能到达2W。

那么,是不是就应该使用spring-cloud-gateway了?

3.开始使用spring-cloud-gateway

在使用上spring-cluod-gateway之后,我开始编辑自己的过滤器,需求要求写两个过滤器,修改请求体和响应体。

因为需要对特定的请求使用过滤器,所以这里使用gateway-filter,有些代码官方有,有些网友提供,两个过滤器代码大致如下:

解密过滤器,pre:

Spring-Cloud-Gateway 从升级到放弃Spring-Cloud-Gateway 从升级到放弃
package com.newland.dc.ctid.fileter;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.newland.dc.common.vo.RequestHeaderVo;
import com.newland.dc.ctid.entity.dto.RequestDto;
import io.netty.buffer.ByteBufAllocator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.core.style.ToStringCreator;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Created by garfield on 2019/2/26.

 */
@Component
public class DecryptGatewayFilterFactory extends AbstractGatewayFilterFactory{

    private static Logger log = LoggerFactory.getLogger(DecryptGatewayFilterFactory.class);

    public static final String DECRYPT_HEADER = "decrypt_header";

    public DecryptGatewayFilterFactory() {
        super(Config.class);
    }

    private Gson gson = new GsonBuilder().serializeNulls().create();

    @Override
    @SuppressWarnings("unchecked")
    public GatewayFilter apply(Config config) {
        return new DecryptGatewayFilter(config);
    }

    public class DecryptGatewayFilter implements GatewayFilter, Ordered {
        Config config;

        DecryptGatewayFilter(Config config) {
            this.config = config;
        }
        @Override
        public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                log.debug(config.toString());
                ServerHttpRequest request = exchange.getRequest();
                MediaType contentType = request.getHeaders().getContentType();

                boolean postRequest = "POST".equalsIgnoreCase(request.getMethodValue()) && !contentType.toString().contains("multipart/form-data");
                //判断是否为POST请求
                if (postRequest) {

                    Flux body = request.getBody();
                    AtomicReference bodyRef = new AtomicReference<>();//缓存读取的request body信息
                    body.subscribe(dataBuffer -> {
                        CharBuffer charBuffer = StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer());
                        DataBufferUtils.release(dataBuffer);
                        bodyRef.set(charBuffer.toString());
                    });//读取request body到缓存
                    String bodyStr = bodyRef.get();//获取request body
                    log.debug(bodyStr);//这里是我们需要做的操作
                    RequestDto requestDto = gson.fromJson(bodyStr, RequestDto.class);
                    log.debug("decrypt filter");
                    //save header to response header
                    RequestHeaderVo headerVo = requestDto.getHeader();
                    headerVo.setAppVersion("1000");
                    //此处可以传递一些变量
                    exchange.getResponse().getHeaders().add(DECRYPT_HEADER, gson.toJson(headerVo));

                    DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
                    Flux bodyFlux = Flux.just(bodyDataBuffer);

                    request = new ServerHttpRequestDecorator(request){
                        @Override
                        public Flux getBody() {
                            return bodyFlux;
                        }
                    };//封装我们的request
                }
                return chain.filter(exchange.mutate().request(request.mutate().header("a","200").build()).build());
            };

        @Override
        public int getOrder() {
            return -10;
        }
    }

    public static DataBuffer stringBuffer(String value) {
        byte[] bytes = value.getBytes(StandardCharsets.UTF_8);

        NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
        DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
        buffer.write(bytes);
        return buffer;
    }

    @Override
    public ServerHttpRequest.Builder mutate(ServerHttpRequest request) {
        return null;
    }

    public static class Config {
        private boolean decrypt;

        public boolean isDecrypt() {
            return decrypt;
        }

        public void setDecrypt(boolean decrypt) {
            this.decrypt = decrypt;
        }

        @Override
        public String toString() {
            return new ToStringCreator(this)
                    .append("decrypt", decrypt)
                    .toString();
        }
    }

    @Override
    public List shortcutFieldOrder() {
        return Arrays.asList("decrypt");
    }
}

View Code

加密过滤器,使用源码的提供的修改方法,post:

Spring-Cloud-Gateway 从升级到放弃Spring-Cloud-Gateway 从升级到放弃
package com.newland.dc.ctid.fileter;

import com.google.gson.Gson;
import com.newland.dc.common.vo.RequestHeaderVo;
import com.newland.dc.ctid.entity.dto.RequestDto;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory;
import org.springframework.core.style.ToStringCreator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;

/**
 * @Auther: garfield
 * @Date: 2019/3/5 15:33
 * @Description:
 */
@Component
public class AnGatewayFilterFactory extends AbstractGatewayFilterFactory {

    private Gson gson = new Gson();

    public AnGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {

        ModifyResponseBodyGatewayFilterFactory m1 = new ModifyResponseBodyGatewayFilterFactory(null);
        ModifyResponseBodyGatewayFilterFactory.Config c1 = new ModifyResponseBodyGatewayFilterFactory.Config();
        c1.setInClass(String.class);
        c1.setOutClass(String.class);
        c1.setNewContentType("application/json");

        c1.setRewriteFunction((exchange, body) -> {
            ServerWebExchange ex = (ServerWebExchange) exchange;
            //此处更改响应体
            RequestHeaderVo requestHeaderVo = new RequestHeaderVo();
            RequestDto requestDto = gson.fromJson(body.toString(), RequestDto.class);
            requestDto.setHeader(requestHeaderVo);
            body = gson.toJson(requestDto);
            return Mono.just(body);
        });
        return m1.apply(c1);
    }

    public static class Config {
        private boolean decrypt;

        public boolean isDecrypt() {
            return decrypt;
        }

        public void setDecrypt(boolean decrypt) {
            this.decrypt = decrypt;
        }

        @Override
        public String toString() {
            return new ToStringCreator(this)
                    .append("encrypt", decrypt)
                    .toString();
        }
    }

    @Override
    public List shortcutFieldOrder() {
        return Arrays.asList("encrypt");
    }
}

View Code

这里需要转移一下话题,这个过滤器修改其实有几种方法,可以自己写,也可以应用源码提供的例子。上面的两种写法已经测试都能使用,其实我还有两种方式,大同小异就是了,但也准备贴出来,也记录一下问题:

下面这个其实就是源码中的例子,只不过不引用,自己写:

Spring-Cloud-Gateway 从升级到放弃Spring-Cloud-Gateway 从升级到放弃
    @Override
        @SuppressWarnings("unchecked")
        public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpResponseDecorator responseDecorator </span>=<span> new ServerHttpResponseDecorator(exchange.getResponse()) {
            @Override
            public Mono</span><void> writeWith(Publisher<? extends DataBuffer><span> body) {
                ServerHttpRequest request </span>=<span> exchange.getRequest();

                MediaType originalResponseContentType </span>=<span> exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
                HttpHeaders httpHeaders </span>=<span> new HttpHeaders();
                httpHeaders.setContentType(originalResponseContentType);
                ResponseAdapter responseAdapter </span>=<span> new ResponseAdapter(body, httpHeaders);
                DefaultClientResponse clientResponse </span>=<span> new DefaultClientResponse(responseAdapter, ExchangeStrategies.withDefaults());

                Mono</span><databuffer> modifiedBody =<span> clientResponse.bodyToMono(DataBuffer.class).map(encrypt(config, new RequestHeaderVo()));

                BodyInserter bodyInserter </span>=<span> BodyInserters.fromPublisher(modifiedBody, DataBuffer.class);
                CachedBodyOutputMessage outputMessage </span>=<span> new CachedBodyOutputMessage(exchange, exchange.getResponse().getHeaders());
                return bodyInserter.insert(outputMessage, new BodyInserterContext())
                        .</span><span>then</span>(Mono.defer(() -&gt;<span> {
                            Flux</span><databuffer> messageBody =<span> outputMessage.getBody();
                            HttpHeaders headers </span>=<span> getDelegate().getHeaders();
                            </span><span>if</span> (headers.getContentLength() &lt; <span>0</span> &amp;&amp; !<span>headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
                                messageBody </span>= messageBody.doOnNext(data -&gt;<span> headers.setContentLength(data.readableByteCount()));
                            }
                            return this.getDelegate().writeWith(messageBody);
                        }));
            }

            @Override
            public Mono</span><void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>&gt;<span> body) {
                return writeWith(Flux.from(body)
                        .flatMapSequential(p </span>-&gt;<span> p));
            }
        };
        return chain.filter(exchange.mutate().response(responseDecorator).build());

    }

    @Override
    public </span><span>int</span><span> getOrder() {
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER </span>- <span>1</span><span>;
    }

}

private Function</span><databuffer,><span> encrypt(Config config, RequestHeaderVo headerVo) {
    </span><span>if</span><span> (config.encrypt) {
        return (i) </span>-&gt;<span> {
            InputStream inputStream </span>=<span> i.asInputStream();

            </span><span>byte</span>[] bytes = new <span>byte</span>[<span>0</span><span>];
            try {
                bytes </span>= new <span>byte</span><span>[inputStream.available()];

                inputStream.read(bytes);
            } catch (IOException e) {
                e.printStackTrace();
            }
            </span><span>//</span><span>&#x8FDB;&#x884C;&#x6211;&#x4EEC;&#x7684;&#x64CD;&#x4F5C;</span>
            String body =<span> new String(bytes);
            log.debug(</span><span>&quot;</span><span>this is response encrypt</span><span>&quot;</span><span>);
            log.debug(body);
            log.debug(headerVo.toString());

// body = encryptService.responseEncrypt(body, headerVo);
            </span><span>//</span><span>&#x8FDB;&#x884C;&#x6211;&#x4EEC;&#x7684;&#x64CD;&#x4F5C;</span>
            return i.<span>write</span><span>(TokenGatewayFilterFactory.stringBuffer(body));

// return i.write(new String(body).getBytes());

};
    } </span><span>else</span><span> {
        return i </span>-&gt;<span> i;
    }

}</span></databuffer,></void></databuffer></databuffer></void></void></pre>View Code

这种例子中,发现修改response body的时候,会引起代码进入NioEventLoop类中的run方法,死循环无法退出,我也不清楚为什么,修改需谨慎。

另一种,跟这位网友写得差不多,只不过我没测试就是了:https://www.jianshu.com/p/9f00e0e1681c

Spring-Cloud-Gateway 从升级到放弃Spring-Cloud-Gateway 从升级到放弃
@Override
        @SuppressWarnings("unchecked")
        public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            return chain.filter(exchange.mutate().response(new ServerHttpResponseDecorator(exchange.getResponse()) {
                @Override
                public Mono writeWith(Publisher body) {
                    DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
                    if (getStatusCode().equals(HttpStatus.OK) && body instanceof Flux) {
                        Flux fluxBody = Flux.first(body);
                        return super.writeWith(fluxBody.map(dataBuffer -> {
                            System.out.println(dataBuffer.readableByteCount());

                            byte[] content = new byte[dataBuffer.readableByteCount()];
                            dataBuffer.read(content);
                            //释放掉内存
                            DataBufferUtils.release(dataBuffer);
                            //responseData就是下游系统返回的内容,可以查看修改
                            String responseData = new String(content, Charset.forName("UTF-8"));

                            log.debug("响应内容:{}", responseData);
                            log.debug("this is response encrypt");
                            System.out.println(responseData);

                            byte[] newContent = responseData.getBytes();
//                body = encryptService.responseEncrypt(body, headerVo);
                            byte[] uppedContent = new String(newContent, Charset.forName("UTF-8")).getBytes();
                            return bufferFactory.wrap(uppedContent);
                        }));
                    } else {
                        log.error("响应code异常:{}", getStatusCode());
                    }
                    return super.writeWith(body);
                }
            }).build());
        }

        @Override
        public int getOrder() {
            return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
        }
    }

View Code

这个方法会出现问题,body的截取长度经常没有完全。

我本来是到这个网址下面寻找答案,作者是这样回复的:

上面只是简单的样例,FIux是发送多个数据的,当报文长时会拆分,处理一次只能拿到一部分报文,可以使用Flux.toArray方法将数据聚合后处理,也可以参照https://www.jianshu.com/p/9b781fb1aaa0里面的响应处理。

确实是这个问题,所以我们也可以仿照他的另外一个例子写,大家可以到他的简书博客中去看,值得提醒的是,他的例子中,版本也是2.0.1,若是版本改为2.1以上,就不能用哦!

这里蛮贴一下:

Spring-Cloud-Gateway 从升级到放弃Spring-Cloud-Gateway 从升级到放弃
package com.newland.dc.ctid.fileter;

import com.google.gson.Gson;
import com.newland.dc.common.vo.RequestHeaderVo;
import com.newland.dc.ctid.service.SecurityService;
import com.newland.dc.log.kafka.KafkaLog;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.support.*;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.style.ToStringCreator;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseCookie;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR;

/**
 * @Auther: garfield
 * @Date: 2019/2/28 上午10:45
 * @Description:
 */
@Component
public class EncryptGatewayFilterFactory extends AbstractGatewayFilterFactory {

    private static Logger log = LoggerFactory.getLogger(EncryptGatewayFilterFactory.class);

    @Autowired
    private SecurityService encryptService;

    public EncryptGatewayFilterFactory() {
        super(Config.class);
    }

    //    private Gson gson = new GsonBuilder().serializeNulls().create();
    private Gson gson = new Gson();

    @Value("${server.host:10.10.10.10}")
    private String serverHost;

    @Value("${server.port}")
    private String serverPort;

    @Override
    @SuppressWarnings("unchecked")
    public GatewayFilter apply(Config config) {
        return new EncryptGatewayFilter(config);
    }

    @Override
    public ServerHttpRequest.Builder mutate(ServerHttpRequest request) {
        return null;
    }

    public static class Config {

        private boolean encrypt;

        public boolean isEncrypt() {
            return encrypt;
        }

        public Config setEncrypt(boolean encrypt) {
            this.encrypt = encrypt;
            return this;
        }

        @Override
        public String toString() {
            return new ToStringCreator(this)
                    .append("encrypt", encrypt)
                    .toString();
        }
    }

    @Override
    public List shortcutFieldOrder() {
        return Arrays.asList("encrypt");
    }

    public class EncryptGatewayFilter implements GatewayFilter, Ordered {
        Config config;

        EncryptGatewayFilter(Config config) {
            this.config = config;
        }

        @Override
        @SuppressWarnings("unchecked")
        public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            String trace = exchange.getRequest().getHeaders().getFirst("trace");
            ServerRequest serverRequest = new DefaultServerRequest(exchange);
            return serverRequest.bodyToMono(String.class).flatMap(reqBody -> {
                //重写原始请求
                ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
                    @Override
                    public HttpHeaders getHeaders() {
                        HttpHeaders httpHeaders = new HttpHeaders();
                        httpHeaders.putAll(super.getHeaders());
                        return httpHeaders;
                    }

                    @Override
                    public Flux getBody() {
                        //打印原始请求日志
                        log.info("[Trace:{}]-gateway request:headers=[{}],body=[{}]", trace, getHeaders(), reqBody);
                        return Flux.just(reqBody).map(bx -> exchange.getResponse().bufferFactory().wrap(bx.getBytes()));
                    }
                };
                //重写原始响应
                BodyHandlerServerHttpResponseDecorator responseDecorator = new BodyHandlerServerHttpResponseDecorator(
                        initBodyHandler(exchange), exchange.getResponse());

                return chain.filter(exchange.mutate().request(decorator).response(responseDecorator).build());
            });
        }

        @Override
        public int getOrder() {
            return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
        }

    }

    public interface BodyHandlerFunction
            extends BiFunction, Mono> {
    }

    protected BodyHandlerFunction initBodyHandler(ServerWebExchange exchange) {
        return (resp, body) -> {
            //拦截
            MediaType originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.setContentType(originalResponseContentType);
            DefaultClientResponseAdapter clientResponseAdapter = new DefaultClientResponseAdapter(body, httpHeaders);
            Mono bodyMono = clientResponseAdapter.bodyToMono(String.class);
            //此处可以获得前面放置的参数
            return bodyMono.flatMap((respBody) -> {
//                打印返回响应日志
                System.out.println(respBody);
                return resp.writeWith(Flux.just(respBody).map(bx -> resp.bufferFactory().wrap(bx.getBytes())));
            }).then();
        };
    }

    public static class DefaultClientResponseAdapter extends DefaultClientResponse {

        /**
         * @param body
         * @param httpHeaders
         */
        public DefaultClientResponseAdapter(Publisher body,
                                            HttpHeaders httpHeaders) {
            this(new ResponseAdapter(body, httpHeaders),
                    ExchangeStrategies.withDefaults());
        }

        /**
         * @param response
         * @param strategies
         */
        public DefaultClientResponseAdapter(ClientHttpResponse response,
                                            ExchangeStrategies strategies) {
            super(response, strategies);
        }

        /**
         * ClientHttpResponse 适配器
         */
        static class ResponseAdapter implements ClientHttpResponse {
            /**
             * 响应数据
             */
            private final Flux flux;
            /**
             *
             */
            private final HttpHeaders headers;

            public ResponseAdapter(Publisher body,
                                   HttpHeaders headers) {
                this.headers = headers;
                if (body instanceof Flux) {
                    flux = (Flux) body;
                } else {
                    flux = ((Mono) body).flux();
                }
            }

            @Override
            public Flux getBody() {
                return flux;
            }

            @Override
            public HttpHeaders getHeaders() {
                return headers;
            }

            @Override
            public HttpStatus getStatusCode() {
                return null;
            }

            @Override
            public int getRawStatusCode() {
                return 0;
            }

            @Override
            public MultiValueMap getCookies() {
                return null;
            }
        }
    }

    class BodyHandlerServerHttpResponseDecorator extends ServerHttpResponseDecorator {

        /**
         * body 处理拦截器
         */
        private BodyHandlerFunction bodyHandler = initDefaultBodyHandler();

        /**
         * 构造函数
         *
         * @param bodyHandler
         * @param delegate
         */
        public BodyHandlerServerHttpResponseDecorator(BodyHandlerFunction bodyHandler, ServerHttpResponse delegate) {
            super(delegate);
            if (bodyHandler != null) {
                this.bodyHandler = bodyHandler;
            }
        }

        @Override
        public Mono writeWith(Publisher body) {
            //body 拦截处理器处理响应
            return bodyHandler.apply(getDelegate(), body);
        }

        @Override
        public Mono writeAndFlushWith(Publisher> body) {
            return writeWith(Flux.from(body).flatMapSequential(p -> p));
        }

        /**
         * 默认body拦截处理器
         *
         * @return
         */
        private BodyHandlerFunction initDefaultBodyHandler() {
            return (resp, body) -> resp.writeWith(body);
        }
    }
}

View Code

那么万事具备,代码都写好了,我们又需要进行性能测试。这边要记住,我用的是官方的那个例子,其他的写法也用过了,但是结果差不多。

4.再一次测试spring-cloud-gateway 网关路由性能

step.1:性能测试,改一下配置,表示加入了过滤器。这里为什么只有一个过滤器,因为这个过滤器问题比较大,过程就略过了。

- id: an
        uri: http://10.1.4.32:14077/hello
        predicates:
        - Path=/an
        filters:
        - An

经过多次测试,其他的过滤器都还好,只有修改response body的过滤器,严重影响性能,且有读写错误。

step.2:测试,以及测试结果

[wrk@localhost wrk]$ ./wrk  -t 15 -c500 -d 10 --latency -s scripts/gateway.lua  http://10.1.4.32:14077/an
Running 10s test @ http://10.1.4.32:14077/an
  15 threads and 500 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.03s   488.75ms   2.00s    60.62%
    Req/Sec    26.59     13.84    80.00     67.60%
  Latency Distribution
     50%  931.54ms
     75%    1.45s
     90%    1.76s
     99%    1.97s
  3848 requests in 10.10s, 1.64MB read
  Socket errors: connect 0, read 0, write 0, timeout 458
Requests/sec:    381.05
Transfer/sec:    166.71KB

结果多出一行,socket错误,而且还是超时,而且,日志中也存在错误:

2019-03-06T16:09:33,396|INFO ||AsyncResolver-bootstrap-executor-0||||Resolving eureka endpoints via configuration
2019-03-06T16:10:38,268|ERROR||reactor-http-server-epoll-18||||Unhandled failure: Connection has been closed, response already set (status=200)
2019-03-06T16:10:38,268|WARN ||reactor-http-server-epoll-18||||Handling completed with error: Connection has been closed
2019-03-06T16:10:38,269|ERROR||reactor-http-server-epoll-18||||Unhandled failure: null, response already set (status=200)
2019-03-06T16:10:38,269|WARN ||reactor-http-server-epoll-18||||Handling completed with error: null
2019-03-06T16:10:38,294|ERROR||reactor-http-server-epoll-18||||Unhandled failure: syscall:write(..) failed: 断开的管道, response already set (status=null)
2019-03-06T16:10:38,294|WARN ||reactor-http-server-epoll-18||||Handling completed with error: syscall:write(..) failed: 断开的管道
2019-03-06T16:10:38,306|ERROR||reactor-http-server-epoll-23||||Unhandled failure: syscall:write(..) failed: 断开的管道, response already set (status=null)
2019-03-06T16:10:38,306|WARN ||reactor-http-server-epoll-23||||Handling completed with error: syscall:write(..) failed: 断开的管道
2019-03-06T16:14:33,397|INFO ||AsyncResolver-bootstrap-executor-0||||Resolving eureka endpoints via configuration

这个问题很严重了,因为单个请求的时候,并不会报错,这个错误只发生在高并发压测下,无法追踪。最重要的是,我们看到性能只剩下300/s,这是万万不能接受的,生产更不能接收。

这个问题很难解释,因为我们采用的是官方提供的写法,我们回头看官方的修改response 类,好吧,不用看了,因为:

package org.springframework.cloud.gateway.filter.factory.rewrite;
/**
 * This filter is BETA and may be subject to change in a future release.

 */
public class ModifyResponseBodyGatewayFilterFactory
        extends AbstractGatewayFilterFactory {

官方已经说了,这是测试版本,不顶用。

不死心,又想起了gateway提供的GlobalFilter,将刚才的代码写到全局过滤器中再试试,但是结果相同!

凉凉...

跪求结论跟我不同的启发文档,或者只能等下一版本了。

Original: https://www.cnblogs.com/garfieldcgf/p/10484119.html
Author: 但行好事-莫问前程
Title: Spring-Cloud-Gateway 从升级到放弃

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/541372/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • spring-boot-dependencies 和 spring-boot-starter-parent

    构建springboot项目有两种方式: 第一种是继承spring-boot-starter-parent pom里面指定parent项目: org.springframework…

    Java 2023年5月30日
    084
  • CSS实现超过一定的宽度添加省略

    CSS实现超过一定的宽度添加省略 .detail-item-line { width: 100%; word-break: keep-all; /* &#x4E0D;&am…

    Java 2023年6月8日
    0136
  • day40-网络编程02

    Java网络编程02 4.TCP网络通信编程 基本介绍 基于客户端–服务端的网络通信 底层使用的是TCP/IP协议 应用场景举例:客户端发送数据,服务端接收并显示控制台…

    Java 2023年6月15日
    086
  • java 静态变量 静态代码块 加载顺序问题

    在网上看了一个这样的题目 问输出顺序是什么? 正确答案是: 产生这个结果的原因的关键在这一句话:static StaticTest st = new StaticTest();st…

    Java 2023年5月29日
    087
  • 登录注册

    经过这段时间的努力,我们对Spring框架已经有了基本认识。是时候按下暂停键,学以致用,写个简单的小项目巩固一下我们至今所学的知识了。 这个小项目名为come-in,是个Web应用…

    Java 2023年6月5日
    081
  • 一致性hash原理 看这一篇就够了

    ​ 在了解一致性哈希算法之前,最好先了解一下缓存中的一个应用场景,了解了这个应用场景之后,再来理解一致性哈希算法,就容易多了,也更能体现出一致性哈希算法的优点,那么,我们先来描述一…

    Java 2023年6月7日
    081
  • netstat 命令查看端口状态详解

    转载请注明出处: netstat 可以查看服务器当前端口列表及指定端口的连接状态等; -t : 指明显示TCP端口,t是TCP的首字母。 -u : 指明显示UDP端口,u是UDP的…

    Java 2023年6月8日
    096
  • 观察线程的状态

    public class ThreadState { public static void main(String[] args) { // 定义一个线程 Thread threa…

    Java 2023年6月13日
    095
  • 使用objc4V818.2源码编译,没有什么比苹果底层源码更有说服力去证明底层原理真假

    前言为什么会想要调试源码? 苹果开源了部分源码, 但相似内容太多, 基本找不到代码见的对应关系, 如果能像自己工程一样进行跳转那多好哇~~苹果源码开源地址: https://ope…

    Java 2023年6月16日
    089
  • SSM-注解开发

    Day4 Spring 注解开发 Srping原始注解主要是替代Bean标签配置 Spring 原始注解 @Component 在类上实例化Bean @Controller 在we…

    Java 2023年6月5日
    064
  • Spring Boot 使用 Log4j2

    Java 中比较常用的日志工具类,有 Log4j、SLF4j、Commons-logging(简称jcl)、Logback、Log4j2(Log4j 升级版)、Jdk Loggin…

    Java 2023年5月30日
    088
  • AFNetworking 遇到错误 Code=-1016 “Request failed: unacceptable content-type: text/plain”

    解决 AFNetworking 遇到 unacceptable content-type 问题。 在开发过程使用了AFNetworking库,版本2.x,先运行第一个官方例子(替换…

    Java 2023年6月6日
    0102
  • 1. Spring Cloud Greenwich SR2 概览

    Distributed/versioned configuration Service registration and discovery Routing Service-to-…

    Java 2023年5月29日
    073
  • 我自定义的拦截器为什么会靠后执行?

    背景 模拟场景 是不是因为 order的值 找出在哪里赋予的 order值 解决办法 背景 项目中自定义了拦截器 Filter,项目中使用了 spring security,它也有…

    Java 2023年6月8日
    084
  • Java中的final关键字

    字面意思:被final修饰的对象是无法改变的。 一、final数据: (2)同时被static和final修饰的数据占据一段不能被改变的存储空间,同时,由于static的作用,该数…

    Java 2023年5月29日
    099
  • java之程序执行windows命令

    博客园 :当前访问的博文已被密码保护 请输入阅读密码: Original: https://www.cnblogs.com/FCWORLD/p/5254844.htmlAuthor…

    Java 2023年6月6日
    099
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球