Spring Cloud

一、微服务架构

单体架构:将业务的所有功能集中在一个项

                  目中开发,打成一个包部署

分布式架构:根据业务功能对系统做拆分

                  每个业务功能模块作为独立项目

                  开发,称为一个服务

微服务架构

    特征:

单一职责:微服务拆分粒度更小,每一个服

                  务都对应唯一的业务能力,做到

                  单一职责

自治:团队独立、技术独立、数据独立,独

           立部署和交付

面向服务:服务提供统一标准的接口,与语

                  言和技术无关

隔离性强:服务调用做好隔离、容错、降级,

                  避免出现级联问题

二、 Spring Cloud

① Spring Cloud 是目前国内使用最广泛的微

     服务框架

② Spring Cloud 集成了各种微服务功能组件

     并基于 SpringBoot 实现了这些组件的

     动装配,从而提供了良好的开箱即用体验

③ Spring  Cloud 底层是依赖于 SpringBoot

     的,并且有版本的兼容关系

2. 五大组件

Eureka:注册中心

Zuul:服务网关

Ribbon:负载均衡

Feign:服务调用

Hystix:熔断器

三、服务拆分和远程调用

任何分布式架构都离不开服务的拆分,微

服务也是一样

 1. 服务拆分规则

① 不同微服务,不要重复开发相同业务

② 微服务数据独立,不要访问其它微服务

    的数据库

③ 微服务可以将自己的业务暴露为接口,

    供其它微服务调用

2. 提供者与消费者

在服务调用关系中,会有两个不同的角色:

服务提供者 (user-service):一次业务中,

被其它微服务调用的服务(提供接口给其它

微服务)

服务消费者 (order-service):一次业务中,

调用其它微服务的服务 (调用其它微服务提

供的接口)

其中,服务提供者与服务消费者的角色并不是

绝对的,而是相对于业务而言

四、Eureka 注册中心

1. Eureka 的作用

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

EurekaServer:服务端,注册中心

        ① 记录服务信息

        ② 心跳监控

EurekaClient:客户端

        ① Provider:服务提供者

             注册自己的信息到 EurekaServer

             每隔30秒向 EurekaServer 发送心跳

        ② consumer:服务消费者

             根据服务名称从 EurekaServer 拉取

             服务列表

             基于服务列表做负载均衡,选中一个

             微服务后发起远程调用

2. 搭建 EurekaServer

① 创建项目,导入依赖

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

② 编写启动类,添加 @EnableEurekaServer 注解

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
​
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }
}

③ 编写一个 application.yml 文件

server:
  port: 10086
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url: 
      defaultZone: http://127.0.0.1:10086/eureka

④ 启动服务

启动微服务,然后在浏览器访问:http://127.0.0.1:10086 

3. 服务注册

将 user-service 注册到 eureka-server 中去

① 引入依赖

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

② 配置文件

spring:
  application:
    name: userservice
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

③ 启动多个 user-service 实例

4. 服务发现

将 order-service 的逻辑修改:向 eureka-server

拉取 user-service 的信息,实现服务发现

① 引入依赖

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

② 配置文件

spring:
  application:
    name: orderservice
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

③ 服务拉取和负载均衡

在 order-service 的 OrderApplication

中,给 RestTemplate 这个 Bean 添加

一个 @LoadBalanced 注解

修改 order-service 服务的 OrderService

类中的 queryOrderById 方法。修改访问

url 路径,用服务名代替 ip 、端口:

五、Ribbon 负载均衡

1. 负载均衡原理

SpringCloudRibbon 的底层采用了一个

拦截器,拦截了 RestTemplate 发出的

请求,对地址做了修改

2. 负载均衡策略

负载均衡的规则都定义在 IRule 接口中,

而 IRule 有很多不同的实现类: 

不同规则的含义如下:

其中默认的实现就是 ZoneAvoidanceRule,

是一种轮询方案

(2) 自定义负载均衡策略

通过定义IRule实现可以修改负载均衡规则,

但一般用默认的负载均衡规则,不做修改

有两种方式:

① 代码方式

在 order-service 中的 OrderApplication 类中,

定义一个新的IRule:

