Spring Cloud Alibaba快速入门及相关组件使用

Spring Cloud Alibaba简介

1、什么是Spring Cloud Alibaba?

Spring Cloud Alibaba是Aalibaba结合自身微服务实践,开源的微服务全家桶。

在Spring Cloud项目中孵化,很可能成为Spring Cloud第二代的标准实现。

在业界广泛使用,已有很多成功案例。

2、Spring Cloud Alibaba真实应用场景?

大型复杂的系统,例如大型电商系统

高并发系统,例如大型门户、秒杀系统

需求不明确,且变更很快的系统,例如创业公司业务系统

3、Spring Cloud Alibaba和Spring Cloud 有什么区别和联系?

Spring Cloud Alibaba是Spring Cloud的子项目,Spring Cloud Alibaba符合Spring Cloud的标准。

是提供微服务开发的一站式解决方案.包含微服务开发的必要组件 。

4、spring cloud的版本说明

第一代版本:Angle
第二代版本:Brixton
第三代版本:Camden
第四代版本:Edgware
第五代版本:Finchley
第六代版本:GreenWich
第七代版本:Hoxton

SpringCloud会以这种方式来发布版本,因为SpringCloud会包含很多子项目的版本,假如我们按照传统的1.1.1.release这种发布的就会给人造成混淆。

SNAPSHOT:快照版本,随时会修改;  Hoxton.BUILD-SNAPSHOT  
M:MileStone,M1表示第一个里程碑版本,一般标注PRE,表示预览版。  Greenwich.M3 PRE   
RC:Release Candidate,候选版本;一般标注PRE,表示预览版。 Greenwich.RC2 PRE 
SR: Service Release,SR1表示第1个正式版本;一般同时标注GA,表示稳定版本。 Greenwich.SR6 GA

5、Springboot SpringCloud SpringCloudalibaba 的版本对应关系

  • 2020.0 分支对应的是 Spring Cloud 2020,最低支持 JDK 1.8。
  • master 分支对应的是 Spring Cloud Hoxton,最低支持 JDK 1.8。
  • greenwich 分支对应的是 Spring Cloud Greenwich,最低支持 JDK 1.8。
  • finchley 分支对应的是 Spring Cloud Finchley,最低支持 JDK 1.8。
  • 1.x 分支对应的是 Spring Cloud Edgware,最低支持 JDK 1.7。

6、Spring Cloud Alibaba重要组件

Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

RocketMQ:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。

Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。

Alibaba Cloud OSS: 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。

Alibaba Cloud SchedulerX: 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。

Alibaba Cloud SMS: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。

[Nacos官网] https://nacos.io/zh-cn/docs/what-is-nacos.html

Nacos介绍

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态 服务发现、服务配置、服务元数据及流量管理。Nacos 的关键特性包括:

  • 服务发现和服务健康监测
  • 动态配置服务
  • 动态 DNS服务
  • 服务及其元数据管理

简单而言,Nacos就是帮助我们管理微服务的组件。

核心功能:

服务注册:Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自 身的元数据,比如ip地址、端口等信息。Nacos Server接收到注册请求后,就会把这些元数据信 息存储在一个双层的内存Map中。

服务心跳:在服务注册后,Nacos Client会维护一个定时心跳来持续通知Nacos Server,说明服 务一直处于可用状态,防止被剔除。默认5s发送一次心跳。

服务同步:Nacos Server集群之间会互相同步服务实例,用来保证服务信息的一致性。

服务发现:服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给 Nacos Server,获取上面注册的服务清单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存

服务健康检查:Nacos Server会开启一个定时任务用来检查注册服务实例的健康情况,对于超过 15s没有收到客户端心跳的实例会将它的healthy属性置为false(客户端服务发现时不会发现),如 果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新 注册)

注册中心

注册中心这一概念在面向服务设计的架构中起着举足轻重的作用,不论是在SOA架构还是微服务架构之中,注册中心的作用一句话概括就是存放和调度服务,实现服务和注册中心,服务和服务之间的相互通信。注册中心可以说是微服务架构中的”通讯录“,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就到这里找到服务的地址,进行调用。

目前市面上用的比较多的服务发现中心有:Nacos、Eureka、Consul和Zookeeper。

Nacos 界面详细介绍

命名空间

通过命名空间,可以对不同的项目的不同的模块进行更好的分离和管理,yml 中通过 sping.cloud.nacos.discovery.namespace=public 来绑定命令空间。

在 Nacos 中通过命名空间(Namespace)+ 分组(Group)+服务名(Name)可以定位到一个唯一的服务实例。

命名空间(Namespace):Nacos 服务中最顶层、也是包含范围最广的概念,用于强制隔离类似环境等场景。Nacos 的服务也需要使用命名空间来进行隔离。 命名空间在 Nacos 控制台的一级目录里可以找到,如下图所示:

在服务列表中也能看到命名空间的身影,如下图所示:

命名空间用法

命名空间默认为 public,在项目开发中,如果不指定命名空间,那么会使用默认值 public。官方推荐使用运行环境来定义命名空间,如生产版本可使用 public,开发版可定义为 private。 在项目开发中,可通过配置“spring.cloud.nacos.discovery.namespace”来定义命名空间,如下图所示:

分组名

分组名(Group):Nacos 中次于命名空间的⼀种隔离概念,区别于命名空间的强制隔离属性,分组属于⼀个弱隔离概念,主要用于逻辑区分⼀些服务使用场景或不同应用的同名服务,最常用的情况主要是同⼀个服务的测试分组和生产分组、或者将应用名作为分组以防止不同应用提供的服务重名。 分组名在 Nacos 控制台的服务列表中可以看到,如下图所示:

分组名默认为 DEFAULT_GROUP,在项目中可通过“spring.cloud.nacos.discovery.group”来设置,如下图所示:

此项可省略,省略时的默认值为 DEFAULT_GROUP。 分组名可以直接在项目中使用,无需像命名空间那样,在使用前还要在控制台中新建,设定了分组名之后,刷新服务列表就可以看到新的分组名称了,如下图所示:

服务详情

服务名:即项目名,对应配置文件中的 spring.application.name

分组:作用与命名空间大致相同,不过命名空间一般用于项目之间的区别,分组一般用作各个开发环境的区别。

