API网关:系统的门面要如何做呢?

目录

前言

一、网关概念

二、API 网关起到的作用

三、API 网关要如何实现

总结


前言

API网关作用就是把各个服务对外提供的API汇聚起来,让外界看起来是一个统一的接口,同时也可以在网关中提供额外的功能。

总结:网关就是所有项目的一个统一入口,网关类似设计模式中的门面模式功能。

整体架构如图

API网关:系统的门面要如何做呢?_第1张图片


一、网关概念

在计算机网络中,网关(Gateway)是转发其他服务器通信数据的服务器,接收从客户端发送来的请求时,它就像自己拥有资源的源服务器一样对请求进行处理。


API网关也是随着对传统庞大的单体应用(All in one)拆分为众多的微服务(Microservice)以后,所引入的统一通信管理系统。用于运行在外部http请求与内部rpc服务之间的一个流量入口,实现对外部请求的协议转换、参数校验、鉴权、切量、熔断、限流、监控、风控等各类共性的通用服务。

各大厂做网关,其实做的就是一套统一方案。将分布式微服务下的RPC到HTTP通信的同类共性的需求,凝练成通用的组件服务,减少在业务需求场景开发下,非业务需求的同类技术诉求的开发成本。


那么以往没有网关的时候怎么做,基本的做法就是再 RPC 服务之上再开发一个对应的 WEB 服务,这些 WEB 服务可以是 Spring MVC 工程,在 Spring MVC 工程中调用 RPC 服务,最终提供 HTTP 接口给到 H5、Web、小程序、APP 等应用中进行使用。如图 1-1 所示

API网关:系统的门面要如何做呢?_第2张图片

增加网关后我们的系统调用流程图:

API网关:系统的门面要如何做呢?_第3张图片

二、API 网关起到的作用

网关概念API 网关(API Gateway)不是一个开源组件,而是一种架构模式,它是将一些服务共有的功能整合在一起,独立部署为单独的一层,用来解决一些服务治理的问题。你可以把它看作系统的边界,它可以对出入系统的流量做统一的管控。在我看来,API 网关可以分为两类:一类叫做入口网关,一类叫做出口网关。入口网关是我们经常使用的网关种类,它部署在负载均衡服务器和应用服务器之间,主要有几方面的作用。

1. 它提供客户端一个统一的接入地址,API 网关可以将用户的请求动态路由到不同的业务服务上,并且做一些必要的协议转换工作。在你的系统中,你部署的微服务对外暴露的协议可能不同:有些提供的是 HTTP 服务;有些已经完成 RPC 改造,对外暴露 RPC 服务;有些遗留系统可能还暴露的是 Web Service 服务。API 网关可以对客户端屏蔽这些服务的部署地址,以及协议的细节,给客户端的调用带来很大的便捷。

2. 另一方面,在 API 网关中,我们可以植入一些服务治理的策略,比如服务的熔断、降级,流量控制和分流等等。

3. 再有,客户端的认证和授权的实现,也可以放在 API 网关中。你要知道,不同类型的客户端使用的认证方式是不同的。在我之前项目中,手机 APP 使用 Oauth 协议认证,HTML5 端和 Web 端使用 Cookie 认证,内部服务使用自研的 Token 认证方式。这些认证方式在 API 网关上,可以得到统一处理,应用服务不需要了解认证的细节。

4. 另外,API 网关还可以做一些与黑白名单相关的事情,比如针对设备 ID、用户 IP、用户ID 等维度的黑白名单。

5. 最后,在 API 网关中也可以做一些日志记录的事情,比如记录 HTTP 请求的访问日志,比如:分布式追踪系统,提到的标记一次请求的 requestId,也可以在网关中来生成。

API网关:系统的门面要如何做呢?_第4张图片

三、API 网关要如何实现

线程池优化

