【Spring Cloud】统一服务入口-Gateway

【Spring Cloud】统一服务入口-Gateway

系列文章目录


文章目录


在这里插入图片描述

一、网关介绍

1.1 问题

之前的文章,我们通过Eureka, Nacos解决了服务注册, 服务发现的问题, 使用Spring CloudLoadBalance解决了负载均衡的问题, 使用OpenFeign解决了远程调用的问题.
但是当前所有微服务的接口都是直接对外暴露的, 可以直接通过外部访问. 为了保证对外服务的安全性,服务端实现的微服务接口通常都带有⼀定的权限校验机制. 由于使用了微服务, 原本⼀个应⽤的的多个模块拆分成了多个应用, 我们不得不实现多次校验逻辑. 当这套逻辑需要修改时, 我们需要修改多个应用, 加重了开发人员的负担.

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

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

1.2 什么是API网关

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

在这里插入图片描述

网关核心功能:

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

1.3 常见网关实现

业界常用的网关方式有很多, 技术方案也较成熟, 其中不乏很多开源产品, 比如Nginx, Kong, Zuul,Spring Cloud Gateway等. 下面介绍两种常见的网关方案.
Zuul
Zuul 是 Netflix 公司开源的⼀个API网关组件, 是Spring Cloud Netflix 子项目的核心组件之⼀,它可以和 Eureka、Ribbon、Hystrix 等组件配合使用.
在Spring Cloud Finchley正式版之前, Spring Cloud推荐的网关是Netflix提供的Zuul(此处指Zuul 1.X).
然而Netflix在2018年宣布⼀部分组件进入维护状态, 不再进行新特性的开发. 这部分组件中就包含Zuul.

Spring Cloud Gateway
Spring Cloud Gateway 是Spring Cloud的⼀个全新的API网关项目, 基于Spring + SpringBoot等技术开发, 目的是为了替换掉Zuul. 旨在为微服务架构提供⼀种简单而有效的途径来转发请求, 并为他们提供横切关注点, 比如: 安全性, 监控/指标和弹性.
在性能方面, 根据官方提供的测试报告, Spring Cloud Gateway的RPS(每秒请求数)是Zuul的1.6倍.

二、Spring Cloud Gateway

2.1 快速上手

2.1.1 创建网关项目

在这里插入图片描述

2.1.2 引入网关依赖

<!--⽹关--><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>

2.1.3 编写启动类

@SpringBootApplicationpublicclassGatewayApplication{publicstaticvoidmain(String[] args){SpringApplication.run(GatewayApplication.class, args);}}

2.1.4 添加Gateway的路由配置

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

server: port:10030 # ⽹关端⼝ spring: application: name: gateway # 服务名称 cloud: nacos: discovery: server-addr:110.41.51.65:10020 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, 保持唯⼀
• uri: 目标服务地址, 支持普通URI 及 lb://应用注册服务名称 . lb表示负载均衡, 使用 lb:// 方式表示从注册中心获取服务地址.
• predicates: 路由条件,根据匹配结果决定是否执⾏该请求路由, 上述代码中, 我们把符合Path规则的 ⼀切请求, 都代理到uri参数指定的地址.

通过网关服务访问order-service的URL:http://127.0.0.1:10030/order/1

2.2 Route Predicate Factories

2.2.1 Predicate

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

@FunctionalInterfacepublicinterfacePredicate<T>{booleantest(T t);//...}

代码演示:

  1. 定义⼀个Predicate
classStringPredicateimplementsPredicate<String>{@Overridepublicbooleantest(String str){return str.isEmpty();}}
  1. 使用这个Predicate
publicclassPredictTest{publicstaticvoidmain(String[] args){Predicate<String> predicate =newStringPredicate();System.out.println(predicate.test(""));System.out.println(predicate.test("bite666"));}}
  1. Predicate的其他写法
    1)内置函数
publicclassPredictTest{publicstaticvoidmain(String[] args){Predicate<String> predicate =newPredicate<String>(){@Overridepublicbooleantest(String s){return s.isEmpty();}};System.out.println(predicate.test(""));System.out.println(predicate.test("bite666"));}}
  1. lambda写法:
publicclassPredictTest{publicstaticvoidmain(String[] args){Predicate<String> predicate = s -> s.isEmpty();System.out.println(predicate.test(""));System.out.println(predicate.test("bite666"));}}

Predicate predicate = s -> s.isEmpty(); 也可以写成
Predicate isEmpty = String::isEmpty;

  1. Predicate 的其他方法
• isEqual(Object targetRef) :比较两个对象是否相等,参数可以为Null
• and(Predicateother): 短路与操作,返回⼀个组成Predicate
• or(Predicate other):短路或操作,返回⼀个组成Predicate
• test(T t) :传入⼀个Predicate参数,用来做判断
• negate() :返回表示此Predicate逻辑否定的Predicate