永久实例可以通过 ephemeral: false 来设置,对比临时实例,注册临时实例的服务,如果宕机了,nacos 会删除该服务,永久实例不会。

保护阈值

健康保护阈值(ProtectThreshold):为了防止因过多实例故障,导致所有流量全部流入剩余实例,继而造成流量压力将剩余实例被压垮形成雪崩效应。应将健康保护阈值定义为⼀个 0 到 1 之间的浮点数。当域名健康实例数占总服务实例数的比例小于该值时,无论实例是否健康,都会将这个实例返回给客户端。这样做虽然损失了⼀部分流量,但是保证了集群中剩余健康实例能正常工作。 简单来说,保护阈值是一个 0-1 的浮点值,保护阈值是允许集群中健康实例占比的最小值,如果实际健康实例的占比小于或等于设置的保护阈值时,就会触发阈值保护。

权重

权重(Weight):实例的级别配置。权重为浮点数,范围为 0-10000。权重越大,分配给该实例的流量越大。 它是针对服务实例进行设置的,如下图所示:

Nacos 更多配置

https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-discovery

配置项Key默认值说明
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">服务端地址</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">spring.cloud.nacos.discovery.server-addr</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">无</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">Nacos Server 启动监听的ip地址和端口</font>
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">服务名</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">spring.cloud.nacos.discovery.service</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">${spring.application.name}</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">给当前的服务命名</font>
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">服务分组</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">spring.cloud.nacos.discovery.group</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">DEFAULT_GROUP</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">设置服务所处的分组</font>
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">权重</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">spring.cloud.nacos.discovery.weight</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">1</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">取值范围 1 到 100,数值越大,权重越大</font>
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">网卡名</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">spring.cloud.nacos.discovery.network-interface</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">无</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">当IP未配置时,注册的IP为此网卡所对应的IP地址,如果此项也未配置,则默认取第一块网卡的地址</font>
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">注册的IP地址</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">spring.cloud.nacos.discovery.ip</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">无</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">优先级最高</font>
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">注册的端口</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">spring.cloud.nacos.discovery.port</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">-1</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">默认情况下不用配置,会自动探测</font>
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">命名空间</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">spring.cloud.nacos.discovery.namespace</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">无</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">常用场景之一是不同环境的注册的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。</font>
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">AccessKey</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">spring.cloud.nacos.discovery.access-key</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">无</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">当要上阿里云时,阿里云上面的一个云账号名</font>
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">SecretKey</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">spring.cloud.nacos.discovery.secret-key</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">无</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">当要上阿里云时,阿里云上面的一个云账号密码</font>
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">Metadata</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">spring.cloud.nacos.discovery.metadata</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">无</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">使用Map格式配置,用户可以根据自己的需要自定义一些和服务相关的元数据信息</font>
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">日志文件名</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">spring.cloud.nacos.discovery.log-name</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">无</font>
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">集群</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">spring.cloud.nacos.discovery.cluster-name</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">DEFAULT</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">配置成Nacos集群名称</font>
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">接入点</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">spring.cloud.nacos.discovery.enpoint</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">UTF-8</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">地域的某个服务的入口域名,通过此域名可以动态地拿到服务端地址</font>
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">是否集成Ribbon</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">ribbon.nacos.enabled</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">true</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">一般都设置成true即可</font>
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">是否开启Nacos Watch</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">spring.cloud.nacos.discovery.watch.enabled</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">true</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">可以设置成false来关闭 watch</font>
<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">注册的IP地址类型</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">spring.cloud.nacos.discovery.ip-type</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">IPv4</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">可以配置IPv4和IPv6两种类型</font>

项目搭建 父项目依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.14</version>
</parent>

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

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

DependencyManagement和Dependencies

Maven使用dependencyManagement元素来提供了一种管理依赖版本号的方式。

通常会在一个组织或者项目的最顶层的父POM中看到dependencyManagement元素。

使用pom.xml中的dependencyManagement元素能让所有在子项目中引用个依赖而不用显式的列出版本量。

Maven会沿着父子层次向上走,直到找到一个拥有dependencyManagement元素的项目,然后它就会使用这个dependencyManagement元素中指定的版本号。

这样做的好处就是:如果有多个子项目都引用同一样依赖,则可以避免在每个使用的子项目里都声明一个版本号,这样当想升级或切换到另一个版本时,只需要在顶层父容器里更新,而不需要一个一个子项目的修改;另外如果某个子项目需要另外的一个版本,只需要声明version就可。

(1)dependencyManagement里只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖。

(2)如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom。

(3)如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。

RestTemplate 简介

RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法。

先来看使用 RestTemplate 发送 GET 请求。在 RestTemplate 中,和 GET 请求相关的方法有如下几个:getForEntity 和 getForObject,每一类有三个重载方法

getForEntity

既然 RestTemplate 发送的是 HTTP 请求,那么在响应的数据中必然也有响应头,如果开发者需要获取响应头的话,那么就需要使用 getForEntity 来发送 HTTP 请求,此时返回的对象是一个 ResponseEntity 的实例。这个实例中包含了响应数据以及响应头。

getForObject

getForObject 方法和 getForEntity 方法类似,getForObject 方法也有三个重载的方法,参数和 getForEntity 一样,这两个的差异主要体现在返回值的差异上, getForObject 的返回值就是服务提供者返回的数据,使用 getForObject 无法获取到响应头。

总结

按Rest风格去理解RestTemplate就对了。

按照HTTP的请求结构(请求头、请求行和请求体)和响应结构(响应头、响应行和响应体)去使用RestTemplate就对了。

Ribbon是springcloud中客户端负载均衡的组件。

我们在微服务架构中,往往通过RestTemplate发送RPC请求,然后通过Ribbon做客户端负载均衡。

Spring Cloud Ribbon 是 Netflix Ribbon 实现的一套客户端 负载均衡工具

简单的说,Ribbon 是 Netflix 发布的开源项目,主要功能是提供 客户端的复杂均衡算法和服务调用。 Ribbon 客户端组件提供一系列完善的配置项如超时、重试等。简单的说,就是配置文件中列出 load Balancer (简称 LB)后面所有的机器,Ribbon 会自动的帮助你基于某种规则(如简单轮询,随机链接等)去链接这些机器。我们很容易使用 Ribbon 自定义的负载均衡算法。

