接上文
作者:🍧dabing(王甜甜)
视频:尚硅谷 2020 周阳 SpringCloud(H 版 & alibaba)
参考链接:https://blog.csdn.net/qq_36903261/article/details/106590923
官方文档:https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/#features
github 上代码地址:https://github.com/dabing85/springcloud2020
gitee 上代码地址:https://gitee.com/hedabing/springcloud2020
# 八、Ribbon 负载均衡调用
# 1 - 概述
文档说明:https://github.com/Netflix/ribbon/wiki/Getting-Started
中文社区:http://docs.springcloud.cn/user-guide/ribbon/
# 1.1. 是什么
Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的一套客户端负载均衡的工具。
简单的说,Ribbon 是 Netflix 发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。
Ribbon 客户端组件提供一系列完善的配置项,如:连接超时,重试等。
简单的说,就是在配置文件中列出 **Load Balancer (简称 LB)** 后面所有的机器,Ribbon 会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。
前面的 Eureka、Zookeeper、Consul 的也因为集成了 Ribbon 所以也能实现负载均衡。
# 1.2. 官网资料
https://github.com/Netflix/ribbon
Ribbon 目前也进入维护模式
- 未来替换方案
- Spring Cloud LoadBalancer
# 1.3. 能干嘛
# 1. LB(负载均衡)
-
简单的说就是将用户的请求平均分配到多个服务器上,从而达到系统的 HA (高可用)。
-
常见的负载均衡有软件
Nginx
,LVS,硬件 F5 等。
3) Ribbon 的本地负载均衡客户端 VS Nginx 服务端负载均衡区别:
-
Nginx 是服务器负载均衡,客户端所有请求都会交给 Nginx,然后,由 nginx 实现转发请求。即负载均衡是由服务器端完成的。
-
Ribbon 本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到 JVM 本地,从而在本地实现 RPC 远程服务调用。
- 集中式 LB
- 即在服务的消费方和提供方之间使用独立的 LB 设施(可以是硬件,如 F5,也可以是软件,如 Nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方;
- 进程内 LB
-
将 LB 逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
-
Ribbon 就属于进程内 LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
# 2. 一句话
Ribbon = 负载均衡 + RestTemplate 调用
# 2 - Ribbon 负载均衡演示
# 2.1. 架构说明
总结:Ribbon 其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和 eureka 结合只是其中一个实例
Ribbon 在工作时分成两步:
第一步,先选择 Eureka Server,它优先选择在同一个区域内负载较少的 server。
第二步,再根据用户指定的策略,在从 server 取到的服务注册列表中选择一个地址。其中 Ribbon 提供了多种策略。比如:轮询、随机和根据响应时间加权。
总结:Ribbon 其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和 eureka 结合只是其中的一个实例。
# 2.2. POM
<dependency> | |
<groupId>org.springframework.cloud</groupId> | |
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId> | |
</dependency> |
注意:这个不需要手动引用,Eureka 客户端自带 Ribbon
# 2.3. RestTemplate
API 文档说明:https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html
RestTemplate 的使用可以参考这篇文章:https://blog.csdn.net/qq_41538097/article/details/123560238
前面的四 - 3.3 有使用过可以回顾一下。在 Bean 注入的配置文件中加入 @LoadBalanced 就能赋予它负载均衡的能力啦~
getForObject 方法 /getForEntity 方法:
# 3 - Ribbon 核心组件 IRule
# 3.1. IRule:根据特定算法从服务列表中选取一个要访问的服务
- com.netflix.loadbalancer.RoundRobinRule :
轮询
,默认策略。 - com.netflix.loadbalancer.RandomRule :
随机
- com.netflix.loadbalancer.RetryRule :先按照 RoundRobinRule (轮询)的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务
- WeightedResponseTimeRule :对 RoundRobinRule 的扩展,响应速度越快的实例选择权重越大,越容易被选择
- BestAvailableRule :会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
- AvailabilityFilteringRule :先过滤掉故障实例,再选择并发较小的实例
- ZoneAvoidanceRule :默认规则,复合判断 server 所在区域的性能和 server 的可用性选择服务器
# 3.2. 如何替换
- 新建 package - com.dabing.myrule
- 新建配置类 - MySelfRule
- 主启动类添加 - @RibbonClient (name = “CLOUD-PAYMENT-SERVICE”, configuration = MySelfRule.class)
- 测试 - http://localhost/consumer/payment/get/1
-
修改 cloud-consumer-order80
-
注意配置细节
官方文档明确给出警告:
https://docs.spring.io/spring-cloud-netflix/docs/2.2.6.RELEASE/reference/html/#customizing-the-ribbon-client
这个自定义配置类不能放在 @ComponentScan 所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的 Ribbon 客户端所共享,达不到特殊化订制的目的了。
# 3.3. 新建 package(注意:包的位置)
com.dabing.myrule
# 3.4. 上面包下新建 MySelfRule 规则类
package com.dabing.myrule; | |
import com.netflix.loadbalancer.IRule; | |
import com.netflix.loadbalancer.RandomRule; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
@Configuration | |
public class MySelfRule { | |
@Bean | |
public IRule myRule(){ | |
return new RandomRule();// 使用随机的负载均衡方式 | |
} | |
} |
# 3.5. 主启动类添加 @RibbonClient
package com.dabing.springcloud; | |
import com.dabing.myrule.MySelfRule; | |
import org.springframework.boot.SpringApplication; | |
import org.springframework.boot.autoconfigure.SpringBootApplication; | |
import org.springframework.cloud.netflix.eureka.EnableEurekaClient; | |
import org.springframework.cloud.netflix.ribbon.RibbonClient; | |
@EnableEurekaClient | |
@SpringBootApplication | |
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class) | |
public class ConsumerMain80 { | |
public static void main(String[] args) { | |
SpringApplication.run(ConsumerMain80.class); | |
} | |
} |
# 3.6. 测试
http://localhost/consumer/payment/get/1
# 4 - Ribbon 负载均衡算法
# 4.1. 原理
在: package com.netflix.loadbalancer
有其源码,核心为 choose 方法。
# 九、 OpenFeign 服务接口调用
# 1 - 概述
# 1.1. OpenFeign 是什么
- Feign 是一个声明式的 web 服务客户端,让编写 web 服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可
- SpringCloud 对 Feign 进行了封装,使其支持了 SpringMVC 标准注解和 HttpMessageConverters。Feign 可以与 Eureka 和 Ribbon 组合使用以支持负载均衡。
- https://docs.spring.io/spring-cloud-openfeign/docs/2.2.6.RELEASE/reference/html/
- Github : https://github.com/spring-cloud/spring-cloud-openfeign
# 1.2. 能干嘛
- Feign 能干什么?
Feign 旨在使用编写 Java Http 客户端变得更容易。
前面在使用 Ribbon+RestTemplate 时,利用 RestTemplate 对 Http 请求的封装处理,形成了一套模板化的调用方法。
但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务端额调用。所以,Feign 在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。
在 Feign 的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是 DAO 接口上面标注 Mapper 注解,现在是一个微服务接口上面标注一个 Feign 注解即可),即可完成对服务提供方的接口绑定,简化了使用 Spring Cloud Ribbon 时,自动封装服务调用客户端的开发量。
- Feign 集成了 Ribbon
利用 Ribbon 维护了 Payment 的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与 Ribbon 不同的是,通过 Feign 只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
- Feign 和 OpenFeign 两者区别 :OpenFeign 支持 SpringMVC 注解
# 2 - OpenFeign 使用步骤
- 有一个服务提供者 - 8001 CLOUD-PAYMENT-SERVICE
- 服务消费者 - cloud-consumer-feign-order80
- 同样的建 moudle、改 pom、yaml、主启动类、service、controller
- pom - spring-cloud-starter-openfeign
- yml - 一样的注册服务即可
- 主启动类 - @EnableFeignClients
- service - 要使用的微服务接口,指定要调用的远程服务名称 @FeignClient (value = “CLOUD-PAYMENT-SERVICE”)
- controller - 直接使用 service 接口即可。
具体见下:
# 2.1. 接口 + 注解
微服务调用接口 +@FeignClient
# 2.2. 新建 Module:cloud-consumer-feign-order80
# 2.3. POM
注意:openFeign 也是自带 ribbon
<?xml version="1.0" encoding="UTF-8"?> | |
<project xmlns="http://maven.apache.org/POM/4.0.0" | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
<parent> | |
<artifactId>Cloud2020</artifactId> | |
<groupId>com.dabing</groupId> | |
<version>1.0-SNAPSHOT</version> | |
</parent> | |
<modelVersion>4.0.0</modelVersion> | |
<artifactId>cloud-consumer-feign-order80</artifactId> | |
<description>OpenFeign服务消费者</description> | |
<properties> | |
<maven.compiler.source>8</maven.compiler.source> | |
<maven.compiler.target>8</maven.compiler.target> | |
</properties> | |
<dependencies> | |
<!--openfeign--> | |
<dependency> | |
<groupId>org.springframework.cloud</groupId> | |
<artifactId>spring-cloud-starter-openfeign</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.cloud</groupId> | |
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>com.dabing</groupId> | |
<artifactId>cloud-api-commons</artifactId> | |
<version>${project.version}</version> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-web</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-actuator</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-devtools</artifactId> | |
<scope>runtime</scope> | |
<optional>true</optional> | |
</dependency> | |
<dependency> | |
<groupId>org.projectlombok</groupId> | |
<artifactId>lombok</artifactId> | |
<optional>true</optional> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-test</artifactId> | |
<scope>test</scope> | |
</dependency> | |
</dependencies> | |
</project> |
# 2.4. YML
server: | |
port: 80 | |
spring: | |
application: | |
name: cloud-consumer-feign-order80 | |
eureka: | |
client: | |
register-with-eureka: true | |
fetch-registry: true | |
service-url: | |
defaultZone: http://localhost:7001/eureka |
# 2.5. 主启动类
package com.dabing.springcloud; | |
import org.springframework.boot.SpringApplication; | |
import org.springframework.boot.autoconfigure.SpringBootApplication; | |
import org.springframework.cloud.netflix.eureka.EnableEurekaClient; | |
import org.springframework.cloud.openfeign.EnableFeignClients; | |
@SpringBootApplication | |
@EnableEurekaClient | |
@EnableFeignClients | |
public class OrderFeignMain80 { | |
public static void main(String[] args) { | |
SpringApplication.run(OrderFeignMain80.class,args); | |
} | |
} |
# 2.6. 业务类
# 1. 业务逻辑接口 +@FeignClient 配置调用 provider 服务
# 2. 新建 PaymentFeignService 接口并新增注解 @FeignClient
package com.dabing.springcloud.service; | |
import com.dabing.springcloud.entities.CommonResult; | |
import org.springframework.cloud.openfeign.FeignClient; | |
import org.springframework.stereotype.Component; | |
import org.springframework.web.bind.annotation.GetMapping; | |
import org.springframework.web.bind.annotation.PathVariable; | |
@Component | |
@FeignClient(value = "CLOUD-PAYMENT-SERVICE") | |
public interface PaymentFeignService { | |
@GetMapping("/payment/get/{id}") | |
public CommonResult getPaymentById(@PathVariable("id") Long id); | |
} |
# 3. 控制层 Controller
package com.dabing.springcloud.controller; | |
import com.dabing.springcloud.entities.CommonResult; | |
import com.dabing.springcloud.service.PaymentFeignService; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.web.bind.annotation.GetMapping; | |
import org.springframework.web.bind.annotation.PathVariable; | |
import org.springframework.web.bind.annotation.RestController; | |
@RestController | |
public class OrderFeignController { | |
@Autowired | |
private PaymentFeignService paymentFeignService; // 调用远程的微服接口 | |
@GetMapping(value = "/consumer/openFeign/payment/get/{id}") | |
public CommonResult getPaymentById(@PathVariable("id") Long id){ | |
return paymentFeignService.getPaymentById(id); | |
} | |
} |
# 2.7. 测试
-
先启动 Eureka7001、7002、7003(嫌麻烦可以先把注册中心服务端集群去掉)
-
再启动 2 个微服务 8001/8002 (客户端集群)
-
启动 OpenFeign 微服务:cloud-consumer-feign-order80
-
http://localhost/consumer/openFeign/payment/get/1
-
Feign 自带负载均衡配置项
# 2.8. 小总结
借图:
# 3 - OpenFeign 超时控制
# 3.1. 超时设置,故意设置超时演示出错情况
# 1. 服务提供方 8001 故意写暂停程序
@GetMapping(value = "/payment/feign/timeout") | |
public String paymentFeignTimeout(){ | |
try { TimeUnit.SECONDS.sleep(3); }catch (Exception e) {e.printStackTrace();} // 单位秒 | |
return "测试feign超时,端口:"+serverPort; | |
} |
# 2. 服务消费方 80 添加超时方法 PaymentFeignService
@GetMapping(value = "/payment/feign/timeout") | |
public String paymentFeignTimeout(); |
# 3. 服务消费方 80 添加超时方法 OrderFeignController
@GetMapping(value = "/consumer/payment/feign/timeout") | |
public String paymentFeignTimeout(){ | |
return paymentFeignService.paymentFeignTimeout(); | |
} |
# 4. 测试
http://localhost/consumer/payment/feign/timeout
错误页面,OpenFeign 默认等待一秒钟,超过后报错
# 3.2. 是什么
默认 Feign 客户端只等待 一秒钟
,但是,服务端处理需要超过 1 秒钟,导致 Feign 客户端不想等待了,直接报错。
为了避免这样的情况,有时候我们需要设置 Feign 客户端的超时控制,也即 Ribbon 的超时时间,因为 Feign 集成了 Ribbon 进行负载均衡。
# 3.3. YML 中需要开启 OpenFeign 客户端超时控制
Feign 设置超时时间
使用 Feign 调用接口分两层,ribbon 的调用和 hystrix 的调用,所以 ribbon 的超时时间和 Hystrix 的超时时间的结合就是 Feign 的超时时间
#设置 Feign 客户端超时时间(openfeign 默认支持 ribbon) | |
ribbon: | |
ReadTimeout: 3000 | |
ConnectTimeout: 3000 | |
MaxAutoRetries: 1 #同一台实例最大重试次数,不包括首次调用 | |
MaxAutoRetriesNextServer: 1 #重试负载均衡其他的实例最大重试次数,不包括首次调用 | |
OkToRetryOnAllOperations: false #非 Get 请求是否重试 | |
#hystrix 的超时时间 | |
hystrix: | |
command: | |
default: | |
execution: | |
timeout: | |
enabled: true | |
isolation: | |
thread: | |
timeoutInMilliseconds: 9000 |
一般情况下 都是 ribbon 的超时时间(<)hystrix 的超时时间(因为涉及到 ribbon 的重试机制)
因为 ribbon 的重试机制和 Feign 的重试机制有冲突,所以源码中默认关闭 Feign 的重试机制,源码如下
要开启 Feign 的重试机制如下:(Feign 默认重试五次 源码中有)
@Bean | |
Retryer feignRetryer() { | |
return new Retryer.Default(); | |
} |
根据上面的参数计算重试的次数:
==(MaxAutoRetries+1)*(MaxAutoRetriesNextServer+1)== 一共产生 4 次调用
如果在重试期间,时间超过了 hystrix 的超时时间,便会立即执行熔断,fallback。所以要根据上面配置的参数计算 hystrix 的超时时间,使得在重试期间不能达到 hystrix 的超时时间,不然重试机制就会没有意义
hystrix 超时时间的计算: (1 + MaxAutoRetries + MaxAutoRetriesNextServer) * ReadTimeout 即按照以上的配置 hystrix 的超时时间应该配置为 (1+1+1)*3=9 秒
当 ribbon 超时后且 hystrix 没有超时,便会采取重试机制。当 OkToRetryOnAllOperations 设置为 false 时,只会对 get 请求进行重试。如果设置为 true,便会对所有的请求进行重试,如果是 put 或 post 等写操作,如果服务器接口没做幂等性,会产生不好的结果,所以 OkToRetryOnAllOperations 慎用。
如果不配置 ribbon 的重试次数,默认会重试一次
注意:
默认情况下,GET 方式请求无论是连接异常还是读取异常,都会进行重试
非 GET 方式请求,只有连接异常时,才会进行重试
测试重试次数是否为 4 次:
在 8001 的 controller 服务中加上打印端口的代码,便于查看:
因此次数 4 次:因为没有开集群,四次重试均通过 8001,如果集群,那 4 次会分布在各个实例。
# 4 - OpenFeign 日志打印功能
# 4.1. 日志打印功能
# 4.2. 是什么
Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。说白了就是对 Feign 接口的调用情况进行监控和输出。
# 4.3. 日志级别
NONE:默认的,不显示任何日志
BASIC:仅记录请求方法、RUL、响应状态码及执行时间
HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息
FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据
# 4.4. 配置日志 bean
package com.atguigu.springcloud.config; | |
import feign.Logger; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
@Configuration | |
public class FeignConfig { | |
@Bean | |
public Logger.Level feignLoggerLevel(){ | |
return Logger.Level.FULL; | |
} | |
} |
# 4.5. YML 文件里需要开启日志的 Feign 客户端
#开启日志的 feign 客户端 | |
logging: | |
level: | |
#feign 日志以什么级别监控哪个接口 | |
com.dabing.springcloud.service.PaymentFeignService: debug |
# 4.6. 后台日志查看
http://localhost/consumer/openFeign/payment/get/1