网关
网关的职责:网关 = 路由器(基础职能) + 过滤器(可选职能)
对于路由:如果需要对流量的特征进行分析,那必须是要在七层操作,否则直接在四层直接进行流量转发就可以了
不同的网关采用的网络IO模型不同 性能表现也不同
网关的可用性考虑:
- 网关应该轻量 达到功能性与可用性的平衡 职责过多是很危险的
- 选择成熟的网关产品
- 在网关之间加上负载均衡器或者路由器 以便让网关也能进行扩展
主要功能点
- 流量出入口
- API管理中心
- 流量管理
- 认证授权
流量网关
- 全局性流控
- 日志统计
- 防止 SQL 注入
- 防止 Web 攻击
- 屏蔽工具扫描
- 黑白名单控制
- 协议适配
业务网关
- 请求接入:作为所有 API 接口服务请求的接入点,管理所有的接入请求;
- 业务聚合:作为所有后端业务服务的聚合点,所有的业务服务都可以在这里被调用;
- 中介策略:实现安全、验证、路由、过滤、流控,缓存等策略,进行一些必要的中介处理;
- 统一管理:提供配置管理工具,对所有 API 服务的调用生命周期和相应的中介策略进行统一管理。
网关部署架构

- nginx面向服务器
- 网关面向服务
BFF( Backend for frontend)网关

Spring Cloud Zuul

