Spring Cloud Gateway作为Spring Cloud生态体系全家桶的一员不可或缺,本篇承接上一文章继续从实战层面如何使用网关局部过滤器和全局路由过滤器,以及如何做自定义实现,进一步通过整合Sentinel实现网关的限流和结合限流控制台详细说明配置操作步骤。... 路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。路由过滤器的作用域是特定的路由。SpringCloud Gateway包括许多内置的GatewayFilter工厂。目前官网提供33种路由过滤器工厂,前面示例中filters里的StripPrefix就是其中一种:

在库存微服务控制器中增加打印
@RequestMapping("/deduct") public String storage(HttpServletRequest request){ log.info("请求头X-Request-red:{}",request.getHeader("X-Request-red")); return "扣减库存"; }Nacos中网关的配置增加AddRequestHeader=X-Request-red, blue
server: port: 4090spring: cloud: gateway: routes: - id: storage_route uri: lb://ecom-storage-service predicates: - Path=/storage-service/** - Quantity=100,200 filters: - StripPrefix=1 - AddRequestHeader=X-Request-red, blue访问http://localhost:4090/storage-service/deduct?quantity=100 ,从库存微服务的日志可以看到网关的过滤器已经添加请求头信息

其他很多种过滤器实现各位有时间可以一一尝试
建立一个授权的自定义工厂过滤器工厂,从redis读取授权信息验证,先创建RedisConfig配置类
package cn.itxs.ecom.gateway.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;import com.fasterxml.jackson.annotation.PropertyAccessor;import com.fasterxml.jackson.databind.ObjectMapper;import lombok.extern.slf4j.Slf4j;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration@Slf4jpublic class RedisConfig{ @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); template.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); //om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance , ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用jackson //template.setValueSerializer(jackson2JsonRedisSerializer); template.setValueSerializer(stringRedisSerializer); // hash的value序列化方式采用jackson //template.setHashValueSerializer(jackson2JsonRedisSerializer); template.setHashValueSerializer(stringRedisSerializer); template.afterPropertiesSet(); return template; }}redis信息放在Nacos配置commons-dev.yaml里

创建过滤器工厂AuthorizeGatewayFilterFactory.java继承自AbstractGatewayFilterFactory
package cn.itxs.ecom.gateway.factory;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.gateway.filter.GatewayFilter;import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpStatus;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.http.server.reactive.ServerHttpResponse;import org.springframework.stereotype.Component;import org.springframework.util.StringUtils;import java.util.Arrays;import java.util.List;@Component@Slf4jpublic class AuthorizeGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthorizeGatewayFilterFactory.Config> { private static final String AUTHORIZE_TOKEN = "token"; private static final String AUTHORIZE_UID = "uid"; @Autowired private RedisTemplate redisTemplate; public AuthorizeGatewayFilterFactory() { super(Config.class); log.info("Loaded GatewayFilterFactory [Authorize]"); } @Override public List<String> shortcutFieldOrder() { return Arrays.asList("enabled"); } @Override public GatewayFilter apply(AuthorizeGatewayFilterFactory.Config config) { return (exchange, chain) -> { if (!config.isEnabled()) { return chain.filter(exchange); } ServerHttpRequest request = exchange.getRequest(); HttpHeaders headers = request.getHeaders(); String token = headers.getFirst(AUTHORIZE_TOKEN); String uid = headers.getFirst(AUTHORIZE_UID); if (token == null) { token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN); } if (uid == null) { uid = request.getQueryParams().getFirst(AUTHORIZE_UID); } ServerHttpResponse response = exchange.getResponse(); if (!StringUtils.hasText(token) || !StringUtils.hasText(uid)) { response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } String authToken = (String) redisTemplate.opsForValue().get(uid); if (authToken == null || !authToken.equals(token)) { response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } return chain.filter(exchange); }; } public static class Config { // 控制是否开启认证 private boolean enabled; public Config() {} public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } }}如果授权不通过返回HttpStatus.UNAUTHORIZED也即是 http 401
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7fDzQbqT-1657432430026)(http://www.itxiaoshen.com:3001/assets/1657431654370DW5dZAyW.png)]
网关Nacos配置文件增加Authorize=true
server: port: 4090spring: cloud: gateway: routes: - id: storage_route uri: lb://ecom-storage-service predicates: - Path=/storage-service/** - Quantity=100,200 filters: - StripPrefix=1 - AddRequestHeader=X-Request-red, blue - Authorize=true由于目前redis里面没有uid和token的信息,访问http://localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc 返回401状态

我们往redis写入uid为1001的数据

再次访问http://localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc ,则可以正常获取结果

spring.cloud.gateway.default-filters实现所配置的过滤器全局生效,但这种方法在实际中比较少用。官方目前全局过滤器有,详细可以查阅

创建ItxsGloablFilter.java的全局过滤器做简单日志打印
package cn.itxs.ecom.gateway.factory;import lombok.extern.slf4j.Slf4j;import org.springframework.cloud.gateway.filter.GatewayFilterChain;import org.springframework.cloud.gateway.filter.GlobalFilter;import org.springframework.core.Ordered;import org.springframework.stereotype.Component;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;@Component@Slf4jpublic class ItxsGloablFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("ItxsGloablFilter start"); return chain.filter(exchange); } @Override public int getOrder() { return -1; }}配置类将ItxsGloablFilter作为Spring的Bean
package cn.itxs.ecom.gateway.config;import cn.itxs.ecom.gateway.factory.ItxsGloablFilter;import org.springframework.cloud.gateway.filter.GlobalFilter;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class FilterConfig { @Bean public GlobalFilter itxsFilter() { return new ItxsGloablFilter(); }}启动网关微服务,访问http://localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc ,可以看到打印日志信息
要启用Reactor Netty访问日志,设置-Dreactor.netty.http.server.accessLogEnabled=true。您可以将日志记录系统配置为具有单独的访问日志文件。创建Logback配置的示例如下:
<appender name="accessLog" > <file>access_log.log</file> <encoder> <pattern>%msg%n</pattern> </encoder> </appender> <appender name="async" > <appender-ref ref="accessLog" /> </appender> <logger name="reactor.netty.http.server.AccessLog" level="INFO" additivity="false"> <appender-ref ref="async"/> </logger>通过配置网关控制CORS行为。“全局”CORS配置是URL模式到Spring Framework CorsConfiguration的映射。配置CORS的示例如下:
spring: cloud: gateway: globalcors: cors-configurations: '[/**]': allowedOrigins: "http://www.itxiaoshen.com" allowedMethods: - GET这个配置对于所有GET请求路径,来自www.itxiaoshen.com的请求都允许使用CORS请求。要为没有被网关路由谓词处理的请求提供相同的CORS配置,可以设置spring.cloud.gateway.globalcors。属性add-to-simple url-handler-mapping为true。
SpringCloud Gateway本身就有限流的功能,但是结合功能更加强大的专业限流组件如sentinel是生产环境的首选,虽然在前面有两篇文章《SpringCloud Alibaba分布式流量控制组件Sentinel实战与源码分析》介绍了Sentinel的内容,sentinel可以作为各微服务的限流组件,也可以作为网关的限流组件,也即是说流控即可以放在各微服务端也可以放在网关中,这小节我们就要完成网关整合Sentinel的流控使用。
Sentinel 从 1.6.0 版本开始就提供了 Spring Cloud Gateway 的适配,可以提供两种资源维度的限流:
在网关微服务加入sentinel及sentinel-gateway的依赖
<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>网关的配置文件中加入控制台配置
spring: cloud: sentinel: transport: dashboard: localhost:8858我们还是放在网关的Nacos配置