@Bean
public IRule randomRule(){
    return new RandomRule();
}

② 配置文件方式

在 order-service 的 application.yml 文件中,添

加新的配置也可以修改规则:

userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则 

3. 饥饿加载

Ribbon 默认是采用懒加载,即第一次访问时

才会去创建 LoadBalanceClient,请求时间会

很长

而饥饿加载则会在项目启动时创建,降低第一

次访问的耗时,通过下面配置开启饥饿加载:

ribbon:
  eager-load:
    enabled: true
    clients: userservice

六、Nacos 注册中心

Nacos 是阿里巴巴的产品,现在是 SpringCloud

中的一个组件,相比 Eureka 功能更加丰富,在

国内受欢迎程度较高

Nacos 是 SpringCloudAlibaba 的组件,而

SpringCloudAlibaba 也遵循 SpringCloud 中定义

的服务注册、服务发现规范。因此使用 Nacos 和

使用 Eureka 对于微服务来说,并没有太大区别

主要差异在于:

① 依赖不同

② 服务地址不同

2. 服务注册到 Nacos

① 引入依赖

在父工程的 pom 文件中引入 SpringCloudAlibaba

的依赖:

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

 然后在 user-service 和 order-service 中的 pom

文件中引入 nacos-discovery 依赖:

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

注意:不要忘了注释掉 eureka 的依赖

② 配置 nacos 地址

在 user-service 和 order-service 的 application.yml

中添加 nacos 地址:

spring:
  cloud:
    nacos:
      server-addr: localhost:8848

注意:不要忘了注释掉 eureka 的地址

③ 重启

 3. 服务分级存储模型

一级是服务

二级是集群

三级是实例

一个服务可以有多个实例,例如我们的

user-service,可以有:

127.0.0.1:8081

127.0.0.1:8082

127.0.0.1:8083

假如这些实例分布于全国各地的不同机房,

例如:

127.0.0.1:8081,在上海机房

127.0.0.1:8082,在上海机房

127.0.0.1:8083,在杭州机房

Nacos 就将同一机房内的实例划分为一个集群

也就是说,user-service 是服务,一个服务可

以包含多个集群,如杭州、上海,每个集群下

可以有多个实例,形成分级模型,如图:

微服务互相访问时,应该尽可能访问同集

群实例,因为本地访问速度更快,当本集

群内不可用时,才访问其它集群

(1) 给 user-service 配置集群

① 修改 user-service 的 application.yml 文件,

     添加集群配置:

spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ # 集群名称

② 重启两个 user-service 实例后,我们可以在

    nacos 控制台看到下面结果:

 ③ 我们再次复制一个user-service启动配置,添加属性:

-Dserver.port=8083
-Dspring.cloud.nacos.discovery.cluster-name=SH

④ 启动 UserApplication3 后再次查看 nacos 控制台: 

 (2) 同集群优先的负载均衡

默认的 ZoneAvoidanceRule 并不能实现

根据同集群优先来实现负载均衡

因此 Nacos 中提供了一个 NacosRule

实现,可以优先从同集群中挑选实例

① 给 order-service 配置集群信息

修改 order-service 的 application.yml 文件,

添加集群配置:

spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ # 集群名称

② 修改负载均衡规则

修改 order-service 的 application.yml 文件,

修改负载均衡规则:

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

4. 权重配置

实际部署中会出现这样的场景:

服务器设备性能有差异,部分实例所在

机器性能较好,另一些较差,我们希望

性能好的机器承担更多的用户请求。

但默认情况下 NacosRule 是同集群内

随机挑选,不会考虑机器的性能问题

因此,Nacos提供了权重配置来控制访

问频率,权重越大则访问频率越高

在 nacos 控制台,找到 user-service 的实

例列表,点击编辑,即可修改权重:

 注:如果权重修改为 0,则该实例永远不会被访问

5. 环境隔离

Nacos 提供了 namespace 来实现环境隔离功能

nacos 中可以有多个 namespace,namespace 下

可以有 group、service 等,不同 namespace 之间

相互隔离

例如不同 namespace 的服务互相不可见

(1) 创建 namespace

默认情况下,所有 service、data、group 都在

