springcloud整合的是zuul1,包内没有引入netty,而springcloud netflix停止维护,zuul2至今未整合到spring,在高并发时zuul1的BIO模型可能会出现性能瓶颈。SpringCloud 最后自己研发了一个网关替代 Zuul1,那就是 SpringCloud Gateway,它是基于 WebFlux 框架实现的,而 WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty。有图有真相!
另外,SpringCloud Gateway 具有更多特性:
①依赖处理:需要去除依赖spring-cloud-starter-netflix-zuul,引入新依赖spring-cloud-starter-gateway,zuul间接引入springcloud hystrix也会移除,如果升级为springcloud gateway需要熔断降级,则需要单独spring-cloud-starter-netflix-hystrix。另外gateway底层使用webflux web层框架(AIO),与springmvc冲突,需要去除依赖“spring-boot-starter-web“
②不再需要@EnabelXX启动注解,gateway网关核心配置可以通过配置文件或java bean配置类实现
配置文件方式示例
spring:
application:
name: gateway
profiles:
active: @package.environment@
cloud:
nacos:
config:
server-addr: @package.nacos.addr@ #注意不要http://,nacaos使用的是raft协议
file-extension: yml #后缀
namespace: @package.environment@ #配置命名空间,默认public
shared-configs:
- data-id: common.yml #配置所有工程共享的配置,注意里面的配置默认不能动态刷新,需要时可配置自动刷新
refresh: true
password: @package.nacos.password@ #nacos 1.3.X才支持
username: @package.nacos.username@
context-path: /nacos
discovery:
password: @package.nacos.password@
username: @package.nacos.username@
server-addr: @package.nacos.addr@ #注意不要http://,nacaos使用的是raft协议
namespace: @package.environment@ #服务命名空间,默认public
gateway:
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由,uri即可支持lb://微服务名进行负载均衡调用
routes:
- id: user # 路由的ID,只要唯一,建议服务名
#uri: http://www.baidu.com # 可以为静态的url列表(相当于绕过注册中心,调用方直连服务方)一般为lb://微服务名
uri: lb://user
predicates:
- Path=/user/** # 各种断言,这里指定3种,为与关系,java bean则可配置或关系,而且After,Before等断言配置比较麻烦,故建议java bean配置,而且java bean方式可以方便结合nacos进行动态更新
- After=2011-12-01T13:11:37.485+08:00[Asia/Shanghai] #电商抢购,抽奖等活动,到时间该路由才生效可以利用这个玩意
- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
filters:
- StripPrefix=1 #如http://localhost:8000/user/version/latest匹配上/user/**,但是转发到后端具体微服务能不需要改path,则可以通过改配置截去掉该前缀在转发后面的微服务
- id: payment
uri: lb://payment
predicates:
- Path=/payment/**
filters:
- StripPrefix=1
java bean配置类方式
这里配置了两个与关系的断言,及两个局部过滤器来修改一下reques,response(zuul过滤器需要改较多代码),局部过滤器还可以通过spring.cloud.gateway.routes.filters配置在具体路由下
@Configuration
@Slf4j
public class GatewayConfig {
@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user", r -> r.path("/user/**")
.and()
.after(ZonedDateTime.of(LocalDateTime.now().plusDays(11), ZoneId.systemDefault()))
.filters(gatewayFilterSpec -> gatewayFilterSpec
.filter((exchange, chain) ->{
log.error(exchange.getRequest().getPath().toString());
return chain.filter(exchange);},1)
.filter((exchange, chain) ->{
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return chain.filter(exchange);},2)
.stripPrefix(1)
)
.uri("lb://user"))
.route("payment", r -> r.path("/payment/**")
.and()
.after(ZonedDateTime.of(LocalDateTime.now().plusDays(11), ZoneId.systemDefault()))
.filters(gatewayFilterSpec -> gatewayFilterSpec
.filter((exchange, chain) ->{
log.error(exchange.getRequest().getPath().toString());
return chain.filter(exchange);},1)
.filter((exchange, chain) ->{
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return chain.filter(exchange);},2)
.stripPrefix(1)
)
.uri("lb://payment"))
.build();
}
}
所有predicates
类型 | 示例 |
---|---|
After | After=2017-01-20T17:42:47.789-07:00[America/Denver] |
Before | Before=2017-01-20T17:42:47.789-07:00[America/Denver] |
Between | 2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | Cookie=chocolate, ch.p |
Header | Header=X-Request-Id, \d+ |
Host | Host=**.somehost.org |
Method | Method=GET |
Path | Path=/foo/{segment} |
Query | Query=baz |
RemoteAddr | RemoteAddr=192.168.1.1/24 |
④配置spring gateway自带过滤器——网关路由重试
spring gateway自带过滤较多——参考,zuul的转发重试通过ribbon.MaxAutoRetriesNextServer等配置项完成,gataway的重试通过过滤器实现
@Configuration
@Slf4j
public class GatewayConfig {
@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user", r -> r.path("/user/**")
.or()
.after(ZonedDateTime.of(LocalDateTime.now().plusDays(11), ZoneId.systemDefault()))
.filters(gatewayFilterSpec -> gatewayFilterSpec
.filter((exchange, chain) -> {
log.error(exchange.getRequest().getPath().toString());
return chain.filter(exchange);
}, 1)
//转发重试
.filter(new RetryGatewayFilterFactory().apply(retryConfig -> {
retryConfig
.setRetries(2).setBackoff(Duration.of(10, ChronoUnit.MILLIS), Duration.of(50, ChronoUnit.MILLIS), 2, false)
.setStatuses(HttpStatus.BAD_GATEWAY, HttpStatus.GATEWAY_TIMEOUT)
.setMethods(HttpMethod.GET, HttpMethod.DELETE);//post/put如有幂等风险建议不重试
}))
.filter((exchange, chain) -> {
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return chain.filter(exchange);
}, 2)
.stripPrefix(1)
)
.uri("lb://user"))
.build();
}
}
⑤配置spring gateway自带的全局过滤器——网关熔断,需引入spring-cloud-starter-netflix-hystrix,全局过滤器还可以通过spring.cloud.gateway.default-filters配置
网关是所有请求的入口,如果部分后端服务延时严重,则可能导致大量请求线程堆积在网关上,拖垮网关进而瘫痪整个系统(微服务内部调用不经过网关,需要通过feign配置熔断),这就需要对响应慢的服务做超时快速失败处理,即熔断
@RestController
public class FallbackController {
@GetMapping("/fallback")
public Object fallback() {
Map result = new HashMap<>();
result.put("data",null);
result.put("message","Hystrix fallback!");
result.put("code",500);
return result;
}
}
spring:
cloud:
gateway:
default-filters: #hystrix熔断转发,全局过滤器
- name: Hystrix
args:
name: default
fallback-uri: forward:/fallback
#熔断器配置
hystrix:
command:
default: #default指路由的所有下级服务都使用相同默认的熔断配置,可指定具体服务具体配置如payment,defulat=>payment
execution:
isolation:
strategy: SEMAPHORE #默认THREAD,网关高扇出适合信号量
thread:
timeoutInMilliseconds: 1 #实现HystrixComand即的run(..业务代码..)方法的超时时间,设置1ms方便测试
semaphore:
maxConcurrentRequests: 100 #允许信号量1000并发-压测
circuitBreaker:
errorThresholdPercentage: 50 #错误或超时50%时开启熔断器
sleepWindowInMilliseconds: 5000 #//熔断器中断请求5秒后会进入半打开状态,放部分流量去正常请求,成功则关闭熔断器
requestVolumeThreshold: 2 #至少有2个请求并且达到错误阈值才会判断是否打开熔断器
⑥集成redis限流,Gateway通过内置的RequestRateLimiter过滤器实现限流,使用令牌桶算法。如果请求太大默认会返回HTTP 429-Too Many Requests(自定义注解也可简单实现)
org.springframework.boot
spring-boot-starter-data-redis-reactive
@Configuration
public class RateLimiterConfiguration {
/**
* 用户可通过自定义KeyResolver设置限流维度,例如:
*
* 对请求的目标URL进行限流
* 对来源IP进行限流
* 通过jwt token等请求头对用户进行限流
* @return
*/
@Bean(value = "ipKeyResolver")
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}
}
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: user
uri: lb://user
predicates:
- Path=/user/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 1
key-resolver: '#{@ipKeyResolver}'
⑦跨域请求配置文件或配置类(这个配置类极似zuul的跨域配置类,但使用的类包路径不一样——reactive)
spring:
cloud:
gateway:
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "*"
maxAge: 60
allowedHeader: "*"
allowCredentials: true
allowedMethods:
- GET
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
@Configuration
public class CrosConfiguration {
@Bean
public CorsWebFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.setAllowCredentials(true);
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
⑧自定义全局过滤器——认证授权
简单鉴权参考——鉴权与日志参考
// 过滤器需要成为Spring的组件
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
// 过滤器逻辑
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("全局过滤器执行...");
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (StringUtils.isBlank(token))
{
// 设置状态码为未授权
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 设为已完成 不再继续执行
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
// 过滤器执行顺序 return的值越小越先执行
@Override
public int getOrder() {
return 1;
}
}
集成oauth2认证授权参考——跨域问题解决参考