诞生:
2018.10.31,Spring Cloud Alibaba 正式入驻了 Spring Cloud 官方孵化器,并在 Maven 中央库发布了第一个版本。
Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。
官网地址:
- 官网
- github文档
- github中文文档
版本对应:
Nacos 是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
Nacos: Dynamic Naming and Configuration Service,Nacos就是注册中心 + 配置中心的组合。等价于:Nacos = Eureka+Config +Bus
这里直接在服务器上Docker部署的Nacos, 并开启相应端口
8848
拉取 nacos
镜像
docker pull nacos/nacos-server
启动 nacos
docker run -d --name nacos -p 8848:8848 -e PREFER_HOST_MODE=hostname -e MODE=standalone nacos/nacos-server
至此,我们已经可以使用nacos服务,UI地址:http://:8848/nacos 账号:nacos 密码:nacos
本机部署 , 本机存放路径:
/Users/hgw/Documents/Software/nacos/bin
下载nacos的压缩包,并解压
https://github.com/alibaba/nacos/releases
进入解压目录的bin目录下,打开终端,输入命令启动,输出nacos is starting with standalone
即为成功 /Users/hgw/Documents/Software/nacos/bin
sh startup.sh -m standalone
进入可视化页面,账号密码都是nacos,进行登录即可,nacos的端口为8848 (账号:nacos 密码:nacos)
http://localhost:8848/nacos/#/login
http://127.0.0.1:8848/nacos/#/login
关闭nacos
sh shutdown.sh
但发现关闭后,仍然能在可视化页面连接nacos,所以需要杀死8848端口的进程
# 查询8848端口的进程,获取到进程id,例如是45025
lsof -i:8848
# 杀死45025进程
kill -9 45025
注意:mac环境 mac 启动nacos失败 日志报“nohup: /Library/Internet: No such file or directory”
这里方便以下做演示,我们新建一个空项目 SpringCloudAlibaba。并创建一个微服务 cloudalibaba-provider-payment9001,以下将 cloudalibaba-provider-payment9001 微服务加入注册中心中去。
第一步、依赖管理
在pom.xml中加入
# 下面是依赖管理,相当于以后再dependencies里引spring cloud alibaba就不用写版本号, 全用dependencyManagement进行管理
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.1.0.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
第二步、修改pom.xml 文件,引入 Nacos Discovery Starter。
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
第三步、配置 Nacos Server 地址和微服务名称
server:
port: 9001
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 配置Nacos地址
第四步、使用 @EnableDiscoveryClient 注解开启服务注册与发现功能
启动微服务测试一下 :
总结:
@EnableDiscoveryClient
注解开启服务注册与发现功能我们编写一个Controller 方便一会儿测试:
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/payment/nacos/{id}")
public String getPayment(@PathVariable("id") Integer id) {
return "nacos registry,serverPort: " + serverPort + "\t id:"+ id;
}
}
按照以上步骤,我们再创建一个cloudalibaba-provider-payment9002微服务,并加入到Nacos注册中心去。
创建我们的服务消费者(cloudalibaba-consumer-nacos-order83)并注册进Nacos
第一步、依赖管理
在pom.xml中加入
# 下面是依赖管理,相当于以后再dependencies里引spring cloud alibaba就不用写版本号, 全用dependencyManagement进行管理
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.1.0.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
第二步、修改pom.xml 文件,引入 Nacos Discovery Starter。
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
第三步、配置 Nacos Server 地址和微服务名称
server:
port: 83
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 配置Nacos地址
# 消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
server-url:
nacos-user-service: http://nacos-payment-provider
第四步、使用 @EnableDiscoveryClient 注解开启服务注册与发现功能
@EnableDiscoveryClient
@SpringBootApplication
public class CloudalibabaConsumerNacosOrder83Application {
public static void main(String[] args) {
SpringApplication.run(CloudalibabaConsumerNacosOrder83Application.class, args);
}
}
第五步、编写配置类 ApplicationContextBean
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
第六步、编写业务类 OrderNacosController
@RestController
@Slf4j
public class OrderNacosController {
@Resource
private RestTemplate restTemplate;
@Value("${server-url.nacos-user-service}")
private String serverURL;
@GetMapping("/consumer/payment/nacos/{id}")
public String paymentInfo(@PathVariable("id") Long id) {
return restTemplate.getForObject(serverURL+"/payment/nacos/"+id, String.class);
}
}
nacos整合了 ribbon
测试:当我们访问 http://localhost:83/consumer/payment/nacos/1
时
C是所有节点在同一时间看到的数据是一致的;而A的定义是所有的请求都会收到响应。
何时选择使用何种模式?
一般来说,
curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'
我们还可以用nacos作为配置中心。配置中心的意思是不在application.properties 等文件中配置了,而是放到nacos配置中心公用,这样无需每台机器都改。
如何使用 Nacos 作为配置中心统一管理配置
resources
目录下创建一个 bootstrap.properties文件并配置应用名.properties
应用名.properties
添加任何配置
@RefreshScope
注解, (动态获取并刷新配置)@Value("${配置项的名}")
注解, (获取到配置)同时加载多个配置集
bootstrap.properties
说明加载配置中心中哪些配置文件即可@Value
、@ConfigurationProperties
… 以前 SpringBoot 任何方法从配置文件中获取值, 都能使用.配置中心有的优先使用配置中心的
说明:之所以需要配置 spring.application.name
,是因为它是构成 Nacos 配置管理 dataId
字段的一部分。
在 Nacos Spring Cloud 中,dataId
的完整格式如下:
${prefix}-${spring.profiles.active}.${file-extension}
prefix
默认为 spring.application.name
的值,也可以通过配置项 spring.cloud.nacos.config.prefix
来配置。spring.profiles.active
即为当前环境对应的 profile,详情可以参考 Spring Boot文档。 注意:当 spring.profiles.active
为空时,对应的连接符 -
也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}
file-exetension
为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension
来配置。目前只支持 properties
和 yaml
类型。最后公式:
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
创建一个 cloudalibaba-config-nacos-client3377 微服务
第一步、引入依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
第二步、修改配置文件,在
bootstrap.properties
中配置 Nacos server 的地址和应用名
Nacos同springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取, 拉取配置之后,才能保证项目的正常启动。
springboot中配置文件的加载是存在优先级顺序的, bootstrap优先级高于application
# nacos 配置
server:
port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #Nacos服务注册中心地址
config:
server-addr: 127.0.0.1:8848 #Nacos配置注册中心地址
file-extension: yaml #指定yaml格式的配置
spring:
profiles:
active: dev #表示开发环境
第三步、在主启动类加上 @EnableDiscoveryClient 注解
package com.hgw.configClient3377;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class CloudalibabaConfigNacosClient3377Application {
public static void main(String[] args) {
SpringApplication.run(CloudalibabaConfigNacosClient3377Application.class, args);
}
}
第四步、在Nacos中添加配置信息
根据 ${prefix}-${spring.profiles.active}.${file-extension}
公式,创建一个名为:
nacos-config-client-dev.yaml
的配置文件
第五步、业务类
@RefreshScope : 是SpringCloud 原生注解,实现配置自动更新
@RestController
@RefreshScope // 支持Nacos的动态刷新功能
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/config/info")
public String getConfigInfo() {
return configInfo;
}
}
第六步、测试
自带动态刷新
修改下Nacos中的yaml配置文件,再次调用查看配置的接口,就会发现配置已经刷新
问题1:
实际开发中,通常一个系统会准备
如何保证指定环境启动时服务能正确读取到Nacos上相应环境的配置文件呢?
问题2:
一个大型分布式微服务系统会有很多微服务子项目,
每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境…
那怎么对这些微服务配置进行管理呢?
Namespace+Group+Data ID三者关系?为什么这么设计?
是什么
三者情况
默认情况:
Namespace=public,Group=DEFAULT_GROUP, 默认Cluster是DEFAULT
Nacos默认的命名空间是public,Namespace主要用来实现隔离。
比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace,不同的Namespace之间是隔离的。
Group默认是DEFAULT_GROUP,Group可以把不同的微服务划分到同一个分组里面去
Service就是微服务;一个Service可以包含多个Cluster(集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。
比方说为了容灾,将Service微服务分别部署在了杭州机房和广州机房,
这时就可以给杭州机房的Service微服务起一个集群名称(HZ), 给广州机房的Service微服务起一个集群名称(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能。
最后是==Instance==,就是微服务的实例。
命名空间 :
用作配置隔离 . (一般每个微服务一个命名空间)
不同的命名空间下, 可以存在相同的 Group
或 Data ID
的配置. Namespace
的常用场景之一是不同环境的配置区分隔离, 例如 开发测试环境和生产环境的资源 (如配置、服务)隔离等.
默认: public
(保留空间); 默认新增的所有配置都在 public
空间
在 dev 命名空间下创建gulimall-coupon.properties
配置文件
修改本地微服务的 bootstrap.properties
配置
也可以为每个微服务配置一个命名空间,微服务互相隔离
配置分组 :
默认所有的配置集都属于 DEFAULT_GROUP
. 自己可以创建分组, 比如双十一、618、双十二
配置集ID :
类似于配置文件名,即Data ID
Data Id
通常用于组织 划分系统的配置集. 一个系统或者应用可以包含多个配置集, 每个配置集都可以被一个有意义的名称标识. Data ID
通常采用类 Java 包 (如 com.hgw.tc.refund.log.level) 的命名规则保证全局唯一性. 此命名非强制 .
配置集 :
一组相关或不相关的配置项的集合.
在系统中, 一个配置文件通常就是一个配置集, 包含了系统各个方面的配置. 例如, 一个配置集可以包含了数据源、线程池、日志级别等配置项
指定 spring.profile.active
和 配置文件的DataID 来使不同环境下读取不同的配置
测试:默认空间+默认分组+新建dev和test两个DataID
**第一步、**在同一个空间同一个分组创建两个DataID,分别是 dev和test
第二步、通过spring.profile.active属性就能进行多环境下配置文件的读取
通过 Group 实现环境区分
第一步、创建两个分组并在分组内新建配置文件DataID
总览:
第二步、修改配置文件
在config下增加一条group的配置即可。可配置为DEV_GROUP或TEST_GROUP
模拟:不同命名空间读取不同的组内容
第一步、新建HZ/BJ的Namespace
回到服务管理-服务列表查看
第二步、在 HZ、BJ命名空间中创建不同分组和配置文件DataID
第三步、修改配置文件
Nacos默认自带的是嵌入式数据库derby
derby到mysql切换配置步骤
1、在mysql中执行 /nacos/conf/nacos-mysql.sql
脚本
2、修改配置文件
\nacos\conf
目录下找到application.properties
[root@hgwtencent conf]# vim application.properties
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=hgw6721224
3、启动服务,创建一个配置文件
SpringCloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架。
Gateway 是在Spring生态系统之上构建的API网关服务,基于 Spring5、SpringBoot 和 Project Reactor等技术。
Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能, 例如:熔断、限流、重试等 。
网关的角色是作为一个 API 架构,用来保护、增强和控制对于 API 服务的访问
API 网关是一个处于应用程序或服务(提供 REST API 接口服务)之前的系统,用来管理授权、访问控制和流量限制等,这样 REST API 接口服务就被 API 网关保护起来,对所有的调用者透明。因此,隐藏在 API 网关后面的业务系统就可以专注于创建和管理服务,而不用去处理这些策略性的基础设施。
网关的职责 :
SpringCloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架。目标是替代ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。
发送请求需要知道商品服务的地址,如果商品服务器有100服务器,1号掉线后,还得改,所以需要网关动态地管理,他能从注册中心中实时地感知某个服务上线还是下线。
请求也要加上询问权限,看用户有没有权限访问这个请求,也需要网关。所以我们使用spring cloud的gateway组件做网关功能。
网关是请求浏览的入口,常用功能包括路由转发,权限校验,限流控制等。
1、neflix不太靠谱,zuul2.0一直跳票,迟迟不发布
一方面因为Zuul1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,是亲儿子产品,值得信赖。
而且很多功能Zuul都没有用起来也非常的简单便捷。
Gateway是基于 异步非阻塞模型上 进行开发的,性能方面不需要担心。虽然Netflix早就发布了最新的 Zuul 2.x, 但 Spring Cloud 貌似没有整合计划。而且Netflix相关组件都宣布进入维护期;不知前景如何?
多方面综合考虑Gateway是很理想的网关选择。
2、SpringCloud Gateway具有如下特性:
基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建;
动态路由:能够匹配任何请求属性;
可以对路由指定 Predicate(断言)和 Filter(过滤器);
集成Hystrix的断路器功能;
集成 Spring Cloud 服务发现功能;
易于编写的 Predicate(断言)和 Filter(过滤器);
请求限流功能;
支持路径重写。
3、Spring Cloud Gateway 与 Zuul的区别
在SpringCloud Finchley 正式版之前,Spring Cloud 推荐的网关是 Netflix 提供的Zuul:
Zuul 1.x,是一个基于阻塞 I/ O 的 API Gateway
Zuul 1.x 基于Servlet 2. 5使用阻塞架构 它不支持任何长连接(如 WebSocket) Zuul 的设计模式和Nginx较像,每次 I/ O 操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx 用C++ 实现,Zuul 用 Java 实现,而 JVM 本身会有第一次加载较慢的情况,使得Zuul 的性能相对较差。
Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。 Zuul 2.x的性能较 Zuul 1.x 有较大提升。在性能方面,根据官方提供的基准测试, Spring Cloud Gateway 的 RPS(每秒请求数)是Zuul 的 1. 6 倍。
Spring Cloud Gateway 建立 在 Spring Framework 5、 Project Reactor 和 Spring Boot 2 之上, 使用非阻塞 API。
Spring Cloud Gateway 还支持 WebSocket, 并且与Spring紧密集成拥有更好的开发体验
Route (路由) :
这是网关的基本构件块. 它由一个ID, 一个目标 URI, 一组断言和一组过滤器定义. 如果断言为真, 路由匹配
Predicate (断言) :
输入类型是一个 ServerWebExchange
. 我们可以使用它来匹配来自 HTTP 请求的任何内容, 例如 headers 或 参数.
Filter (过滤) :
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
Gateway 中的 Filter 分为两种类型的Filter, 分别是 Gateway Filter 和 Global Filter. 过滤器Filter 将会对请求和响应进行修改处理 .
客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。
Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,
在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
第一步、创建模块 并调整版本一致
创建完项目进行springboot降级 . 并加入到主项目
第二步、引入依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
<version>2.1.0.RELEASEversion>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
<version>2.1.0.RELEASEversion>
dependency>
第三步、开启 服务注册发现
/*
* 1.开启服务注册发现
* */
@EnableDiscoveryClient
@SpringBootApplication
public class GuilmallGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GuilmallGatewayApplication.class, args);
}
}
第四步、配置nacos注册中心地址
applicaion.properties
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.application.name=gulimall-gateway
server.port=88
第五步、在 nacos 中为
gateway
模块创建名称空间 . 并创建配置
spring:
application:
name: gulimall-gateway
第六步、创建
bootstrap.properties
并填写配置中心地址
spring.application.name=gulimall-gateway
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=064c2e8d-15a5-499a-83c1-1691ccb740d3
测试:
- 模拟输入 http://localhost:88/?url=biadu # 跳到百度页面
- 模拟输入 http://localhost:88/?url=qq # 跳到qq页面
1、引入依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
2、配置API网关
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2P3uRVRr-1652607118361)(SpringCloudAlibaba.assets/image-20220309213615119.png)]
在项目里创建application.yml , 并配置
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://www.baidu.com
predicates:
- Query=url,baidu
- id: test_route
uri: https://www.qq.com
predicates:
- Query=url,qq
run 起来, 成功啦!
见前面的步骤
配置了一个 id 为 path_route_atguigu 的路由规则,当访问地址 http://localhost:9527/guonei 时会自动转发到地址: http://news.baidu.com/guonei
package com.atguigu.springcloud.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GateWayConfig
{
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder)
{
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_route_atguigu",
r -> r.path("/guonei")
.uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
默认情况下Gateway会根据注册中心注册的服务列表,
以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。
lb://serviceName
是springcloud gateway在微服务中自动为我们创建的负载均衡uri如以下配置:
Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。
Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合
Spring Cloud Gateway 创建 Route 对象时, 使用 RoutePredicateFactory 创建 Predicate 对象,Predicate 对象可以赋值给 Route。 Spring Cloud Gateway 包含许多内置的Route Predicate Factories。
所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。
说白了,Predicate就是为了实现一组匹配规则,
让请求过来找到对应的Route进行处理。
Cookie Route Predicate需要两个参数,一个是 Cookie name ,一个是正则表达式。 路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行
两个参数:一个是属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行。
Host Route Predicate 接收一组参数,一组匹配的域名列表,这个模板是一个 ant 分隔的模板,用.号作为分隔符。
它通过参数中的主机地址作为匹配规则。
路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。
Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生
生命周期
种类
自定义过滤器
编写一个类实现 implements GlobalFilter,Ordered ,并加入Spring容器中。
package com.atguigu.springcloud.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Date;
/**
* @auther zzyy
* @create 2020-02-21 16:40
*/
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter,Ordered
{
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
{
log.info("***********come in MyLogGateWayFilter: "+new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if(uname == null)
{
log.info("*******用户名为null,非法用户,o(╥﹏╥)o");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder()
{
return 0;
}
}
网关是介于nignx以及业务应用之间的中间层,主要负责将请求路由到不同的微服务中以及对请求的合法性进行校验。
我们目前网关的选型是spring cloud gateway,位置在nignx之后,各个微服务应用之前。为何在已经有nignx的情况下,中间还需要一道负责负载均衡功能的网关呢?
一个原因是spring cloud gateway天然集成了注册中心eureka,能实现应用的自动注册与发现,而nginx每增加一个服务应用都需要手动去设置配置文件。
另一个原因是,在spring cloud gateway中我们可以很方便的进行功能的扩展,比如我们现在的用户登录权限校验,就是放在网关中实现的。
gateway和nginx网关的区别
网关可以看做系统与外界联通的入口,我们可以在网关进行处理一些非业务逻辑的逻辑,比如权限验证,监控,缓存,请求路由等等。
==nginx==是 用户 到 前端工程 之间的网关,对外网关
gateway 是 前端工程 到 后台服务器 之间的一个 对内网关
Nginx在其中扮演的角色是什么?
SpringGateway在其中扮演的角色是什么?
熔断
当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,**进而熔断该节点微服务的调用,快速返回错误的响应信息。**检测到该节点微服务调用响应正常后恢复调用链路。
A服务调用 B服务的某个功能,由于网络不稳定问题,或者 B服务卡机,导致功能时间超长。如果这样的次数很多。我们就可以直接将 B服务段路了(A不再请求 B接口),凡是调用 B得直接返回降级数据,不必等待 B的超长执行。这样 B的故障问题,就不会级联影响到 A服务。
降级
限流
熔断和降级异同:
简介:
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
Sentinel 分为两个部分:
中文官网
官网下载
文档
1、去官网下载项目里sentinel对应的版本的控制台官网下载
2、在路径下执行
$ java -jar sentinel-dashboard-1.7.0.jar --server.port=8833
3、访问http://localhost:8833/
用户名:sentinel
密码:sentinel
首先、按照之前创建一个 cloudalibaba-sentinel-service8401 微服务,并注入我们的Nacos注册中心
第一步、导入依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
第二步、配置Sentinel
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
sentinel:
transport:
# 配置 dashboard的地址
dashboard: 127.0.0.1:8833
# 默认 8719端口,加入被占用会自动从8719开始依次+1,直至找到未被占用的端口
port: 8719
第三步、编写业务类FlowLimitController进行流控检测
package com.hgw.sentinelService8401.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Description: 测试类,用来进行流控检测
*/
@RestController
public class FlowLimitController {
@GetMapping("/testA")
public String testA() {
return "---testA";
}
@GetMapping("/testB")
public String testB() {
return "---testB";
}
}
第四步、测试
启动8401微服务后查看sentienl控制台,此时发现空空如也。
由于Sentinel采用的懒加载,执行一次访问即可:http://localhost:8401/testA
结论:sentinel8080正在监控微服务8401
直接->快速失败
添加以下流控,表示一秒钟内查询1次就是OK,若超过次数1,就直接快速失败,报默认错误
测试当我们在一分钟多次访问 http://localhost:8401/testA 时候:
当关联的资源达到阈值时,就限流自己,
比如 当与A关联的资源B达到阀值后,就限流A自己,即B惹事,A挂了
1、配置A
设置效果:当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址, 当关联资源到阈值后限制配置好的资源名
2、postman模拟并发密集访问testB
3、大批量线程高并发访问B,导致A失效了
链路模式:只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值。
需要注意sentinel流控模式中的关联类型和链路类型的区别:
例如有两条请求链路:
/testA /common
/testB /common
如果只希望统计从/testA进入到/common的请求,对/testA 进行限流,则可以这样配置:
案例:流控模式-链路
需求:有查询订单和创建订单业务,两者都需要查询商品。针对从查询订单进入到查询商品的请求统计,并设置限流。
步骤:
1.在OrderService中添加一个queryGoods方法,不用实现业务
Sentinel默认只标记Controller中的方法为资源,如果要标记其它方法,需要利用@SentinelResource注解,示例:
@Service
public class OrderService {
@SentinelResource("goods")
public String queryGoods(){
return "查询商品";
}
}
package com.hgw.sentinelService8401.controller;
@RestController
public class OrderController {
@Autowired
OrderService orderService;
@GetMapping("/order/query")
public String query() {
return orderService.queryGoods()+"并展示";
}
@GetMapping("/order/save")
public String save() {
return orderService.queryGoods()+"并保存";
}
}
聚合导致链路不生效
有2个方法:
方法一 ,添加配置类,配置CommonFilter过滤器,指定WEB_CONTEXT_UNIFY=false,禁止收敛URL的入口context
@Configuration
public class SentinelConfig {
@Bean
public FilterRegistrationBean sentinelFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new CommonFilter());
registration.addUrlPatterns("/*");
// 入口资源关闭聚合 解决流控链路不生效的问题
registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
registration.setName("sentinelFilter");
registration.setOrder(1);
return registration;
}
}
方案二 需要修改application.yml,添加配置 web-context-unify: false ,表示关闭:
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 # sentinel控制台地址
web-context-unify: false # 关闭context整合
直接失败,抛出异常,Blocked by Sentinel (flow limiting)
源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值
即:在预热时长内 阈值被设置为 阈值除以coldFactor(默认值为3)
,预热时长之后恢复设置阈值。
默认coldFactor为3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。
源码:com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
案例:阈值为10 + 预热时长设置5秒
系统初始化的阈值为 10/3 约等于3,即阈值刚开始为3;然后过了预热时长5秒之后阈值才慢慢升高恢复10
测试:
匀速排队,让请求以均匀的速度通过,匀速排队,阈值必须设置为QPS
案例:设置含义:/testA每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒
通过postman进行测试:
所有的请求都排队进行执行。
package com.hgw.sentinelService8401.config;
import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler;
import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import org.bouncycastle.asn1.cms.SCVPReqRes;
import org.springframework.context.annotation.Configuration;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Data time:2022/5/14 10:55
* StudentID:2019112118
* Author:hgw
* Description: Sentinel-自定义流控响应
*/
@Configuration
public class SeckillSentinelConfig {
public SeckillSentinelConfig() {
WebCallbackManager.setUrlBlockHandler(new UrlBlockHandler() {
@Override
public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException {
Map<String,String> error = new HashMap<>();
error.put("code", "1001");
error.put("msg", "请求流量过大");
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json");
httpServletResponse.getWriter().write(JSON.toJSONString(error));
}
});
}
}
降级规则官网
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制, 让请求快速失败,避免影响到其它的资源而导致级联错误。
Sentinel的断路器是没有半开状态的
Sentinel 提供以下几种熔断策略:
RT(平均响应时间,秒级)
平均响应时间超出阈值 且 在时间窗口内通过的请求>=5 ,两个条件同时满足后触发降级。窗口期过后关闭断路器
RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)
异常比列(秒级)
QPS >= 5 且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
异常数(分钟级)
异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
注意异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException
)不生效。为了统计异常比例或异常数,需要通过 Tracer.trace(ex)
记录业务异常。示例:
平均响应时间(DEGRADE_GRADE_RT
):
在接下的时间窗口(DegradeRule
中的timeWindow
,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException
)。
注意 Sentinel 默认统计的 RT 上限是 4900 ms,超过此阈值的都会作 4900ms,若需要变更此上线可以通过启动配置项 -Dcsp.sentinel,statistic.max.rt=xxx
来配置。
案例:测试RT
1、业务代码
package com.hgw.sentinelService8401.controller;
/**
* Description: 测试类,用来进行流控检测
*/
@Slf4j
@RestController
public class FlowLimitController {
@GetMapping("/testC")
public String testC() {
// 暂停几秒钟线程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("testD 测试RT");
return "---testB";
}
}
2、配置降级规则
上述配置:
异常比例(DEGRADE_GRADE_EXCEPTION_RATIO
)
在接下的时间窗口(DegradeRule
中的timeWindow
,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是[0.0,1.0]
,代表 0%-100%。
案例:异常比例
1、业务代码
@GetMapping("/testC")
public String testC() {
log.info("testC 测试异常比例");
// 制造一个异常
int num = 10/0;
return "---testC";
}
2、配置降级规则
单独访问一次,必然来一次报错一次(int age = 10/0),调一次错一次;
开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。
断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务降级了。
异常数(DEGRADE_GRADE_EXCEPTION_COUNT
)
之后进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindo
小于60s,则结束熔断状态后可能再进入熔断状态。
异常数是按照分钟统计的
案例:异常比例
1、业务代码
@GetMapping("/testC")
public String testC() {
log.info("testC 测试异常");
// 制造一个异常
int num = 10/0;
return "---testC";
}
2、配置降级规则
http://localhost:8401/testC,第一次访问绝对报错,因为除数不能为零,
我们看到error窗口,但是达到6次报错后,进入熔断后降级。
在实际应用过程中,我们可能需要限流的层面不仅限于接口。可能对于某个方法的调用限流,对于某个外部资源的调用限流等都希望做到控制。那么,这个时候我们就不得不手工定义需要限流的资源点,并配置相关的限流策略等内容了。
自定义资源点
在需要通过Sentinel来控制流量的地方使用@SentinelResource
注解,比如下面以控制Service逻辑层的某个方法为例:
@Service
public class OrderService {
@SentinelResource("goods")
public String queryGoods(){
return "查询商品";
}
}
到这里一个需要被保护的方法就定义完成了。下面我们分别说说,定义了资源点之后,我们如何实现不同的保护策略,包括:限流、降级等。
在定义了资源点之后,我们就可以通过Dashboard来设置限流和降级策略来对资源点进行保护了。同时,也可以通过@SentinelResource
来指定出现限流和降级时候的异常处理策略。下面,就来一起分别看看限流和降级都是如何实现的。
第一步:在Web层调用这个被保护的方法:
@RestController
public class OrderController {
@Autowired
OrderService orderService;
@GetMapping("/order/query")
public String query() {
return orderService.queryGoods()+"并展示";
}
}
第二步:启动测试应用,启动Sentinel-Dashboard。发一个请求到
/order/query
接口上,使得Sentinel-Dashboard上可以看到如下图所示的几个控制点:
可以看到,除了如之前入门实例中那样有/order/query
资源点之外,多了一个goods
资源点。可以通过界面为这个资源点设置限流规则,比如将其QPS设置为2。由于/order/query
资源不设置限流规则,所以只要请求/order/query
接口,就可以直接模拟调用goods
资源,来观察限流规则是否生效。
下面可以通过任何你喜欢的工具来调用/order/query
接口,只要QPS超过2,那么就会出现如下的错误返回,代表限流策略生效了。
默认情况下,Sentinel对控制资源的限流处理是直接抛出异常。在没有合理的业务承接或者前端对接情况下可以这样,但是正常情况为了更好的用户业务,都会实现一些被限流之后的特殊处理,我们不希望展示一个生硬的报错。那么只需要基于上面的例子做一些加工,比如:
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "dealHandler_testHotKey")
public String testHotKey(@RequestParam(value = "cID",required = false) String cId,
@RequestParam(value = "uID",required = false) String uID) {
return "--testHotKey;cID:" +(StringUtils.isNotEmpty(cId) ? cId : "")+";uID:"+(StringUtils.isNotEmpty(uID)?uID:"");
}
public String dealHandler_testHotKey(String cId, String uID, BlockException exception) {
return "----dealHandler_testHotKey";
}
主要做了两件事:
@SentinelResource
注解的blockHandler
属性制定具体的处理函数BlockException
异常参数;同时,返回类型也必须一样。以上:
1、创建CustomerBlockHandler类用于自定义限流处理逻辑
public class CustomerBlockHandler {
public static String handleException(BlockException exception) {
return "自定义的限流处理信息 ......CustomerBlockHandler";
}
}
2、指定异常处理类和方法
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException")
public String testHotKey(@RequestParam(value = "cID",required = false) String cId,
@RequestParam(value = "uID",required = false) String uID) {
return "--testHotKey;cID:" +(StringUtils.isNotEmpty(cId) ? cId : "")+";uID:"+(StringUtils.isNotEmpty(uID)?uID:"");
}
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。
热点key限流官网介绍
案例:对商品id,和用户ID进行限制
1、业务逻辑代码
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "dealHandler_testHotKey")
public String testHotKey(@RequestParam(value = "cID",required = false) String cId,
@RequestParam(value = "uID",required = false) String uID) {
return "--testHotKey;cID:" +(StringUtils.isNotEmpty(cId) ? cId : "")+";uID:"+(StringUtils.isNotEmpty(uID)?uID:"");
}
public String dealHandler_testHotKey(String cId, String uID, BlockException exception) {
return "----dealHandler_testHotKey";
}
兜底方法
分为系统默认和客户自定义,两种
之前的case,限流出问题后,都是用sentinel系统默认的提示: Blocked by Sentinel (flow limiting)
我们能不能自定?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?
结论
从@HystrixCommand到 @SentinelResource
2、配置热点规则
本次配置:
限流模式只支持QPS模式,固定写死了。(这才叫热点)
@SentinelResource注解的方法参数索引,0代表第一个参数,1代表第二个参数,以此类推
单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。
特殊情况
我们期望cID 参数当它是某个特殊值时,它的限流值和平时不一样
加入:假如当cID的值等于5时,它的阈值可以达到200
当cID不等于5的时候,阈值就是平常的1
热点参数的注意点,参数必须是基本类型或者String
使用Sentinel来保护feign远程调用:熔断。举一个案例:
1、在gulimall-product类配置文件添加配置
#sentinel是不会对feign进行监控的,需要开启配置
feign.sentinel.enabled=true
2、编写 熔断回调方法
package com.atguigu.gulimall.product.feign.fallback;
@Slf4j
@Component
public class SeckillFeignServiceFallBack implements SeckillFeignService {
@Override
public R getSkuSeckillInfo(Long skuId) {
log.error("熔断方法调用...getSkuSeckillInfo");
return R.error(BizCodeEnume.TO_MANY_REQUEST.getCode(),BizCodeEnume.TO_MANY_REQUEST.getMsg());
}
}
3、指定 服务熔断回调方法@FeignClient(fallback = 指定的熔断回调方法)
package com.atguigu.gulimall.product.feign;
@FeignClient(value = "gulimall-seckill", fallback = SeckillFeignServiceFallBack.class)
public interface SeckillFeignService {
@GetMapping("/sku/seckill/{skuId}")
R getSkuSeckillInfo(@PathVariable("skuId") Long skuId);
}
一旦我们重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效
修改 cloudalibaba-sentinel-service8401 服务进行持久化
第一步、导入依赖
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
dependency>
第二步、添加Nacos数据源配置
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
sentinel:
transport:
# 配置 dashboard的地址
dashboard: 127.0.0.1:8833
# 默认 8719端口,加入被占用会自动从8719开始依次+1,直至找到未被占用的端口
port: 8719
datasource:
ds1:
nacos:
server-addr: 127.0.0.1:8848
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
第三步、添加Nacos业务规则配置
[
{
"resource": "/rateLimit/byUrl",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
resource:资源名称;
limitApp:来源应用;
grade:阈值类型,0表示线程数,1表示QPS;
count:单机阈值;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
clusterMode:是否集群。
一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题
问题:
SpringBoot事务的坑:
在同一个类里面,编写两个方法,内部调用的的时候,会导致事务设置失效。原因没有用到代理对象的缘故。
spring-boot-starter-aop
,(帮我们引入了aspectj)cap定理
CAP 原则又称 CAP 定理,指的是在一个分布式系统中
CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾
BASE理论
是对 CAP 理论的延伸,思想是即使无法做到强一致性(CAP 的一致性就是强一致性),但可以适当的采取若一致性,即 最终一致性。
BASE 是指:
从客户端角度,多进程并发访问时,更新过的数据在不同进程如何获取的不同策略,决定了不同的一致性。
Satia概述:
我们只需要使用一个 @GlobalTransactional
注解在业务方法上:
@GlobalTransactional
public void purchase(String userId, String commodityCode, int orderCount) {
......
}
分布式事务处理过程的一ID+三组件模型
Transaction ID XID: 全局唯一的事务ID
处理过程
第一步、官网下载
第二步、seata-server-0.9.0.zip解压到指定目录并修改conf目录下的file.conf配置文件
先备份原始file.conf文件
主要修改:自定义事务组名称+事务日志存储模式为db+数据库连接信息
$ cp file.conf fileInit.conf
$ vim file.conf
第三步、数据库新建库seata 并 在seata库里建表
建表db_store.sql在\seata-server-0.9.0\seata\conf目录里面
第四步、修改seata-server-0.9.0\seata\conf目录下的registry.conf配置文件
$ cp $ vim registry.conf
$ vim registry.conf
目的是:指明注册中心为nacos,及修改nacos连接信息
第五步、启动
先启动nacos
再启动seata-server
看到以下内容说明安装成功!
==注意:==使用高版本的数据库会报错,将 lib 下的 mysql驱动jar包删掉,加入对应自己数据库版本的mysql驱动jar包即可!
1、导入依赖 :spring-cloud-starter-alibaba-seata seata-all-0.7.1
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-seataartifactId>
dependency>
2、给分布式大事务的路口标注==@GlobalTransactional;== 每一个远程的小事务用 @Transactional
在 gulimall-order服务中com/atguigu/gulimall/order/service/impl/OrderServiceImpl.java
的 SubmitOrderResponseVo方法加上@GlobalTransactional
注解
@GlobalTransactional
@Transactional // 本地事务,在分布式系统,只能控制住自己的回滚,控制不了其他服务的回滚。
@Override
public SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) {
//.....
}
3、配置代理数据源 使用 seata DataSourceProxy代理自己的数据源
因为 Seata 通过代理数据源实现分支事务,如果没有注入,事务无法回滚
添加“com.atguigu.gulimall.order.config.MySeataConfig”类,代码如下:
package com.atguigu.gulimall.order.config;
@Configuration
public class MySeataConfig {
@Autowired
DataSourceProperties dataSourceProperties;
@Bean
public DataSource dataSource(DataSourceProperties dataSourceProperties){
//得到数据源
HikariDataSource dataSource = dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
if (StringUtils.hasText(dataSourceProperties.getName())){
dataSource.setPoolName(dataSourceProperties.getName());
}
return new DataSourceProxy(dataSource);
}
}
4、每个微服务都必须要导入
registry.conf
file.conf
分别给gulimall-order和gulimall-ware加上file.conf和registry.conf这两个配置,并修改file.conf
5、给所有还不使用seata的服务排除掉,修改其pom.xml文件
<exclusion>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-seataartifactId>
exclusion>
Feign是一个声明式的Web服务客户端,让编写Web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可
Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。
它的使用方法是 定义一个服务接口然后在上面添加注解 。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡
SpringCloud集成了Ribbon和Eureka,可以使用Feigin提供负载均衡的http客户端
只需要创建一个接口,然后添加注解即可~
Feign,主要是社区版,大家都习惯面向接口编程。这个是很多开发人员的规范。调用微服务访问两种方法
Feign能干什么?
Feign旨在使编写Java Http客户端变得更容易。
前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处, 往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。 所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下, 我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可) ,即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
Feign默认集成了Ribbon
利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是, 通过feign只需要定义服务绑定接口且以声明式的方法 ,优雅而简单的实现了服务调用
Feign和OpenFeign两者区别
< dependency >
< groupId > org.springframework.cloud groupId >
< artifactId > spring-cloud-starter- feign artifactId >
dependency >
<dependency>
<groupId> org.springframework.cloud groupId>
<artifactId> spring-cloud-starter- openfeign artifactId>
dependency>
第一步、改造springcloud-api模块
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
<version>2.2.6.RELEASEversion>
dependency>
新建service包,并新建DeptClientService.java接口,
@Component
// @FeignClient:微服务客户端注解,value:指定微服务的名字,这样就可以使Feign客户端直接找到对应的微服务
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT")
public interface DeptClientService {
![在这里插入图片描述](https://img-blog.csdnimg.cn/3ef42976420e4a45865820bd03cf9261.png#pic_center)
@GetMapping("/dept/get/{id}")
public Dept queryById(@PathVariable("id") Long id);
@GetMapping("/dept/list")
public List<Dept> queryAll();
@PostMapping("/dept/add")
public Boolean addDept(Dept dept);
}
第二步、创建springcloud-consumer-fdept-feign模块
拷贝springcloud-consumer-dept-80模块下的pom.xml,resource,以及java代码到springcloud-consumer-feign模块.
第三步、添加feign依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
<version>2.2.6.RELEASEversion>
dependency>
第三步、通过Feign实现:改造后controller:DeptConsumerController.java
@RestController
public class DeptConsumerController {
@Autowired
private DeptClientService service = null;
@RequestMapping("/consumer/dept/add")
public boolean add(Dept dept) {
return this.service.addDept(dept);
}
@RequestMapping("/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
return this.service.queryById(id);
}
@RequestMapping("/consumer/dept/list")
public List<Dept> list() {
return this.service.queryAll();
}
}
Feign和Ribbon二者对比,前者显现出面向接口编程特点,代码看起来更清爽,而且Feign调用方式更符合我们之前在做SSM或者SprngBoot项目时,Controller层调用Service层的编程习惯!
第四步、主配置类
@SpringBootApplication
@EnableEurekaClient //开启Eureka 客户端
// feign客户端注解,并指定要扫描的包以及配置接口DeptClientService
@EnableFeignClients(basePackages = {"com.hgw.springcloud"})
//@ComponentScan("com.haust.springcloud") 切记不要加这个注解,不然会出现404访问不到
public class FeignDeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(FeignDeptConsumer_80.class,args);
}
}
OpenFeign 默认等待1秒钟,超过后报错。
默认Feign客户端只等待一秒钟,但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。
为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。
yml文件中开启配置 :
# 设置 feign 客户端超时时间(OpenFeign默认支持 ribbon )
ribbon:
# 指的是建立连接所用的时间,适用于网络状况正常的情况下 , 两端连接所用的时间
ReadTimeout: 5000
# 指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。
说白了就是 对Feign接口的调用情况进行监控和输出
日志级别:
NONE:默认的,不显示任何日志;
BASIC:仅记录请求方法、URL、响应状态码及执行时间;
HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;
FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。
实现步骤:
第一步、配置日志 bean
package com.atguigu.springcloud.cfgbeans;
package com.atguigu.springcloud.cfgbeans;
import org.springframework.context.annotation. Bean ;
import org.springframework.context.annotation. Configuration ;
import feign.Logger;
@Configuration
public class FeignConfig
{
@Bean
Logger.Level feignLoggerLevel()
{
return Logger.Level.FULL ;
}
}
第二步、修改配置文件
logging:
level:
# feign 日志以什么级别监控哪个接口
com.hgw.springcloud.service.PaymentFeignService: debug

