SpringCloudGateway微服务网关实战与源码分析,如何深入理解与高效应用?
- 内容介绍
- 文章标签
- 相关推荐
本文共计2317个文字,预计阅读时间需要10分钟。
Spring Cloud Gateway作为Spring Cloud生态系统中的网关组件,是不可或缺的。本文将延续上一章节,从实战层面探讨如何使用网关过滤器、全局路由过滤器,以及如何进行自定义实现。
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: 4090
spring:
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
访问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
@Slf4j
public 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.www.itxiaoshen.com:3001/assets/1657431654370DW5dZAyW.png)]
网关Nacos配置文件增加Authorize=true
server:
port: 4090
spring:
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的信息,访问localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc 返回401状态
我们往redis写入uid为1001的数据
再次访问localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc ,则可以正常获取结果
全局过滤器- 前面配置使用的lb就是全局过滤器来实现的。ReactiveLoadBalancerClientFilter在名为ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR的交换属性中查找URI。如果URL有一个lb方案(例如lb://myservice),它使用Spring Cloud ReactorLoadBalancer来解析名称(在本例中是myservice)到一个实际的主机和端口,并替换相同属性中的URI。未修改的原始URL被追加到ServerWebExchangeUtils中的列表中。GATEWAY_ORIGINAL_REQUEST_URL_ATTR属性。GATEWAY_SCHEME_PREFIX_ATTR属性来查看它是否等于lb。如果等于,则应用相同的规则。
- 全局过滤器作为bean注册成功后,不需要进行配置,就可以直接生效。全局过滤器的作用范围是对所有的请求,而局部过滤器是针对路由。GlobalFilter 是用来定义全局过滤器的接口,通过实现GlobalFilter接口可以实现各种自定义过滤器。有多个拦截器时通过Ordered接口实现getOrder()方法来指定执行顺序,返回值越小执行顺序越靠前需要添加@Component注解;
- 利用全局过滤器,可以实现统一的鉴权处理,日志处理等。
- 在配置时可以通过
spring.cloud.gateway.default-filters实现所配置的过滤器全局生效,但这种方法在实际中比较少用。
官方目前全局过滤器有,详细可以查阅
- org.springframework.cloud.gateway.filter.NettyWriteResponseFilter, order = -1
- 将Netty代理调用的response数据流写入ServerHttpResponse的body中。当NettyRouting拿到远程调用的结果数据流之后会将其写入当前请求exchange的attributes中。
- org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter, order = 10000
- 将网关上的请求转为对应业务应用的真实ip的请求。请求进来时path的前缀是gateway的地址(ip+port或域名),需要将其uri映射至服务id上;比如:将path的192.168.20.134:10080映射至服务lb://{serviceId};对于绝对路径配置的服务,exchange的GATEWAY_ROUTE_ATTR属性将会是null,直接过滤到下一个过滤器,不会发生path的真实映射。
- org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter, order = 10150
- 负责服务真实ip的映射,对服务进行负载均衡。
- org.springframework.cloud.gateway.filter.WebsocketRoutingFilter, order = 2147483646
- 实现了gateway对于websocket的支持,内部通过websocketClient实现将一个localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc ,可以看到打印日志信息
Reactor Netty Access Logs
要启用Reactor Netty访问日志,设置-Dreactor.netty.www.itxiaoshen.com" allowedMethods: - GET
这个配置对于所有GET请求路径,来自www.itxiaoshen.com的请求都允许使用CORS请求。要为没有被网关路由谓词处理的请求提供相同的CORS配置,可以设置spring.cloud.gateway.globalcors。属性add-to-simple url-handler-mapping为true。
Gateway整合Sentinel流控降级SpringCloud Gateway本身就有限流的功能,但是结合功能更加强大的专业限流组件如sentinel是生产环境的首选,虽然在前面有两篇文章《SpringCloud Alibaba分布式流量控制组件Sentinel实战与源码分析》介绍了Sentinel的内容,sentinel可以作为各微服务的限流组件,也可以作为网关的限流组件,也即是说流控即可以放在各微服务端也可以放在网关中,这小节我们就要完成网关整合Sentinel的流控使用。
Sentinel 从 1.6.0 版本开始就提供了 Spring Cloud Gateway 的适配,可以提供两种资源维度的限流:
- route维度:即在配置文件中配置的路由条目,资源名为对应的 routeId,这种属于粗粒度的限流,一般是对某个微服务进行限流。
- 自定义API维度:用户可以利用 Sentinel 提供的API来自定义一些API分组,这种属于细粒度的限流,针对某一类的uri进行匹配限流,可以跨多个微服务。
在网关微服务加入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实战与源码分析-上》中启动命令
访问localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc 后在sentinel控制台中可以看到和前面sentinel启动控制台有一些不一样,如多了一个API管理菜单,在请求链路中也少了一些操作功能,包括流控功能都有锁变化,各位可以一一感受。
新增一个网关流控规则测试下,在请求链路找到API名称为刚问的访问路由id,点击流控按钮,这个页面和原本Sentinel控制台已经有差异了,选择API类型为Route ID,新增规则后
快速连续访问2次localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc 后出现被流控的提示,网关整合sentinel流控功能已生效。
Sentinel流控降级详细配置 Burst size设置点击流控规则菜单,在网关流控规则列表中选择上一次设置storage_route这个流控规则,点击右边编辑按钮,修改Burst size为2点击保存
这时候快速连续访问2次 localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc 后不会被流控,而快速连续访问4次后则又出现被流控的提示。
针对请求属性设置编辑storage_route,勾选针对请求属性,其中我们选择URL参数,填写参数名称和属性值匹配,点击保存
这时候快速连续访问4次 localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc&code=1002 不会被流控,而再快速连续访问4次localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc&code=1001 则出现被流控的提示。还可以设置为子串或者正则匹配模式。
API分组如果需要针对URL进行流控,可以使用API分组的功能,先删除前面流控规则,这里使用库存微服务中两个接口
添加自定API,输入两个匹配串/storage-service/deduct和/storage-service/list
在请求链路中设置流控规则,API类型为API分组,然后选择刚才创建的storage-api,点击新增
快速连续访问2次localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc 出现被流控的提示
快速连续访问2次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
- 实现了gateway对于websocket的支持,内部通过websocketClient实现将一个localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc ,可以看到打印日志信息
Reactor Netty Access Logs
本文共计2317个文字,预计阅读时间需要10分钟。
Spring Cloud Gateway作为Spring Cloud生态系统中的网关组件,是不可或缺的。本文将延续上一章节,从实战层面探讨如何使用网关过滤器、全局路由过滤器,以及如何进行自定义实现。
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: 4090
spring:
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
访问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
@Slf4j
public 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.www.itxiaoshen.com:3001/assets/1657431654370DW5dZAyW.png)]
网关Nacos配置文件增加Authorize=true
server:
port: 4090
spring:
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的信息,访问localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc 返回401状态
我们往redis写入uid为1001的数据
再次访问localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc ,则可以正常获取结果
全局过滤器- 前面配置使用的lb就是全局过滤器来实现的。ReactiveLoadBalancerClientFilter在名为ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR的交换属性中查找URI。如果URL有一个lb方案(例如lb://myservice),它使用Spring Cloud ReactorLoadBalancer来解析名称(在本例中是myservice)到一个实际的主机和端口,并替换相同属性中的URI。未修改的原始URL被追加到ServerWebExchangeUtils中的列表中。GATEWAY_ORIGINAL_REQUEST_URL_ATTR属性。GATEWAY_SCHEME_PREFIX_ATTR属性来查看它是否等于lb。如果等于,则应用相同的规则。
- 全局过滤器作为bean注册成功后,不需要进行配置,就可以直接生效。全局过滤器的作用范围是对所有的请求,而局部过滤器是针对路由。GlobalFilter 是用来定义全局过滤器的接口,通过实现GlobalFilter接口可以实现各种自定义过滤器。有多个拦截器时通过Ordered接口实现getOrder()方法来指定执行顺序,返回值越小执行顺序越靠前需要添加@Component注解;
- 利用全局过滤器,可以实现统一的鉴权处理,日志处理等。
- 在配置时可以通过
spring.cloud.gateway.default-filters实现所配置的过滤器全局生效,但这种方法在实际中比较少用。
官方目前全局过滤器有,详细可以查阅
- org.springframework.cloud.gateway.filter.NettyWriteResponseFilter, order = -1
- 将Netty代理调用的response数据流写入ServerHttpResponse的body中。当NettyRouting拿到远程调用的结果数据流之后会将其写入当前请求exchange的attributes中。
- org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter, order = 10000
- 将网关上的请求转为对应业务应用的真实ip的请求。请求进来时path的前缀是gateway的地址(ip+port或域名),需要将其uri映射至服务id上;比如:将path的192.168.20.134:10080映射至服务lb://{serviceId};对于绝对路径配置的服务,exchange的GATEWAY_ROUTE_ATTR属性将会是null,直接过滤到下一个过滤器,不会发生path的真实映射。
- org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter, order = 10150
- 负责服务真实ip的映射,对服务进行负载均衡。
- org.springframework.cloud.gateway.filter.WebsocketRoutingFilter, order = 2147483646
- 实现了gateway对于websocket的支持,内部通过websocketClient实现将一个localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc ,可以看到打印日志信息
Reactor Netty Access Logs
要启用Reactor Netty访问日志,设置-Dreactor.netty.www.itxiaoshen.com" allowedMethods: - GET
这个配置对于所有GET请求路径,来自www.itxiaoshen.com的请求都允许使用CORS请求。要为没有被网关路由谓词处理的请求提供相同的CORS配置,可以设置spring.cloud.gateway.globalcors。属性add-to-simple url-handler-mapping为true。
Gateway整合Sentinel流控降级SpringCloud Gateway本身就有限流的功能,但是结合功能更加强大的专业限流组件如sentinel是生产环境的首选,虽然在前面有两篇文章《SpringCloud Alibaba分布式流量控制组件Sentinel实战与源码分析》介绍了Sentinel的内容,sentinel可以作为各微服务的限流组件,也可以作为网关的限流组件,也即是说流控即可以放在各微服务端也可以放在网关中,这小节我们就要完成网关整合Sentinel的流控使用。
Sentinel 从 1.6.0 版本开始就提供了 Spring Cloud Gateway 的适配,可以提供两种资源维度的限流:
- route维度:即在配置文件中配置的路由条目,资源名为对应的 routeId,这种属于粗粒度的限流,一般是对某个微服务进行限流。
- 自定义API维度:用户可以利用 Sentinel 提供的API来自定义一些API分组,这种属于细粒度的限流,针对某一类的uri进行匹配限流,可以跨多个微服务。
在网关微服务加入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实战与源码分析-上》中启动命令
访问localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc 后在sentinel控制台中可以看到和前面sentinel启动控制台有一些不一样,如多了一个API管理菜单,在请求链路中也少了一些操作功能,包括流控功能都有锁变化,各位可以一一感受。
新增一个网关流控规则测试下,在请求链路找到API名称为刚问的访问路由id,点击流控按钮,这个页面和原本Sentinel控制台已经有差异了,选择API类型为Route ID,新增规则后
快速连续访问2次localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc 后出现被流控的提示,网关整合sentinel流控功能已生效。
Sentinel流控降级详细配置 Burst size设置点击流控规则菜单,在网关流控规则列表中选择上一次设置storage_route这个流控规则,点击右边编辑按钮,修改Burst size为2点击保存
这时候快速连续访问2次 localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc 后不会被流控,而快速连续访问4次后则又出现被流控的提示。
针对请求属性设置编辑storage_route,勾选针对请求属性,其中我们选择URL参数,填写参数名称和属性值匹配,点击保存
这时候快速连续访问4次 localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc&code=1002 不会被流控,而再快速连续访问4次localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc&code=1001 则出现被流控的提示。还可以设置为子串或者正则匹配模式。
API分组如果需要针对URL进行流控,可以使用API分组的功能,先删除前面流控规则,这里使用库存微服务中两个接口
添加自定API,输入两个匹配串/storage-service/deduct和/storage-service/list
在请求链路中设置流控规则,API类型为API分组,然后选择刚才创建的storage-api,点击新增
快速连续访问2次localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc 出现被流控的提示
快速连续访问2次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
- 实现了gateway对于websocket的支持,内部通过websocketClient实现将一个localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc ,可以看到打印日志信息
Reactor Netty Access Logs

