开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情
前言:
项目中网关使用的是spring自带的gateway来进行网络请求入口流量接收及请求转发。为了应对后续的更大的业务需求,需要进行流控和熔断降级的介入。之前使用的hystrix来进行的熔断处理,准备采用alibaba一套的sentinel,接入到我们的微服务生态中。
控制台搭建
阿里的组件有一个特别好的地方,比如nacos,sentinel等都是会带一个独立的控制台页面,可以直接进行处理。非常人性化,开箱即用。
参考官方文档,我使用的8089来进行的启动。默认账户密码都是sentinel。
新版的控制台,直接可以监控自身的流量控制。左边的是接入的资源,右边列表是流控和降级操作。非常简洁
接入
官网有接入的方式,我这边没有采用adapter的方式,而是参考另一种接入
<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是懒加载的方式,所以还是看不到我们的网关资源,因此我们需要发起请求才会显示出来。
sentinel接入gateway,默认只会监控route纬度,粒度有点粗。
流控
先设置一个流控规则,QPS(代表每秒请求数)为1,看一下路由的限制情况。
短时间内点击两次请求
很容易看到请求限制,第一个正常,第二个失败,后续的也是失败。
- 状态码返回为429
- 返回内容,显示被流控
下一秒再次请求:
第一个正常,第二个失败。流量限制成功。 也可以根据图表查看整个路由的请求qps,非常直观。
熔断
熔断降级规则配置
以异常数为例:
- 熔断时长:触发熔断后,多长时间走降级策略。结束后进行half-open(半开)探测,如果失败,继续执行熔断时长。一直循环等待服务正常。
- 最小请求数:单位时间内统计的最小样本。分母的最小值
- 统计时长:统计多长时间内的异常数量,异常比例,慢调用比例
- 异常数:单位时间内的异常数量,分子
我手动将服务下线。第一个请求返回找不到对应的服务,第二个请求就返回sentinel的降级策略拦截。
非常简单就对异常数进行熔断降级处理。有一个点需要注意需要先去掉hystrix的熔断配置,sentinel才能生效。
网关扩展
Referer过滤
通过断言的方式来判断用户请求的referer进行判断,指定的referer走对应的路由。
package com.deng.gateway.predicate;
import com.deng.gateway.filter.MetricFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* @author:zooooooooy
* @date: 2022/9/21 - 16:36
*/
@Component
public class RefererRoutePredicateFactory extends AbstractRoutePredicateFactory<RefererRoutePredicateFactory.Config> {
public static final Logger LOGGER = LoggerFactory.getLogger(RefererRoutePredicateFactory.class);
public RefererRoutePredicateFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("referer");
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
List<String> referer = exchange.getRequest().getHeaders().get("Referer");
if(CollectionUtils.isEmpty(referer)) {
return true;
}
LOGGER.info("request referer is => [{}], config referer is => [{}]", referer.get(0), config.getReferer());
return Stream.of(config.getReferer().split(",")).anyMatch(s -> referer.get(0).contains(s));
};
}
public static class Config {
private String referer;
public String getReferer() {
return referer;
}
public void setReferer(String referer) {
this.referer = referer;
}
}
}
在路由中配置断言,支持数组配置。
请求时长统计
通过filter来进行实现,当满足条件的url时,进行时长统计。
package com.deng.gateway.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
/**
* @author:zooooooooy
* @date: 2022/6/21 - 15:25
*/
@Component
public class MetricFilter implements GlobalFilter, Ordered {
public static final Logger LOGGER = LoggerFactory.getLogger(MetricFilter.class);
public static final String START_TIME = "startTime";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
Long startTime = exchange.getAttribute(START_TIME);
if(startTime != null) {
Long executeTime = System.currentTimeMillis() - startTime;
// /busOnline/checkHealthcode/V1
String rawPath = exchange.getRequest().getURI().getRawPath();
if("/busOnline/checkHealthcode/V1".equals(rawPath)) {
String deviceId = exchange.getRequest().getHeaders().getFirst("deviceId");
if(deviceId == null) {
deviceId = "";
}
LOGGER.info("request deviceId is [{}], url is [{}], cost time => [{}ms]", deviceId, rawPath, executeTime);
}
}
}));
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
写法要符合webflux的规则,返回Mono对象,其他还是比较简单。
springcloud gateway使用非常方便,易于集成,统计。性能也是非常高。