引⼊Gateway⽹关,这些坑⼀定要学会避开
Spring cloud gateway是替代zuul的⽹关产品,基于Spring 5、Spring boot 2.0以上、Reactor, 提供任意的路由匹配和断⾔、过滤功能。上⼀篇⽂章谈了⼀下,这篇⽂章将会侧重于其他的⼏个需要注意的地⽅。
⽹关实现
这⾥介绍编码⽅式实现
HystrixObservableCommand.Setter getSetter() {
HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("group-accept");
HystrixObservableCommand.Setter setter = HystrixObservableCommand.Setter.withGroupKey(groupKey);
HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("command-accept");
setter.andCommandKey(commandKey);
HystrixCommandProperties.Setter proertiesSetter = HystrixCommandProperties.Setter();
proertiesSetter
/* *
* 线程策略配置
*/
//设置线程模式缺省 1000ms
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
//执⾏是否启⽤超时时间缺省 true
.withExecutionTimeoutEnabled(true)
//使⽤线程隔离时,是否对命令执⾏超时的线程调⽤中断缺省false
.withExecutionIsolationThreadInterruptOnFutureCancel(false)
/
/执⾏超时的时候是否要它中断缺省 true
.withExecutionIsolationThreadInterruptOnTimeout(true)
//执⾏的超时时间缺省 1000ms
.withExecutionTimeoutInMilliseconds(2000)
/* *
* 熔断策略
*/
//是否开启溶断缺省 true
.withCircuitBreakerEnabled(true)
// 是否允许熔断器忽略错误,默认false, 不开启;
// true,断路器强制进⼊“关闭”状态,它会接收所有请求。
/
/ 如果forceOpen属性为true,该属性不⽣效
.withCircuitBreakerForceClosed(false)
// 是否强制开启熔断器阻断所有请求, 默认为false
// 为true时,所有请求都将被拒绝,直接到fallback.
// 如果该属性设置为true,断路器将强制进⼊“打开”状态,
// 它会拒绝所有请求。该属性优于forceClosed属性
.withCircuitBreakerForceOpen(false)
// ⽤来设置当断路器打开之后的休眠时间窗。
// 休眠时间窗结束之后,会将断路器设置为“半开”状态,尝试熔断的请求命令,
// 如果依然请求错误就将断路器继续设置为“打开”状态,如果成功,就设置为“关闭”状态
// 熔断器默认⼯作时间,默认:5000豪秒.
/
/ 熔断器中断请求10秒后会进⼊半打开状态,放部分流量过去重试.
.withCircuitBreakerSleepWindowInMilliseconds(5000)
// 熔断器在整个统计时间内是否开启的阀值.
// 在metricsRollingStatisticalWindowInMilliseconds(默认10s)内默认⾄少请求10次,
// 熔断器才发挥起作⽤,9次熔断器都不起作⽤。
.withCircuitBreakerRequestVolumeThreshold(100)
// 该属性⽤来设置断路器打开的错误百分⽐条件。默认值为50.
// 表⽰在滚动时间窗中,在请求值超过requestVolumeThreshold阈值的前提下,
// 如果错误请求数百分⽐超过50,就把断路器设置为“打开”状态,否则就设置为“关闭”状态
.withCircuitBreakerErrorThresholdPercentage(50);
setter.andCommandPropertiesDefaults(proertiesSetter);
return setter;
}
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
RouteLocatorBuilder.Builder routes = utes();
RouteLocatorBuilder.Builder serviceProvider = routes
.route("accept",
r -> r.method(HttpMethod.GET)
.and()
.path("/gateway-accept/**")
.and()
.
header(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8")
.filters(f -> {
config -> config.setKeyResolver(new GenericAccessResolver())
.setRateLimiter(redisRateLimiter()));
f.hystrix(config -> confi
g.setName("accept")
.setFallbackUri("forward:/gateway-fallback")
.setSetter(getSetter()));
return f;
})
.uri("localhost:8888")
);
return serviceProvider.build();
}
在上⾯的代码中,主要做了3件事情:限流、熔断策略及降级⽅法配置
限流
配置redis
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
password:
timeout: 1500
lettuce:
pool:
max-active: 300 #连接池最⼤连接数(使⽤负值表⽰没有限制)
max-idle: 10    #连接池中的最⼤空闲连接
min-idle: 5    #连接池中的最⼩空闲连接
max-wait: -1    #连接池最⼤阻塞等待时间(使⽤负值表⽰没有限制)
⾃定义解析
/
**
* @description: 按照访问地址进⾏限流(也可以安装其他条件进⾏限流),具体可以看Request()的⽅法和属性
**/
public class GenericAccessResolver implements KeyResolver {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.Request().getPath().value());
}
}
⾃定义限流配置
RedisRateLimiter redisRateLimiter() {
//1000,1500对应replenishRate、burstCapacity
return new RedisRateLimiter(1000, 1500);
}
⽹关使⽤⾃定义限流器(⽹关使⽤代码实现)
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
RouteLocatorBuilder.Builder routes = utes();
RouteLocatorBuilder.Builder serviceProvider = routes
.route("accept",
r -> r.method(HttpMethod.GET)
.and()
.
path("/gateway-accept/**")
.and()
.header(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8")
//.and()
//.readBody(String.class, readBody -> true)
.filters(f -> {
g.setKeyResolver(new GenericAccessResolver()).setRateLimiter(redisRateLimiter()));
return f;
})
.
uri("localhost:8888")
);
return serviceProvider.build();
}
测试
jmeter配置
结果
其他
如果有多个路由,使⽤不同的限流策略,可以⾃定义KeyResolver和RedisRateLimiter,在路由定义时加⼊//基于ip限流
public class OtherAccessResolver implements KeyResolver {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.Request().getRemoteAddress().getHostName());
}
}
RedisRateLimiter otherRedisRateLimiter() {
//1000,1500对应replenishRate、burstCapacity
return new RedisRateLimiter(100, 500);
springcloud难学吗}
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
RouteLocatorBuilder.Builder routes = utes();
RouteLocatorBuilder.Builder serviceProvider = routes
.route("accept",
r -> r.method(HttpMethod.GET)
.and()
.path("/gateway-accept/**")
.and()
.header(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8")
.filters(f -> {
config -> config.setKeyResolver(new GenericAccessResolver())
.setRateLimiter(redisRateLimiter()));
f.hystrix(config -> confi
g.setName("accept")
.setFallbackUri("forward:/gateway-fallback")
.setSetter(getSetter()));
return f;
})
.uri("localhost:8888"))
.
route("sign",
r -> r.method(HttpMethod.POST)
.and()
.path("/gateway-sign/**")
.and()
.header(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8")
.filters(f -> {
config -> config.setKeyResolver(new OtherAccessResolver())
.setRateLimiter(otherRedisRateLimiter()));
f.hystrix(config -> confi
g.setName("sign")
.setFallbackUri("forward:/gateway-fallback")
.setSetter(getSetter()));
return f;
})
.uri("localhost:7777")
);
return serviceProvider.build();
}
熔断策略
熔断策略主要是线程配置和熔断配置,上⾯已经说明很清楚了。在上篇⽂章中,为了解决⽹关调⽤后台服务Connection prematurely closed BEFORE response的问题,要设置后台服务线程的空闲时间和⽹关线程池线程的空闲时间,并让⽹关线程池线程的空闲时间⼩于后台服务的空闲时间
配置⽅法
spring:
cloud:
gateway:
httpclient:
pool:
max-connections: 500
max-idle-time: 10000
编码实现
翻阅Spring Cloud Gateway英⽂资料,知道路由提供⼀个metadata⽅法,可以设置路由的元数据(docs.spring.io/spring-cloud-
gateway/docs/2.2.6.RELEASE/reference/html/#route-metadata-configuration),这些元数据在RouteMetadataUtils中定义:
package org.springframework.cloud.gateway.support;
public final class RouteMetadataUtils {
public static final String RESPONSE_TIMEOUT_ATTR = "response-timeout";
public static final String CONNECT_TIMEOUT_ATTR = "connect-timeout";
private RouteMetadataUtils() {
throw new AssertionError("Must not instantiate utility class.");
}
}
其中没有我要的线程数量(max-connection)和空闲时间(max-idle-time)的设置,没有关系,⾃⼰加上去:
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
RouteLocatorBuilder.Builder routes = utes();
RouteLocatorBuilder.Builder serviceProvider = routes
.route("accept",
r -> r.method(HttpMethod.GET)
.and()
.path("/gateway-accept/**")
.and()
.header(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8")
.filters(f -> {
config -> config.setKeyResolver(new GenericAccessResolver())
.setRateLimiter(redisRateLimiter()));
f.hystrix(config -> confi
g.setName("accept")
.setFallbackUri("forward:/gateway-fallback")
.setSetter(getSetter()));
return f;
})
.
uri("localhost:8888")
.metadata("max-idle-time", 10000)  //⽹关调⽤后台线程空闲时间设置
.metadata("max-connections", 200)  //⽹关调⽤后台服务线程数量设置
);
return serviceProvider.build();
}
测试果然和yml配置⼀样有效果。
降级⽅法
降级⽅法本⾝没有什么特别,有⼀个问题需要注意,调⽤降级⽅法也是使⽤线程池的,缺省在HystrixThreadPoolProperties中定义:
public abstract class HystrixThreadPoolProperties {
/* defaults */
static int default_coreSize = 10;            // core size of thread pool
static int default_maximumSize = 10;        // maximum size of thread pool
static int default_keepAliveTimeMinutes = 1; // minutes to keep a thread alive
static int default_maxQueueSize = -1;        // size of queue (this can't be dynamically changed so we use 'queueSizeRejectionThreshold' to artificially limit and reject)
// -1 turns it off and makes us use SynchronousQueue
错误
如果上⾯的限流设置⽐较⼤,⽐如1000,最⼤突发2000,⽹关调⽤后台服务发⽣熔断降级,熔断后降级的⽅法调⽤太频繁,10个线程不够⽤,会导致以下500错误:
2021-02-01 14:29:45.076 ERROR 64868 --- [ioEventLoop-5-1] AbstractErrorWebExceptionHandler : [a0ed6911-18982]  500 Server Error for HTTP GET "/gateway-accept/test" ption.HystrixRuntimeException: command-accept fallback execution rejected.
at comflix.hystrix.AbstractCommand.handleFallbackRejectionByEmittingError(AbstractCommand.java:1043) ~[hystrix-core-1.5.18.jar:1.5.18]
Suppressed: publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ HTTP GET "/gateway-accept/test" [ExceptionHandlingWebHandler]
ption.HystrixRuntimeException: command-accept fallback execution rejected.
at comflix.hystrix.AbstractCommand.handleFallbackRejectionByEmittingError(AbstractCommand.java:1043) ~[hystrix-core-1.5.18.jar:1.5.18]
Suppressed: publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ HTTP GET "/gateway-accept/test" [ExceptionHandlingWebHandler]
配置⽅法
所以要在yml中设置合适的调⽤降级⽅法的线程池, 合理的配置能够杜绝⽹关500错误的发⽣。
hystrix:
threadpool:
group-accept:  #代码⾥⾯设置的HystrixCommandGroupKey.Factory.asKey("group-accept")
coreSize: 50 #并发执⾏的最⼤线程数,默认10
maxQueueSize: 1500 #BlockingQueue的最⼤队列数
#即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝
queueSizeRejectionThreshold: 1400
⽹关异常截获
上⾯的异常后,没有捕获异常直接返回前端500错误,⼀般情况下需要返回⼀个统⼀接⼝,⽐如:
@Data
@ToString
@EqualsAndHashCode
@Accessors(chain = true)
public class Result<T> implements Serializable {
private Integer code;
private String message;
private T data;
private String sign;
public static final String SUCCESS = "成功";
public static final String FAILURE = "失败";
public Result(int code, String message) {
}
public Result(int code, String message, T data) {
this.data = data;
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。