11-微服务技术栈(基础):Gateway服务网关

本文涉及的产品
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
云原生网关 MSE Higress,422元/月
简介: 微服务中另一重要组件:网关 进行了实战性演练,网关作为分布式架构中的重要中间件,不仅承担着路由分发(重点关注Path规则配置),同时可根据自身负载均衡策略,对多个注册服务实例进行均衡调用。本节我们借助GateWay实现的网关只是技术实现的方案之一,后续大家可能会接触像:Zuul、Kong等,其实现细节或有差异,但整体目标是一致的。

1.为什么需要网关

Gateway网关是我们服务的守门神,所有微服务的统一入口。网关的核心功能特性

  • 请求路由
  • 权限控制
  • 限流

其架构图如下:

权限控制:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。

路由和负载均衡:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。

限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。


什么是API网关,可参考:https://www.yuque.com/xiankanpengyouquandisitiaodongtai/yeq5ax/blo41c

在SpringCloud中网关的实现包括两种:

Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。(响应式变成WebFlux了解请移步至:什么是WebFlux)

2.gateway快速上手

1 创建gateway服务,引入依赖

引入依赖

 org.springframework.cloud

 spring-cloud-starter-gateway

 com.alibaba.cloud

 spring-cloud-starter-alibaba-nacos-discovery

2 编写启动类

package cn.itcast.gateway;


import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication

public class GatewayApplication {


   public static void main(String[] args) {

       SpringApplication.run(GatewayApplication.class, args);

   }

}

3 编写基础配置和路由规则

创建application.yml文件,内容如下(示例配置多个路由规则):

server:

 port: 10010 # 网关端口

spring:

 application:

   name: gateway # 服务名称

 cloud:

   nacos:

     server-addr: localhost:8848 # nacos地址

   gateway:

     routes: # 网关路由配置

       - id: user-service # 路由id,自定义,只要唯一即可

         # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址

         uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称

         predicates: # 路由断言,也就是判断请求是否符合路由规则的条件

           - Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求

       - id: order-service # 路由id,自定义,只要唯一即可

         uri: lb://orderservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称

         predicates: # 路由断言,也就是判断请求是否符合路由规则的条件

           - Path=/order/** # 这个是按照路径匹配,只要以/user/开头就符合要求

我们将符合Path 规则的一切请求,都代理到 uri参数指定的地址。本例中,我们将 /user/**开头的请求,代理到lb://userservice,lb是负载均衡,根据服务名拉取服务列表,实现负载均衡。

4 重启测试

重启网关,访问http://localhost:10010/user/1时,符合/user/**规则,请求转发到uri:http://userservice/user/1,效果如下,说明网关路由生效:

5 网关路由流程图

当用户请求过来之后首先请求网关,网关根据自己的路由规则判断决定路由到哪个服务(依赖Nacos注册中心的服务列表信息),同时借助自身负载均衡能力分配到指定服务多个实例中的某一个实例,完成一次请求&响应。


路由配置包括:

  1. 路由id:路由的唯一标示
  2. 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
  3. 路由断言(predicates):判断路由的规则,
  4. 路由过滤器(filters):对请求或响应做处理

接下来,就重点来学习路由断言和路由过滤器的详细知识。

3.断言工厂

我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件,例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的,像这样的断言工厂在SpringCloudGateway还有十几个:

名称

说明

示例

After

是某个时间点后的请求

- After=2037-01-20T17:42:47.789-07:00[America/Denver]

Before

是某个时间点之前的请求

- Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]

Between

是某两个时间点之前的请求

- Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]

Cookie

请求必须包含某些cookie

- Cookie=chocolate, ch.p

Header

请求必须包含某些header

- Header=X-Request-Id, \d+

Host

请求必须是访问某个host(域名)

- Host=.somehost.org,.anotherhost.org

Method

请求方式必须是指定方式

- Method=GET,POST

Path

请求路径必须符合指定规则

- Path=/red/{segment},/blue/**

Query

请求参数必须包含指定参数

- Query=name, Jack或者- Query=name

RemoteAddr

请求者的ip必须是指定范围

- RemoteAddr=192.168.1.1/24

Weight

权重处理

虽然断言规则有很多,但基本使用的都是Path这种路由工程,大家也重点关注此即可。

4.过滤器工厂

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


1 过滤器种类

Spring提供了31种不同的路由过滤器工厂,具体如下:

名称

说明

AddRequestHeader

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

RemoveRequestHeader

移除请求中的一个请求头

AddResponseHeader

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

RemoveResponseHeader

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

RequestRateLimiter

限制请求的流量

2 请求头过滤器

下面我们以AddRequestHeader 为例来讲解。

需求:给所有进入userservice的请求添加一个请求头:Truth=itcast is freaking awesome!

只需要修改gateway服务的application.yml文件,添加路由过滤即可:

spring:

 cloud:

   gateway:

     routes:

     - id: user-service

       uri: lb://userservice

       predicates:

       - Path=/user/**

       filters: # 过滤器

       - AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头

当前过滤器写在userservice路由下,因此仅仅对访问userservice的请求有效。

3 默认过滤器

如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下:

spring:

 cloud:

   gateway:

     routes:

     - id: user-service

       uri: lb://userservice

       predicates:

       - Path=/user/**

     default-filters: # 默认过滤项

     - AddRequestHeader=Truth, Itcast is freaking awesome!

4 总结

过滤器的作用:

① 对路由的请求或响应做加工处理,比如添加请求头

② 配置在路由下的过滤器只对当前路由的请求生效

defaultFilters的作用:

① 对所有路由都生效的过滤器

5.全局过滤器

上一节学习的过滤器,网关提供了31种,但每一种过滤器的作用都是固定的。如果我们希望拦截请求,做自己的业务逻辑则没办法实现。因此需要一种全局过滤器帮我们实现此功能。

1 全局过滤器作用

处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义,处理逻辑是固定的;而GlobalFilter的逻辑需要自己写代码实现。定义方式是实现GlobalFilter接口。

public interface GlobalFilter {

/**

*  处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理

*

* @param exchange 请求上下文,里面可以获取Request、Response等信息

* @param chain 用来把请求委托给下一个过滤器

* @return {@code Mono} 返回标示当前过滤器业务结束

*/

   Mono filter(ServerWebExchange exchange, GatewayFilterChain chain);

}

