JAVA开发(Spring Gateway 的原理和使用)

时间:2022-07-16 01:07:09

     在springCloud的架构中,业务服务都是以微服务来划分的,每个服务可能都有自己的地址和端口。如果前端或者说是客户端直接去调用不同的微服务的话,就要配置不同的地址。其实这是一个解耦和去中心化出现的弊端。所以springCloud体系中,又将这一层的调用封装一层,使一切调用都经过网关,前端和客户端只需要和网关交互,而不需要关注每个微服务的地址,只需要知道微服务的名称就可以。当微服务的地址改变时,只需要修改网关就可以,前端和客户端不需要任何修改,这也方便了服务的扩容和分布式部署。这里的网关就是相当于一个队长的作用。外部的东西一切找队长,团队里自己的事情由队长和成员内部解决。

JAVA开发(Spring Gateway 的原理和使用)

spring gateway的作用有点像封装了post和get的请求过程,增加不同微服务的路由断言判断访问哪个微服务,这里的微服务需要注册中心的协助,网关主要去找注册中心里注册的服务;

客户端向 Spring Cloud Gateway 发出请求,如果请求与网关程序定义的路由匹配,则该请求就会被发送到网关 Web 处理程序,此时处理程序运行特定的请求过滤器链。过滤器之间用虚线分开的原因是过滤器可能会在发送代理请求的前后执行逻辑。所有 pre 过滤器逻辑先执行,然后执行代理请求;代理请求完成后,执行 post 过滤器逻辑。

其原理架构如下:

JAVA开发(Spring Gateway 的原理和使用)

JAVA开发(Spring Gateway 的原理和使用)

 Gateway 接收客户端请求。客户端请求与路由信息进行匹配,匹配成功的才能够被发往相应的下游服务。请求经过 Filter 过滤器链,执行 pre 处理逻辑,如修改请求头信息等。请求被转发至下游服务并返回响应。响应经过 Filter 过滤器链,执行 post 处理逻辑。向客户端响应应答。

JAVA开发(Spring Gateway 的原理和使用)

 API 网关也存在不足之处,在微服务这种去中心化的架构中,网关又成了一个中心点,它增加了一个我们必须开发、部署和维护的高可用组件。正是由于这个原因,在网关设计时必须考虑即使 API 网关宕机也不要影响到服务的调用和运行,所以需要对网关的响应结果有数据缓存能力,通过返回缓存数据或默认数据屏蔽后端服务的失败。

主要网关对比选型:

JAVA开发(Spring Gateway 的原理和使用)
 

网关的作用:
性能:API高可用,负载均衡,容错机制。
安全:权限身份认证、脱敏,流量清洗,后端签名(保证全链路可信调用),黑名单(非法调用的限制)。
限流:流量控制,错峰流控,可以定义多种限流规则。
缓存:数据缓存。
日志:日志记录,一旦涉及分布式,全链路跟踪必不可少。
监控:记录请求响应数据,api耗时分析,性能监控。 

JAVA开发(Spring Gateway 的原理和使用)

网关的使用,配置

pom文件:

 <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>3.0.7</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        
        <dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
		</dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>com.thoughtworks.xstream</groupId>
                    <artifactId>xstream</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.thoughtworks.xstream</groupId>
            <artifactId>xstream</artifactId>
            <version>${xstream.version}</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>

        <dependency>
            <groupId>com.ctrip.framework.apollo</groupId>
            <artifactId>apollo-client</artifactId>
            <version>1.8.0</version>
            <scope>compile</scope>
        </dependency>

        <!-- 健康检查 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <version>2.6.3</version>
        </dependency>

 yml文件配置详解:

#应用ID
app:
  id: cn-maoheyeren-plat

#端口
server:
  port: 8300

#应用版本
deploy:
  version: -v1

