服务网关Gateway

92 阅读2分钟

服务网关在微服务中的应用

  • 官方主推
  • 底层Netty构建
  • 社区维护

Gateway做什么

  1. 路由寻址(主要)
  2. 负载均衡(Ribbon)
  3. 限流
  4. 鉴权

第二代网关组件Gateway介绍(Zuul的对比)

GatewayZuul 1.xZuul 1.x
靠谱性官方支持曾经靠谱过专业放鸽子
性能Netty同步阻塞性能慢Netty
RPS>3200020000左右25000左右
Spring Cloud已整合已整合占无整合计划
长链接支持不支持支持
编程体验略难简单易上手略难
调试&链路追踪略难无压力略难
  • 用Gateway

Gateway急速落地

创建gateway-sample项目

  1. 引入依赖
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>
</dependencies>
  1. 创建启动类
@EnableDiscoveryClient
@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class,args);
    }
}
  1. 配置文件
server:
  port: 65000
  
spring:
  rabbitmq:
    username: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true

eureka:
  instance:
    preferIpAddress: true
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
  client:
    service-url:
      defaultZone: http://localhost:20000/eureka/

management:
  security:
    enabled: false
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: always

连接Eureka自动创建路由

  1. 启动服务
  2. 验证路由规则
  3. 设置小写访问
spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          # 新增
          lower-case-service-id: true
  1. 验证路由规则

通过Actuator实现动态路由功能

{
    "predicates":[
        {
        "name":"Path",
        "args":{
            "_genkey_0":"/dynamic/**"
            }
        }
    ],
    "filters":[
        {
            "name":"StripPrefix",
            "args":{
                "_genkey_0":"1"
            }
        }
    ],
    "uri":"lb://FEIGN-CLIENT",
    "order":0
}

相关接口文档

Gateway断言功能

Path断言

  1. 使用Path断言转发请求(yml配置+java配置)
  • yml配置
spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
      # 新增
      routes:
      - id: feignclient
        uri: lb://FEIGN-CLIENT
        predicates:
        - Path=/yml/**
        filters:
        - StripPrefix=1
  • java配置
@Configuration
public class GatewayConfiguration {
    @Bean
    @Order
    public RouteLocator customizedRoutes(RouteLocatorBuilder builder){
        return builder.routes()
                .route(r-> r.path("/java/**")
                    .and().method(HttpMethod.GET)
                    .and().header("name")
                    .filters(f->f.stripPrefix(1)
                        .addRequestHeader("java-param","gateway-config")
                    )
                    .uri("lb://FEIGN-CLIENT")
                ).build();
    }
}

After断言

  1. 创建模拟下单接口
@RestController
@Slf4j
@RequestMapping("/gateway")
public class GatewayController {
    public static final Map<Long, Product> items = new ConcurrentHashMap<>();

    @GetMapping("/details")
    public Product get(@RequestParam("pid") Long pid) {
        if (items.containsKey(pid)) {
            Product prod = Product.builder()
                    .productId(pid)
                    .description("好吃不贵")
                    .stock(100L)
                    .build();
            items.putIfAbsent(pid, prod);
        }
        return items.get(pid);
    }

    @PostMapping("/placeOrder")
    public String buy(@RequestParam("pid") String pid) {
        Product prod = items.get(pid);
        if (prod == null) {
            return "Product not found";
        } else if (prod.getStock() <= 0L) {
            return "Sold out";
        }
        synchronized (prod) {
            if (prod.getStock() <= 0L) {
                return "Sold out";
            }
            prod.setStock(prod.getStock()-1);
        }
        return "Order Placed";
    }
}
  1. 通过After断言设置生效时间
@Configuration
public class GatewayConfiguration {
    @Bean
    @Order
    public RouteLocator customizedRoutes(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(r -> r.path("/java/**")
                        .and().method(HttpMethod.GET)
                        .and().header("name")
                        .filters(f -> f.stripPrefix(1)
                                .addRequestHeader("java-param", "gateway-config")
                        )
                        .uri("lb://FEIGN-CLIENT")
                )
                .route(r -> r.path("/seckill/**")
                        .and().after(ZonedDateTime.now().plusMinutes(1))
                        // .and().before()
                        // .and().between()
                        .filters(f -> f.stripPrefix(1))
                        .uri("lb://FEIGN-CLIENT"))

                .build();
    }
}

Gat过滤器原理和生命周期

自定义过滤器

@Slf4j
@Component
public class TimerFilter implements GatewayFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        StopWatch timer = new StopWatch();
        timer.start(exchange.getRequest().getURI().getRawPath());
//        exchange.getAttributes().put("requestTimeBegain",System.currentTimeMillis());
        return chain.filter(exchange).then(
                Mono.fromRunnable(()->{
                    timer.stop();
                    log.info(timer.prettyPrint());
                })

        );
    }

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

添加TimerFilter到路由

  • 局部filter
.route(r -> r.path("/java/**")
        .and().method(HttpMethod.GET)
        .and().header("name")
        .filters(f -> f.stripPrefix(1)
                .addRequestHeader("java-param", "gateway-config")
                .filter(timerFilter)
        )
        .uri("lb://FEIGN-CLIENT")
)
  • 全局filter 将TimerFilter实现的GatewayFilter换为GlobalFilter