状态 - <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">停更进维</font>

替代方案 - <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">Spring Cloud Loadbalancer</font>

(1)LB负载均衡(Load Balance)是什么

简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA (高可用)。

常见的负载均衡有 ( 用于服务器端:Nginx, 用于客户端:Ribbon,Feign)

Netflix Ribbon已经闭源了,所以spring官方自己开发了客户端负载均衡器 spring-cloud-loadbalancer,属于Spring Cloud Common的一部分,但是就实现的负载均衡算法而言,Ribbon实现更为丰富。

ribbon只支持RestTemplate, spring-cloud-loadbalancer支持RestTemplate和WebClient

客户端的负载均衡

例如spring cloud中的ribbon,客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,这是客户端负载均衡;即在客户端就进行负载均衡算法分配。

服务端的负载均衡

例如Nginx,通过Nginx进行负载均衡,先发送请求,然后通过负载均衡算法,在多个服务器之间选择一个进行访问;即在服务器端再进行负载均衡算法分配。

7种负载均衡策略

1.轮询策略

轮询策略:RoundRobinRule,按照一定的顺序依次调用服务实例。比如一共有 3 个服务,第一次调用服务 1,第二次调用服务 2,第三次调用服务3,依次类推。此策略的配置设置如下:

springcloud-nacos-provider: # nacos中的服务id
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #设置负载均衡

2.权重策略

权重策略:WeightedResponseTimeRule,根据每个服务提供者的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性也就越低。它的实现原理是,刚开始使用轮询策略并开启一个计时器,每一段时间收集一次所有服务提供者的平均响应时间,然后再给每个服务提供者附上一个权重,权重越高被选中的概率也越大。此策略的配置设置如下:

springcloud-nacos-provider: # nacos中的服务id
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule

3.随机策略

随机策略:RandomRule,从服务提供者的列表中随机选择一个服务实例。此策略的配置设置如下:

springcloud-nacos-provider: # nacos中的服务id
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #设置负载均衡

4.最小连接数策略

最小连接数策略:BestAvailableRule,也叫最小并发数策略,它是遍历服务提供者列表,选取连接数最小的⼀个服务实例。如果有相同的最小连接数,那么会调用轮询策略进行选取。此策略的配置设置如下:

springcloud-nacos-provider: # nacos中的服务id
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule #设置负载均衡

5.重试策略

重试策略:RetryRule,按照轮询策略来获取服务,如果获取的服务实例为 null 或已经失效,则在指定的时间之内不断地进行重试来获取服务,如果超过指定时间依然没获取到服务实例则返回 null。此策略的配置设置如下:

ribbon:
  ConnectTimeout: 2000 # 请求连接的超时时间
  ReadTimeout: 5000 # 请求处理的超时时间
springcloud-nacos-provider: # nacos 中的服务 id
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #设置负载均衡

6.可用性敏感策略

可用敏感性策略:AvailabilityFilteringRule,先过滤掉非健康的服务实例,然后再选择连接数较小的服务实例。此策略的配置设置如下:

springcloud-nacos-provider: # nacos中的服务id
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.AvailabilityFilteringRule

7.区域敏感策略

区域敏感策略:ZoneAvoidanceRule,根据服务所在区域(zone)的性能和服务的可用性来选择服务实例,在没有区域的环境下,该策略和轮询策略类似。此策略的配置设置如下:

springcloud-nacos-provider: # nacos中的服务id
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.ZoneAvoidanceRule

注册RestTemplate的Bean

@Bean
@LoadBalanced  // 负载均衡注解
public RestTemplate restTemplate(){
    return new RestTemplate();
}

[Ribbon]

Ribbon 是 Netflix开源的基于HTTP和TCP等协议负载均衡组件

Ribbon 可以用来做客户端负载均衡,调用注册中心的服务

Ribbon的使用需要代码里手动调用目标服务

[Feign]

Feign是Spring Cloud组件中的一个轻量级<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">RESTful</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">HTTP服务客户端</font>

Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。

Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务

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

[OpenFeign]

OpenFeign是Spring Cloud 在Feign的基础上支持了Spring MVC的注解,如@RequesMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

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

需要在启动类上加@EnableFeignClients

@FeignClient标签的常用属性如下

  • <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">value</font>: 服务名
  • <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">name</font>: 指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现
  • <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">url</font>: url一般用于调试,可以手动指定@FeignClient调用的地址
  • <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">decode404</font>:当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException
  • <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">configuration</font>: Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract
  • <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">fallback</font>: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口
  • <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">fallbackFactory</font>: 工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
  • <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">path</font>: 定义当前FeignClient的统一前缀

【JAVA----工具类】MAPSTRUCT,VO、DTO、ENTITY之间的转换工具

介绍

在工作中,我们经常要进行各种对象之间的转换。

  • PO:persistent object 持久对象,对应数据库中的一条记录
  • VO:view object 表现层对象,最终返回给前端的对象
  • DTO:data transfer object数据传输对象,服务之间传输的对象

如果这些对象的属性名相同还好,可以用如下工具类赋值

  • Spring BeanUtils
  • Cglib BeanCopier
  • 避免使用Apache BeanUtils,性能较差

如果属性名不同呢?如果是将多个PO对象合并成一个VO对象呢?好在有MapStruct神器,可以帮助我们快速转换在pom文件中加入如下依赖即可

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.2.Final</version>
</dependency>

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.2.Final</version>
</dependency>

对象互转

@Data
public class StudentPO {
    private Integer id;
    private String name;
    private Integer age;
    private String className;
}
@Data
public class StudentVO {
    private Integer id;
    private String studentName;
    private Integer studentAge;
    private String schoolName;
}
@Mapper
public interface StudentMapper {
    StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);
    @Mappings({
            @Mapping(source = "name", target = "studentName"),
            @Mapping(source = "age", target = "studentAge")
    })
    StudentVO po2Vo(StudentPO studentPO);
}

Gateway服务网关

[官方文档] https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/?spm=a2c6h.12873639.article-detail.6.595f617b3xSAJ8#configuring-route-predicate-factories-and-gateway-filter-factories

1、什么是服务网关

