[SpringCloud][15]网关介绍以及实现,Spring Cloud Gateway快速上手

文章目录

网关介绍

问题

前面我们通过 EurekaNacos 解决了服务注册,服务发现的问题,使用 Spring Cloud LoadBalance 解决了负载均衡的问题,使用 OpenFeign 解决了远程调用的问题

但是当前所有微服务的接口都是直接对外暴露的,可以直接通过外部访问。为了保证对外服务的安全性,服务端实现的微服务接口通常都带有一定的权限校验机制。由于使用了微服务,原本一个应用的多个模块拆分成了多个应用,我们不得不实现多次校验逻辑。当这套逻辑需要修改时,我们需要修改多个应用,加重了开发人员的负担

针对以上问题,一个常用的解决方案是使用 API 网关

比如企业管理

  • 外部人员去公司办理业务,公司需要先核实对方的身份再去进行办理
  • 最开始只有一个员工,这个员工核实之后直接办理即可(单体架构)
  • 随着公司的发展,划分了多个部门,每个部门负责的事情不同,每个部门需要先核实对方的身份再进行办理(微服务架构)
    这个流程存在一些问题
  1. 办事效率低
  2. 增加了员工的工作流程
    我们对此进行改进,设立前台,统一由前台来进行身份的校验。前台身份校验通过后,其他部门就设置信任,直接办理
  • 前台就类似 API 网关

什么是 API 网关

API 网关(简称网关)也是一个服务,通常是后端服务的唯一入口。它的定义类似设计模式中的 Facade 模式(门面模式,也称外观模式),它就类似整个微服务架构的门面,所有的外部客户端访问,都需要经过它来进行调度和过渡

网关核心功能

  • 权限控制:作为微服务的入口,对用户进行权限校验,如果校验失败则进行拦截
  • 动态路由:一切请求先经过网关,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务
  • 负载均衡:当路由的目标服务有多个时,还需要做负载均衡
  • 限流:请求流量过高时,按照网关中配置微服务能够接受的流量进行放行,避免服务压力过大

类似前台的工作

  1. 权限控制:身份验证
  2. 动态路由:根据外来客户的需求,把客户带到指定的部门去处理
  3. 负载均衡:一个部门有很多人时,前台会帮客户选择具体某个人处理
  4. 限流:公司到访客户较多时,进行流量限制,比如告知明天再来

常见网关实现

业界常用的网管方式有很多,技术方案也较成熟,其中不乏很多开源产品,比如 NginxKongZuul Spring Cloud Gateway

Zuul

ZuulNetFlix 公司开源的一个 API 网关组件,是 Spring Cloud Netflix 子项目的核心组件之一,它可以和 EurekaRibbonHystrix 等组件配合使用

Spring Cloud Gateway

其是 Spring Cloud 的一个全新的 API 网关项目,基于 Spring + SpringBoot 等技术开发,目的是为了替换掉 Zuul。旨在为微服务架构提供一种简单而有效的途径来转发请求,并为他们提供横切关注点,比如:安全性,监控/指标和弹性

Spring Cloud Gateway

快速上手

我们通过以下的演示,先来了解网关的基本功能

常见网关项目

API 网关也是一个服务

引入网关依赖

xml 复制代码
<!-- 网络关-->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<!-- 基于nacos实现服务发现依赖-->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!-- 负载均衡-->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

添加路由配置

创建 application.yml 文件,添加如下配置:

yml 复制代码
server:  
  port: 8848 # 网关端口  
spring:  
  application:  
    name: gateway # 服务名称  
  cloud:  
    nacos:  
      discovery:  
        server-addr: 127.0.0.1:8848  
  
    gateway:  
      routes: # 网关路由配置  
        - id: product-service # 路由ID,自定义,唯一即可  
          uri: lb://product-service # 目标服务地址  
          predicates: # 路由条件  
            - Path=/product/**  
        - id: order-service  
          uri: lb://order-service  
          predicates:  
            - Path=/order/**

配置字段说明:

  • id:自定义路由 ID,保持唯一
  • url:目标服务机制,支持普通 URIlb://应用注册服务名称lb 表示负载均衡,使用 lb:// 方式表示从注册中心获取服务地址
  • predicates:路由条件,根据匹配结果决定是否执行该请求路由,上述代码中,我们把符合 Path 规则的一切请求,都代理到 uri 参数指定的地址

测试

启动 API 网关服务

  1. 通过网关服务访问 product-service
    http://127.0.0.1:8858/product/1001
  1. 通过网关服务访问 order-service
    http://127.0.0.1:8858/order/1

Route Predicate Factories

Predicate

PredicateJava 8 提供的一个函数式编程接口,它接收一个参数并返回一个布尔值,用于条件过滤,请求参数的校验

java 复制代码
@FunctionalInterface
public interface Predicate<T> {
	boolean test(T t);
	//...
}
代码演示
  1. 定义一个 Predicate
java 复制代码
public class StringPredicate implements Predicate<String> {  
    @Override  
    public boolean test(String s) {  
        return s.isEmpty();  
    }  
}
  1. 使用这个 Predicate
java 复制代码
public class StringPredicateTest {  
    public static void main(String[] args) {  
        Predicate<String> predicate = new StringPredicate();  
        System.out.println(predicate.test(""));  
        System.out.println(predicate.test("hahahaha"));  
    }  
}
  1. 运行结果
Predicate 的其他写法
  1. 内置函数
java 复制代码
public class StringPredicateTest {  
    public static void main(String[] args) {  
        Predicate<String> predicate = new Predicate<String>(){  
  
            @Override  
            public boolean test(String s) {  
                return s.isEmpty();  
            }  
        };  
        System.out.println(predicate.test(""));  
        System.out.println(predicate.test("hahahaha"));  
    }  
}
  1. lambda 写法
java 复制代码
class StringPredicateTest {  
    public static void main(String[] args) {  
       Predicate<String> predicate = s -> s.isEmpty();  
        System.out.println(predicate.test(""));  
        System.out.println(predicate.test("hahahaha"));  
    }  
}
  • Predicate<String> predicate = s -> s.isEmpty(); 也可以写成 Predicate<String> isEmpty = String::isEmpty;
  1. isEqual (ObjecttargetRef) :⽐较两个对象是否相等, 参数可以为 Null
  2. and (Predicateother):短路与操作, 返回⼀个组成 Predicate
  3. or (Predicateother) : 短路或操作, 返回⼀个组成 Predicate
  4. test (Tt) : 传⼊⼀个 Predicate 参数,⽤来做判断
  5. negate() :返回表⽰此 Predicate 逻辑否定的 Predicate