#服务名称  
spring:
  application:
    name: base-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: false # 这个配置是默认给每个服务创建一个router,设置为false防止请求默认转发到url中包含的微服务名上
                         #例:/auth/**会默认转发到服务auth下,而不是转发到配置的uri
          lower-case-service-id: true # 微服务名称以小写形式呈现
      routes:
        - id: base-admin #微服务路由规则
          uri: lb://base-admin #负载均衡,将请求转发到注册中心的base-admin
          predicates: #断言,如果前端请求包含/base-admin/,则走这条规则
            - Path=/base-admin/**
          filters: # 过滤器 /base-admin/** 转发到 uri/**
            - StripPrefix=1

        - id: maoheyeren-business
          uri: lb://maoheyeren-business
          predicates:
            - Path=/maoheyeren-business/**
          filters: # /maoheyeren-business/** 转发到 uri/**
            - StripPrefix=1

        - id: maoheyeren-cockpit
          uri: lb://maoheyeren-cockpit
          predicates:
            - Path=/maoheyeren-cockpit/**
          filters: # /maoheyeren-cockpit/** 转发到 uri/**
            - StripPrefix=1

        - id: maoheyeren-data
          uri: lb://maoheyeren-data
          predicates:
            - Path=/maoheyeren-data/**
          filters: # /maoheyeren-data/** 转发到 uri/**
            - StripPrefix=1

网关中过滤器的编写使用:

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;

@Slf4j
@Component
public class PermissionGatewayFilter implements GatewayFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        log.info("执行了自定义的全局过滤器");
        //1.获取请求参数access-token
        String token = exchange.getRequest().getQueryParams().getFirst("access-token");
        //2.判断是否存在
        if(token == null) {
            //3.如果不存在 : 认证失败
        	log.info("没有获取到token");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete(); //请求结束
        }
        //4.如果存在,继续执行
        return chain.filter(exchange); //继续向下执行
    }

    @Override
    public int getOrder() {
        return 0;
    }

}

spring gateway 实现限流

/**
 * 自定义过滤器
 */
@Component
@Slf4j
@Order(-1)
public class RequestRateLimitFilter implements GlobalFilter {
    private static final Cache<String, RateLimiter> RATE_LIMITER_CACHE = CacheBuilder
            .newBuilder()
            .maximumSize(1000)
            .expireAfterAccess(1, TimeUnit.HOURS)
            .build();

    private static final double DEFAULT_PERMITS_PER_SECOND = 1; // 令牌桶每秒填充速率

    @SneakyThrows
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String remoteAddr = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
        RateLimiter rateLimiter = RATE_LIMITER_CACHE.get(remoteAddr, () -> RateLimiter.create(DEFAULT_PERMITS_PER_SECOND));
        if (rateLimiter.tryAcquire()) {
            return chain.filter(exchange);
        }
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        DataBuffer dataBuffer = response.bufferFactory().wrap("Too Many Request!!!".getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(dataBuffer));
    }
}
/**
 * 自定义局部限流
 *
 */
@Component
public class CustomRequestRateLimitGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomRequestRateLimitGatewayFilterFactory.Config> {
    public CustomRequestRateLimitGatewayFilterFactory() {
        super(Config.class);
    }

    private static final Cache<String, RateLimiter> RATE_LIMITER_CACHE = CacheBuilder
            .newBuilder()
            .maximumSize(1000)
            .expireAfterAccess(1, TimeUnit.HOURS)
            .build();

    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @SneakyThrows
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                String remoteAddr = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
                RateLimiter rateLimiter = RATE_LIMITER_CACHE.get(remoteAddr, () ->
                        RateLimiter.create(Double.parseDouble(config.getPermitsPerSecond())));
                if (rateLimiter.tryAcquire()) {
                    return chain.filter(exchange);
                }
                ServerHttpResponse response = exchange.getResponse();
                response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
                response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
                DataBuffer dataBuffer = response.bufferFactory().wrap("Too Many Request!!!".getBytes(StandardCharsets.UTF_8));
                return response.writeWith(Mono.just(dataBuffer));
            }
        };
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList("permitsPerSecond");
    }

    @Data
    public static class Config {
        private String permitsPerSecond; // 令牌桶每秒填充速率
    }
}