为了提升网关对于请求的并行处理能力,我们一般会使用线程池来并行的执行请求。不过,这就带来一个问题:如果商品服务出现问题,造成响应缓慢,那么调用商品服务的线程就会被阻塞无法释放,久而久之,线程池中的线程就会被商品服务所占据,那么其他服务也会受到级联的影响。因此,我们需要针对不同的服务做线程隔离,或者保护。在我看来有两种思路:

如果你后端的服务拆分得不多,可以针对不同的服务,采用不同的线程池,这样商品服务的故障就不会影响到订单服务和用户服务了;

在线程池内部可以针对不同的服务,甚至不同的接口做线程的保护。比如说,线程池的最大线程数是 1000,那么可以给每个服务设置一个最多可以使用的配额。一般来说,服务的执行时间应该在毫秒级别,线程被使用后会很快被释放,回到线程池给后续请求使用,同时处于执行中的线程数量不会很多,对服务或者接口设置线程的配额,不会影响到正常的执行。可是一旦发生故障,某个接口或者服务的响应时间变长,造成线程数暴涨,但是因为有配额的限制,也就不会影响到其他的接口或者服务了。

你在实际应用中也可以将这两种方式结合,比如说针对不同的服务使用不同的线程池,在线程池内部针对不同的接口设置配额。

如何在你的系统中引入 API 网关呢?

目前为止,我们的电商系统已经经过了服务化改造,在服务层和客户端之间有一层薄薄的Web 层,这个 Web 层做的事情主要有两方面:

一方面是对服务层接口数据的聚合。比如,商品详情页的接口,可能会聚合服务层中,获取商品信息、用户信息、店铺信息以及用户评论等多个服务接口的数据;

另一方面 Web 层还需要将 HTTP 请求转换为 RPC 请求,并且对前端的流量做一些限制,对于某些请求添加设备 ID 的黑名单等等。因此,我们在做改造的时候,可以先将 API 网关从 Web 层中独立出来,将协议转换、限流、黑白名单等事情,挪到 API 网关中来处理,形成独立的入口网关层;而针对服务接口数据聚合的操作,一般有两种解决思路:

1. 再独立出一组网关专门做服务聚合、超时控制方面的事情,我们一般把前一种网关叫做流量网关,后一种可以叫做业务网关;
2. 抽取独立的服务层,专门做接口聚合的操作。这样服务层就大概分为原子服务层和聚合服务层。

我认为,接口数据聚合是业务操作,与其放在通用的网关层来实现,不如放在更贴近业务的服务层来实现,所以,我更倾向于第二种方案。

API网关:系统的门面要如何做呢?_第5张图片

 Spring Cloud Gateway

Spring Cloud Gateway是Spring Cloud团队在Spring反应生态系统(Webflux)之上的API网关实现。它提供了一种简单有效的方法,即使用网关处理程序映射将传入的请求路由到适当的目的地。Spring Cloud Gateway使用Netty服务器提供非阻塞异步请求处理。

API网关:系统的门面要如何做呢?_第6张图片

网关请求流图:

API网关:系统的门面要如何做呢?_第7张图片

SpringCloud Gateway具有如下特性

  • 基于Spring Framework 5,Project Reactor和Spring Boot 2.0进行构建;
  • 动态路由:能够匹配任何请求属性;
  • 可以对路由指定Predicate (断言)和Filter(过滤器);
  • 集成Hystrix的断路器功能;
  • 集成Spring Cloud 服务发现功能;
  • 易于编写的Predicate (断言)和Filter (过滤器);
  • 请求限流功能;
  • 支持路径重写。

什么是断言?

通俗的说,断言就是一些布尔表达式 ,满足条件的返回 true ,不满足的返回 false 。
        Spring Cloud Gateway 将路由作为 Spring WebFlux HandlerMapping 基础架构的一部分 进行匹配。Spring Cloud Gateway 包括许多内置的路由断言工厂。所有这些断言都与 HTTP 请求的不同属性匹配。您可以将 多个路由断言可以组合 使用。