同一个 namespace,名为 public:

① 我们可以点击页面新增按钮,添加一个namespace:

② 然后,填写表单:

③ 就能在页面看到一个新的 namespace:

(2) 给微服务配置 namespace

① 配置 namespace 只能通过修改配置来实现

例如,修改 order-service 的 application.yml

文件:

spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ
        namespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f9 # 命名空间,填ID

② 重启 order-service 后,访问控制台,可以

    看到下面的结果:

③ 此时访问 order-service,因为 namespace

    不同,会导致找不到 userservice,控制台

    会报错:

 6. Nacos 和 Eureka 的区别

(1) Nacos 的服务实例分为两种 l 类型:

临时实例:如果实例宕机超过一定时间,

               会从服务列表剔除,默认的类型

非临时实例:如果实例宕机,不会从服务

                  列表剔除,也可以叫永久实例

配置一个服务实例为永久实例:

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

(2) Nacos 和 Eureka 整体结构类似,服务注册、

    服务拉取、心跳等待,但是也存在一些差异:

(3) Nacos 与 eureka 的共同点

都支持服务注册服务拉取

都支持服务提供者心跳方式做健康检测

(4) Nacos 与 Eureka 的区别

Nacos 支持服务端主动检测提供者状态:

临时实例采用心跳模式

非临时实例采用主动检测模式

临时实例心跳不正常会被剔除,非临时

实例则不会被剔除

Nacos 支持服务列表变更的消息推送模

,服务列表更新更及时

Nacos 集群默认采用AP方式,当集群中

存在非临时实例时,采用CP模式

Eureka采用AP方式

七、Nacos 配置管理

1. 统一配置管理

当微服务部署的实例越来越多,达到数

十、数百时,逐个修改微服务配置就会

让人抓狂,而且很容易出错。我们需要

一种统一配置管理方案,可以集中管理

所有实例的配置

 

Nacos 一方面可以将配置集中管理,另一

方可以在配置变更时,及时通知微服务,

实现配置的热更新

(1) 在 nacos 中添加配置文件

然后在弹出的表单中,填写配置信息:

注意:项目的核心配置,需要热更新的配置

           才有放到 nacos 管理的必要。基本不

           会变更的一些配置还是保存在微服务

           本地比较好 

(2) 从微服务拉取配置

微服务要拉取 nacos 中管理的配置,并且与

本地的 application.yml 配置合并,才能完成

项目启动

① 引入 nacos-config 依赖

在 user-service 服务中,引入 nacos-config

的客户端依赖:

<!--nacos配置管理依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

 ② 添加 bootstrap.yaml

在 user-service 中添加一个 bootstrap.yaml

文件,优先级高于application.yml,

内容如下:

spring:
  application:
    name: userservice # 服务名称
  profiles:
    active: dev #开发环境,这里是dev 
  cloud:
    nacos:
      server-addr: localhost:8848 # Nacos地址
      config:
        file-extension: yaml # 文件后缀名

在 user-service 中将 pattern.dateformat 这个

属性注入到 UserController 中做测试:

@RestController
@RequestMapping("/user")
public class UserController {

    //注入nacos中的配置属性
    @Value("${pattern.dateformat}")
    private String dateformat;

    //编写controller,通过日期格式化器来格式化现在时间并返回
    @GetMapping("now")
    public String now(){
        return LocalDate.now().format(
            DateTimeFormatter.ofPattern(dateformat, Locale.CHINA)
        );
    }
}

2. 配置热更新

最终目的,是修改 nacos 中的配置后

微服务中无需重启即可让配置生效

也就是配置热更新

要实现配置热更新,可以使用两种方式:

(1) 方式一

在 @Value 注入的变量所在类上添加注解

@RefreshScope

(2) 方式二

使用 @ConfigurationProperties 注解代替

@Value 注解

在 user-service 服务中,添加一个类,读

patterrn.dateformat 属性:

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
​
@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
    private String dateformat;
}

在 UserController 中使用这个类代替 @Value: 

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
​
    @Autowired
    private UserService userService;
​
    @Autowired
    private PatternProperties patternProperties;
​
    @GetMapping("now")
    public String now(){
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));
    }
}

