当前位置 : 主页 > 编程语言 > java >

微服务网关

来源:互联网 收集:自由互联 发布时间:2023-09-03
1 微服务网关 1.1 为什么需要网关 如果想在微服务架构中实现 权限校验功能,怎么实现? 1. 每个服务自己实现一遍 2. 写到一个公共的服务中,然后其他所有服务都依赖这个服务 3. 写到

1 微服务网关

1.1 为什么需要网关

如果想在微服务架构中实现 权限校验功能,怎么实现?

1. 每个服务自己实现一遍

2. 写到一个公共的服务中,然后其他所有服务都依赖这个服务

3. 写到服务网关的前置过滤器中,所有请求过来进行权限校验

第一种,缺点太明显,基本不用;

第二种,相较于第一点好很多,代码开发不会冗余,但是有两个缺点:

  • 由于每个服务引入了这个公共服务,那么相当于在每个服务中都引入了相同的权限校验的代码,使得每个服务的jar包大小无故增加了一些,尤其是对于使用docker镜像进行部署的场景,jar越小越好;
  • 由于每个服务都引入了这个公共服务,那么我们后续升级这个服务可能就比较困难,而且公共服务的功能越多,升级就越难,而且假设我们改变了公共服务中的权限校验的方式,想让所有的服务都去使用新的权限校验方式,我们就需要将之前所有的服务都重新引包,编译部署。
    第三种,服务网关恰好可以解决这样的问题:
  • 将权限校验的逻辑写在网关的过滤器中,后端服务不需要关注权限校验的代码,所以服务的jar包中也不会引入权限校验的逻辑,不会增加jar包大小;
  • 如果想修改权限校验的逻辑,只需要修改网关中的权限校验过滤器即可,而不需要升级所有已存在的微服务。

所以,需要服务网关!!!

1.2 什么是网关

微服务中的网关相当于我们整个微服务的统一入口。

举例:你去了医院看病,那一定会先去分诊台咨询。分诊台护士根据你的需求,如:挂号、取药、复诊、体检等不同需求,为你指路,告诉你应该去哪个地方,怎么走。这个分诊台就相当于医院的网关入口,将流量接入进来,转发到它应该去的地方。

微服务中的网关是介于客户端(外部调用方比如app,h5)和微服务的中间层,是微服务系统的“入口”。

1.3 网关功能(面试)

路由:将外部请求,转发到具体的微服务实例上,是外部访问微服务系统的统一入口。

过滤:过滤功能,负责对请求的处理过程进行干预,实现功能的增强,如:统一鉴权、请求校验、日志监控、流量控制、熔断等功能。

网关本身也是一个微服务,网关可以和注册中心进行整合,将网关服务自身注册为服务,同时从注册中心中获得其他微服务的消息,以后服务路由,都可以通过注册中心获取服务实例信息,实现路由转发功能。

1.4 常见网关(面试)

Zuul:Netflix 公司产品,公司内部产生分歧,有的人想自己出一个Zuul2,不推荐。

*Zuul2:也是Netflix 公司准备出的产品,但是由于内部分歧,所以Zuul2已经胎死腹中了,不推荐。*

gateway:Spring社区自己出的网关组件,官方隆重介绍和极度推荐的网关服务组件,推荐。

官网:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-starter

1.5 Gateway 入门

1.5.1 相关概念

Route(路由):网关的基本构建块。它由服务ID、目标URI,谓词集合和过滤器集合定义。如果聚合条件为true,则匹配路由。

Predicate(谓词、断言(条件判断)):用于匹配HTTP请求中的所有内容,例如标头或参数,理解为匹配HTTP请求的条件,请求符合断言的条件,就向后路由。

Filter(过滤器):这些是GatewayFilter由特定工厂构造的实例。在这里,您可以在发送下游请求之前或之后修改请求和响应,实现过滤器功能。

1.5.2 工作原理(重要)

1.5.3 Gateway入门案例(路由)

需求:前台访问网关,网关路由到某个服务

  1. 创建SpringBoot工程gateway工程,引入网关依赖
<!--网关-->
<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>
  1. 编写启动类
@SpringBootApplication
public class GatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}
  1. 编写基础配置和路由规则
server:
  port: 5001 # 网关端口