网关路由可以配置的内容包括:
        路由id:路由唯一标示
        uri:路由目的地,支持lb和http两种
        predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
        filters: 路由过滤器 ,处理请求或响应

server:
  port: 9999
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:80
    gateway:
      discovery:
        locator:
          # 启用DiscoveryClient网关集成,实现服务发现功能
          enabled: true
      # 路由集合
      routes:
        # 路由id
        - id: user-service
          # 资源地址 lb表示开启负载均衡
          uri: lb://user-service
          predicates:
            - Path=/user/**
            - Method=GET,POST

 表示如果访问/user 的地址就会转发到/user-service上;Method代表该请求必须是GET或者Post请求才可以。两者都满足时才能进行访问。 

什么是过滤器?

路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。Spring Cloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。

Spring Cloud Gateway的Filter:

  • 生命周期:
    • pre
    • post
  • 种类(具体看官方文档):
    • GatewayFilter - 有31种
    • GlobalFilter - 有10种

如果我们要自定义一个全局GlobalFilter,需要实现GlobalFilter, Ordered接口。

请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器:

API网关:系统的门面要如何做呢?_第8张图片

排序规则如下:

每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定。路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。

启动类代码

@ComponentScan("com.test")
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class GatewayClientApplication {

    @Bean
    public RouteLocator customerRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();

        routes.route("user-service",
                r -> r.path("/user/**")
                        .uri("lb:user-service")).build();
        return routes.build();
    }

    public static void main(String[] args) {
        SpringApplication.run(GatewayClientApplication.class, args);
    }
}

使用GatewayFilter进行ip拦截

前端发起请求---->过滤器拦截获取访问ip(request.getHeaders().gethost().getHostString())--->去数据库中查询黑名单的ip集合--->list.contions(ip)---->判断访问的ip是否为黑名单里的--->放行/拦截

       如果并发量大的情况,所有的请求都去数据库中查询黑名单,这样的话数据库的压力较大。可以使用redis缓存黑名单ip来减轻数据库的压力。

@Slf4j
@Component
public class IPCheckFilter implements GlobalFilter, Ordered {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String ip = request.getHeaders().getHost().getHostString();


        if (!stringRedisTemplate.opsForSet().isMember("backList", ip)) {
            return chain.filter(exchange);
        }

        return out(exchange.getResponse());
    }

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

    private Mono out(ServerHttpResponse response) {
        HashMap map = new HashMap<>(4);
        map.put("code", 438);
        map.put("msg", "你是黑名单用户");
        byte[] bits = JSONObject.toJSONString(map).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        //指定编码,否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }
}

API网关:系统的门面要如何做呢?_第9张图片

使用Gateway做token校验 

使用GateWay做统一用户认证,使用的是jwt生成的token。用户发起登录请求到网关,请求头中携带token,网关的全局过滤器进行拦截,校验token。

@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered {
    private static final Set ALLOW_URL = new HashSet<>();

    static {
        ALLOW_URL.add("/user/login");
    }

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();

        if (ALLOW_URL.contains(path)) {
            return chain.filter(exchange);
        }

        HttpHeaders httpHeaders = request.getHeaders();
        List authorizations = httpHeaders.get("authorization");
        if (authorizations != null && authorizations.size() > 0) {
            String authorization = authorizations.get(0);

            
            if (authorization != null && stringRedisTemplate.hasKey(authorization)) {
                return chain.filter(exchange);
            }
        }

        return out(exchange.getResponse());
    }

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

    private Mono out(ServerHttpResponse response) {
        HashMap map = new HashMap<>(4);
        map.put("code", HttpStatus.UNAUTHORIZED);
        map.put("msg", "未授权");
        byte[] bits = JSONObject.toJSONString(map).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        //指定编码,否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }
}

API网关:系统的门面要如何做呢?_第10张图片

API网关 - 知乎

你可能感兴趣的:(分布式,tomcat,java,分布式)