是的,你的行为实在不会给我什么好印象,你是个不折不扣的混蛋!米彩直言不讳到
“是吗... 可是你不知道,你眼中那个坏事做尽的混蛋,曾经心里也有过一座干净的城池!”
"我不太懂你在说些什么。"
“你不需要懂... ...因为今天这座城池已经从我的身边脱离,成为了一座我永远也触摸不到的天 空之城!”我低头摸索着口袋,却已经没有一支烟供我燃烧掉惆怅,缝补我碎裂的灵魂。
Spring Cloud
1、集群
不同的服务器部署同一套应用服务对外提供访问,实现服务的负载均衡。
集群指的是将几台服务器集中在一起,实现同一业务。
2、微服务
- SOA的去中心化实现
- 服务单一职责
微服务是在SOA上做的升华,微服务架构强调的一个重点是业务需要彻底的组件化和服务化,原有的单个业务系统会拆分为多个可以独立开发、设计、运行的小应用。这些小应用之间通过服务完成交互和集成。
SOA:面向服务的架构,一种设计方法,其中包含多个服务,服务之间通过相互依赖最终提供一 系列的功能。一个服务通常以独立的形式存在于操作系统进程中。各个服务之间通过网络调用。
3、分布式
分布式:一个业务拆分成多个子业务,每个子业务分别部署在不同的服务器上
- 分布式是指将不同的子业务分布在不同的服务器上。
- 集群指的是将几台服务器集中在一起,实现同一业务。
分布式中的每一个节点 ,都可以做集群,而集群并不一定就是分布式的。
分布式是以缩短单个任务的执行时间来提升效率的,而集群则是通过提高单位时间内执行的任务数来提升效率。
例如:如果一个任务由 10 个子任务组成,每个子任务单独执行需 1 小时,则在一台服务器上执行该任务需 10 小时。
采用分布式方案,提供 10 台服务器,每台服务器只负责处理1个子任务,1小时完成1个服务。(这种工作模式的一个典型代表就是 Hadoop 的 Map/Reduce 分布式计算模型)
而采用集群方案,同样提供 10 台服务器,每台服务器都能独立处理这个任务。假设有 10 个任务同时到达,10 个服务器将同时工作,10小时完成10个服务。
分布式一定是微服务,微服务不一定是分布式。微服务可以放在同一个服务器上,也可以放在不同的服务器上。
4、SpringCloud版本选择
- Springcloud版本号全部使用英文单词形式命名,Finchley.SR4是Finchley大版本的第四个小版本。。
- 最近 Spring Cloud 把版本号从 A 到 Z 的伦敦地铁站,改成用日期命名了。以日期为版本号,即所谓的
Calendar Versioning
通过官网查询:Spring Cloud
如本次我们使用的版本就是
- SpringBoot 2.3.6.RELEASE
- SpringCloud Hoxton.SR9
- SpringCloud Alibaba 2.2.6.RELEASE
5、Spring Cloud Alibaba
参考连接:版本发布说明 | Spring Cloud Alibaba (aliyun.com)
Spring Cloud 本身并不是一个开箱即用的框架,它是一套微服务规范,共有两代实现。
- Spring Cloud Netflix 是 Spring Cloud 的第一代实现,主要由 Eureka、Ribbon、Feign、Hystrix 等组件组成。
- Spring Cloud Alibaba 是 Spring Cloud 的第二代实现,主要由 Nacos、Sentinel、Seata 等组件组成。
Spring Cloud Alibaba 是阿里巴巴结合自身丰富的微服务实践而推出的微服务开发的一站式解决方案,是 Spring Cloud 第二代实现的主要组成部分。吸收了 Spring Cloud Netflix 微服务框架的核心架构思想,并进行了高性能改进。自 Spring Cloud Netflix 进入停更维护后,Spring Cloud Alibaba 逐渐代替它成为主流的微服务框架。
适配 Spring Boot 为 2.4,Spring Cloud Hoxton 版本及以下的 Spring Cloud Alibaba 版本按从新到旧排列如下表(最新版本用*标记):
Spring Cloud Alibaba Version | Spring Cloud Version | Spring Boot Version |
---|---|---|
2.2.10-RC1* | Spring Cloud Hoxton.SR12 | 2.3.12.RELEASE |
2.2.9.RELEASE | Spring Cloud Hoxton.SR12 | 2.3.12.RELEASE |
2.2.8.RELEASE | Spring Cloud Hoxton.SR12 | 2.3.12.RELEASE |
2.2.7.RELEASE | Spring Cloud Hoxton.SR12 | 2.3.12.RELEASE |
2.2.6.RELEASE | Spring Cloud Hoxton.SR9 | 2.3.2.RELEASE |
2.2.1.RELEASE | Spring Cloud Hoxton.SR3 | 2.2.5.RELEASE |
2.2.0.RELEASE | Spring Cloud Hoxton.RELEASE | 2.2.X.RELEASE |
2.1.4.RELEASE | Spring Cloud Greenwich.SR6 | 2.1.13.RELEASE |
2.1.2.RELEASE | Spring Cloud Greenwich | 2.1.X.RELEASE |
2.0.4.RELEASE(停止维护,建议升级) | Spring Cloud Finchley | 2.0.X.RELEASE |
1.5.1.RELEASE(停止维护,建议升级) | Spring Cloud Edgware | 1.5.X.RELEASE |
每个 Spring Cloud Alibaba 版本及其自身所适配的各组件对应版本如下表所示(注意,Spring Cloud Dubbo 从 2021.0.1.0 起已被移除出主干,不再随主干演进)
Spring Cloud Alibaba Version | Sentinel Version | Nacos Version | RocketMQ Version | Dubbo Version | Seata Version |
---|---|---|---|---|---|
2022.0.0.0 | 1.8.6 | 2.2.1 | 4.9.4 | ~ | 1.7.0 |
2022.0.0.0-RC2 | 1.8.6 | 2.2.1 | 4.9.4 | ~ | 1.7.0-native-rc2 |
2021.0.5.0 | 1.8.6 | 2.2.0 | 4.9.4 | ~ | 1.6.1 |
2.2.10-RC1 | 1.8.6 | 2.2.0 | 4.9.4 | ~ | 1.6.1 |
2022.0.0.0-RC1 | 1.8.6 | 2.2.1-RC | 4.9.4 | ~ | 1.6.1 |
2.2.9.RELEASE | 1.8.5 | 2.1.0 | 4.9.4 | ~ | 1.5.2 |
2021.0.4.0 | 1.8.5 | 2.0.4 | 4.9.4 | ~ | 1.5.2 |
2.2.8.RELEASE | 1.8.4 | 2.1.0 | 4.9.3 | ~ | 1.5.1 |
2021.0.1.0 | 1.8.3 | 1.4.2 | 4.9.2 | ~ | 1.4.2 |
2.2.7.RELEASE | 1.8.1 | 2.0.3 | 4.6.1 | 2.7.13 | 1.3.0 |
2.2.6.RELEASE | 1.8.1 | 1.4.2 | 4.4.0 | 2.7.8 | 1.3.0 |
2021.1 or 2.2.5.RELEASE or 2.1.4.RELEASE or 2.0.4.RELEASE | 1.8.0 | 1.4.1 | 4.4.0 | 2.7.8 | 1.3.0 |
2.2.3.RELEASE or 2.1.3.RELEASE or 2.0.3.RELEASE | 1.8.0 | 1.3.3 | 4.4.0 | 2.7.8 | 1.3.0 |
2.2.1.RELEASE or 2.1.2.RELEASE or 2.0.2.RELEASE | 1.7.1 | 1.2.1 | 4.4.0 | 2.7.6 | 1.2.0 |
2.2.0.RELEASE | 1.7.1 | 1.1.4 | 4.4.0 | 2.7.4.1 | 1.0.0 |
2.1.1.RELEASE or 2.0.1.RELEASE or 1.5.1.RELEASE | 1.7.0 | 1.1.4 | 4.4.0 | 2.7.3 | 0.9.0 |
2.1.0.RELEASE or 2.0.0.RELEASE or 1.5.0.RELEASE | 1.6.3 | 1.1.1 | 4.4.0 | 2.7.3 | 0.7.1 |
RestTemplate远程调用
1、服务交互的两种方式
-
RPC(Dubbo)
- Socket包+自定义序列化
-
RestAPI (SpringCloud)
- HTTP协议+JSON
RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,它是一种技术的思想,而不是规范,dubbo是其实现
2、高并发与高可用
高并发
工具限制(本身性能):
Tomcat 默认配置的最大请求数是 150,也就是说同时支持 150 个并发。当某个应用拥有 250 个以上并发的时候,应考虑应用服务器的集群。系统限制
Windows 每个进程中的线程数不允许超过 2000
Linux 每个进程中的线程数不允许超过 1000
高并发衡量指标
- 响应时间(RT) :请求做出响应的时间,即一个http请求返回所用的时间
- 吞吐量: 系统在单位时间内处理请求的数量
- QPS、 TPS:每秒查询(请求)数、每秒事务数
- Apache JMeter: 模拟高并发
高可用
服务集群部署,数据库双主机方式
主-备方式(Active-Standby方式)
主-备方式即指的是一台服务器处于某种业务的激活状态(即Active状态),另一台服务器处于该业务的备用状态(即Standby状态)。
双主机方式(Active-Active方式)
双主机方式即指两种不同业务分别在两台服务器上互为主备状态(即Active-Standby和Standby-Active状态)
3、RestTemplate入门案例
先创建一个父工程cloud-paraent,再在该父工程下创建其余的工程。
1、先保证cloud-consumer-order80 可以远程调用 cloud-provider-payment8001成功。
在消费端使用配置类配置bean对象RestTemplate
@SpringBootConfiguration
public class ApplicationContextConfig {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
并在controller层注入使用RestTemplate,使用以下方法实现远程调用
RestTemplate提供了多种便捷访问远程Http服务的方法
- restTemplate.getForObject()完成查询
- restTemplate.postForObject()完成添加
远程调用方法需要的三个参数
REST请求地址、请求参数、Http响应转换被转换成的对象类型。【其实看着都挺麻烦的,一会用OpenFeign替换】
@RestController
@Slf4j
public class OrderController {
public static final String PAYMENT_URL = "http://localhost:8001";
@Resource
private RestTemplate restTemplate;
@PostMapping("/consumer/payment/create")
public CommonResult<Payment> create(@RequestBody Payment payment){
return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment, CommonResult.class);
}
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
}
}
注意一个点,被远程调用的服务cloud-provider-payment8001的添加方法一定要使用@RequestBody,因为RESTAPI的风格就是http+json.
Eureka服务注册中心
1、Eureka理解
Eureka采用了CS的设计架构,Eureka Server作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接
Eureka Server 提供 服务注册服务
各个微服务节点通过配置启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息。
Eureka Client 通过 注册中心进行访问
是一个Java客户端,用于简化Eureka Server的交互。
2、单机Eureka的构建步骤
IDEA生成EurekaServer端服务注册中心,也就是工程: cloud-eureka-server7001
1、引入以下依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
2、同时在application.yml中做如下配置,一会浏览器访问 http://localhost:7001/
server: port: 7001 # eureka的服务端 eureka: instance: hostname: localhost #注册中心所在的服务器地址 client: register-with-eureka: false #因为我就是服务端,不需要注册到Eureka中 fetchRegistry: false #我就是服务端不需要从Eureka中获取信息 service-url: defaultZone: http://localhost:7001/eureka # 注册中心的访问地址
3、在主启动类上加注解 @EnableEurekaServer
@SpringBootApplication @EnableEurekaServer public class Application7001 { public static void main(String[] args) { SpringApplication.run(Application7001.class,args); } }
此时,启动工程 cloud-eureka-server7001,在浏览器端访问 http://localhost:7001/
在服务的提供者cloud-provider-payment8001和服务的消费者cloud-consumer-order80 中
都引入以下依赖,此时它俩都是Eurake的客户端(Eureka Client )
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
在配置文件application.yml中加入以下配置
提供者 cloud-provider-payment8001配置如下:
eureka: client: register-with-eureka: true #8001支付微服务 是客户端需要向注册中心注册信息 fetchRegistry: true #8001支付微服务 是客户端需要从注册中心获取信息 service-url: defaultZone: http://localhost:7001/eureka
消费者 cloud-consumer-order80配置如下:
eureka: client: register-with-eureka: true fetchRegistry: true service-url: defaultZone: http://localhost:7001/eureka
分别在主启动类加上注解:@EnableEurekaClient
提供者 cloud-provider-payment8001主启动类如下:
@SpringBootApplication @MapperScan(basePackages = "com.atguigu.springcloud.dao") @EnableEurekaClient public class PaymentMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentMain8001.class,args); } }
消费者 cloud-consumer-order80主启动类如下:
@SpringBootApplication @EnableEurekaClient public class OrderMain80 { public static void main(String[] args) { SpringApplication.run(OrderMain80.class,args); } }
依次启动工程 7001,8001,80
3、Eureka默认开启自我保护机制
Eureka 自我保护模式 主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。
Eureka采用AP思想:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存
Eurake默认开启自我保护
为什么会产生Eureka自我保护机制?
为了处理这样一种特殊情况:
EurekaClient(服务消费者和服务提供者)可以正常运行,但是网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,EurekaServer不会立刻将EurekaClient服务剔除
正常情况,即Eureka不进入自我保护模式
如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。
怎么触发Eureka的自我保护模式
当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。
自我保护机制的工作机制是如果在 15 分钟内超过 85%的客户端节点都没有正常的心跳, 那么 Eureka就认为客户端与注册中心出现了网络故障,Eureka Server 自动进入自我保护机制,
在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。
1、Eureka Server 不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。 2、Eureka Server 仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前 节点依然可用。 3、当网络稳定时,当前 Eureka Server 新的注册信息会被同步到其它节点中。
综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服 务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。
4、Eureka 禁止自我保护
Eurake服务端配置
Eurake客户端配置
Ribbon负载均衡服务调用
Spring Cloud Ribbon 是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
Ribbon目前也进入维护模式,未来替换方案是Spring Cloud LoadBalancer
LB负载均衡【load balancing】
将用户的请求平均分配到多个服务器上,从而达到系统的HA(高可用)。
Ribbon的本地负载均衡客户端 VS Nginx服务端负载均衡区别:
- Nginx是服务器负载均衡,客户端所有请求都会交给Nginx(http的服务器),然后由nginx实现转发请求到tomcat。即负载均衡是由服务器端完成的。
- Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用。
集中式LB(如Nginx)
- 即在服务的消费方和提供方之间使用独立的LB设施,由该设施负责把访问请求通过某种策略转发至服务的提供方;
进程内LB(如Ribbon)
- 将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
Ribbon=负载均衡+RestTemplate调用
Ribbon在工作时分成两步:
第一步,先选择EurekaServer,它优先选择在同一个区域内负载较少的server。
第二步,再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。其中Ribbon提供了多种策略。比如:轮询、随机和根据响应时间加权。
总结:Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和Eureka结合只是其中的一个实例。
Eureka客户端自带Ribbon的依赖,所以就没必要再引入下面的Ribbon依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
Ribbon核心组件IRule
IRule 接口及实现类如下:
IRule接口实现类:根据特定算法从服务列表中选取一个要访问的服务
- com.netflix.loadbalancer.RoundRobinRule 轮询,默认策略。
- com.netflix.loadbalancer.RandomRule 随机
- com.netflix.loadbalancer.RetryRule 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务
- WeightedResponseTimeRule 对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择(主动处理)
- BestAvailableRule 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
- AvailabilityFilteringRule 先过滤掉故障实例,再选择并发较小的实例
- ZoneAvoidanceRule 默认规则,复合判断server所在区域的性能和server的可用性选择服务器
IRule接口实现类配置负载均衡策略
新建一个配置类,要特别注意该配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化订制的目的了。
@SpringBootConfiguration
public class MySelfRule {
@Bean
public IRule myRule(){
return new RoundRobinRule();
}
}
主启动类添加@RibbonClient
在RestTemplate配置类上加注解,@LoadBalanced
在controller层修改代码,url不再是 http://localhost:8001
复刻8001,再生成8002的服务
做到这些还不够,现在只有8001的提供者,我们再给他复刻出来一个8002的服务
8001(右键复制)
想要上面那样的效果,需要快速运行设置
vm options: -Dserver.port=8002
意味着服务名一样,同一个服务多个实例
重启80服务,重新获取服务列表。
此时再测试就达到了负载均衡轮询的效果(很帅)
Ribbon负载均衡算法
rest接口第几次请求数%服务器集群总数量=实际调用服务器位置下标
每次服务重启动后rest接口计数从1开始。
OpenFeign服务接口调用
Feign是一个声明式的web服务客户端,Feign旨在使用编写Java Http客户端变得更容易。
OpenFeign的玩法分以下三步:
引入openfeign的启动器,已添加
在引导类上添加@EnableFeignClients
编写feign接口
1、Feign依赖、配置+@EnableFeignClients
1、利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。
2、与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。(下面会专门说Feign和OpenFeign的区别)
引入依赖
<!--openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.2.0.RELEASE</version> </dependency>
Feign本身不需要在配置文件中再多余的配置,但在sentinel中需要开启feign的支持
我们这里不需要开启对sentinel熔断降级的支持,配置文件application.yml配置
server: port: 80 spring: application: name: cloud-consumer-feign-order80
在主启动类或者配置类加一个注解 @EnableFeignClients
@EnableFeignClients 该注解的作用:启动对Feign接口的扫描
@SpringBootApplication @EnableFeignClients public class OrderFeignMain80 { public static void main(String[] args) { SpringApplication.run(OrderFeignMain80.class,args); } }
之前使用Ribbon+RestTemplate,因为不符合编程习惯。咱还是喜欢三层架构,没有Service层浑身不爽啊,那就使用OpenFeign的方式
在Controller层,调用service层,接着啥也不管。
@RestController public class OrderFeignController { @Resource private PaymentFeignService paymentFeignService; //调用远程的微服接口 @GetMapping(value = "/consumer/payment/get/{id}") public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){ //远程调用 return paymentFeignService.getPaymentById(id); } }
就是这么任性,哼。让我想起了傲娇的央视六公主,在b站的简介,就一个字。哼~
2、Feign接口+@FeignClient
新建Feign接口并添加注解@FeignClient
OpenFeign发起远程访问,底层仍然使用 HttpClient
1、远程访问的服务器ip+端口号
@FeignClient(value = "CLOUD-PAYMENT-SERVICE") CLOUD-PAYMENT-SERVICE可以到注册中心获取服务ip+端口号
2、请求方式
3、接口路径
4、请求参数列表
5、返回值如何转换
@Component @FeignClient(value = "CLOUD-PAYMENT-SERVICE") //下载注册中心的服务信息 public interface PaymentFeignService { @PostMapping(value = "/payment/create") public CommonResult<Payment> create(@RequestBody Payment payment); @GetMapping(value = "/payment/get/{id}") public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id); }
编写Feign接口注意2个点:
1、路径要完整,控制器类上有映射路径也要拷贝
2、注解要完整,@PathVariable("id")中的“Id” 不要省略
启动服务7001,8001,8002,openFeign80
浏览器测试
Feign自带负载均衡(轮询)配置项
3、OpenFeign超时控制
Feign说:我只等一秒(默认等待一秒),等不到就错误渲染
在服务提供者处模拟3秒的延迟
浏览器访问超时报错
那肯定1秒是太短了呀,就要设置Feign超时时间,在application.yml中加入以下配置(顶格)
ribbon: ReadTimeout: 4000 # 读取(处理业务的时间) ConnectTimeout: 4000 # 连接(连接提供者端的时间)
4、OpenFeign项目实际使用
业务需求:在新添加商品的时候,需要保存商品相关的营销信息,如积分活动,满减活动,折扣活动,是否叠加其他优惠。。。
需要gmall-pms【商品管理服务】中使用openFeign远程调用gmall-ssm服务【营销管理服务】
1.创建一个新模块 gmall-sms-interface
提供api接口,要特别的注意,借口上没有写@FeignClient注解
public interface GmallSmsApi { @PostMapping("/sms/skubounds/skusale/save") public ResponseVo<Object> saveSkuSaleInfo(@RequestBody SkuSaleVo skuSaleVo); }
GmallSmsApi接口里的内容是怎么来的呢,当然是从服务的提供方(ssm服务)拷贝的。
@Api(tags = "商品spu积分设置 管理") @RestController @RequestMapping("sms/skubounds") public class SkuBoundsController { @PostMapping("skusale/save") public ResponseVo<Object> saveSkuSaleInfo(@RequestBody SkuSaleVo skuSaleVo) { skuBoundsService.saveSkuSaleInfo(skuSaleVo); return ResponseVo.ok(); } }
2、在服务的消费方(gmall-pms)如何使用呐?
在服务的消费方(gmall-pms)的pom文件需要去引入依赖
<dependency> <groupId>com.atguigu</groupId> <artifactId>gmall-sms-interface</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
继承这个接口,服务消费方不用自己编写接口方法声明,直接继承提供方给的Api接口即可
@FeignClient("sms-service") public interface GmallSmsClient extends GmallSmsApi { }
之后就可以在gmall-pms服务使用这个Feign接口远程调用gmall-ssm服务了
//在gmall-pms服务 @Autowired private GmallSmsClient client; private void saveSkus(SpuVo spuVo, Long spuId) { client.saveSkuSaleInfo(skuSaleVo); }
当然前提:消费方服务的启动类上加了这个注解,@EnableFeignClients。但是好处在于不用去指定Feign接口的包路径
@SpringBootApplication @EnableSwagger2 @EnableFeignClients @EnableTransactionManagement public class GmallPmsApplication { public static void main(String[] args) { SpringApplication.run(GmallPmsApplication.class, args); } }
5、OpenFeign日志打印
Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign中Http请求的细节。
第一步:配置打印日志详细信息的级别,这里不是日志级别
日志详细信息的级别 配置类
@Configuration public class FeignConfig { @Bean Logger.Level feignLoggerLevel(){ return Logger.Level.FULL; } }
第二步:日志级别配置
在application.yml中,配置 feign日志以什么级别监控哪个接口
#feign日志级别配置
logging:
level: #feign日志以什么级别监控哪个接口
com.atguigu.gmall.pms.feign: debug
=======
测试 http://localhost:18081/pms/spu
打印的feign日志如下:
6、Feign的执行原理
1.SpringCloud为每一个FeignClient标注的Feign接口生成一个代理对象
2.代理对象会分析类与方法上的注解,就可判断出服务名与请求方法名的路由
3.从注册中心获取指定服务名的所有真实地址
4.利用负载均衡策略选择一个最佳地址,利用RestTemplate进行调用
5.等待接收返回
对于服务的地址,每次请求都需要去注册中心获取吗?
不是每次都需要从注册中心获取地址。Feign 维护了一个服务与地址的关系清单List当我们使用了Nacos,注册中心这个服务的地址有变化。nacos将数据实时推送给SpringCloud;我们就会将List里面的数据进行更换。我们每次服务调用这个清单都是最新的。
如果选择的最佳地址由于某种原因无法使用该如何处理?
SpringCloud Feign会进行重试。全部可以配置。重试次数不包含本次;如下图
7、Feign与OpenFeign的区别
他们底层都是内置了Ribbon,去调用注册中心的服务。
1.Feign是Netflix公司写的,是SpringCloud中的第一代负载均衡客户端。OpenFeign是SpringCloud自己研发的,是SpringCloud中的第二代负载均衡客户端。
2.OpenFeign在Feign的基础上支持了Spring MVC的注解,如@RequesMapping等等。
OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口, 并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务
3.feign已不在维护,openfeign维护频繁。
8、Feign远程调用请求头丢失
请求头丢失原因:消费方通过Feign调用提供方接口的时候,Feign会发起一个与消费方不同的一个新的请求,导致新的请求头中信息与消费方发起的请求头信息不一致
在进行服务与服务之间调用时,如果不通过Feign拦截器来添加请求头信息。下游服务是接收不到认证过的token令牌,无法进行身份验证。
SpringCloud的微服务使用Feign进行服务间调用的时候可以使用RequestInterceptor统一拦截请求,从request中取参数RequestTemplate对象,进行设置请求参数、添加请求头。
并将所有头文件数据再次加入到Feign请求的微服务头文件中
@Component public class FeignInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate requestTemplate) { RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); if (requestAttributes!=null){ HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); if (request!=null){ Enumeration<String> headerNames = request.getHeaderNames(); if (headerNames!=null){ while (headerNames.hasMoreElements()){ String headerName = headerNames.nextElement(); if (headerName.equals("authorization")){ String headerValue = request.getHeader(headerName);//Bearer jwt requestTemplate.header(headerName,headerValue);//向下传递令牌 } } } } } } }
利用tomcat请求与线程绑定机制 即 spring提供的 RequestContextHolder 解决。每次RequestContextHolder,getRequestAttributes获取的都是当前执行的请求线程的requestAttributes,也就获取了当前请求的request而requestAttributes具备ThreadLocal属性,属于线程内变量,各个线程之间互不干扰
RequestContextHolder 持有上下文的Request容器
RequestContextHolder这个类,里面有两个ThreadLocal保存当前线程下的request
//得到存储进去的request
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<RequestAttributes>("Request attributes");
//可被子线程继承的request
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<RequestAttributes>("Request context");
getRequestAttributes()`方法,相当于直接获取ThreadLocal里面的值,这样就保证了每一次获取到的Request是该请求的request.
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}