启动sentinel的控制台,前面文章《SpringCloud Alibaba分布式流量控制组件Sentinel实战与源码分析-上》中启动命令
访问http://localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc 后在sentinel控制台中可以看到和前面sentinel启动控制台有一些不一样,如多了一个API管理菜单,在请求链路中也少了一些操作功能,包括流控功能都有锁变化,各位可以一一感受。

新增一个网关流控规则测试下,在请求链路找到API名称为刚问的访问路由id,点击流控按钮,这个页面和原本Sentinel控制台已经有差异了,选择API类型为Route ID,新增规则后

快速连续访问2次http://localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc 后出现被流控的提示,网关整合sentinel流控功能已生效。

点击流控规则菜单,在网关流控规则列表中选择上一次设置storage_route这个流控规则,点击右边编辑按钮,修改Burst size为2点击保存

这时候快速连续访问2次 http://localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc 后不会被流控,而快速连续访问4次后则又出现被流控的提示。
编辑storage_route,勾选针对请求属性,其中我们选择URL参数,填写参数名称和属性值匹配,点击保存

这时候快速连续访问4次 http://localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc&code=1002 不会被流控,而再快速连续访问4次http://localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc&code=1001 则出现被流控的提示。还可以设置为子串或者正则匹配模式。

如果需要针对URL进行流控,可以使用API分组的功能,先删除前面流控规则,这里使用库存微服务中两个接口

添加自定API,输入两个匹配串/storage-service/deduct和/storage-service/list

在请求链路中设置流控规则,API类型为API分组,然后选择刚才创建的storage-api,点击新增


快速连续访问2次http://localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc 出现被流控的提示

快速连续访问2次http://localhost:4090/storage-service/list?quantity=100&uid=1001&token=abc 也出现被流控的提示

其他如降级设置,可以参考前面sentinel的文章,这里就不再做介绍了

统一返回降级提示的yaml配置如下,也可以通过GatewayCallbackManager的java配置方式实现。
spring: cloud: sentinel: scg: fallback: mode: response response-body: '{"code":403,"message":"被降级了"}'**本人博客网站 **IT小神 www.itxiaoshen.com