传统的单体架构中只需要开放一个服务给客户端调用,但是微服务架构中是将一个系统拆分成多个微服务,如果没有网关,客户端只能在本地记录每个微服务的调用地址,当需要调用的微服务数量很多时,它需要了解每个服务的接口,这个工作量很大。那有了网关之后,能够起到怎样的改善呢?

网关作为系统的唯一流量入口,封装内部系统的架构,所有请求都先经过网关,由网关将请求路由到合适的微服务,所以,使用网关的好处在于:

(1)简化客户端的工作。网关将微服务封装起来后,客户端只需同网关交互,而不必调用各个不同服务;(2)降低函数间的耦合度。 一旦服务接口修改,只需修改网关的路由策略,不必修改每个调用该函数的客户端,从而减少了程序间的耦合性(3)解放开发人员把精力专注于业务逻辑的实现。由网关统一实现服务路由(灰度与ABTest)、负载均衡、访问控制、流控熔断降级等非业务相关功能,而不需要每个服务 API 实现时都去考虑

API网关作用就是把各个服务对外提供的API汇聚起来,让外界看起来是一个统一的接口。同时也可在网关中提供额外的功能。

总结:网关就是所有项目的一个统一入口。

2、作用

身份认证:用户能不能访问

服务路由:用户访问到那个服务中去

负载均衡:一个服务可能有多个实例,甚至集群,负载均衡就是你的请求到哪一个实例上去

3、API网关的组成

API网关由路由转发和过滤器(编写额外功能)组成。

3.1 路由转发

接收外界请求,通过网关的路由转发,转发到后端的服务上。

3.2 过滤器

网关非常重要的功能就是过滤器。在Gateway中,过滤器默认提供了25种内置功能还支持额外的自定义功能。

对于我们来说比较常用的功能有网关的容错、限流以及请求及相应的额外处理等。

Spring Cloud Gateway介绍

1、Spring Cloud Gateway简介

Spring Cloud Gateway是Spring Cloud 的二级子项目,提供了用于在Spring网络流程中构建API网关的代码库,旨在提供一个简单有效的方式,路由到API;并提供了必要的各种功能,如:路由、权限安全、监控/指标等。

2、Spring Cloud Gateway名词

在学习Gateway时里面有一些名词需要提前了解,这对于后面的学习是很有帮助的。

2.1 Route - 路由

Route中文称为路由,Gateway里面的Route是主要学习内容,一个Gateway项目可以包含多个Route。

一个路由包含ID、URI、Predicate集合、Filter集合。在Route中ID是自定义的,URI就是一个地址。剩下的Predicate和Filter学习明白了,Route就学习清楚了。

2.2 Predicate - 谓词

谓词是学习Gateway非常重要的一点,简单点理解:谓词就是一些路由前的条件和内容。

2.3 Filter - 过滤器

在Gateway运行过程中,Filter负责在路由后,代理服务“之前”或“之后”做的一些事情。

Gateway入门案例

1. 准备Nacos注册中心

使用前面课程中的Nacos注册中心即可。

2. 准备一个微服务工程

使用前面课程中微服务工程即可。

3. 搭建Gateway网关微服务

