springcloud gateway集成sentinel

386 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情

前言:

项目中网关使用的是spring自带的gateway来进行网络请求入口流量接收及请求转发。为了应对后续的更大的业务需求,需要进行流控和熔断降级的介入。之前使用的hystrix来进行的熔断处理,准备采用alibaba一套的sentinel,接入到我们的微服务生态中。

控制台搭建

阿里的组件有一个特别好的地方,比如nacos,sentinel等都是会带一个独立的控制台页面,可以直接进行处理。非常人性化,开箱即用。

image.png 参考官方文档,我使用的8089来进行的启动。默认账户密码都是sentinel。

image.png

新版的控制台,直接可以监控自身的流量控制。左边的是接入的资源,右边列表是流控和降级操作。非常简洁

接入

image.png

官网有接入的方式,我这边没有采用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纬度,粒度有点粗。

流控

image.png 先设置一个流控规则,QPS(代表每秒请求数)为1,看一下路由的限制情况。 短时间内点击两次请求

image.png 很容易看到请求限制,第一个正常,第二个失败,后续的也是失败。

  • 状态码返回为429
  • 返回内容,显示被流控

image.png

下一秒再次请求:

image.png

第一个正常,第二个失败。流量限制成功。 也可以根据图表查看整个路由的请求qps,非常直观。

image.png

熔断

熔断降级规则配置

image.png 以异常数为例:

  • 熔断时长:触发熔断后,多长时间走降级策略。结束后进行half-open(半开)探测,如果失败,继续执行熔断时长。一直循环等待服务正常。
  • 最小请求数:单位时间内统计的最小样本。分母的最小值
  • 统计时长:统计多长时间内的异常数量,异常比例,慢调用比例
  • 异常数:单位时间内的异常数量,分子

image.png

image.png 我手动将服务下线。第一个请求返回找不到对应的服务,第二个请求就返回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使用非常方便,易于集成,统计。性能也是非常高。