SpringCloud Ribbon和Feign 的使用和源码分析
1. Ribbon 介绍
Ribbon 是 Netflix 公司开源的一款 客户端 负载均衡软件,并被SpringCloud集成 作为SpringCloud 负载均衡的工具
服务端负载均衡 :
即在服务的消费方和提供方之间使用独立的负载均衡设施,可以是硬件也可以是软件.比如nginx,客户端统一访问nginx 由nginx进行负载均衡并转发到对应的服务,也是平时最常见的方式
示意图:
客户端负载均衡 :
将负载均衡逻辑集成到消费方, 比如ribbon ,它将从注册中心中获取服务列表与地址,并缓存到本地,然后调用时,在本地计算好合适的服务器直接进行访问
示意图:
2. 替换默认策略
Ribbon的基本使用,在我的Eureka
那篇文章中几节中已经展示过了,https://www.cnblogs.com/xjwhaha/p/14000370.html
结合SpringMvc的RestTemplate使用 非常简单,
@Bean
@LoadBalanced
public RestTemplate initRestTemplate(){
return new RestTemplate();
}
只需在注入RestTemplate方法时, 加上@LoadBalanced
注解,就会自动在RestTemplate加入相关的拦截器,加强该类,当使用时 进行负载均衡,默认为 轮询的方式
如果想要在调用某一个服务时, 使用其他的负载均衡策略 ,也可以单独指定
定义一个配置类,并注入相关的负载均衡策略类
/** 随机负载均衡算法
* @author 1625963331@qq.com
* @date 2020/8/23
*/
@Configuration
public class MySelfRule {
@Bean
public IRule myRule(){
return new RandomRule();
}
}
此类的位置不能被主启动类扫描到,不然将会替换默认的策略,即全部的服务调用都使用这个策略,不符合单独指定的要求
再在主启动类上加上配置,指定服务名 与配置类
@SpringBootApplication
//Ribbon访问 该服务的负载均衡算法 使用该自定义配置类
@RibbonClient(name="CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class, args);
}
}
这样 在调用服务名为CLOUD-PAYMENT-SERVICE
时 使用随机算法
3. 实现一个简单的Ribbon
Ribbon的实现方式相对简单,模仿其思想 实现一个简单的负载均衡工具
使用DiscoveryClient
类, 此为Spring-Cloud 的类,可以获取注册信息的相关信息
LoadBalance接口: 根据服务列表 计算出合适的 具体服务
public interface LoadBalance {
ServiceInstance instance(List<ServiceInstance> serviceInstances);
}
实现: 使用CAS 原子操作类 实现自旋锁自增 ,并对服务列数长度取模得出实际的服务
@Component
public class MyLB implements LoadBalance {
private AtomicInteger atomicInteger = new AtomicInteger(0);
public final int getAndIncrement() {
int current;
int next;
///
do {
current = this.atomicInteger.get();
next = current >= Integer.MAX_VALUE ? 1 : current + 1;
} while (!this.atomicInteger.compareAndSet(current, next));
System.out.println("****next: " + next);
return next;
}
@Override
public ServiceInstance instance(List<ServiceInstance> serviceInstances) {
int index = getAndIncrement()%serviceInstances.size();
return serviceInstances.get(index);
}
}
controller调用
@RestController
@Slf4j
public class OrderController {
@Resource
private RestTemplate restTemplate;
@Resource
private DiscoveryClient discoveryClient;
@Resource
private LoadBalance loadBalance;
/**
* 使用自定义的 从注册中心获取服务并实现负载均衡的算法
* @return
*/
@GetMapping("/consumer/payment/lb")
public String getPaymentLB() {
//获取CLOUD-PAYMENT-SERVICE 服务列表
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
if (instances == null || instances.size() <= 0) {
return null;
}
ServiceInstance serviceInstance = loadBalance.instance(instances);
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri + "/payment/lb", String.class);
}
}
4. Feign 与 OpenFeign
前面在使用Ribbon+RestTemplate 时, 利用 @LoadBalanced 注解 将RestTemplate 类加强,并实现客户端对服务端的调用并负载均衡,但是我们发现,在调用服务端时 必须手动指定其服务名,而客户端往往不止调用一个服务端,这使的Ribbon的使用变得复杂
Feign 集成了Ribbon,它是一个声明式的客户端工具,可以通过定义一个接口,并通过注解的方式生成代理类,封装了Ribbon的调用,它有一套自己的注解
OpenFeign是springcloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping
等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
下面通过OpenFeign 对之前的服务端进行调用
pom(基于前面文章中的工程):
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
定义接口: 指定服务名 CLOUD-PAYMENT-SERVICE
当调用 getPaymentById
方法时 ,将参数 id替换url中的 id 并进行调用
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id}")
String getPaymentById(@PathVariable("id") Long id);
}
服务消费方Controller: 声明注入接口,实际注入的是 Feign的代理类,并进行调用
@RestController
@Slf4j
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping(value = "/consumer/payment/get/{id}")
public String getPaymentById(@PathVariable("id") Long id){
return paymentFeignService.getPaymentById(id);
}
}
服务提供者Controller, 返回 自己的端口
@GetMapping(value = "/payment/get/{id}")
public String getPaymentById(@PathVariable("id") Long id) {
return "查询成功,serverPort: "+ serverPort;
}
启动类: @EnableFeignClients
开启 OpenFeign 的 自动配置
@SpringBootApplication
@EnableFeignClients
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class, args);
}
}
调用客户端Controller:http://127.0.0.1/consumer/payment/get/10
成功返回提供方响应信息:查询成功,serverPort: 8001
5. Feign 超时时间设置
当消费方调用服务方时,因为网络或者服务方业务流程过长,将导致消费方读取超时, Feign 最大的等待时间为1秒, 超过一秒,将直接报错超时
添加服务提供方长流程操作, 操作时间需要两秒
// 代表 服務提供方 某一个操作很耗时,要消费方设置超时时间
@GetMapping("/payment/feign/timeout")
public String paymentFeignTimeOut(){
try {
TimeUnit.SECONDS.sleep(2);
}catch (InterruptedException e){
e.printStackTrace();
}
return serverPort;
}
OpenFeign接口中添加调用该接口的方法
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id}")
String getPaymentById(@PathVariable("id") Long id);
@GetMapping(value = "/payment/feign/timeout")
String paymentFeignTimeOut();
}
消费方调用
@GetMapping(value = "/consumer/payment/feign/timeout")
public String paymentFeignTimeOut(){
//客户端默认等待1秒钟
return paymentFeignService.paymentFeignTimeOut();
}
浏览器访问http://127.0.0.1/consumer/payment/feign/timeout
报错页面,显示超时
我们也可以手动调整这个时间,
修改yaml
#设置feign客户端超时时间 (单位:毫秒)
ribbon:
#最大读取时间
ReadTimeout: 5000
#最大连接时间
ConnectTimeout: 5000
重启后重新调用,成功返回服务方信息 8001
6. Ribbon 源码分析
使用Ribbon非常简单,在之前的代码中,我们仅仅只是 在RestTemplate 类上 加了了注解 ,就自动将RestTemplate类进行加强,可以获取EurekaServer上注册的服务 并进行负载均衡,
看看SpringCloud如何实现:
1.RestTemplate如何被加强
在LoadBalanced 注解包下,有个LoadBalancerAutoConfiguration
类,这个类在META-INf/spring-factories 中被声明,在启动过程中被加载 (SpringBoot自动配置原理,详情查看这篇博客:https://www.cnblogs.com/xjwhaha/p/13615288.html )
源码:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
/**
* 容器中被@LoadBalanced 注解修饰的RestTemplate 都会注入到本集合中
*/
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
/**
* 循环集合 将所有的 RestTemplate 类用定制器定制加强
*/
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
}
/**
* 配置类, 根据条件注入到容器中
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
/*
* 从容器中接受一个 LoadBalancerClient (主要的工作类)
* 并作为参数初始化一个拦截器注入到容器中
*/
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
/*
* 接受上面那个拦截器,并构建一个 RestTemplate定制器
* 定制器的内容为 循环 项目中的 RestTemplate类列表,并将拦截器加入到 每个RestTemplate实例的拦 * 截器链中
*/
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
/**
* Auto configuration for retry mechanism.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RetryTemplate.class)
public static class RetryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryFactory() {
return new LoadBalancedRetryFactory() {
};
}
}
/**
* Auto configuration for retry intercepting mechanism.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RetryTemplate.class)
public static class RetryInterceptorAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRetryProperties properties,
LoadBalancerRequestFactory requestFactory,
LoadBalancedRetryFactory loadBalancedRetryFactory) {
return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
requestFactory, loadBalancedRetryFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
}
我们发现 该类有一个属性,为
RestTemplate
集合,并被@LoadBalanced
修饰,在初始化该类时,会将容器中 被@LoadBalanced
修饰的RestTemplate
,都注入到集合中,这样我们就拿到了我们自己声明的RestTemplate
类了该类中 还根据条件 注入了
LoadBalancerInterceptorConfig
配置类,,其中的restTemplateCustomizer
方法 接收一个LoadBalancerInterceptor
拦截器, 并返回一个RestTemplateCustomizer
的定制器函数式类,其中的实现为 向原生的RestTemplate
拦截器链中加入该拦截器.而这个拦截器的初始化就在上方, 接收loadBalancerClient
实现类 初始化拦截器,并注入到容器中,最后由
loadBalancedRestTemplateInitializerDeprecated
方法 将定制器接收 并循环restTemplates 定制加强每个RestTemplate
SpringCloud Ribbon和Feign 的使用和源码分析的更多相关文章
- Quartz学习--二 Hello Quartz! 和源码分析
Quartz学习--二 Hello Quartz! 和源码分析 三. Hello Quartz! 我会跟着 第一章 6.2 的图来 进行同步代码编写 简单入门示例: 创建一个新的java普通工程 ...
- Android Debuggerd 简要介绍和源码分析(转载)
转载: http://dylangao.com/2014/05/16/android-debuggerd-%E7%AE%80%E8%A6%81%E4%BB%8B%E7%BB%8D%E5%92%8C%E ...
- Java并发编程(七)ConcurrentLinkedQueue的实现原理和源码分析
相关文章 Java并发编程(一)线程定义.状态和属性 Java并发编程(二)同步 Java并发编程(三)volatile域 Java并发编程(四)Java内存模型 Java并发编程(五)Concurr ...
- Kubernetes Job Controller 原理和源码分析(一)
概述什么是 JobJob 入门示例Job 的 specPod Template并发问题其他属性 概述 Job 是主要的 Kubernetes 原生 Workload 资源之一,是在 Kubernete ...
- Kubernetes Job Controller 原理和源码分析(二)
概述程序入口Job controller 的创建Controller 对象NewController()podControlEventHandlerJob AddFunc DeleteFuncJob ...
- Kubernetes Job Controller 原理和源码分析(三)
概述Job controller 的启动processNextWorkItem()核心调谐逻辑入口 - syncJob()Pod 数量管理 - manageJob()小结 概述 源码版本:kubern ...
- OpenMP For Construct dynamic 调度方式实现原理和源码分析
OpenMP For Construct dynamic 调度方式实现原理和源码分析 前言 在本篇文章当中主要给大家介绍 OpenMp for construct 的实现原理,以及与他相关的动态库函数 ...
- OPENMP FOR CONSTRUCT GUIDED 调度方式实现原理和源码分析
OPENMP FOR CONSTRUCT GUIDED 调度方式实现原理和源码分析 前言 在本篇文章当中主要给大家介绍在 OpenMP 当中 guided 调度方式的实现原理.这个调度方式其实和 dy ...
- 微服务生态组件之Spring Cloud LoadBalancer详解和源码分析
Spring Cloud LoadBalancer 概述 Spring Cloud LoadBalancer目前Spring官方是放在spring-cloud-commons里,Spring Clou ...
- 【SpringCloud技术专题】「Eureka源码分析」从源码层面让你认识Eureka工作流程和运作机制(上)
前言介绍 了解到了SpringCloud,大家都应该知道注册中心,而对于我们从过去到现在,SpringCloud中用的最多的注册中心就是Eureka了,所以深入Eureka的原理和源码,接下来我们要进 ...
随机推荐
- 【0基础学爬虫】爬虫基础之自动化工具 Pyppeteer 的使用
大数据时代,各行各业对数据采集的需求日益增多,网络爬虫的运用也更为广泛,越来越多的人开始学习网络爬虫这项技术,K哥爬虫此前已经推出不少爬虫进阶.逆向相关文章,为实现从易到难全方位覆盖,特设[0基础学爬 ...
- 设计模式学习-使用go实现解释器模式
解释器模式 定义 优点 缺点 适用范围 代码实现 参考 解释器模式 定义 解释器模式(interpreter):给定一种语言,定义它的文法的一种表示,并定一个解释器,这个解释器使用该表示来解释语言中的 ...
- 大规模语言LLaVA:多模态GPT-4智能助手,融合语言与视觉,满足用户复杂需求
大规模语言LLaVA:多模态GPT-4智能助手,融合语言与视觉,满足用户复杂需求 一个面向多模式GPT-4级别能力构建的助手.它结合了自然语言处理和计算机视觉,为用户提供了强大的多模式交互和理解.LL ...
- C/C++ 常用开发代码片段
由于内容较少,所以,不想把它放在我的本地博客中了,暂时保存在这里,代码有一部分来源于网络,比较经典的案例,同样收藏起来. Stack 栈容器 Stack容器适配器中的数据是以LIFO的方式组织的,它是 ...
- automapper 10 +autofac+asp.net web api
automapper 不必多说 https://automapper.org autofac 这里也不多说 https://autofac.org 这里主要 说 automapper 10.0 版本+ ...
- Nginx负载均衡、location匹配
nginx的日志 ``` #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$ ...
- 天玑9300大战骁龙8 Gen3:十余项数据实测 到底谁才是安卓之王?
一.前言:全大核天玑9300正面硬钢骁龙8 Gen3 究竟谁才是安卓芯片之王? 今年,两家移动芯片厂商都开始放大招了,骁龙首发Cortex-X4超大核,联发科也不甘示弱,初次将"全大核&qu ...
- 教你轻松用上ChatGPT
最近ChatGPT大火呀,小伙伴们是不是在网上看到各种和ChatGPT有趣聊天的截图,奈何自己实力不够,被网络拒之门外,只能眼馋别人的东西.看别人玩,肯定不如自己玩一把舒服的啊.今天小卷就给大家汇总了 ...
- CH59X/CH58X/CH57X sleep模式下串口唤醒收发数据
整体程序逻辑: 下方的具体程序及使用是基于CH592进行的 SLEEP模式睡眠唤醒是由协议栈管理的,还在睡眠时,无法接收到数据. 已经通过使能HAL_SLEEP开启睡眠.如果需要在睡眠时实时接收串口传 ...
- 使用了未经检查或不安全的操作。 有关详细信息, 请使用 -Xlint:unchecked 重新编译