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的原理和源码,接下来我们要进 ...
随机推荐
- rider代码折叠
可折叠元素块 rider那些元素块是可折叠?参考官方文档:Fold Code Elements Code folding works for the keywords if/ while/ else/ ...
- 构建Keepalived高可用集群
Keepalived的作用是检测服务器的状态,如果有一台web服务器宕机或工作出现故障,Keepalived将检测到,并将有故障的服务器从系统中剔除,同时使用其他服务器代替该服务器的工作,当服务器工作 ...
- centos编译安装tcpdump
环境 CentOS Linux release 7.9.2009 (Core) 准备安装包 libpcap-1.5.3.tar.gz tcpdump-4.9.2.tar.gz 下载地址:https:/ ...
- 安装kali linux操作系统(转) - 初学者系列 - 学习者系列文章
前段时间想到操作系统安全问题,所以对操作系统的防火墙和安全软件都进行了安装.然后,涉及到Linux系统的安全测试问题,所以找到了Linux系统里的安全测试的版本Kali Linux系统.本文仅对该系统 ...
- CH32V208蓝牙从机sleep模式下功耗测试
本测试基于CH32V208W的开发板:蓝牙从机模式:使用程序BLE_UART 在进行功耗测试的时候尽量去除额外耗电器件,将开发板上的VDD于VIO相连接,测功耗时直接给VDD供电. 将会对500ms, ...
- Python 国内常用python模块下载地址
国内常用python模块下载地址 清华大学:https://pypi.tuna.tsinghua.edu.cn/simple 中国科技大学 https://pypi.mirrors.ustc.edu. ...
- 选课 洛谷P2014
传送门 \(\Large \textbf{问题描述}\) 大学里实行学分.每门课程都有一定的学分,学生只要选修了这门课并考核通过就能获得相应的学分.学生最后的学分是他选修的各门课的学分的总和. 每个学 ...
- ES6学习 第七章 函数的扩展
前言 本章介绍函数的扩展.有些不常用的知识了解即可. 本章原文链接:函数的扩展. 函数参数的默认值 ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面. 当函数形参没有被赋值时,才会将默认值 ...
- NC50454 A Simple Problem with Integers
题目链接 题目 题目描述 给定数列 \(a[1],a[2], \dots,a[n]\) ,你需要依次进行q个操作,操作有两类: 1 l r x:给定l,r,x,对于所有 \(i \in[l,r]\) ...
- NC17872 CSL的校园卡
题目链接 题目 题目描述 今天是阳光明媚,晴空万里的一天,CSL早早就高兴地起床走出寝室到校园里转悠. 但是,等到他回来的时候,发现他的校园卡不见了,于是他需要走遍校园寻找它的校园卡.CSL想要尽快地 ...