根据个人习惯而定,如果喜欢REST风格使用Ribbon;如果喜欢社区版的面向接口风格使用Feign.
Feign 本质上也是实现了 Ribbon,只不过后者是在调用方式上,为了满足一些开发者习惯的接口调用习惯!
下面我们关闭springcloud-consumer-dept-80 这个服务消费方,换用springcloud-consumer-dept-feign(端口还是80) 来代替:(依然可以正常访问,就是调用方式相比于Ribbon变化了)
在 gulimall-order 服务中 com.atguigu.gulimall.order.config
路径下编写Feign配置类:GulimallFeignConfig类 并编写请求拦截器
package com.atguigu.gulimall.order.config;
@Configuration
public class GulimallFeignConfig {
/**
* feign在远程调用之前会执行所有的RequestInterceptor拦截器
* @return
*/
@Bean("requestInterceptor")
public RequestInterceptor requestInterceptor(){
return new RequestInterceptor(){
@Override
public void apply(RequestTemplate requestTemplate) {
// 1、使用 RequestContextHolder 拿到请求数据,RequestContextHolder底层使用过线程共享数据 ThreadLocal
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes!=null){
HttpServletRequest request = attributes.getRequest();
// 2、同步请求头数据,Cookie
String cookie = request.getHeader("Cookie");
// 给新请求同步了老请求的cookie
requestTemplate.header("Cookie",cookie);
}
}
};
}
}
此时:查询购物项、库存和收货地址都要调用远程服务,串行会浪费大量时间,因此我们进行异步编排优化
ThreadLocal
,我们知道线程共享数据的域是 当前线程下,线程之间是不共享的。所以在开启异步时获取不到老请求的信息,自然也就无法共享cookie
了。修改 gulimall-order 服务中 com.atguigu.gulimall.order.service.impl
目录下的 OrderServiceImpl 类
@Override
public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
OrderConfirmVo confirmVo = new OrderConfirmVo();
MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
// 获取主线程的域
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 1、远程查询所有的地址列表
CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
RequestContextHolder.setRequestAttributes(requestAttributes);
// 将主线程的域放在该线程的域中
List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
confirmVo.setAddressVos(address);
}, executor);
// 2、远程查询购物车所有选中的购物项
CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
// 将老请求的域放在该线程的域中
RequestContextHolder.setRequestAttributes(requestAttributes);
List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
confirmVo.setItems(items);
}, executor);
// feign在远程调用请求之前要构造
// 3、查询用户积分
Integer integration = memberRespVo.getIntegration();
confirmVo.setIntegration(integration);
// 4、其他数据自动计算
// TODO 5、防重令牌
CompletableFuture.allOf(getAddressFuture,cartFuture).get();
return confirmVo;
}