微服务入门(RestTemplate、Eureka、Nacos、Feign、Gateway)

本文阅读 25 分钟
首页 代码,Java 正文

1.1 单体架构

基本介绍: 将业务的所有功能集中在一个项目中开发,打成一个包部署。

优点: 架构简单、部署成本低

缺点: 耦合度高

1.2 分布式架构

基本介绍: 根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,称为一个服务。

优点: 降低服务耦合、有利于服务升级拓展

缺点: 架构复杂、难度大

分布式架构的要考虑的问题:

  • 服务拆分粒度如何?
  • 服务集群地址如何维护?
  • 服务之间如何实现远程调用?
  • 服务健康状态如何感知?

1.3 微服务架构

基本介绍: 微服务是一种经过良好架构设计的分布式架构方案。

特征:

  • 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
  • 面向服务:微服务对外暴露业务接口
  • 自治:团队独立、技术独立、数据独立、部署独立
  • 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题。

优点: 拆分粒度更小、服务更独立、耦合度更低

缺点: 架构非常复杂,运维、监控、部署难度提高

2.1 SpringCloud

  • SpringCloud 是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud
  • SpringCloud 集成了各种微服务功能组件,并基于 SpringBoot 实现了这些组件的自动装配,从而提供了良好的开箱即用体验
    - 服务注册发现:Eureka、Nacos、Consul - 统一配置管理:SpringCloudConfig、Nacos - 服务远程调用:OpenFegin、Dubbo - 统一网关路由:SpringCloudGateway、zuul - 服务链路监控:Zipkin、Sleuth - 流程、降级、保护:Hystix、Sentinel
  • SpringCloud 与 SpringBoot 的版本兼容关系:
Realesase Train(版本序列) Boot Version
2020.0.x aka Ilford 2.4.x
Hoxton 2.2.x, 2.3.x
Greenwich 2.1.x
Finchley 2.0.x
Edgware 1.5.x
Dalston 1.5.x

2.2 微服务拆分注意事项

  • 不同微服务,不要重复开发相同业务
  • 微服务数据独立,不要访问其它微服务的数据库
  • 微服务可以将自己的业务暴露为接口,供其它微服务调用

2.3 微服务远程调用方式

2.3.1 提供者与消费者

  • 服务提供者:一次业务中,被其他微服务调用的服务。(提供接口给其它微服务)
  • 服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)
  • 一个服务既可以是提供者又可以是消费者。

2.3.2 RestTemplate

基本介绍:

当我们需要远程调用一个 HTTP 接口时,我们经常会用到 RestTemplate 这个类,这个类是 Spring 框架提供的一个工具类。它提供了常见的 REST 请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。

常用方法:

简单使用:

  • 首先将 RestTemplate 在配置类中进行注入(启动类也属于配置类,可以直接在启动类中使用 @Bean 进行注入)
  • 通过 RestTemplate 对象的 getForObject(url, Class<T> responseType) 方法,就能够通过 get 方法向其它微服务发送对应的请求去获取数据。

RestTamplate 远程调用出现的问题:

  • 服务消费者该如何获取服务提供者的地址信息?
  • 如果有多个服务提供者,消费者该如何选择?
  • 消费者如何得知服务提供者的健康状态?
  • 代码可读性差,编程体验不统一
  • 参数复杂 URL 难以维护

3.1 基本介绍

在 Eureka 框架中,微服务角色有两类:

  • EurekaServer(服务端,注册中心):用于记录服务信息,进行心跳监控
  • EurekaClient(客户端):包括服务提供者和消费者
    - 服务提供者能够将注册自己的信息到 EurekaServer 中,并且每隔 30s 向 EurekaServer 发送心跳 - 服务消费者能够根据服务名称从 EurekaServer 中拉取服务列表,并基于服务列表做负载均衡,选中一个微服务后发起远程调用

在进行之后的步骤之前,需要在父工程中引入 spring-cloud-dependencies 依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>Hoxton.SR10</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

3.2 搭建 EurekaServer

  1. 创建项目,引入 spring-cloud-starter-netflix-eureka-server eureka 服务端依赖 <dependency>

    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>

    </dependency>

  2. 编辑启动类,添加 @EnableEurekaServer 注解 <pre><span class="token annotation punctuation">@SpringBootApplication</span>
    <span class="token annotation punctuation">@EnableEurekaServer</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">EurekaApplication</span> <span class="token punctuation">{