网关项目依赖
<!--服务发现 需要在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-gateway</artifactId>
</dependency>
配置
server:
  port: 10000
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848 #nacos地址
    gateway:
      routes:
        - id: user-service # 路由标示,必须唯一
          uri: lb://userservice # 路由的目标地址
          predicates: # 路由断言,判断请求是否符合规则
            - Path=/user/** # 路径断言,判断路径是否以/user开头,如果是则符合,跳转路由
          filters:
            - StripPrefix=1   # 去掉一层路径
        - id: order-service
          uri: lb://orderservice
          predicates:
            - Path=/order/**
路由 Route:

(1)id:路由标识,要求唯一,名称任意(默认值 uuid,一般不用,需要自定义)

(2)uri:请求最终被转发到的目标地址

(3)order: 路由优先级,数字越小,优先级越高

(4)predicates:断言数组,即判断条件,如果返回值是boolean,则转发请求到 uri 属性指定的服务中

(5)filters:过滤器数组,在请求传递过程中,对请求做一些修改

谓词-Predicate

谓词:当满足条件在进行路由转发。

在Spring Cloud Gateway中谓词实现GatewayPredicate接口。其中类名符合:XXXRoutePredicateFactory,其中XXX就是在配置文件中谓词名称。

所有的谓词都设置在predicates属性中,当设置多个谓词时取逻辑与条件,且一个谓词只能设置一组条件,如果需要有个多条件,添加多个相同谓词。

1. Path

用于匹配路由地址规则的谓词。

1.1 使用方式

修改配置文件application.yml:

spring:
  application:
    name: cloud-gateway
  cloud: 
    gateway:
      routes:  # 配置多路由策略的属性,类型是List。配置方案是:回车 + 缩进 + - + 空格。集合中的每个对象的属性,对齐多行配置
        - id: application-service # 路由的唯一名称
          uri: lb://application-service # 规则满足后,转发到的地址。lb是spring cloud gateway支持的一种协议名
          predicates: # 谓词
            - Path=/service/**  # 路由地址规则
          filters:  # 过滤器,先使用,后续课程细致讲解。后续案例配置统一,文档中省略
            - StripPrefix=1

2. Query

用于校验请求中是否包含指定的请求参数,同时可也校验请求参数值是否符合要求。

注意:只能校验请求地址参数,也就是 /path?参数

2.1 使用方式
2.1.1 请求必须包含指定参数

下述配置代表,请求中必须包含命名为name的参数。

修改配置文件application.yml:

- id: application-service
  uri: lb://application-service 
  predicates: # 谓词
    - Path=/service/**  
    - Query=name # 请求参数必须包含name
2.1.2请求中必须包含指定参数,且参数值必须满足某要求

下述配置代表,请求中必须包含命名为name和age的参数,且参数值必须已’gangge'开头。下述配置中gangge.*是一个正则表达式。在正则表达式中点(.)表示匹配任意一个字符。所以当请求参数name=gangge或name=ganggeAdmin等都能满足谓词条件。

如果设定请求中必须包含多个参数及值。则设置多个Query。在此处演示多个相同谓词配置,其他谓词中就不在强调如何配置多个谓词。

修改配置文件application.yml:

- id: application-service 
  uri: lb://application-service 
  predicates:
    - Path=/service/**  
    - Query=name,gangge.* # 请求参数必须包含name,请求参数的值必须以 gangge 开头
    - Query=age # 请求参数必须包含age

3. Header

用于匹配路由地址规则的谓词。

用于校验请求中是否包含指定的请求头,同时可也校验请求头数值是否符合要求。配置方式和Query类似。

3.1 使用方式

修改配置文件application.yml:

- id: application-service
  uri: lb://application-service
  predicates:
    - Path=/service/**  
    - Query=name,gangge.* 
    - Query=age
    - Header=Host,.* # 请求头必须有Host,值为任意字符串

4. Method

Method表示请求方式。支持多个值,使用逗号分隔,多个值之间为or条件。

4.1 使用方式

修改配置文件application.yml:

- id: application-service
  uri: lb://application-service
  predicates:
    - Path=/service/** 
    - Method=GET,POST # 请求方式必须是GET或POST

5. RemoteAddr

允许访问的客户端地址。

5.1 使用方式

修改配置文件application.yml:

- id: application-service
  uri: lb://application-service
  predicates:
    - Path=/service/** 
    - RemoteAddr=192.168.41.252 # 客户端IP必须是192.168.41.252

6. Host

匹配请求中Host请求头的值。满足Ant模式(之前在Spring Security中学习过)可以使用:

  • ? 匹配一个字符
  • * 匹配0个或多个字符
  • ** 匹配0个或多个目录
6.1 使用方式

修改配置文件application.yml:

- id: application-service
  uri: lb://application-service
  predicates:
    - Path=/service/** 
    - Host=127.0.0.1:9999 # 请求头Host值必须是127.0.0.1:9999

7. Cookie

要求请求中包含指定Cookie名和满足特定正则要求的值。

Cookie必须有两个值,第一个Cookie包含的参数名,第二个表示参数对应的值,正则表达式。不支持一个参数写法。

7.1 使用方式

修改配置文件application.yml:

- id: application-service
  uri: lb://application-service
  predicates:
    - Path=/service/** 
    - Cookie=name,gangge.* # 请求必须包含名称是name,值符合gangge开头的cookie。

8. Before

在指定时间点之前。

8.1 使用方式

修改配置文件application.yml:

- id: application-service
  uri: lb://application-service
  predicates:
    - Path=/service/** 
    - Before=2022-10-01T18:00:00.000+08:00[Asia/Shanghai] # 2022-10-01晚18点前可以访问

9. After

在指定时间点之后。

9.1 使用方式

修改配置文件application.yml:

- id: application-service
  uri: lb://application-service
  predicates:
    - Path=/service/** 
    - After=2020-10-01T08:00:00.000+08:00[Asia/Shanghai] # 2020-10-01早8点后可以访问

10. Between

请求时必须在设定的时间范围内,才进行路由转发。

10.1 使用方式

修改配置文件application.yml:

- id: application-service
  uri: lb://application-service
  predicates:
    - Path=/service/** 
    - Between=2023-10-01T08:00:00.000+08:00[Asia/Shanghai],2023-10-01T18:00:00.000+08:00[Asia/Shanghai] # 2023-10-01早8点后,2023-10-01晚18点前可以访问

过滤器-Filter

1. Filter的作用

在路由转发到代理服务之前和代理服务返回结果之后额外做的事情。Filter是在路由转发之后,被代理的服务执行前后运行的。只要Filter执行了,说明一定满足了谓词条件。 在Spring Cloud Gateway的路由中Filter分为:

  • 路由过滤器:框架内置的Filter实现都是路由过滤器,都是GatewayFilter实现类型。
  • 全局过滤器:框架未内置全局过滤器实现,需自定义。全局过滤器需实现接口GlobalFilter。

2. 路由过滤器

之前使用StripPrefix就是框架内置的路由过滤器,所有内置Filter都实现GatewayFilter接口。使用时Filters属性中过滤器名为XXXGatewayFilterFactory的类对应的名称为XXX。

所有的过滤器都设置在filters属性中,且一个过滤器只能设置一组附加功能,如果需要有个多附加功能,添加多个相同过滤器。

3. StripPrefix

跳过路由uri中前几段后发送给下游。

3.1 使用方式

后续案例只提供filters属性部分配置。

routes:  
        - id: application-service 
          uri: lb://application-service
          predicates: 
            - Path=/service/**  
          filters:  # 过滤器
            - StripPrefix=1 # 跳过路由uri中前1段后发送给下游。

4. AddRequestHeader

添加请求头参数,参数名和值之间使用逗号分隔。

4.1 使用方式
filters:
  - StripPrefix=1
  - AddRequestHeader=company,gangge

5. AddRequestParameter

添加请求表单参数,多个参数需要有多个过滤器。

5.1 使用方式
filters:
  - StripPrefix=1
  - AddRequestParameter=name,gangge
  - AddRequestParameter=age,18

6. AddResponseHeader

添加响应头。

6.1 使用方式
filters:
  - StripPrefix=1
  - AddResponseHeader=company,gangge

7. DedupeResponseHeader

对指定响应头去重复。配置语法:DedupeResponseHeader=响应头参数 或 DedupeResponseHeader=响应头参数,strategy。

去重策略strategy可选值:

  • RETAIN_FIRST :默认值,保留第一个
  • RETAIN_LAST 保留最后一个
  • RETAIN_UNIQUE 保留唯一的,出现重复的属性值,会保留一个。例如有两个My:bbb的属性,最后会只留一个。
7.1 使用方式
filters:
  - StripPrefix=1
  - DedupeResponseHeader=MyHeader,RETAIN_UNIQUE

8. CircuitBreaker

实现熔断时使用,支持CircuitBreaker和Hystrix两种。

9. FallbackHeaders

可以添加降级时的异常信息。

10. PrefixPath

匹配所有前缀满足条件的URI。

11. RequestRateLimiter

限流过滤器。后续章节详细讲解

12. RedirectTo

重定向。有两个参数,status和url。其中status应该300系列重定向状态码。

13. RemoveRequestHeader

删除请求头参数。

14. RemoveResponseHeader

删除响应头参数。

15. RemoveRequestParameter

删除请求参数。

16. RewritePath

重写请求路径。

17. RewriteResponseHeader

重写响应头参数。

18. SaveSession

如果项目中使用Spring Security和Spring Session整合时,此属性特别重要。

19. SecureHeaders

具有权限验证时,建议的头信息内容。

20. SetPath

功能和StripPrefix有点类似。语法更贴近restful。

20.1 使用方式
- id: setpath_route 
  uri: https://example.org 
  predicates: 
    - Path=/api/{segment} 
  filters: 
    - SetPath=/{segment}

21. SetRequestHeader

替换请求参数头数。不是添加。

22. SetResponseHeader

替换响应头参数。

23. SetStatus

设置响应状态码。

24. Retry

设置重试次数。

25. RequestSize

请求最大大小。包含maxSize参数,单位包括“KB”或“MB”等。默认为“B”。

26. ModifyRequestBody

修改请求体内容。

27. ModifyResponseBody

修改响应体

自定义过滤器

1. 自定义全局过滤器

1.1 全局过滤器特性

全局过滤器不需要工厂,也不需要配置,只要被Spring容器管理,默认对所有的路由都生效。 可以使用GlobalFilter实现统一的权限验证、日志记录等希望对所有代理的项目都生效的内容都可以定义在全局过滤器中。且在项目中可以定义多个GlobalFilter的实现类。都可以自动执行。

1.2 编辑全局过滤器
/**
 * 自定义全局过滤器。
 * 必须实现接口GlobalFilter
 * 当前类型的对象,必须被spring容器管理。
 * 无须配置,所有路由都生效。
 *
 * 执行顺序:
 *  先执行网关过滤器,后执行全局过滤器
 *  多个全局过滤器,执行顺序由Spring boot扫描管理当前对象的顺序决定。
 *  每个过滤器,都是完整执行后,才执行下一个过滤器。
 */