注意: 不是所有的配置都适合放到配置中

           心,维护起来比较麻烦 建议将一些

           关键参数,需要运行时调整的参数

           放到 nacos 配置中心,一般都是自

           定义配置

3. 配置共享

其实微服务启动时,会去 nacos 读取多个

配置文件,

[spring.application.name]-[spring.profiles.active].yaml

    例如:userservice-dev.yaml

[spring.application.name].yaml

    例如:userservice.yaml

而 [spring.application.name].yaml 不包含环

境,因此可以被多个环境共享

(1) 添加一个环境共享配置

在 nacos 中添加一个 userservice.yaml 文件:

(2) 在 uer-service 中读取共享配置

① 在 user-service 服务中,修改 PatternProperties

类,读取新添加的属性:

② 在 user-service 服务中,修改 UserController,

添加一个方法:

(3) 运行两个 UserApplication,使用不同

    profile

修改 UserApplication2 这个启动项,改变其

profile 值:

UserApplication(8081) 使用的 profile 是

dev,UserApplication2(8082) 使用的

profile 是 test

启动 UserApplication 和 UserApplication2,

访问 http://localhost:8081/user/prop,

结果:

显然 dev 和 test 两个环境,都读取到了

envSharedValue 这个属性的值

(4) 配置共享的优先级

① 多环境配置共享

当 nacos、服务本地同时出现相同属性时,优先

级有高低之分:

[服务名]-[环境].yaml > [服务名].yaml > 本地配置

② 多服务配置共享

环境配置 >服务名.yaml > extension-config >

extension-configs > shared-configs > 本地配置

不同微服务之间可以共享配置文件,通过下面的

两种方式来指定:

方式一:

spring:
  application:
    name: userservice # 服务名称
  profiles:
    active: dev # 环境,
  cloud:
    nacos:
      server-addr: localhost:8848 # Nacos地址
      config:
        file-extension: yaml # 文件后缀名
        shared-configs: # 多微服务间共享的配置列表
          - dataId: common.yaml # 要共享的配置文件id

方式二:

spring:
  application:
    name: userservice # 服务名称
  profiles:
    active: dev # 环境,
  cloud:
    nacos:
      server-addr: localhost:8848 # Nacos地址
      config:
        file-extension: yaml # 文件后缀名
        extends-configs: # 多微服务间共享的配置列表
          - dataId: extend.yaml # 要共享的配置文件id

4.  搭建 Nacos 集群

(1) 集群结构图

(2) 搭建集群的基本步骤

① 搭建数据库,初始化数据库表结构

② 下载 nacos 安装包

③ 配置 nacos

④ 启动 nacos 集群

⑤ nginx 反向代理

1) 配置 nacos:

 ① 将配置文件重命名为 cluster.conf,添加内容

127.0.0.1.8845

127.0.0.1.8846

127.0.0.1.8847

② 打开注释,修改账号密码

 ③ 将 nacos 文件夹复制三份,分别命名

     为:nacos1、nacos2、nacos3

     然后分别修改三个文件夹中的

     application.properties,

④ 分别启动三个 nacos 节点:startup.cmd 

2) nginx 反向代理

① 修改conf/nginx.conf文件,配置如下:

② 启动 start nginx.exe

③ 在浏览器访问:http://localhost/nacos

④ 修改 java 代码中的 nacos 地址

3) 优化

① 实际部署时,需要给做反向代理的 nginx

    服务器设置一个域名,这样后续如果有服

    务器迁移 nacos 的客户端也无需更改配置.

② Nacos 的各个节点应该部署到多个不同服

    务器,做好容灾和隔离

八、Feign 远程调用

以前利用 RestTemplate 发起远程调用的代码:

 缺点:

① 代码可读性差,编程体验不统一

② 参数复杂 URL 难以维护

Feign 是一个声明式的 http 客户端,官方地址:

https://github.com/OpenFeign/feign

其作用就是帮助我们优雅的实现 http 请求的发

送,解决上面提到的问题

1. Feign 替代 RestTemplate

(1) 引入依赖

在 order-service 服务的 pom 文件中引入 Feign

的依赖:

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

(2) 添加注解