在filter中编写自定义逻辑,可以实现下列功能:

  • 登录状态判断
  • 权限校验
  • 请求限流等

2 自定义全局过滤器

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

  • 参数中是否有authorization,
  • authorization参数值是否为admin

如果同时满足则放行,否则拦截。其实现只要在gateway中定义一个过滤器:

package cn.itcast.gateway.filters;


import org.springframework.cloud.gateway.filter.GatewayFilterChain;

import org.springframework.cloud.gateway.filter.GlobalFilter;

import org.springframework.core.annotation.Order;

import org.springframework.http.HttpStatus;

import org.springframework.stereotype.Component;

import org.springframework.web.server.ServerWebExchange;

import reactor.core.publisher.Mono;


@Order(-1)

@Component

public class AuthorizeFilter implements GlobalFilter {

   @Override

   public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {

       // 1.获取请求参数

       MultiValueMap params = exchange.getRequest().getQueryParams();

       // 2.获取authorization参数

       String auth = params.getFirst("authorization");

       // 3.校验

       if ("admin".equals(auth)) {

           // 放行

           return chain.filter(exchange);

       }

       // 4.拦截

       // 4.1.禁止访问,设置状态码

       exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);

       // 4.2.结束处理

       return exchange.getResponse().setComplete();

   }

}

此时我们访问必须追加参数才可以成功:http://localhost:10010/order/101?authorization=admin

3 过滤器执行顺序

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

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

排序的规则具体如下

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

详细内容,可以查看源码:

org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()方法是先加载defaultFilters,然后再加载某个route的filters,然后合并。

org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链

6.跨域问题

1 什么是跨域

跨域:域名不一致就是跨域,主要包括:

  • 域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
  • 域名相同,端口不同:localhost:8080和localhost8081

跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题

解决方案:CORS,这个以前应该学习过,这里不再赘述了。不知道的小伙伴可以查看:链接

2 模拟跨域问题

将文件:📎index.html

放入tomcat或者nginx:📎nginx-1.18.0.zip

这样的web服务器中,启动并访问。可以在浏览器控制台看到下面的错误:

从localhost:8090访问localhost:10010,端口不同,显然是跨域的请求。

3 解决跨域问题

在gateway服务的application.yml文件中,添加下面的配置:

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 # 这次跨域检测的有效期

7.总结

本节针对微服务中另一重要组件:网关 进行了实战性演练,网关作为分布式架构中的重要中间件,不仅承担着路由分发(重点关注Path规则配置),同时可根据自身负载均衡策略,对多个注册服务实例进行均衡调用。本节我们借助GateWay实现的网关只是技术实现的方案之一,后续大家可能会接触像:Zuul、Kong等,其实现细节或有差异,但整体目标是一致的。

