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的原理和源码,接下来我们要进 ...
随机推荐
- vs2019系统内置方法无提示
有个同事问我为什么他的vs编写C#代码,对于引用System.dll中的方法,鼠标移上去没有方法的使用说明或接口注释,具体可以看下面的截图,而我绝大多数情况下是使用Rider开发,并没有遇到这个问题, ...
- 错误的daemon.json配置,导致docker容器启动失败 Failed to start Docker Application Container Engine
docker学习-配置错误的源 问题点剖析 参考 docker学习-配置错误的源 问题点剖析 使用docker安装了nginx,编写Dockerfile,映射端口,终于跑起来了.但是,当我重启服务器, ...
- 第三届人工智能,大数据与算法国际学术会议 (CAIBDA 2023)
第三届人工智能,大数据与算法国际学术会议 (CAIBDA 2023) 大会官网:http://www.caibda.org/ 大会时间:2023年6月16-18日 大会地点:中国郑州 截稿日期:2 ...
- 强化学习从基础到进阶-案例与实践[1]:强化学习概述、序列决策、动作空间定义、策略价值函数、探索与利用、Gym强化学习实验
强化学习从基础到进阶-案例与实践[1]:强化学习概述.序列决策.动作空间定义.策略价值函数.探索与利用.Gym强化学习实验 1.1 强化学习概述 强化学习(reinforcement learning ...
- 【CSDN浏览器插件测评超详细版】——万能便捷的C功能,直接爱不释手,强烈推荐!更多功能等你探索
我的界面预览: 1.下载安装 下载链接:https://plugin.csdn.net/?utm_source=chajian-contest-1210#/chrome-index-help 说明:若 ...
- Win12不会取代Win11!真正目标是Google
Windows 11之后自然应该是Windows 12,但这一次不太一样. 据多个消息源确认,Windows的下一个重大版本将不会是Windows 11的直接升级版,而是更专注于云和Web,同时大力接 ...
- 【奶奶看了都会】ChatGPT3.5接入企业微信,可连续对话
1.连续对话效果 小伙伴们,这周ChatGPT放出大招,开放了GPT3.5的API.说简单点,就是提供了和ChatGPT页面对话一样模型的接口.而之前接的ChatGPT接口都是3.0,并不是真正的Ch ...
- [Java] 详细解说final关键字
final final 可以修饰变量.方法和类,表示所修饰的内容一旦赋值之后就不会再被改变.例如String类就是一个final类型的类. 1.具体使用场景 1.1 变量 1.1.1 成员变量 每个类 ...
- Codeforces Round #887 (Div. 2) A-D
比赛链接 A 代码 #include <bits/stdc++.h> using namespace std; using ll = long long; int a[507]; bool ...
- 【Unity3D】相机
1 简介 相机用于渲染游戏对象,每个场景中可以有多个相机,每个相机独立成像,每个成像都是一个图层,最后渲染的图层在最前面显示. 相机的属性面板如下: Clear Flags:设置清屏颜色,Sk ...