spring:
  application:
    name: gateway # 服务名称
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos地址
    gateway:
      routes: # 网关路由配置
        - id: user-service # 路由id,自定义,只要唯一即可
          uri: http://localhost:8001 # 路由的目标地址 http就是固定地址   目标服务地址
          # uri: lb://consumer # 路由的目标地址 lb就是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/consumer* # 这个是按照路径匹配,只要以/consumer开头就符合要求
  1. 启动网关服务进行测试

1.5.4 Gateway配置详解

官方解释:

查看源代码,解析gateway与配置类的映射关系:

1.5.5 基于服务名称路由案例

一个服务往往会发布多个实例,如果路由的URI写死,固定向一台服务器路由也不合适。

Gateway默认支持了基于Ribbon的负载均衡,默认规则是轮询,可通过服务名直接路由:

以前的:

现在的:

1.5.6 断言匹配(高级应用-了解)

Predicate(断言)定义了URI与服务的匹配条件,符合Predicate的请求,才能转到后面的服务中。

断言有很多种:

https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories

Predicate(断言) 用于进行条件判断,只有断言都返回真,才会真正的执行路由。

SpringCloud Gateway包括许多内置的断言工厂,所有这些断言都与HTTP请求的不同属性匹配。具体如下:

基于Datetime

此类型的断言根据时间做判断,主要有三个:

AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期

BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期

BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内

  • After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]

基于Cookie

CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。 判断请求cookie是否具有给定名称且值与正则表达式匹配。

Cookie=chocolate, ch.

基于Header

HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。 判断请求Header是否具有给定名称且值与正则表达式匹配。

Header=X-Request-Id, \d+

基于Path请求路径

PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。