@Component
public class MyGlobalFilter implements GlobalFilter {
    /**
     * 过滤方法。
     * 实现上,只有唯一的要求。必须调用方法chain.filter(exchange),并把方法的返回值,返回。
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("前置全局过滤");
        Mono<Void> result = chain.filter(exchange);
        System.out.println("后置全局过滤");
        return result;
    }
}

2. 自定义路由过滤器

2.1 路由过滤器自定义要求

定义针对于Router的Filter。必须经由配置才能生效。注意:

  1. 类名必须定义为XxxGatewayFilterFactory。注入到Spring容器后使用时的名称就叫做xxx。
  2. 类建议继承AbstractGatewayFilterFactory。如果不继承,则必须实现接口GatewayFilterFactory,这种方式开发成本高。
  3. 所有需要传递进来的参数都配置到当前类的静态内部类Config中。
/**
 * 自定义网关过滤器(路由过滤器),必须经过配置使用才能生效的过滤器。
 * 要求当前类型的对象必须被spring容器管理。
 * 要求必须实现接口 GatewayFilterFactory, 建议继承AbstractGatewayFilterFactory
 *
 * 多个网关过滤器执行顺序:
 *  按照配置文件中,过滤器的配置顺序,依次运行。每个过滤器完整运行结束后,执行下一个过滤规则。
 */
@Component
public class LoggerFilterGatewayFilterFactory
            extends AbstractGatewayFilterFactory<LoggerFilterGatewayFilterFactory.Config> {

    /**
     * 建议提供2个构造方法。一个无参数。一个有参数,参数类型就是当前类型中的Config静态内部类的类对象类型。
     * 父类型,可以帮助解析配置文件,并创建Config对象。
     */
    public LoggerFilterGatewayFilterFactory(){
        this(Config.class);
    }

    public LoggerFilterGatewayFilterFactory(Class<Config> configClass){
        super(configClass);
    }

    /**
     * 如果需要简化配置方案。提供方法shortcutFieldOrder
     * 有当前方法,配置文件使用,可以简化配置为    LoggerFilter=abc
     * 没有当前方法,配置文件完整编写,内容是:
     *   name: LoggerFilter
     *   args:
     *     remark: abc
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("remark");
    }

    /**
     * 创建网关过滤器的方法。
     * @param config 就是配置文件中的内容,就是当前类型中的静态内部类对象。
     * @return 一般使用匿名内部类,创建GatewayFilter接口的实现对象。
     */
    @Override
    public GatewayFilter apply(Config config) {

        return new GatewayFilter() {
            /**
             * 过滤方法。要求必须调用chain.filter(exchange),并返回方法的返回结果
             * @param exchange
             * @param chain
             * @return
             */
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                System.out.println("前置 - 日志过滤器 - config.remark = " + config.getRemark());
                Mono<Void> result = chain.filter(exchange);
                System.out.println("后置 - 日志过滤器 - config.remark = " + config.getRemark());

                return result;
            }
        };
    }

    /**
     * 定义静态内部类,作为配置对象
     * 定义的每个属性,都是用于在配置文件中配置的对应属性。
     * 必须提供getter和setter方法。
     */
    public static class Config{
        private String remark;

        public String getRemark() {
            return remark;
        }