在 order-service 的启动类添加注解开启 Feign

的功能:

(3) 编写 Feign 的客户端

在 order-service 中新建一个接口,内容如下:

@FeignClient("userservice")
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

这个客户端主要是基于 SpringMVC 的注解来

声明远程调用的信息,比如:

服务名称:userservice

请求方式:GET

请求路径:/user/{id}

请求参数:Long id

返回值类型:User

这样,Feign 就可以帮助我们发送 http 请求,

无需自己使用 RestTemplate 来发送了

(4) 测试

修改 order-service 中的 OrderService 类中的

queryOrderById 方法,使用 Feign 客户端代

替 RestTemplate:

(5) 总结

使用 Feign 的步骤:

① 引入依赖

② 添加 @EnableFeignClients 注解

③ 编写 FeignClient 接口

④ 使用 FeignClient 中定义的方法代替

    RestTemplate

2. 自定义配置

配置 Feign 日志有两种方式:

(1) 配置文件方式

① 局部生效

feign:  
  client:
    config: 
      userservice: # 针对某个微服务的配置
        loggerLevel: FULL #  日志级别 

② 全局生效

feign:  
  client:
    config: 
      default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
        loggerLevel: FULL #  日志级别 

日志的级别分为四种: 

NONE不记录任何日志信息,这是默认值

BASIC:仅记录请求的方法,URL以及响应

              状态码和执行时间

HEADERS:在BASIC的基础上,额外记录

                     了请求和响应的头信息

FULL:记录所有请求和响应的明细,包括头

            信息、请求体、元数据

(2) Java 代码方式

需要先声明一个Bean:

public class DefaultFeignConfiguration  {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.BASIC; // 日志级别为BASIC
    }
}

如果要全局生效,将其放到启动类的

@EnableFeignClients 这个注解中:

@EnableFeignClients(defaultConfiguration =

                DefaultFeignConfiguration .class) 
如果是局部生效,则把它放到对应的

@FeignClient这个注解中:

@FeignClient(value = "userservice", configuration

                = DefaultFeignConfiguration .class) 

3. Feign 使用优化

Feign 底层发起 http 请求,依赖于其它的框架

其底层客户端实现包括:

URLConnection默认实现,不支持连接池

Apache HttpClient :支持连接池

OKHttp:支持连接池

因此提高 Feign 的性能主要手段就是

① 使用连接池代替默认的 URLConnection

② 日志级别,最好用 basic none

Feign 添加 HttpClient 的支持:

(1)引入依赖

在 order-service 的 pom 文件中引入 Apache

的 HttpClient 依赖:

<!--httpClient的依赖 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

(2)配置连接池

在 order-service 的 application.yml 中添加配置:

feign:
  client:
    config:
      default: # default全局的配置
        loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
  httpclient:
    enabled: true # 开启feign对HttpClient的支持
    max-connections: 200 # 最大的连接数
    max-connections-per-route: 50 # 每个路径的最大连接数

4. 最佳实践

Feign 客户端:

UserController: 

 

 代码一样,优化方法有两种:

(1) 继承

给消费者的 FeignClient 和提供者的

controller 定义统一的父接口作为标准

缺陷: 

① 不推荐这种方法,服务端和客户端共用

    接口,会出现服务紧耦合

② 父接口参数列表中的映射不会被继承

    因此 Controller 中必须再次声明方法、

    参数列表、注解

(2) 抽取

将 FeignClient 抽取为独立模块,并且把

接口有关的 POJO、默认的 Feign 配置都

放到这个模块中,提供给所有消费者使用

 基于抽取的最佳实践实现方式:

1) 创建一个 module,命名为 feign-api

    然后引入 feign 的 starter 依赖

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

2) 将 order-service 中编写的 UserClient

     UserDefaultFeignConfiguration

     制到 feign-api 项目中

删除 order-service 中的 UserClient、User、

DefaultFeignConfiguration 等类或接口

3) 在 order-service 中引入 feign-api 的依赖

<dependency>
    <groupId>cn.itcast.demo</groupId>
    <artifactId>feign-api</artifactId>
    <version>1.0</version>
</dependency>