截至目前我们的工程如下:📎cloud-demo.zip

8.推荐阅读资料

相关文章
|
27天前
|
Cloud Native Java API
聊聊从单体到微服务架构服务演化过程
本文介绍了从单体应用到微服务再到云原生架构的演进过程。单体应用虽易于搭建和部署,但难以局部更新;面向服务架构(SOA)通过模块化和服务总线提升了组件复用性和分布式部署能力;微服务则进一步实现了服务的独立开发与部署,提高了灵活性;云原生架构则利用容器化、微服务和自动化工具,实现了应用在动态环境中的弹性扩展与高效管理。这一演进体现了软件架构向着更灵活、更高效的方向发展。
|
5天前
|
Kubernetes 负载均衡 Docker
构建高效后端服务:微服务架构的探索与实践
【10月更文挑战第20天】 在数字化时代,后端服务的构建对于任何在线业务的成功至关重要。本文将深入探讨微服务架构的概念、优势以及如何在实际项目中有效实施。我们将从微服务的基本理念出发,逐步解析其在提高系统可维护性、扩展性和敏捷性方面的作用。通过实际案例分析,揭示微服务架构在不同场景下的应用策略和最佳实践。无论你是后端开发新手还是经验丰富的工程师,本文都将为你提供宝贵的见解和实用的指导。
|
4天前
|
监控 API 持续交付
构建高效后端服务:微服务架构的深度探索
【10月更文挑战第20天】 在数字化时代,后端服务的构建对于支撑复杂的业务逻辑和海量数据处理至关重要。本文深入探讨了微服务架构的核心理念、实施策略以及面临的挑战,旨在为开发者提供一套构建高效、可扩展后端服务的方法论。通过案例分析,揭示微服务如何帮助企业应对快速变化的业务需求,同时保持系统的稳定性和灵活性。
27 9
|
6天前
|
监控 安全 Java
构建高效后端服务:微服务架构深度解析与最佳实践###
【10月更文挑战第19天】 在数字化转型加速的今天,企业对后端服务的响应速度、可扩展性和灵活性提出了更高要求。本文探讨了微服务架构作为解决方案,通过分析传统单体架构面临的挑战,深入剖析微服务的核心优势、关键组件及设计原则。我们将从实际案例入手,揭示成功实施微服务的策略与常见陷阱,为开发者和企业提供可操作的指导建议。本文目的是帮助读者理解如何利用微服务架构提升后端服务的整体效能,实现业务快速迭代与创新。 ###
29 2
|
11天前
|
消息中间件 Kafka 数据库
微服务架构中,如何确保服务之间的数据一致性?
微服务架构中,如何确保服务之间的数据一致性?
|
8天前
|
运维 Kubernetes 开发者
构建高效后端服务:微服务架构与容器化技术的结合
【10月更文挑战第18天】 在数字化转型的浪潮中,企业对后端服务的要求日益提高,追求更高的效率、更强的可伸缩性和更易于维护的系统。本文将探讨微服务架构与容器化技术如何结合,以构建一个既灵活又高效的后端服务体系。通过分析当前后端服务面临的挑战,介绍微服务和容器化的基本概念,以及它们如何相互配合来优化后端服务的性能和管理。本文旨在为开发者提供一种实现后端服务现代化的方法,从而帮助企业在竞争激烈的市场中脱颖而出。
11 0
|
2月前
|
负载均衡 Java 网络架构
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
66 5
|
2月前
|
消息中间件 Kafka 数据库
微服务架构中,如何确保服务之间的数据一致性
微服务架构中,如何确保服务之间的数据一致性
|
2月前
|
Java API 对象存储
微服务魔法启动!Spring Cloud与Netflix OSS联手,零基础也能创造服务奇迹!
这段内容介绍了如何使用Spring Cloud和Netflix OSS构建微服务架构。首先,基于Spring Boot创建项目并添加Spring Cloud依赖项。接着配置Eureka服务器实现服务发现,然后创建REST控制器作为API入口。为提高服务稳定性,利用Hystrix实现断路器模式。最后,在启动类中启用Eureka客户端功能。此外,还可集成其他Netflix OSS组件以增强系统功能。通过这些步骤,开发者可以更高效地构建稳定且可扩展的微服务系统。
47 1
|
2月前
|
测试技术 微服务
微服务(八)-服务网关zuul(四)
微服务(八)-服务网关zuul(四)