        public void setRemark(String remark) {
            this.remark = remark;
        }
    }
}
2.3 编辑配置文件
- id: logger
  uri: lb://application-service
  predicates:
    - Path=/logger/**
  filters:
    - StripPrefix=1
    - LoggerFilter=simpleTestGatewayFilter
    - name: LoggerFilter
        args:
          remark: fullyTestGatewayFilter

全局过滤器接口方法描述

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

接口代码:

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

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

  • 参数中是否有authorization
@Order(0) //顺序注解:指定过滤器的顺序。(也可以通过Ordered接口实现)
@Component
public class AuthorizeFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String, String> params = request.getQueryParams();
        //2.获取参数中的 authorization参数
        String auth = params.getFirst("authorization");
        if(auth != null){
            //3.有该参数就放行
            return chain.filter(exchange);
        }
        //3 设置状态码  // 该状态码为401
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();
    }
 
}

这里是我写的案例:

  1. 全局过滤器验证是否登录 响应JSON形式
@Component
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory{

    @Autowired // 注入common模块的redis工具类
    RedisUtil redisUtil;

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange,chain)->{
            System.out.println("进入过滤器");
            // 获得请求路径
            String path = exchange.getRequest().getURI().getPath();
            System.out.println(path);
            // 如果是请求登录与注销就直接放行  不验证
            if(path.endsWith("login") || path.endsWith("logout")){
                return chain.filter(exchange);
            }
            // 从请求头中获取token
            String token = exchange.getRequest().getHeaders().getFirst("token");
            System.out.println("请求的token:"+token);
            // 如果请求中携带了token
            if(token!=null){
                // 从redis查询是否存在该token 存在表示登录了
                SysUser userToken = (SysUser) redisUtil.get(token);
                if(userToken != null){
                    System.out.println("已登录");
                    // 每次请求重置会话时间  测试  会话时间为2分钟
                    redisUtil.expire(token,60*2);
                    return chain.filter(exchange);
                }
            }
            System.out.println("未登录");
            // 当没有登录或会话结束后 响应401状态码 并返回JSON数据
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.OK);
            byte[] bytes = JSON.toJSONBytes(R.err("当前未登录"));
            DataBuffer db = response.bufferFactory().wrap(bytes);
            return response.writeWith(Flux.just(db));
        };
    }

}
  1. 网关整合shiro 路由过滤器 验证是否具有权限访问某服务
public class RoleGatewayFilterFactory extends AbstractGatewayFilterFactory<RoleGatewayFilterFactory.MyConfig>{

    public RoleGatewayFilterFactory(){
        super(MyConfig.class);
    }


    @Autowired
    RedisUtil redisUtil;
    @Autowired
    SysUserFeign userFeign;

    @Override
    public GatewayFilter apply(MyConfig config) {
        return (exchange,chain)->{
            System.out.println("进入角色验证过滤器");
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();
            // 获得请求路径
            String path = request.getURI().getPath();
            // 如果是请求登录与注销就直接放行  不验证
            if(path.endsWith("login") || path.endsWith("logout")){
                return chain.filter(exchange);
            }

            String token = request.getHeaders().getFirst("token");
            // 从redis取出当前登录用户、角色与权限
            SysUser user = (SysUser) redisUtil.get(token);
            List<String> roles = (List<String>) redisUtil.get(user.getUsername()+"-roles");
            List<String> perms = (List<String>) redisUtil.get(user.getUsername()+"-perms");
            System.out.println(Arrays.toString(config.getRole()));
            // 验证是否具有指定的角色
            for (String role : config.getRole()) {
                if(roles.contains(role)){
                    return chain.filter(exchange);
                }
            }

            // 没有角色就 给与对应响应
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            byte[] bytes = JSON.toJSONBytes(R.err("您不具备该访问权限!!!"));
            DataBuffer db = response.bufferFactory().wrap(bytes);
            return response.writeWith(Mono.just(db));
        };
    }



    @Getter
    @Setter
    public static class MyConfig{
        private String[] role;
    }
}

路由配置(这里复制过来没对齐)

- id: test-user  # 这里是配置某个微服务的id 自定义 唯一就行
          uri: lb://test-user  # 这里配置微服务地址  lb=loadBlanced 负载均衡
          predicates: # 谓词工厂 需要满足指定条件才能访问
            - Path=/user/**
          filters:
            - name: Role
              args:
                role: ["经理","组长"]

网关中调用Feign接口

一般不需要,如果需要在网关服务中 使用Feign调用其他服务 需要如下配置

/**
 * 解决网关调用Feign报错
 */
@Configuration
public class FeignConfig {

    @Bean
    public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
        return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
    }

}

配置跨域

在网关项目中加入该配置类

@Configuration
public class CorsConfig {
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*");//允许所有请求方法,例如get,post等
        config.addAllowedOriginPattern("*");//允许所有的请求来源
        config.addAllowedHeader("*");//允许所有请求头
        config.setAllowCredentials(true); //允许携带cookie

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        //对所有经过网关的请求都生效
        source.registerCorsConfiguration("/**", config);

        return new CorsWebFilter(source);
    }
}

[sentinel官网] https://sentinelguard.io/zh-cn/docs/introduction.html

Sentinel 介绍

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。

Sentinel使用

在dependencies中添加依赖,即可整合Sentinel

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

搭建Sentinel控制台

从官网下载后 运行命令启动控制台:<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">java -jar sentinel-dashboard.jar</font>

打开浏览器,输入http://localhost:8080进入控制台页面(账号密码默认sentinel)

整合到Cloud项目

spring:
  cloud:
    sentinel:
      transport:
        # 指定sentinel控制台地址
        dashboard: localhost:8080

这样,就为应用整合好Sentinel了,应用发生请求后,控制台如下:

流控规则

  • 资源名:唯一名称,默认请求路径
  • 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
  • 阈值类型/单机阈值:
    • QPS(每秒钟的请求数量):当调用该api的QPS达到阈值的时候,进行限流
    • 线程数:当调用该api的线程数达到阈值的时候,进行限流
  • 是否集群:不需要集群,暂不研究
  • 流控模式:
    • 直接:api达到限流条件时,直接限流
    • 关联:当关联的资源达到阈值时,就限流自己

- <font style="color:rgb(51, 51, 51);">链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api级别的针对来源】</font>
  • 流控效果:
    • 快速失败:直接失败,抛异常
    • Warm Up:根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值
    • 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效

参数

Field说明默认值
resource资源名,资源名是限流规则的作用对象
count限流阈值
grade限流阈值类型,QPS 或线程数模式QPS模式
limitApp流控针对的调用来源default,代表不区分调用来源
strategydefault,代表不区分调用来源根据资源本身
controlBehavior流控效果(直接拒绝 / 排队等待 / 慢启动模式)直接拒绝

降级规则

降级策略:

  • RT:平均响应时间(秒级统计)超出阈值且在时间窗口内的请求 >= 5时,触发降级;时间窗口结束后,关闭降级【Sentinel默认最大的RT为4900ms,可以通过-Dcsp.sentinel.statistic.max.rt=xxx修改】
  • 异常比例:QPS >= 5 且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
  • 异常数:异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级【时间窗口 < 60秒可能会出现问题】