Path=/foo/**

基于Query请求参数

QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配。

localhost:8080/aaa?id=1&name=zs

Query=id, \d+

基于路由权重

WeightRoutePredicateFactory:接收一个[组名,权重],然后对于同一个组内的路由按照权重转发

routes:
- id: weight_route1
  uri: host1
  predicates:
  - Path=/product/**
  - Weight=group3, 2
- id: weight_route2
  uri: host2
  predicates:
  - Path=/product/**
- Weight= group3, 8

断言匹配规则

在predicates配置下,以数组的形式配置多个断言选项。

格式为:每一个断言类型后跟一个等号(=),然后是由逗号分隔的参数值(,)。

1.6 Gateway过滤器

1.6.1 过滤器概述

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:

gateWay 提供了路由过滤器功能,可以实现在请求到达前、请求响应前做出过滤操作。

SpringCloud gateway官方提供了许多内置的过滤器,通过简单的yml配置即可实现,当然也可以自定义过滤器,在自定义过滤器中,实现如:日志记录、统一鉴权、限流、熔断 等操作。

Spring提供了31种不同的路由过滤器工厂:

https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories

常用的也就那么几个,例如:

名称

说明

AddRequestHeader

给当前请求添加一个请求头

RemoveRequestHeader

移除请求中的一个请求头

AddResponseHeader

给响应结果中添加一个响应头

RemoveResponseHeader

从响应结果中移除有一个响应头

RequestRateLimiter

限制请求的流量

网关过滤器的继承体系:

1.6.2 过滤器配置入门案例(了解)

客户端请求到达网关,网关在请求中使用【AddRequestHeader】过滤器给当前请求添加请求头,然后再转发到后面的微服务中,网关处配置文件如下:

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: https://example.org
        filters:
        - AddRequestHeader=X-Request-red, blue

在微服务中,就可以从请求头中获取到【X-Request-red】请求头的值为【blue】

过滤器可以根据它的作用范围,分为全局过滤器和局部过滤器,上面的案例是局部过滤器,仅针对它所在路由规则生效。

如下配置,是全局过滤器配置,针对所有路由规则生效:

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: https://example.org
      default-filters:
        - AddRequestHeader=X-Request-red, blue

1.6.3 其他过滤器配置使用(了解)

StripPrefix去掉前缀

有的时候,希望在路由时,在URL中添加一层路径,用于区别不同的服务,或者其他用途。

然后在网关向后路由时,使用过滤器 StripPrefix属性,将这添加的这一层路径自动去掉,如下图:

去掉几层前缀,StripPrefix就写几。

PrefixPath添加前缀

添加前缀与去掉前缀刚好相反,去掉前缀是请求在网关处,向后路由时,去掉一层前缀,而添加前缀是向后路由时,自动添加指定的前缀,如:

1.6.4 过滤器执行顺序(了解)

请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter

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

排序的规则是什么呢?

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

1.6.5 过滤器实现-登录校验

应用自定义全局过滤器:

在全局过滤器中,可以实现统一认证、授权、日志记录、熔断、限流等需要在网关处统一进行的各种行为。

需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:

  • 请求参数是否包含token,
  • token请求参数的值是否为jiyun

如果同时满足则放行,否则拦截,返回无访问权限信息。

1.创建过滤器

在服务网关中定义过滤器只需要实现 GlobalFilter, Ordered接口就可对请求进行拦截与过滤。

@Component
public class LoginFilter implements GlobalFilter, Ordered {

    /**
     * 过滤器的业务逻辑
     * @param exchange:请求和响应的上下文,存储了Request和Response对象
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if (!"jiyun".equals(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();//结束请求
        }
        return chain.filter(exchange);//放行
    }

    /**
     * 过滤器的执行顺序:通过整数表示顺序,数值越小,优先级越高
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

2.测试

不加token测试

加token测试

1.7 网关处统一处理跨域(面试)

跨域资源共享(Cross-Origin Resource Sharing,CORS)是指在 Web 应用中,浏览器发现访问的目标资源违背了同源策略,所作出的限制行为。

同源策略,即:访问的域名、端口、协议有任意一项与当前页面的域名、端口、协议不一致,就会引起跨域问题,这是发生在浏览器端,浏览器会进行控制。

举例来说,如果一个页面在域名为 example.com 的服务器上运行,但是需要获取域名为 api.example.org 的服务器上的数据,就会遇到跨域问题。因为不同的域名被认为是不同的安全域,浏览器会禁止页面访问不同源的资源。

浏览器会在发生同源策略的时候,先向目标资源发起一次预检请求,如果后端应答了本次请求,则浏览器会发起第二次正确请求。所以处理跨域问题,需要后端的支持。

为了解决跨域问题,需要在服务器端进行一些配置,允许不同源之间的通信。通常的解决方案是通过设置 HTTP 头部信息中的 Access-Control-Allow-Origin 字段,来允许指定的源可以访问服务器上的资源。

在微服务项目中,我们在网关微服务处,直接针对跨域问题进行统一处理即可,通过编写全局过滤器实现。

spring:
  cloud:
    gateway:
      # 。。。
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求 
              - "http://localhost:8090"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期

案例演示:

1、编写一个前端项目,前端项目向微服务网关发起ajax请求,此时端口号不一样,一定会被跨域拒绝;

2、在微服务网关处,编写跨域处理配置,再次测试,发现跨域问题被处理掉了。

1.8 灰度发布(了解)

当服务功能更新迭代后,该如何发布?

A、立刻使用新版本替换所有旧版本服务,万一新版本有bug,意味着新版本集体下线,导致整体服务暂时不可用。

B、逐步替换,同时部署新版和旧版服务,请求到达后,释放少量请求到新版服务中,经过一定时间测试,服务稳定后,再逐步替换其他旧版服务,如果中途出现不稳定情况,可以随时下线新版服务及时维护,线上由旧版服务继续服务。这里就需要用到灰度发布实现了。

灰度发布是当服务版本更新时,通过开放适量请求到新版本服务,当经过测试,新版本服务功能稳定后,逐步更新服务版本的版本发布方式。

如何实现灰度发布:

针对相同的访问路径,配置不同的路由URI,路由到不同的服务地址:

利用断言提供的权重配置,实现随机路由效果,达到灰度发布。

routes:
  - 
    id: weight_route1
    uri: lb://bg-consumer
    predicates:
      - Path=/consumer*
      - Weight=group1, 70
  - 
    id: weight_route2
    uri: lb://bg-consumer-v2
    predicates:
      - Path=/consumer*
      - Weight= group1, 30

要实现动态修改网关微服务,而不重启网关微服务,可以将配置文件放到nacos配置中心,动态修改配置中心的配置即可。


上一篇:Dubbo高手之路5,Dubbo服务接口详解
下一篇:没有了
网友评论