02_Spring Cloud Gateway实战
微服务接入Spring Cloud Gateway
1) 引入依赖
<!-- gateway网关 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!-- nacos服务注册与发现 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
注意:getway网关的表现层使用的是webflux,会和spring-webmvc的依赖冲突,需要排除spring-webmvc。
或者指定使用Netty服务器:
spring:
main:
web-application-type: reactive # 指定使用Netty
2) 编写yml配置文件
spring: main: web-application-type: reactive # 指定使用Netty application: name: mall-gateway #配置nacos注册中心地址 cloud: nacos: discovery: server-addr: 127.0.0.1:8848 gateway: #设置路由:路由id、路由到微服务的uri、断言 routes: - id: order_route #路由ID,全局唯一,建议配置服务名 # 测试 http://localhost:8888/order/findOrderByUserId/1 uri: lb://mall-order #lb 整合负载均衡器ribbon,loadbalancer predicates: - Path=/order/** # 断言,路径相匹配的进行路由 - id: user_route #路由ID,全局唯一,建议配置服务名 uri: lb://mall-user #lb 整合负载均衡器ribbon,loadbalancer predicates: - Path=/user/** # 断言,路径相匹配的进行路由
2.2 路由断言工厂(Route Predicate Factories)配置
predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地。application.yml配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
通过网关启动日志,可以查看内置路由断言工厂:
2.2.1 路径匹配
spring: main: web-application-type: reactive # 指定使用Netty application: name: mall-gateway #配置nacos注册中心地址 cloud: gateway: #设置路由:路由id、路由到微服务的uri、断言 routes: - id: order_route #路由ID,全局唯一,建议配置服务名 # 测试 http://localhost:8888/order/findOrderByUserId/1 uri: lb://mall-order #lb 整合负载均衡器ribbon,loadbalancer predicates: - Path=/order/** # 断言,路径相匹配的进行路由
2.2.2 Header匹配
spring: main: web-application-type: reactive # 指定使用Netty application: name: mall-gateway #配置nacos注册中心地址 cloud: gateway: #设置路由:路由id、路由到微服务的uri、断言 routes: - id: order_route #路由ID,全局唯一,建议配置服务名 # 测试 http://localhost:8888/order/findOrderByUserId/1 uri: lb://mall-order #lb 整合负载均衡器ribbon,loadbalancer predicates: - Path=/order/** # 断言,路径相匹配的进行路由 # Header匹配 请求中带有请求头名为 x-request-id,其值与 \d+ 正则表达式匹配 - Header=X-Request-Id, \d+
测试:
未添加请求头则断言失败
请求头中添加X-Request-Id则断言成功:
过滤器工厂(GatewayFilter Factories)配置
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
添加请求头
需求:给所有进入mall-order的请求添加一个请求头:X-Request-color=red。只需要修改gateway服务的application.yml文件,添加路由过滤即可:
spring: application: name: mall-gateway gateway: #设置路由:路由id、路由到微服务的uri、断言 routes: - id: order_route #路由ID,全局唯一,建议配置服务名 # 测试 http://localhost:8888/order/findOrderByUserId/1 uri: lb://mall-order #lb 整合负载均衡器ribbon,loadbalancer predicates: - Path=/order/** # 断言,路径相匹配的进行路由 filters: - AddRequestHeader=X-Request-color, red #添加请求头
@GetMapping("/test/gateway") public String testGateway(HttpServletRequest request) throws Exception { log.info("gateWay获取请求头X-Request-color:" + request.getHeader("X-Request-color")); return "success"; } @GetMapping("/test/gateway/2") public String testGateway(@RequestHeader("X-Request-color") String color) throws Exception { log.info("gateWay获取请求头X-Request-color:" + color); return "success"; }
结果:
添加请求参数:
spring: application: name: mall-gateway #配置nacos注册中心地址 cloud: gateway: #设置路由:路由id、路由到微服务的uri、断言 routes: - id: order_route #路由ID,全局唯一,建议配置服务名 # 测试 http://localhost:8888/order/findOrderByUserId/1 uri: lb://mall-order #lb 整合负载均衡器ribbon,loadbalancer predicates: - Path=/order/** # 断言,路径相匹配的进行路由 #配置过滤器工厂 filters: - AddRequestParameter=color, blue # 添加请求参数
添加测试controller:
@GetMapping("/test/gateway/3") public String testGateway3(@RequestParam("color") String color) throws Exception { log.info("gateWay获取请求参数color:" + color); return "success"; }
测试:http://192.168.3.174:8888/order/test/gateway/3
自定义过滤器工厂
继承AbstractNameValueGatewayFilterFactory且我们的自定义名称必须要以GatewayFilterFactory结尾并交给spring管理。
@Component @Slf4j public class CheckAuthGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { @Override public GatewayFilter apply(NameValueConfig config) { return (exchange, chain) -> { log.info("调用CheckAuthGatewayFilterFactory===" + config.getName() + ":" + config.getValue()); // TODO return chain.filter(exchange); }; } }
配置自定义的过滤器工厂 :
server: port: 8888 spring: application: name: mall-gateway gateway: #设置路由:路由id、路由到微服务的uri、断言 routes: - id: order_route #路由ID,全局唯一,建议配置服务名 # 测试 http://localhost:8888/order/findOrderByUserId/1 uri: lb://mall-order #lb 整合负载均衡器ribbon,loadbalancer predicates: - Path=/order/** # 断言,路径相匹配的进行路由 #配置过滤器工厂 filters: - CheckAuth=yyj, 男 #自定义过滤器工厂
测试:
全局过滤器(Global Filters)配置
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。
GatewayFilter:网关过滤器,需要通过spring.cloud.routes.filters配置在具体的路由下,只作用在当前特定路由上,也可以通过配置spring.cloud.default-filters让它作用于全局路由上。
GlobalFilter:全局过滤器,不需要再配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain能够识别的过滤器。
LoadBalancerClientFilter
LoadBalancerClientFilter 会查看exchange的属性ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值(一个URI),如果该值的scheme是 lb,
比如:lb://myservice ,它将会使用Spring Cloud的LoadBalancerClient 来将myservice 解析成实际的host和port,并替换掉ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的内容。
其实就是用来整合负载均衡器Ribbon的
server: port: 8888 spring: application: name: mall-gateway gateway: #设置路由:路由id、路由到微服务的uri、断言 routes: - id: order_route #路由ID,全局唯一,建议配置服务名 # 测试 http://localhost:8888/order/findOrderByUserId/1 uri: lb://mall-order #lb 整合负载均衡器ribbon,loadbalancer predicates: - Path=/order/** # 断言,路径相匹配的进行路由
自定义全局过滤器:
自定义全局过滤器定义方式是实现GlobalFilter接口。每一个过滤器都必须指定一个int类型的order值,order值越小,过滤器优先级越高,执行顺序越靠前。GlobalFilter通过实现Ordered接口来指定order值。
/** * 每一个过滤器都必须指定一个int类型的order值,order值越小,过滤器优先级越高,执行顺序越靠前。 * GlobalFilter通过实现Ordered接口,来指定order值 */ @Component @Slf4j public class CheckAuthFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //获取token String token = exchange.getRequest().getHeaders().getFirst("token"); if (null == token) { log.info("token is null"); ServerHttpResponse response = exchange.getResponse(); response.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); // 401 用户没有访问权限 response.setStatusCode(HttpStatus.UNAUTHORIZED); byte[] bytes = HttpStatus.UNAUTHORIZED.getReasonPhrase().getBytes(); DataBuffer buffer = response.bufferFactory().wrap(bytes); // 请求结束,不继续向下请求 return response.writeWith(Mono.just(buffer)); } //TODO 校验token进行身份认证 log.info("校验token"); return chain.filter(exchange); } @Override public int getOrder() { return 1; } }
测试:
Gateway跨域配置(CORS Configuration)
在前端领域中,跨域是指浏览器允许向服务器发送跨域请求,从而克服Ajax只能同源使用的限制。同源策略(Same Orgin Policy)是一种约定,它是浏览器核心也最基本的安全功能,它会阻止一个域的js脚本和另外一个域的内容进行交互,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源(即在同一个域)就是两个页面具有相同的协议(protocol)、主机(host)和端口号(port)。
测试代码 :
order.html:
<input type="button" value="订单列表" onclick="getData()"> <script> function getData() { $.get('http://localhost:8888/order/findOrderByUserId/1',function(data){ console.log(data.orders) var list = data.orders; var str = ''; for(var i=0;i<list.length;i++){ str +='<tr><th>'+list[i].id+'</th><th>'+list[i].userId+'</th><th>'+list[i].commodityCode+'</th><th>'+list[i].count+'</th><th>'+list[i].amount+'</th></tr>'; } $('#orderlist').html(str); }); } </script>
如何解决gateway跨域问题:
通过yml配置的方式:
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#cors-configuration
spring: application: name: mall-gateway #配置nacos注册中心地址 cloud: gateway: #跨越配置 globalcors: cors-configurations: '[/**]': allowedOrigins: "*" allowedMethods: - GET - POST - DELETE - PUT - OPTION
通过java配置的方式:
@Configuration public class CorsConfig { @Bean public CorsWebFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedMethod("*"); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser()); source.registerCorsConfiguration("/**", config); return new CorsWebFilter(source); } }
Gateway基于redis+lua脚本限流
spring cloud alibaba官方提供了RequestRateLimiter过滤器工厂,基于redis+lua脚本方式采用令牌桶算法实现了限流。
1)添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency>
2)修改 application.yml ,添加redis配置和RequestRateLimiter过滤器工厂配置
spring: #配置redis地址 redis: host: localhost port: 6379 database: 0 timeout: 5000 lettuce: pool: max-active: 200 max-wait: 10000 max-idle: 100 min-idle: 10 cloud: gateway: #设置路由:路由id、路由到微服务的uri、断言 routes: - id: order_route #路由ID,全局唯一,建议配置服务名 uri: lb://mall-order #lb 整合负载均衡器ribbon,loadbalancer predicates: - Path=/order/** # 断言,路径相匹配的进行路由 #配置过滤器工厂 filters: - name: RequestRateLimiter #限流过滤器 args: redis-rate-limiter.replenishRate: 1 #令牌桶每秒填充速率 redis-rate-limiter.burstCapacity: 2 #令牌桶的总容量 key-resolver: "#{@keyResolver}" #使用SpEL表达式,从Spring容器中获取Bean对象
3) 配置keyResolver,可以指定限流策略,比如url限流,参数限流,ip限流等等
@Configuration public class RateLimiterConfig { @Bean KeyResolver keyResolver() { //url限流 return exchange -> Mono.just(exchange.getRequest().getURI().getPath()); //参数限流 // return exchange -> Mono.just(Objects.requireNonNull(exchange.getRequest().getQueryParams().getFirst("user"))); } }
4) 测试
Gateway整合sentinel限流
从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:
route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId
自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组
Gateway整合sentinel实现网关限流
1)引入依赖
<!-- gateway接入sentinel --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
2)添加yml配置,接入sentinel dashboard,通过sentinel控制台配置网关流控规则
server: port: 8888 spring: application: name: mall-gateway-sentinel-demo main: allow-bean-definition-overriding: true #配置nacos注册中心地址 cloud: nacos: discovery: server-addr: 127.0.0.1:8848 sentinel: transport: # 添加sentinel的控制台地址 dashboard: 127.0.0.1:8080 datasource: gateway-flow-rules: nacos: server-addr: 127.0.0.1:8848 dataId: ${spring.application.name}-gateway-flow-rules groupId: SENTINEL_GROUP data-type: json rule-type: gw-flow gateway-api-rules: nacos: server-addr: 127.0.0.1:8848 dataId: ${spring.application.name}-gateway-api-rules groupId: SENTINEL_GROUP data-type: json rule-type: gw-api-group degrade-rules: nacos: server-addr: 127.0.0.1:8848 dataId: ${spring.application.name}-degrade-rules groupId: SENTINEL_GROUP data-type: json rule-type: degrade system-rules: nacos: server-addr: 127.0.0.1:8848 dataId: ${spring.application.name}-system-rules groupId: SENTINEL_GROUP data-type: json rule-type: system gateway: #设置路由:路由id、路由到微服务的uri、断言 routes: - id: order_route #路由ID,全局唯一,建议配合服务名 uri: lb://mall-order #lb 整合负载均衡器ribbon,loadbalancer predicates: - Path=/order/** - id: user_route uri: lb://mall-user #lb 整合负载均衡器ribbon,loadbalancer predicates: - Path=/user/**
关于网关流控规则持久化需要自行进行改造(待补充)
Sentinel网关流控实现原理
当通过 GatewayRuleManager 加载网关流控规则(GatewayFlowRule)时,无论是否针对请求属性进行限流,Sentinel 底层都会将网关流控规则转化为热点参数规则(ParamFlowRule),存储在 GatewayRuleManager 中,与正常的热点参数规则相隔离。转换时 Sentinel 会根据请求属性配置,为网关流控规则设置参数索引(idx),并同步到生成的热点参数规则中。
外部请求进入 API Gateway 时会经过 Sentinel 实现的 filter,其中会依次进行 路由/API 分组匹配、请求属性解析和参数组装。Sentinel 会根据配置的网关流控规则来解析请求属性,并依照参数索引顺序组装参数数组,最终传入 SphU.entry(res, args) 中。Sentinel API Gateway Adapter Common模块向 Slot Chain 中添加了一个 GatewayFlowSlot,专门用来做网关规则的检查。
GatewayFlowSlot 会从 GatewayRuleManager 中提取生成的热点参数规则,根据传入的参数依次进行规则检查。若某条规则不针对请求属性,则会在参数最后一个位置置入预设的常量,达到普通流控的效果。