Sentinel 提供以下几种熔断策略:

  • 慢调用比例 (<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">SLOW_REQUEST_RATIO</font>):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">statIntervalMs</font>)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
  • 异常比例 (<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">ERROR_RATIO</font>):当单位统计时长(<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">statIntervalMs</font>)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">[0.0, 1.0]</font>,代表 0% - 100%。
  • 异常数 (<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">ERROR_COUNT</font>):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

参数

Field说明默认值
resource资源名,即规则的作用对象
grade熔断策略,支持慢调用比例/异常比例/异常数策略慢调用比例
count慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值
timeWindow熔断时长,单位为 s
minRequestAmount熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入)5
statIntervalMs统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入)1000 ms
slowRatioThreshold慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)

热点规则

在时间窗口以内,一旦该api指定索引的参数QPS达到了域名,就会触发限流

  • 参数索引:从0开始,上面的代码中:a的参数索引为0;b的参数索引为1【参数索引对应的参数必须时基本类型或者String】

参数

Field说明默认值
resource资源名,必填
count限流阈值,必填
grade限流模式QPS 模式
durationInSec统计窗口时间长度(单位为秒)1s
controlBehavior流控效果(支持快速失败和匀速排队模式)快速失败
maxQueueingTimeMs最大排队等待时长(仅在匀速排队模式生效)0ms
paramIdx热点参数的索引,必填,对应 SphU.entry(xxx, args) 中的参数索引位置
paramFlowItemList参数例外项,可以针对指定的参数值单独设置限流阈值,不受前面 count 阈值的限制。仅支持基本类型
clusterMode是否是集群参数流控规则false
clusterConfig集群流控相关配置

系统规则

阈值类型

  • LOAD(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5
  • RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒
  • 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护
  • CPU 使用率:当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0

[注解官网] https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81#sentinelresource-%E6%B3%A8%E8%A7%A3

@SentinelResource 注解

注意:注解方式埋点不支持 private 方法。

<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">@SentinelResource</font> 用于定义资源,并提供可选的异常处理和 fallback 配置项。 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">@SentinelResource</font> 注解包含以下属性:

  • <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">value</font>:资源名称,必需项(不能为空)
  • <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">entryType</font>:entry 类型,可选项(默认为 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">EntryType.OUT</font>
  • <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">blockHandler</font> / <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">blockHandlerClass</font>: <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">blockHandler</font> 对应处理 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">BlockException</font> 的函数名称,可选项。blockHandler 函数访问范围需要是 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">public</font>,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">BlockException</font>。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">blockHandlerClass</font> 为对应的类的 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">Class</font> 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • fallback<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">/</font>fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要和原函数一致,或者可以额外多一个 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">Throwable</font> 类型的参数用于接收对应的异常。
    • fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">fallbackClass</font> 为对应的类的 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">Class</font> 对象,注意对应的函数必需为 static 函数,否则无法解析。defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要为空,或者可以额外多一个 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">Throwable</font> 类型的参数用于接收对应的异常。
    • defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">fallbackClass</font> 为对应的类的 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">Class</font> 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">exceptionsToIgnore</font>(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

1.8.0 版本开始,<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">defaultFallback</font> 支持在类级别进行配置。

注:1.6.0 之前的版本 fallback 函数只针对降级异常(<font style="color:rgb(119, 119, 119);background-color:rgb(243, 244, 244);">DegradeException</font>)进行处理,不能针对业务异常进行处理

特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">BlockException</font> 时只会进入 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">blockHandler</font> 处理逻辑。若未配置 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">blockHandler</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">fallback</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">defaultFallback</font>,则被限流降级时会将 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">BlockException</font>直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">UndeclaredThrowableException</font>)。

整合seata事务管理

application配置:

seata:
  enabled: true
  application-id: ${spring.application.name}
  tx-service-group: spring-cloud-demo    #此处配置自定义的seata事务分组名称
  enable-auto-data-source-proxy: true    #开启数据库代理
  service:
    vgroup-mapping:
      spring-cloud-demo: default
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace:
      group: SEATA_GROUP
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      namespace:

seata建表语句:

#分支事务表
CREATE TABLE `branch_table` (
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `resource_group_id` varchar(32) DEFAULT NULL,
  `resource_id` varchar(256) DEFAULT NULL,
  `lock_key` varchar(128) DEFAULT NULL,
  `branch_type` varchar(8) DEFAULT NULL,
  `status` tinyint(4) DEFAULT NULL,
  `client_id` varchar(64) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`branch_id`),
  KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='分支事务表';

#全局事务表
CREATE TABLE `global_table` (
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `status` tinyint(4) NOT NULL,
  `application_id` varchar(32) DEFAULT NULL,
  `transaction_service_group` varchar(32) DEFAULT NULL,
  `transaction_name` varchar(128) DEFAULT NULL,
  `timeout` int(11) DEFAULT NULL,
  `begin_time` bigint(20) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`xid`),
  KEY `idx_gmt_modified_status` (`gmt_modified`,`status`),
  KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='全局事务表';

#表数据锁信息(具体加锁的表数据id)
CREATE TABLE `lock_table` (
  `row_key` varchar(128) NOT NULL,
  `xid` varchar(96) DEFAULT NULL,
  `transaction_id` mediumtext,
  `branch_id` mediumtext,
  `resource_id` varchar(256) DEFAULT NULL,
  `table_name` varchar(32) DEFAULT NULL,
  `pk` varchar(36) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`row_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='存储锁表';

-- the table to store seata xid data
-- 0.7.0+ add context
-- you must to init this sql for you business databese. the seata server not need it.
-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
drop table if exists `undo_log`;
CREATE TABLE `undo_log` (
 `id` bigint(20) NOT NULL AUTO_INCREMENT,
 `branch_id` bigint(20) NOT NULL,
 `xid` varchar(100) NOT NULL,
 `context` varchar(128) NOT NULL,
 `rollback_info` longblob NOT NULL,
 `log_status` int(11) NOT NULL,
 `log_created` datetime NOT NULL,
 `log_modified` datetime NOT NULL,
 `ext` varchar(100) DEFAULT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

整合sprin cloud依赖

		<!--seata Cloud依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <!--排除seata-spring-boot-starter-->
            <exclusions>
                <exclusion>
                    <artifactId>seata-spring-boot-starter</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.4.2</version>
        </dependency>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vic2334

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值