在一个大型的购物网站中,以微服务架构进行拆分,会分为很多种服务,比如购物车、订单服务、评论服务、库存服务、用户服务等等,服务相互之间调用,那么就会产生很多个链接地址,如果有成百上千个服务之间进行调用,那么维护起来是很麻烦的,所以根据环境需要就产生了服务网关。
所谓的网关:就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等。
功能特性:
缺点:
路由(Route) 是 Gateway 中最基本的组件之一,表示一个具体的路由信息载体。由ID、URI、一组Predicate、一组Filter组成,根据Predicate进行匹配转发。:
断言(谓语):路由转发的判断条件,目前SpringCloud Gateway支持多种方式,常见如:Path、Query、Method、Header等,写法必须遵循 key=vlue的形式 一个请求满足多个路由的断言条件时,请求只会被首个成功匹配的路由转发
过滤器是路由转发请求时所经过的过滤逻辑,可用于修改请求、响应内容
因为用Nacos作为注册中心,所以需要引入spring-cloud-starter-alibaba-nacos-discovery
。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
application.yml
配置文件spring:
application:
name: myGateway
profiles:
active: dev
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
username: nacos
password: rstyro
# # 网关配置在nacos中配置
gateway:
enabled: true
discovery:
locator:
enabled: true #表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务。
lower-case-service-id: true #是将请求路径上的服务名配置为小写
routes:
- id: provider
uri: lb://nacos-provider
predicates:
- Path=/provider/**
filters:
# StripPrefix 数字表示要截断的路径的数量
- StripPrefix=1
# 日志追踪
logging:
level:
org.springframework.cloud.gateway: trace
按需将如下包的日志级别设置成 debug
或 trace
。
org.springframework.cloud.gateway
org.springframework.http.server.reactive
org.springframework.web.reactive
org.springframework.boot.autoconfigure.web
reactor.netty
redisratelimiter
例子:
# 日志追踪
logging:
level:
org.springframework.cloud.gateway: trace
配置跨域过滤器CorsWebFilter。如下
/**
* 网关跨域
*/
@Configuration
public class GatewayCorsFilter {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// 允许cookies跨域
config.setAllowCredentials(true);
// #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
config.addAllowedOrigin("*");
// #允许访问的头信息,*表示全部
config.addAllowedHeader("*");
// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
config.setMaxAge(18000L);
config.addAllowedMethod("OPTIONS");// 允许提交请求的方法类型,*表示全部允许
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
@EnableDiscoveryClient
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
集成sentinel流量控制、熔断降级
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<!-- sentinel使用nacos 持久化动态配置 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
# sentinel 默认配置,可在nacos配置覆盖
spring:
cloud:
sentinel:
#服务启动直接建立心跳连接,访问sentinel 控制台就可以看到服务连接情况,不需要第一次访问应用的某个接口,才连接sentinel
eager: true
enabled: true
transport:
#控制台地址
dashboard: localhost:8080
resource
:资源名称,可以是网关中的 route 名称或者用户自定义的 API 分组名称。resourceMode
:资源模式,0
=规则是针对 API Gateway 的 route,1
=用户在 Sentinel 中定义的 API 分组.默认是 route。grade
:限流阈值类型(QPS 或并发线程数);0代表根据并发数量来限流,1代表根据QPS来进行流量控制count
:限流阈值intervalSec
:统计时间窗口,单位是秒,默认是 1 秒。controlBehavior
:流量整形的控制效果,同限流规则的 controlBehavior
字段,目前支持0=快速失败和2=匀速排队两种模式,默认是快速失败。burst
:应对突发请求时额外允许的请求数目。maxQueueingTimeoutMs
:匀速排队模式下的最长排队时间,单位是毫秒,仅在匀速排队模式下生效。paramItem
:参数限流配置。若不提供,则代表不针对参数进行限流,该网关规则将会被转换成普通流控规则;否则会转换成热点规则。其中的字段:
parseStrategy
:从请求中提取参数的策略,查看类:SentinelGatewayConstants
,目前支持提取来源 IP(0=PARAM_PARSE_STRATEGY_CLIENT_IP
)、Host(1=PARAM_PARSE_STRATEGY_HOST
)、任意 Header(2=PARAM_PARSE_STRATEGY_HEADER
)和任意 URL 参数(3=PARAM_PARSE_STRATEGY_URL_PARAM
)四种模式。fieldName
:若提取策略选择 Header 模式或 URL 参数模式,则需要指定对应的 header 名称或 URL 参数名称。pattern
:参数值的匹配模式,只有匹配该模式的请求属性值会纳入统计和流控;若为空则统计该请求属性的所有值。(1.6.2 版本开始支持)matchStrategy
:参数值的匹配策略,查看类:SentinelGatewayConstants
目前支持精确匹配(0=PARAM_MATCH_STRATEGY_EXACT
)、子串匹配(3=PARAM_MATCH_STRATEGY_CONTAINS
)和正则匹配(2=PARAM_MATCH_STRATEGY_REGEX
)。(1.6.2 版本开始支持)从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:
# 在 bootstrap.yml 配置如下
spring:
cloud:
sentinel:
datasource:
ds1:
nacos:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
data-id: sentinel-geteway
group-id: DEFAULT_GROUP
data-type: json
namespace: 94455b2a-cf66-40b5-819b-bba352aaa4f1
rule-type: gw-flow
[
{
"resource": "cloud-user",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
cloud-user
就是网关的routeId,可以配置多个,上面就是简单的网关流控配置,下面来 配置API分组spring:
cloud:
sentinel:
datasource:
# 下面是测试 gateway sentinel api分组
group-api:
nacos:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
data-id: sentinel-group-api
group-id: DEFAULT_GROUP
data-type: json
namespace: 94455b2a-cf66-40b5-819b-bba352aaa4f1
rule-type: gw-api-group
group-rule:
nacos:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
data-id: sentinel-group-rule
group-id: DEFAULT_GROUP
data-type: json
namespace: 94455b2a-cf66-40b5-819b-bba352aaa4f1
rule-type: gw-flow
[
{
"apiName":"all-url-qps",
"predicateItems":[
{
"pattern":"/**",
"matchStrategy": 1
}
]
},
{
"apiName":"user-qps",
"predicateItems":[
{
"pattern":"/user/**",
"matchStrategy": 1
}
]
}
]
all-url-qps
api拦截/**
所有请求user-qps
只拦截/user
开头的请求ApiDefinition
ApiPathPredicateItem
,
[
{
"resource": "all-url-qps",
"resourceMode": 1,
"controlBehavior": 0,
"count": 3,
"intervalSec": 1,
"grade": 1,
"limitApp": "default",
"strategy": 0,
"paramItem": {
"parseStrategy": 2,
"fieldName": "uid"
}
}
,{
"resource": "user-qps",
"resourceMode": 1,
"controlBehavior": 0,
"count": 20,
"intervalSec": 86400,
"grade": 1,
"limitApp": "default",
"strategy": 0,
"paramItem": {
"parseStrategy": 2,
"fieldName": "uid"
}
}
]
uid
字段您可以在 GatewayCallbackManager
注册回调进行定制:
setBlockHandler
:注册函数用于实现自定义的逻辑处理被限流的请求,对应接口为 BlockRequestHandler
。DefaultBlockRequestHandler
,当被限流时会返回类似于下面的错误信息:Blocked by Sentinel: FlowException
。BlockRequestHandler
接口@Slf4j
public class SentinelFallbackHandler implements BlockRequestHandler {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable e) {
// 默认流控
ApiResultEnum apiResultEnum = ApiResultEnum.BLOCKED_FLOW;
if (e instanceof FlowException) {
log.error("触发流控,url={}", exchange.getRequest().getPath());
apiResultEnum = ApiResultEnum.BLOCKED_FLOW;
} else if (e instanceof DegradeException) {
log.error("触发熔断降级,url={}", exchange.getRequest().getPath());
apiResultEnum = ApiResultEnum.BLOCKED_DEGRADE;
} else if (e instanceof AuthorityException) {
log.error("请求未授权,url={}", exchange.getRequest().getPath());
apiResultEnum = ApiResultEnum.BLOCKED_AUTHORITY;
} else if (e instanceof ParamFlowException) {
ParamFlowException paramFlowException = (ParamFlowException) e;
ParamFlowRule rule = paramFlowException.getRule();
log.error("触发热点参数流控,url={},resourceName={},时间={}ms,阀值={}"
, exchange.getRequest().getPath()
, paramFlowException.getResourceName()
, rule.getDurationInSec(), rule.getCount());
apiResultEnum = ApiResultEnum.BLOCKED_FLOW;
// 重置
// resetFlowLimit(exchange,"user-qps");
}
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(R.fail(apiResultEnum)));
}
/**
* 重置限流规则,可能有需求,所以这里记录个入口
* @param exchange exchange
* @param resourceName 资源名称
*/
public void resetFlowLimit(ServerWebExchange exchange, String resourceName) {
// String resourceName="user-qps";
//获取已转换的参数限流规则
List<ParamFlowRule> rules = GatewayRuleManager.getConvertedParamRules(resourceName);
// 获取资源的参数度量指标,其中包含了该资源相关的令牌桶计数器。
ParameterMetric metric = ParameterMetricStorage.getParamMetricForResource(resourceName);
// 获取参数度量指标,则从中获取第一条规则对应的令牌桶计数器(CacheMap),键值对表示各个用户的请求计数。
CacheMap<Object, AtomicLong> tokenCounters = metric == null ? null : metric.getRuleTokenCounter(rules.get(0));
if (Objects.nonNull(tokenCounters)) {
// 令牌桶余量
AtomicLong oldQps = tokenCounters.get(exchange.getRequest().getHeaders().getFirst(SecurityConst.USER_ID));
if (Objects.nonNull(oldQps) && oldQps.get() == 0) {
long oldValue = oldQps.get();
// 重置请求计数值
oldQps.compareAndSet(oldValue, (long) rules.get(0).getCount());
log.info("已重置resourceName={}的流控,count={}",resourceName,rules.get(0).getCount());
}
}
}
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。