使用
- 添加依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
- 配置
server.port=8010
spring.application.name=gateway
# 这里的配置表示,访问/bd/** 直接重定向到http://baidu.com/**
zuul.routes.baidu.path=/bd/**
zuul.routes.baidu.url=http://baidu.com/
eureka.client.serviceUrl.defaultZone=http://localhost:8001/eureka/
@EnableZuulProxy
默认路由规则
http://ZUUL_HOST:ZUUL_PORT/微服务在Eureka上的serviceId/**
比如访问http://localhost:8010/producer/
则gateway就会把请求转发到producer服务上面去
简化路由配置
zuul:
  routes:
    user-service: /user-service/** # 这里是映射路径
Zuul的核心
- Fliter
- 场景- 请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
- 异常处理:一般会在error类型和post类型过滤器中结合来处理。
- 服务调用时长统计
 
自定义Filter
- 实现ZuulFilter
@Component
public class MyFilter extends ZuulFilter {
    @Override
    public String filterType() {return FilterConstants.PRE_TYPE;}
    @Override
    public int filterOrder() {return FilterConstants.PRE_DECORATION_FILTER_ORDER;}
    @Override
    public boolean shouldFilter() {return true;}
    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String token = request.getParameter("token");
        if ("my".equals(token)){
            ctx.setSendZuulResponse(true);
            ctx.setResponseStatusCode(200);
            ctx.set("isSuccess",true);
        }else{
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(400);
            ctx.setResponseBody("error token");
            ctx.set("isSuccess",false);
        }
        return null;
    }
}
这样当通过网关访问服务时,不符合条件的请求将会被过滤掉
整合配置中心
- 依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 手动配置zuul配置对象
@RefreshScope
@ConfigurationProperties("zuul")
public ZuulProperties zuulProperties() {
    return new ZuulProperties();
}
进行跨域配置
@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true); // 允许cookies跨域
        config.addAllowedOrigin("*");// #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
        config.addAllowedHeader("*");// #允许访问的头信息,*表示全部
        config.setMaxAge(18000L);// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
        config.addAllowedMethod("*");// 允许提交请求的方法,*表示全部允许
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}
忽略
# 忽略该服务
zuul.ignored-services: upload-servie 
路由熔断
- 自定义fallback
@Component
public class MyFallback implements FallbackProvider {
    @Override
    public String getRoute() {
        return "producer";
    }
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        cause.printStackTrace();
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.valueOf(500);
            }
            @Override
            public int getRawStatusCode() throws IOException {
                return 500;
            }
            @Override
            public String getStatusText() throws IOException {
                return "SERVER ERROR";
            }
            @Override
            public void close() {
            }
            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("service is unavailable".getBytes());
            }
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.TEXT_PLAIN);
                return headers;
            }
        };
    }
}
当producer挂掉时,将会返回相关信息
路由重试
- 添加依赖
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>
- 配置
#是否开启重试功能
zuul.retryable=true
#对当前服务的重试次数
ribbon.MaxAutoRetries=2
#切换相同Server的次数
ribbon.MaxAutoRetriesNextServer=0
当相同的服务挂掉一部分后,如果多次请求不成功,则接下来的请求则会转发到其他服务上
也就是说,自动的寻找到正确响应的服务上去.错误的实例被抛弃
zuul高可用
- 启动两个网关实例8000和7000
- 配置nginx负载均衡
upstream gateway {
        server 127.0.0.1:8000;
        server 127.0.0.1:7000;
}
server {
        listen 6060;
        location / {
                proxy_pass http://gateway;
                proxy_connect_timeout 1s;
                proxy_send_timeout 1s;
                proxy_read_timeout 1s;
        }
}
Spring Cloud Gateway
- 路由
- 断言
- 过滤器

配置
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
定义路由规则
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("path_route", r -> r.path("/hi")
                    .uri("http://localhost:8503"))
            .build();
}
- 通过时间匹配
builder.routes()
       .route("path_route", r -> r.before(ZonedDateTime.now())
               .uri("http://localhost:8503"))
       .build();
- 通过Cookie匹配
builder.routes()
       .route("path_route", r -> r.cookie("key","value")
               .uri("http://localhost:8503"))
       .build();
- 通过header属性匹配
- 通过Host匹配
- 通过请求方式匹配
- 通过请求路径匹配
- 通过请求参数匹配
- 通过请求IP匹配
服务化
- 依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 配置
server:
  port: 8888
spring:
  application:
    name: gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8001/eureka/
logging:
  level:
    org.springframework.cloud.gateway: debug
- 默认转发规则
Filter
- PRE
这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等
- POST
这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等
简单过滤器实例
- 配置
server:
  port: 8888
spring:
  application:
    name: gateway
  cloud:
    gateway:
      routes:
      - id: add_request_parameter_route
        uri: http://localhost:9003
        filters:
        - AddRequestParameter=name, my
        predicates:
          - Method=GET
      discovery:
        locator:
          enabled: true
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8001/eureka/
logging:
  level:
    org.springframework.cloud.gateway: debug
在上面的配置中,添加了一个路由过滤规则:对9003端口的get请求添加一个请求参数name:my
- 服务化配置
改成uri: lb://producer 则路由配置只会对producer服务生效
修改路径的过滤器
- StripPrefix Filter
- id: nameRoot
  uri: lb://producer
  predicates:
    - Path=/name/**
  filters:
    - StripPrefix=2
如果访问/name/my/hello,网关就会将路径修改为/hello
- PrefixPath Filter
- id: prefixpath_route
  uri: lb://producer
  predicates:
  - Path=*
  filters:
  - PrefixPath=/mypath
自动给URL加上mypath前缀
自定义Filter
@Component
public class TokenFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        var token = exchange.getRequest().getQueryParams().get("token");
        if (!CollectionUtils.isEmpty(token)){
            return chain.filter(exchange);
        }
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.FORBIDDEN);
        return response.writeWith(
                Mono.just(
                        response.bufferFactory().wrap("token is null".getBytes())
                )
        );
    }
}
限速路由器
熔断路由器
- 依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- id: hystrix_route
  uri: http://localhost:9001
  predicates:
    - Path=/*
  filters:
    - Hystrix=myCommandName
重试路由器
比较
| 网关 | 限流 | 鉴权 | 监控 | 易用性 | 可维护性 | 成熟度 | 
|---|---|---|---|---|---|---|
| Spring Cloud Gateway | 可以通过IP,用户,集群限流,提供了相应的接口进行扩展 ,gateway不依赖servlet api,所以性能更强 | |||||
| gateway的转发是在tcp层 | 普通鉴权、auth2.0 | Gateway Metrics Filter | 简单易用 | spring系列可扩展强,易配置 可维护性好 | spring社区成熟,但gateway资源较少 | |
| Zuul2 | 可以通过配置文件配置集群限流和单服务器限流亦可通过filter实现限流扩展 | filter中实现 | filter中实现 | 参考资料较少 | 可维护性较差 | 开源不久,资料少 | 
| OpenResty | 需要lua开发 | 需要lua开发 | 需要开发 | 简单易用,但是需要进行的lua开发很多 | 可维护性较差,将来需要维护大量lua脚本 | 很成熟资料很多 | 
| Kong | 根据秒,分,时,天,月,年,根据用户进行限流。可在原码的基础上进行开发 | 普通鉴权,Key Auth鉴权,HMAC,auth2.0 | 可上报datadog,记录请求数量,请求数据量,应答数据量,接收于发送的时间间隔,状态码数量,kong内运行时间 | 简单易用,api转发通过管理员接口配置,开发需要lua脚本 | "可维护性较差,将来需要维护大量lua库 | 相对成熟,用户问题汇总,社区,插件开源 | 
不同技术栈网关选型
- 单体时代: apache nginx haproxy
- 微服务: nginx kong
- spring cloud: gateway zuul
- k8s: ingress nginx traefik
- service mesh: envoy mosn istio