2.2.2 Route Predicate Factories

Route Predicate Factories (路由断言工厂, 也称为路由谓词工厂, 此处谓词表示⼀个函数), 在Spring Cloud Gateway中, Predicate提供了路由规则的匹配机制.我们在配置文件中写的断言规则只是字符串, 这些字符串会被Route Predicate Factory读取并处理, 转变为路由判断的条件.

比如前面章节配置的 Path=/product/** , 就是通过Path属性来匹配URL前缀是 /product 的请求.这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory 来实现的.Spring Cloud Gateway 默认提供了很多Route Predicate Factory, 这些Predicate会分别匹配HTTP请求的不同属性, 并且多个Predicate可以通过and逻辑进行组合.

具体可以参考相关网站:Spring Cloud Gateway

2.2.3 代码演示

  1. 添加Predicate规则
    在application.yml中添加如下规则
spring: cloud: gateway: routes: # ⽹关路由配置 - id: product-service #路由ID, ⾃定义, 唯⼀即可 uri: lb://product-service #⽬标服务地址 predicates: #路由条件 -Path=/product/## -After=2026-01-01T00:00:00.000+08:00[Asia/Shanghai]

这样我们可以通过修改时间访问限制,发现只有在时间限制范围内的请求可以通过。

2.3 Gateway Filter Factories(网关过滤器工厂)

Predicate决定了请求由哪⼀个路由处理, 如果在请求处理前后需要加⼀些逻辑, 这就是Filter(过滤器)的作用范围了.
Filter分为两种类型: Pre类型和Post类型.
Pre类型过滤器: 路由处理之前执行(请求转发到后端服务之前执行), 在Pre 类型过滤器中可以做鉴权, 限流等.
Post类型过滤器: 请求执行完成后, 将结果返回给客户端之前执行

在这里插入图片描述

Spring Cloud Gateway 中内置了很多Filter, 用于拦截和链式处理web请求. 比如权限校验, 访问超时等设定.
Spring Cloud Gateway从作用范围上, 把Filter可分为GatewayFilter 和GlobalFilter.
GatewayFilter: 应用到单个路由或者⼀个分组的路由上.
GlobalFilter: 应用到所有的路由上, 也就是对所有的请求生效.

2.3.1 GatewayFilter

GatewayFilter 同 Predicate 类似, 都是在配置文件 application.yml 中配置,每个过滤器的逻辑都是固定的. 比如 AddRequestParameterGatewayFilterFactory 只需要在配置文件中写 AddRequestParameter , 就可以为所有的请求添加一个参数, 我们先通过⼀个例子来演示GatewayFilter如何使用
快速上手:

server: port:10030 # ⽹关端⼝ spring: application: name: gateway # 服务名称 cloud: nacos: discovery: server-addr:110.41.51.65:10020 gateway: routes: # ⽹关路由配置 - id: product-service #路由ID, ⾃定义, 唯⼀即可 uri: lb://product-service #⽬标服务地址 predicates: #路由条件 -Path=/product/## -After=2024-01-01T00:00:00.000+08:00[Asia/Shanghai] filters:-AddRequestParameter=userName, bite - id: order-service uri: lb://order-service predicates:-Path=/order/** 

该filter只添加在了 product-service 路由下, 因此只对 product-service 路由生效, 也就是对 /product/** 的请求生效.

2 . 接收参数并打印
在 product-service 服务中接收请求的参数,并打印出来

@RequestMapping("/product")@RestControllerpublicclassProductController{@AutowiredprivateProductService productService;@RequestMapping("/{productId}")publicProductInfogetProductById(@PathVariable("productId")Integer productId,String userName){System.out.println("收到请求,Id:"+ productId);System.out.println("userName:"+ userName);return productService.selectProductById(productId);}}

GatewayFilter说明:

Spring Cloud Gateway提供了的Filter非常多,具体网站网址:网站

大家可以细化学习。

Default Filters
前⾯的filter添加在指定路由下, 所以只对当前路由生效, 若需要对全部路由⽣效, 可以使用spring.cloud.gateway.default-filters 这个属性需要⼀个filter的列表.
配置举例:

spring: cloud: gateway:default-filters:-AddResponseHeader=X-Response-Default-Red,Default-Blue-PrefixPath=/httpbin 

2.3.2 GlobalFilter

GlobalFilter是Spring Cloud Gateway中的全局过滤器, 它和GatewayFilter的作用是相同的.GlobalFilter 会应用到所有的路由请求上, 全局过滤器通常用于实现与安全性, 性能监控和日志记录等相关的全局功能.
Spring Cloud Gateway 内置的全局过滤器也有很多, 比如:
• Gateway Metrics Filter: 网关指标, 提供监控指标
• Forward Routing Filter: 用于本地forword, 请求不转发到下游服务器
• LoadBalancer Client Filter: 针对下游服务, 实现负载均衡.
• …
更多过滤器参考: 网址

快速上手

  1. 添加依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency>
  1. 添加配置
spring: cloud: gateway: metrics: enabled:true management: endpoints: web: exposure: include:"*" endpoint: health: show-details: always shutdown: enabled:true

http://127.0.0.1:10030/actuator, 显示所有监控的信息链接

2.4 过滤器执行顺序

⼀个项目中, 既有GatewayFilter, 又有 GlobalFilter时, 执行的先后顺序是什么呢?
请求路由后, 网关会把当前项目中的GatewayFilter和GlobalFilter合并到⼀个过滤器链(集合)中, 并进行排序, 依次执行过滤器

在这里插入图片描述

每⼀个过滤器都必须指定⼀个int类型的order值, 默认值为0, 表示该过滤的优先级. order值越小,优先级越高,执行顺序越靠前.
• Filter通过实现Order接口或者添加@Order注解来指定order值.
• Spring Cloud Gateway提供的Filter由Spring指定. 用户也可以自定义Filter, 由用户指定.
• 当过滤器的order值⼀样时, 会按照 defaultFilter > GatewayFilter > GlobalFilter的顺序执行.

2.5 自定义过滤器

Spring Cloud Gateway提供了过滤器的扩展功能, 开发者可以根据实际业务来自定义过滤器, 同样自定义过滤器也支持GatewayFilter 和 GlobalFilter两种.

2.5.1 ⾃定义GatewayFilter

自定义GatewayFilter, 需要去实现对应的接口 GatewayFilterFactory , Spring Boot 默认帮我们实现的抽象类是 AbstractGatewayFilterFactory , 我们可以直接使用

2.5.1.1 定义GatewayFilter
@Slf4j@ServicepublicclassCustomGatewayFilterFactoryextendsAbstractGatewayFilterFactory<CustomGatewayFilterFactory.CustomConfig>implementsOrdered{publicCustomGatewayFilterFactory(){super(CustomConfig.class);}@OverridepublicGatewayFilterapply(CustomConfig config){/** * Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) * ServerWebExchange: HTTP请求-响应交互的契约, 提供对HTTP请求和响应的访问, 服 务器端请求属性, 请求实例,响应实例等, 类似Context⻆⾊ * GatewayFilterChain: 过滤器链 * Mono: Reactor核⼼类, 数据流发布者, Mono最多只触发⼀个事件, 所以可以把 Mono ⽤于在异步任务完成时发出通知. * Mono.fromRunnable: 创建⼀个包含Runnable元素的数据流 */return((exchange, chain)->{ log.info("[Pre] Filter Request, name:"+ config.getName());return chain.filter(exchange).then(Mono.fromRunnable(()->{ log.info("[Post] Response Filter");}));});}@OverridepublicintgetOrder(){returnOrdered.LOWEST_PRECEDENCE;//配置优先级, order越⼤, 优先级越低}

针对这个Filter的配置, 使用CustomConfig 定义

@DatapublicclassCustomConfig{privateString name;}

代码说明:

类名统一以GatewayFilterFactory结尾, 因为默认情况下, 过滤器的name会采用该定义类的前缀. 这里的name=Custom(yml配置中使用)apply方法中, 同时包含Pre和Post过滤, then方法中是请求执行结束之后处理的CustomConfig 是一个配置类, 该类只有⼀个属性name, 和yml的配置对应该类需要交给Spring管理, 所以需要加 @Service 注解getOrder表示该过滤器的优先级, 值越大, 优先级越低
2.5.1.2 配置过滤器
spring: cloud: gateway: routes: # ⽹关路由配置 - id: product-service #路由ID, ⾃定义, 唯⼀即可 uri: lb://product-service #⽬标服务地址 predicates: #路由条件 -Path=/product/## filters:- name:Custom args: name: custom filter 

2.5.2 自定义GlobalFilter

GlobalFilter的实现比较简单, 它不需要额外的配置, 只需要实现GlobalFilter接口, 自动会过滤所有的Filter.

2.5.2.1 定义GlobalFilter
@Slf4j@ServicepublicclassCustomGlobalFilterimplementsGlobalFilter,Ordered{@OverridepublicMono<Void>filter(ServerWebExchange exchange,GatewayFilterChain chain){ log.info("[Pre] CustomGlobalFilter enter...");return chain.filter(exchange).then(Mono.fromRunnable(()->{ log.info("[Post] CustomGlobalFilter return...");}));}@OverridepublicintgetOrder(){returnOrdered.LOWEST_PRECEDENCE;//配置优先级, order越⼤, 优先级越低}}

总结

以上就是本文全部内容,本文主要为大家介绍了Spring Cloud中统一服务入口的重要组件Gateway的相关操作和知识。感谢各位能够看到最后,如有问题,欢迎各位大佬在评论区指正,希望大家可以有所收获!创作不易,希望大家多多支持。

Read more

贪心算法篇——万千抉择中的唯一考量,最优解追寻的跬步累积(1)

贪心算法篇——万千抉择中的唯一考量,最优解追寻的跬步累积(1)

文章目录 * 引言:在选择的海洋中 * 贪心算法的哲学:局部最优,全球最优 * 贪心算法的经典应用 * 贪心算法的局限与挑战 * 结语:智者的选择,最优的未来 引言:在选择的海洋中 在人生的旅途上,每个人都要面临无数的选择。每一个选择,都是一次抉择;每一次抉择,都是命运的交汇点。数学与计算机科学的世界里,贪心算法正是对这种“选择”的一种深刻体现。在一系列的选择面前,贪心算法如同一位睿智的旅行者,始终秉持着最优的哲学:每一次决策都应基于局部最优,以期在最后抵达全局最优的境地。 贪心算法(Greedy Algorithm),正如其名所示,是一种每次都选择当前看起来最优解的算法。这种算法策略简单却充满智慧,常常能够解决很多看似复杂的问题。它通过一种局部的、贪婪的方式,一步步走向最终解。然而,正如智慧的旅行者需要对道路有所预见一样,贪心算法也有其适用的范围,只有在满足某些条件时,它才能发挥出最优解的魅力。 在这篇报告中,我们将深入探讨贪心算法的基本理念、适用范围、经典应用,并通过具体的代码示例,揭开这一算法的神秘面纱。 贪心算法的哲学:

By Ne0inhk
动态规划 线性 DP 经典四题一遍吃透

动态规划 线性 DP 经典四题一遍吃透

文章目录 * 台阶问题 * 最大子段和 * 传球游戏 * 乌龟棋 线性dp 是动态规划问题中最基础、最常⻅的⼀类问题。它的特点是状态转移只依赖于前⼀个或前⼏个状态,状态之间的关系是线性的,通常可以⽤⼀维或者⼆维数组来存储状态。 我们在⼊⻔阶段解决的《下楼梯》以及《数字三⻆形》其实都是线性dp,⼀个是⼀维的,另⼀个是⼆ 维的。 台阶问题 题目描述 题目解析 本题就是上一节下楼梯的问题的加强版,总体思路不变,下面我们还是按照动规5板斧来分析一下这道题。 1、状态表示 dp[i]表示走到第i个台阶的所有方案数 2、状态转移方程 第i个台阶的方案数等于从i-1阶到i-k阶的所有方案数之和,因为本题数据比较大,用long long都无法保证数据不越界,所以题目规定方案数还需要模100003,第i个台阶的方案数等于从i-1阶到i-k阶的所有方案数之和再模上100003,所以但是注意是可能越界访问的,比如i为3,

By Ne0inhk
【数据结构与算法】环与相遇:链表带环问题的底层逻辑与工程实现

【数据结构与算法】环与相遇:链表带环问题的底层逻辑与工程实现

🔥小龙报:个人主页 🎬作者简介:C++研发,嵌入式,机器人等方向学习者 ❄️个人专栏:《C语言》《【初阶】数据结构与算法》 ✨ 永远相信美好的事情即将发生 文章目录 * 前言 * 一、带环链表 * 1.1题目 * 1.2 算法原理 * 1.3 代码 * 1.4 数学证明 * 1.4.1 为什么带环slow与fast必定能相遇? * 1.4.2 fast一定只能走2步吗?可以是2步甚至更多吗? * 1.4.2.1 以3步为例 * 1.4.3结论 * 二、环形链表(寻找相遇点) * 2.1 题目

By Ne0inhk
Python 调用 Ollama 本地大模型 API 完全指南

Python 调用 Ollama 本地大模型 API 完全指南

Python 调用 Ollama 本地大模型 API 完全指南 Ollama 是一个开源工具,允许开发者在本地轻松运行 Llama、Mistral、Gemma 等主流大语言模型(LLM)。它不仅提供命令行交互,还内置了 HTTP API 服务,使得我们可以通过 Python 等编程语言远程调用本地模型,实现私有化、低延迟、无网络依赖的 AI 应用开发。 本文将手把手教你如何在 Python 中通过 HTTP 请求调用 Ollama 的 API,完成文本生成、对话交互等任务。 一、前提准备 1. 安装并启动 Ollama * 官网下载安装:https://ollama.com/ * 首次运行会自动下载模型(需联网),之后即可离线使用。 安装后,

By Ne0inhk