4) 修改 order-service 中的所有与上述三个组

    件有关的 import 部分,改成导入 feign-api

    中的包

5) 重启测试

报错是因为 UserClient 现在在 cn.itcast.feign.clients

包下,而 order-service 的 @EnableFeignClients

注解是在 cn.itcast.order 包下,不在同一个包,

无法扫描到 UserClient

6) 解决扫描包问题

方式一:

指定 Feign 应该扫描的包:

@EnableFeignClients(basePackages = "cn.itcast.feign.clients")

方式二:

指定需要加载的 Client 接口:(精准指定)

@EnableFeignClients(clients = {UserClient.class})

九、Gateway 服务网关

1. 网关的功能特性

① 对用户请求做身份认证权限校验

② 将用户请求路由到微服务,并实现

     负载均衡

③ 对用户请求做限流

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

gateway

zuul

① Zuul 是基于 Servlet 的实现,属于阻塞

    式编程

② SpringCloudGateway 则是基于 Spring5

    中提供的 WebFlux,属于响应式编程

    实现,具备更好的性能。

2. 搭建网关服务基本步骤

(1) 创建 SpringBoot 工程 gateway,引入网关

     依赖和 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>

(2) 编写启动类

@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/开头就符合要求

(4) 启动网关服务进行测试

重启网关,访问 http://localhost:10010/user/1 

时,符合 /user/** 规则,请求转发到 uri:

http://userservice/user/1,得到了结果:

(5) 网关路由的流程图

3. 路由断言工厂 (Route Predicate Factory)

路由配置包括:

路由 id:路由的唯一标示

路由目标 (uri):路由的目标地址,http

                        代表固定地址,lb 代表

                        根据服务名负载均衡

路由断言 (predicates):判断请求是否符合

                要求,符合则转发到路由目的地

路由过滤器 (filters):对请求或响应做处理

在配置文件中写的断言规则只是字符串

这些字符串会被 Predicate Factory 读取

并处理,转变为路由的判断条件

例如 Path=/user/** 是按照路径匹配,这个

规则是由 org.springframework.cloud.gateway.

handler.predicate.PathRoutePredicateFactory

类来处理的

像这样的断言工厂在 SpringCloudGateway

还有十几个

4. 过滤工厂 (GatewayFilter)

GatewayFilter 是网关中提供的一种过滤器,

可以对进入网关的请求和微服务返回的响应

做处理:

(1) 路由过滤器的种类

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

例如:

名称说明
AddRequestHeader给当前请求添加一个请求头
RemoveRequestHeader移除请求中的一个请求头
AddResponseHeader给响应结果中添加一个响应头
RemoveResponseHeader从响应结果中移除有一个响应头
RequestRateLimiter限制请求的流量

(2) 请求头过滤器

例如:给所有进入 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! 

5. 全局过滤器 (GlobalFilter)

(1) 全局过滤器的作用

全局过滤器与过滤器工厂都是处理一切进入

网关的请求和微服务响应

区别:

GatewayFilter 通过配置定义,处理逻辑是

固定的;

GlobalFilter 的逻辑需要自己写代码实现

定义方式是实现 GlobalFilter 接口

public interface GlobalFilter {
    /**
     *  处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
     *
     * @param exchange 请求上下文,里面可以获取Request、Response等信息
     * @param chain 用来把请求委托给下一个过滤器 
     * @return {@code Mono<Void>} 返回标示当前过滤器业务结束
     */
    Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

(2) 自定义全局过滤器

1) 需求:定义全局过滤器,拦截请求,判断

              请求的参数是否满足下面条件:

① 参数中是否有 authorization,

② authorization 参数值是否为 admin

如果同时满足则放行,否则拦截

2) 实现:

自定义类,实现GlobalFilter接口,添加

@Order 注解:

@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.获取请求参数
        MultiValueMap<String, String> 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();
    }
}

(3) 过滤器执行顺序

请求进入网关会碰到三类过滤器:

当前路由的过滤器、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 和

                                    localhost:8081

跨域问题:浏览器禁止请求的发起者与服务端

                 发生跨域 ajax 请求,请求被浏览器

                 拦截的问题

解决方案:CORS

2. 解决方式

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


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值