<!-- --></span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{ 
<!-- --></span>
    <span class="token class-name">SpringApplication</span><span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span><span class="token class-name">EurekaApplication</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token punctuation">}</span>

  1. 添加 application.yml 文件,编写下面的配置: server:
    port: 10086 # 服务端口

spring:
application:

name: eurekaserver # eureka 的服务名称

eureka:
client:

service-url: # eureka 的地址信息
  defaultZone: http://127.0.0.1:10086/eureka  
  1. 通过配置的 eureka 的地址信息就能够访问到 eureka 注册中心的状况

3.3 服务注册

  1. 在要注册的服务中引入 spring-cloud-starter-netflix-eureka-server eureka 客户端依赖 <dependency>

    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>

    </dependency>

  2. 在 application.yml 文件中编写下面的配置: server:
    port: 8080 # 服务端口

spring:
application:

name: aservice # 注册到 eureka 中的服务名称

eureka:
client:

service-url: # 配置 eureka 的地址信息
  defaultZone: http://127.0.0.1:10086/eureka  
  1. 通过配置的 eureka 的地址信息就能够查看注册到 eureka 中的实例
  2. 以上是对某个服务进行一个实例的部署,如果要部署多个实例,则为了避免端口冲突,需要修改端口配置
    4. 右击要部署多个的服务,点击 Copy Configuration… 4. 修改配置的名字,并在 VM options 这栏加上 -Dserver.port=新的端口号 却修改配置的端口号 4. 启动项目后,在 eureka 的注册中心就能看到 aservice 服务注册了两个实例

3.4 服务拉取(含负载均衡)

  1. 修改 RestTemplate 的 url 路径,用服务名代替 ip 和端口号
  2. 在 RestTemplate 注册到 Spring 容器的位置加上 @LoadBalanced 负载均衡注解
  3. 启动项目,通过 bservice 服务访问 aservice 服务中的方法

4.1 负载均衡流程

img

通过浏览器直接访问 http://aservice/say/dog 是访问不到的,因为 aservice 不是正确的 ip 和端口号,而加上负载均衡的注解 @LoadBalanced 注解就能够对这个请求进行拦截,在 eureka-server 中找到 aservice 对应的 ip 和端口号,最终返回请求的数据。

加上 @LoadBalanced 就能就行拦截,那么拦截的动作是谁处理的呢?

请求会被一个叫 LoadBalancerInterceptor 的负载均衡拦截器给拦住,它能获取拦截请求中的主机名,即上述请求中的 aservice,并把它交给 RibbonLoadBalancerClient。RibbonLoadBalancerClient 又会把服务名交给 DynamicServerListLoadBalancer。DynamicServerListLoadBalancer 就会到 eureka-server 中获取到对应服务的服务列表并交给 IRule。IRule 会从服务列表中基于规则选取其中一个服务,将 ip 和端口号返回给 RibbonLoadBalancerClient。RibbonLoadBalancerClient 就会将请求修改成正确的地址并发送出去,最终获取到数据。

4.2 负载均衡策略

Ribbon 的负载均衡规则是一个叫做 IRule 的接口来定义的,它的每一个子接口都是一种规则img

修改负载均衡规则方式:

  • 直接在配置类或启动类中定义一个新的 IRule(定义后,该服务调用其它微服务都会使用如下规则) <pre><span class="token comment">// 将规则修改成随机选择</span>
    <span class="token annotation punctuation">@Bean</span>

<span class="token keyword">public</span> <span class="token class-name">IRule</span> <span class="token function">randomRule</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{

<!-- --></span>
<span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">RandomRule</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token punctuation">}</span>

  • 在 application.yml 文件中,添加新的配置(配置后,该服务会对指定要调度的服务进行规则配置) aservice: # 要调度的服务名
    ribbon: # 负载均衡规则,修改为要使用的负载均衡的类的名称

    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule 
    

4.3 饥饿加载

Ribbon 默认是采用懒加载,即第一次访问时会去创建 LoadBalanceCilent,请求时间很长。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:

ribbon:
  eager-load:
    enabled: true # 开启饥饿加载
    clients:  # 指定饥饿加载的服务名称
    - aservice # 指定对 aservice 服务开启饥饿加载

5.1 安装 Nacos

Nacos 是阿里巴巴的产品,现在是 SpringCloud 中的一个组件,相比于 Eureka 功能更加丰富。

Nacos 的安装方式如下:

  1. 下载 Nacos 的安装包,并解压好(下载地址为:https://github.com/alibaba/nacos/tags
  2. 启动 Nacos(进入解压好的 bin 目录,通过控制台执行下面的命令) # 单机启动 Nacos
    startup.cmd -m standalone
  3. Console 后面的地址就是 Nacos 的控制台地址,Ctrl + 鼠标左键就能够直接打开
  4. 登录 Nacos(默认账号和密码都是 nacos),登录成功后就会跳转到控制台页面

5.2 服务注册

  1. 在父工程中添加 spring-cloud-alilbaba 的管理依赖: <dependency>

    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2.2.5.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>

    </dependency>

  2. 在指定的服务中添加 nacos 的客户端依赖: <dependency>

    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>

    </dependency>

  3. 配置 application.yml 文件 spring:
    application:

      name: aservice # 服务名

    cloud:

    nacos:
      server-addr: localhost:8848 # nacos 服务地址  
  4. 运行项目后,指定服务就会注册到 nacos 中。在 nacos 的控制台就可以看到具体的信息。
  5. 通过 RestTemplate 就可以对注册到 nacos 上的服务进行调度。

5.3 Nacos 服务分级存储模型

一个服务可以分成多个集群,每个集群中又可以包括多个实例。img

服务跨集群调用问题:

服务调用尽可能选择本地集群的服务,跨集群调用延迟较高。当本地集群不可访问时,再去访问其它集群。

集群的配置方式:

当我们不配置集群时,Nacos 中集群显示为 DEFAULT,即表示没有集群img

可以在 application.yml 文件中编辑如下配置,去配置集群

spring:
  cloud:
    nacos:
      discovery:
        cluster-name: CS # 集群名称

在 aservice 服务中设置好了集群名为 CS 后启动 aservice 的 8080 实例。然后再修改 aservice 的集群名为 HZ 并启动 aservice 的 8082 实例,Nacos 中的集群就会显示下面结果:img

5.4 根据集群负载均衡

上面已经修改了 aservice 服务的配置,将 aservice 的两个实例分别配置到了两个集群中。不过将 bservice 设置集群名为 CS,当调用 asevice 实例时并不是优先调用的同集群中的 aservice 实例。这是因为服务在选择实例时是通过负载均衡的规则去选择的,而负载均衡的默认规则是轮询调度。因此想要优先调度同集群的实例,就要修改负载均衡的规则。

修改方式如下:

  1. 先设置好调度者的服务的集群名
  2. 在该服务中设置负载均衡的 IRule 为 NacosRule,这个规则优先会寻找与自己同集群的服务(不过是同集群中随机调度) aservice: # 要调度的服务名
    ribbon:

    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则 
    

5.5 根据权重负载均衡

Nacos 提供了权重配置来控制实例的访问频率,权重越大者访问频率越大。

应用场景:

将性能好的机器设置权重多一点,将性能差的机器设置权重少一点。

修改方式如下:

  1. 在 Nacos 控制台就可以设置实例的权重值,首先点击实例的编辑按钮
  2. 将权重修改为你要的值即可(权重值在 0~1 之间,0 表示不会被访问到)

5.6 环境隔离 namespace

Nacos 中服务存储和数据存储的最外层都是一个名为 namespace 的东西,用来做最外层的隔离。每个 namespace 都有唯一 ID,不同 namespace 下的服务是不可见的。img

在 Nacos 控制台我们可以看到,我们注册的实例都默认存放在一个叫 public 的命名空间中img

配置新的命名空间的方式:

  1. 在 Nacos 的控制台新建一个命名空间
  2. 创建好了之后,在服务列表就能看到新创建的命名空间了。
  3. 修改服务的 application.yml 文件,添加如下配置: spring:
    cloud:

    nacos:
    discovery:
     namespace: a6fedd4e-c8fc-4695-b5d1-60bf72205819 # 命名空间的ID  
  4. 重启服务

5.7 临时实例和非临时实例

Nacos 支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式。默认采用临时实例。

配置非临时实例方式:

spring:
  cloud:
    nacos:
      discovery:
        ephemeral: false # 设置非临时实例

5.8 Nacos 和 Eureka 的差别

共同点:

  • 都支持服务注册和服务拉取
  • 都支持服务提供者心跳方式做健康检测

区别:

  • Nacos 支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式。
  • 临时心跳不正常会被剔除,非临时实例则不会被剔除。
  • Nacos 支持服务列表变更的消息推送模式,服务列表更新更及时。
  • Nacos 集群默认采用 AP 模式,当集群中存在非临时实例时,采用 CP 模式;Eureka 采用 AP 模式

6.1 Nacos 进行配置管理

  1. 在 Nacos 控制台中添加配置信息
  2. 在弹出的表单中填写配置信息

6.2 微服务获取 Nacos 中的配置

当只读取本地配置文件时,步骤如下:img

当需要读取 nacos 控制台中的配置文件时,步骤如下:img

  1. 引入 Nacos 的配置管理客户端依赖 <dependency>

    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>

    </dependency>

  2. 在服务中的 resources 目录中添加一个 bootstrap.yml 文件(这个文件是引导文件,优先级高于 application.yml) spring:
    application:

    name: aservice # 服务名称(与 nacos 控制台配置文件名对应)

    profiles:

    active: dev # 开发环境,这里是 dev(与 nacos 控制台配置文件名对应)

    cloud:

    nacos:
      server-addr: localhost:8848 # Nacos 地址
      config:
        file-extension: yaml # 文件后缀名(与 nacos 控制台配置文件名对应) 
    

6.3 配置热更新

Nacos 中的配置文件变更后,微服务无需重启就可以感知。不过需要通过下面两种配置实现:

  • 方式一:在 @Value 注入的变量所在类上添加注解 @RefreshScope
<p>@Value 该注解的作用是将配置文件的属性读出来,有 @Value("${}")@Value("#{}") 两种方式</p>
  • 方式二:使用 @ConfigurationProperties 注解
<p>上面的例子将会读取配置文件中所有以 pattern 开头的属性,并和 bean 中的字段进行匹配。</p>

6.4 多种配置的优先级

微服务从 nacos 读取配置文件时会读取两个配置:

  • [服务名]-[spring.profile.active].yaml 环境配置
  • [服务名].yaml 默认配置,多环境共享

优先级:

nacos 中的当前环境配置(服务名-环境.yaml)> nacos 中共享配置(服务名.yaml)> 本地配置

7.1 定义和使用 Feign

基本介绍:

Feign 是一个声明式的 HTTP 客户端,能够帮助我们优雅地实现 HTTP 请求的发送。官方地址: https://github.com/OpenFeign/feign

使用 Feign 步骤如下:

  1. 引入依赖: <dependency>

    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>

    </dependency>

  2. 在消费者的启动类中添加 @EnableFeignClients 注解开启 Feign 的功能: <pre><span class="token annotation punctuation">@SpringBootApplication</span>
    <span class="token annotation punctuation">@EnableFeignClients</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BApplication</span> <span class="token punctuation">{

<!-- --></span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{ 
<!-- --></span>
    <span class="token class-name">SpringApplication</span><span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span><span class="token class-name">BApplication</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token punctuation">}</span>

  1. 编写 FeignClient 接口: <pre><span class="token annotation punctuation">@FeignClient</span><span class="token punctuation">(</span><span class="token string">"aservice"</span><span class="token punctuation">)</span> <span class="token comment">// 要调度的微服务</span>
    <span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">AClient</span> <span class="token punctuation">{

    <!-- --></span> <span class="token comment">// 该接口中包含和 aservice 服务中的多有需要调度的方法</span>
    
    <span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"say/dog"</span><span class="token punctuation">)</span>
    <span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">dogSay</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token punctuation">}</span>

  2. 使用编写好的 FeignClient 中定义的方法 <pre><span class="token annotation punctuation">@RestController</span>
    <span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"b"</span><span class="token punctuation">)</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BController</span> <span class="token punctuation">{

<!-- --></span>

<span class="token annotation punctuation">@Autowired</span>
<span class="token keyword">private</span> <span class="token class-name">AClient</span> aClient<span class="token punctuation">;</span>

<span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"say"</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">bSay</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{ 
<!-- --></span>
    <span class="token keyword">return</span> aClient<span class="token punctuation">.</span><span class="token function">dogSay</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token punctuation">}</span>
</pre>

7.2 自定义 Feign 的配置

Feign 运行自定义配置来覆盖默认配置,可以修改的配置如下:

配置 Feign 日志有两种方式:

  • 方式一:配置文件方式
    - 全局生效 feign: client: config: default: # default 表示全局配置,即对调度的所有微服务都作如下配置 loggerLevel: Full # 日志级别 - 局部生效 feign: client: config: aservice: # 具体服务名称,针对某个要被调度的微服务进行配置 loggerLevel: Full # 日志级别
  • 方式二:java 代码方式,需要先声明一个 Bean <pre><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">FeignClientConfiguration</span><span class="token punctuation">{

    <!-- --></span>
    <span class="token annotation punctuation">@Bean</span>
    <span class="token keyword">public</span> <span class="token class-name">Logger<span class="token punctuation">.</span>Level</span> <span class="token function">feignLogLevel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{ 
    <!-- --></span>
        <span class="token keyword">return</span> <span class="token class-name">Logger<span class="token punctuation">.</span>Level</span><span class="token punctuation">.</span>BASIC<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token punctuation">}</span>

    - 而后如果是全局配置,则把它放到 @EnableFeignClients 这个注解中 @EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class); - 如果是局部配置,则把它放到 @FeignClient 这个注解中 @FeignClient(value = "aservice", configuration = FeignClientConfiguration.class);

7.3 Feign 的性能优化

Feign 底层的客户端实现:

  • URLConnection:默认实现,不支持连接池
  • Apache HttpClient:支持连接池
  • OKHttp:支持连接池

优化 Feign 的性能主要包括:

  • 使用连接池代替默认的 URLConnection,可以减少连接创建和销毁的性能损耗
  • 日志级别最好使用 basic 或 none,级别低的话性能更高

Feign 添加 HttpClient 的支持方式:

  1. 引入依赖 <dependency>

    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>

    </dependency>

  2. 配置连接池: feign:
    httpclient:

    enabled: true # 开启 feign 对 httpClient 的支持
    max-connections: 200 # 最大连接数
    max-connections-per-route: 50 # 每个路径的最大连接数 
    

7.4 Feign 的最佳实现

方式一(继承):给消费者的 FeignClient 和提供者的 controller 定义统一的父接口作为标准

方式二(抽取):

  1. 首先创建一个 module,命名为 feign-api,然后引入 feign 的 starter 依赖
  2. 在 feign-api 中编写提供者的 FeignClient、POJO 和 Feign 的默认配置
  3. 在消费者中引入 feign-api 的依赖
  4. 重启项目
  5. 当定义的 FeignClient 不在 SpringBootApplication 的扫描包范围时,这些 FeignClient 无法使用。有两种方式解决:
    5. 方式一:指定 FeignClient 所在包 @EnableFeignClients(basePackages = "com.mmr.feign.clients") 5. 方式二:指定 FeignClient 字节码 <pre><span class="token annotation punctuation">@EnableFeignClients</span><span class="token punctuation">(</span>clients <span class="token operator">=</span> <span class="token punctuation">{ <!-- --></span><span class="token class-name">AClient</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">}</span><span class="token punctuation">)</span> </pre>

8.1 认识网关

网关的功能:

  • 对用户请求作身份认证和权限校验
  • 将用户请求路由到微服务,并实现负载均衡
  • 对用户请求做限流

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

  • gateway
  • zuul

Zuul 是基于 Servlet 的实现,属于阻塞式编程。而 SpringCloudGateway 则是基于 Spring5 中提供的 WebFlux,属于响应式编程的实现,具备更好的性能。

8.2 搭建网关服务

搭建网关服务端步骤:

  1. 创建新的 module,引入 SpringCloudGateway 和 nacos 的服务发现依赖 <!-- 网关依赖 -->
    <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. 编写路由配置及 nacos 地址 server:
    port: 10010 # 网关端口

spring:
application:

name: gateway # 服务名称

cloud:

nacos:
  server-addr: localhost:8848 # nacos 地址
gateway:
  routes: # 网关路由配置
    - id: a-service # 路由id,自定义,唯一
      uri: lb://aservice # 路由的目标地址,lb 表示负载均衡,后面接服务名称
      predicates: # 路由断言,用来判断请求是否符合路由规则的条件
        - Path=/a/** # 路径匹配,只要是以 /a/ 开头就符合要求 

8.3 路由断言工厂 Route Predicate Factory

网关路由可以配置的内容包括:

  • 路由id:路由唯一标识
  • uri:路由的目的地,支持 lb 和 http 两种
  • predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地,不符合则跳过该规则
  • filters:路由过滤器,处理请求或响应

路由断言工厂 Route Predicate Factory 的作用:

配置文件中写的断言规则只是字符串,这些字符串会被 Predicate Factory 读取并处理,转变为路由判断的条件。

Spring 提供了11种基本的 Predicate 工厂:

8.4 路由过滤器 GatewatyFilter

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

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

给某个路由添加过滤器的配置方式:

spring:
  cloud:
    gateway:
      routes: # 网关路由配置
        - id: a-service
          uri: lb://aservice
          predicates:
            - Path=/a/**
          filters: # 过滤器
            - AddRequestHeader=Truth, new header # 添加请求头,键值对用逗号隔开

默认过滤器:

如果要对所有的路由都添加一个过滤器,则可以讲过滤器工厂写到 default-filters 下。

spring:
  cloud:
    gateway:
      routes: # 网关路由配置
        - id: a-service
          uri: lb://aservice
          predicates:
            - Path=/a/**
      default-filters: # 默认过滤器,会对所有的路由请求都生效
        - AddRequestHeader=Truth, new header # 添加请求头,键值对用逗号隔开

8.5 全局过滤器

全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与 GatewayFilter 的作用一样。

区别在于 GatewayFilter 通过配置定义,处理逻辑是固定的。而 GlobalFilter 的逻辑需要自己写代码实现。

定义方式是实现 GlobalFilter 接口:

img

案例:定义全局过滤器,拦截并判断用户身份

需求:参数中是否有 authorization,且 authorization 的参数值是否为 admin

@Order(-1) // 顺序注解,也可以通过实现接口 Ordered
@Component
public class Authorization implements GlobalFilter { 
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { 
        // 1. 获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String, String> queryParams = request.getQueryParams();

        // 2. 获取参数中的 authorization 参数
        String authorization = queryParams.getFirst("authorization");

        // 3. 判断参数值是否等于 admin
        if("admin".equals(authorization)){ 
            // 4. 是,放行
            return chain.filter(exchange);
        }
        // 5. 否,拦截
        // 5.1 设置状态码,表示未登录
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        // 5.2 拦截请求
        return exchange.getResponse().setComplete();
    }
}

8.6 过滤器的执行顺序

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

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

过滤器执行顺序:

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

8.7 网关跨域问题处理

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

  • 域名不同
  • 域名相同,端口不同

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

解决方案: CORS

网关处理跨域采用的同样是 CORS 方案,并且只需要简单配置即可实现:

spring:
  cloud:
    gateway:
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决 options 请求被拦截问题
        corsConfigurations:
          '[/**]': # 拦截一切请求,都进行跨域处理
            allowedOrigins: # 允许哪些网站的跨域请求
              - "http://localhost:8090"
              - "http://www.baidu.com"
            allowedMethods: # 允许的跨域 ajax 的请求方式
              - "GET"
              -  "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带 cookie
            maxAge: 360000 # 这次跨域检测的有效期
本文为互联网自动采集或经作者授权后发布,本文观点不代表立场,若侵权下架请联系我们删帖处理!文章出自:https://t4dmw.blog.csdn.net/article/details/125610698
-- 展开阅读全文 --
BUUCTF Web [极客大挑战 2019]Knife
« 上一篇 06-24
安全面试之XSS(跨站脚本攻击)
下一篇 » 07-24

发表评论

成为第一个评论的人

热门文章

标签TAG

最近回复