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 的使用和源码分析的更多相关文章

  1. Quartz学习--二 Hello Quartz! 和源码分析

    Quartz学习--二  Hello Quartz! 和源码分析 三.  Hello Quartz! 我会跟着 第一章 6.2 的图来 进行同步代码编写 简单入门示例: 创建一个新的java普通工程 ...

  2. 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 ...

  3. Java并发编程(七)ConcurrentLinkedQueue的实现原理和源码分析

    相关文章 Java并发编程(一)线程定义.状态和属性 Java并发编程(二)同步 Java并发编程(三)volatile域 Java并发编程(四)Java内存模型 Java并发编程(五)Concurr ...

  4. Kubernetes Job Controller 原理和源码分析(一)

    概述什么是 JobJob 入门示例Job 的 specPod Template并发问题其他属性 概述 Job 是主要的 Kubernetes 原生 Workload 资源之一,是在 Kubernete ...

  5. Kubernetes Job Controller 原理和源码分析(二)

    概述程序入口Job controller 的创建Controller 对象NewController()podControlEventHandlerJob AddFunc DeleteFuncJob ...

  6. Kubernetes Job Controller 原理和源码分析(三)

    概述Job controller 的启动processNextWorkItem()核心调谐逻辑入口 - syncJob()Pod 数量管理 - manageJob()小结 概述 源码版本:kubern ...

  7. OpenMP For Construct dynamic 调度方式实现原理和源码分析

    OpenMP For Construct dynamic 调度方式实现原理和源码分析 前言 在本篇文章当中主要给大家介绍 OpenMp for construct 的实现原理,以及与他相关的动态库函数 ...

  8. OPENMP FOR CONSTRUCT GUIDED 调度方式实现原理和源码分析

    OPENMP FOR CONSTRUCT GUIDED 调度方式实现原理和源码分析 前言 在本篇文章当中主要给大家介绍在 OpenMP 当中 guided 调度方式的实现原理.这个调度方式其实和 dy ...

  9. 微服务生态组件之Spring Cloud LoadBalancer详解和源码分析

    Spring Cloud LoadBalancer 概述 Spring Cloud LoadBalancer目前Spring官方是放在spring-cloud-commons里,Spring Clou ...

  10. 【SpringCloud技术专题】「Eureka源码分析」从源码层面让你认识Eureka工作流程和运作机制(上)

    前言介绍 了解到了SpringCloud,大家都应该知道注册中心,而对于我们从过去到现在,SpringCloud中用的最多的注册中心就是Eureka了,所以深入Eureka的原理和源码,接下来我们要进 ...

随机推荐

  1. linux虚拟机固定ip

    1.查看宿主机IP信息 在windows宿主机上,键盘输入win+r,输出cmd,打开终端命令行: 输入ipconfig /all,查看宿主机IP信息: 2.修改Linux虚拟机的配置文件 Linux ...

  2. 数据挖掘机器学习[五]---汽车交易价格预测详细版本{模型融合(Stacking、Blending、Bagging和Boosting)}

    题目出自阿里天池赛题链接:零基础入门数据挖掘 - 二手车交易价格预测-天池大赛-阿里云天池 相关文章: 特征工程详解及实战项目[参考] 数据挖掘---汽车车交易价格预测[一](测评指标:EDA) 数据 ...

  3. 设计模式-1 单例模式 SingletonPattern

    23种设计模式 一.创建型 1,AbstractFactory(抽象工厂,对象模式) 2,Builder(建造者,对象模式) 3,Factory Method(工厂方法,类创模式) 4,Prototy ...

  4. P10114 [LMXOI Round 1] Size 题解

    题目链接:[LMXOI Round 1] Size 挺有意思的诈骗题,其实这类题都喜欢批一个外壳,例如数据范围提示之类的.记得以前遇到的很多诈骗题,有一道 cf 的高分题,问的是区间出现次数的次数 \ ...

  5. 教你用Java实现动态调色板

    案例介绍 欢迎来到我的小院,我是霍大侠,恭喜你今天又要进步一点点了!我们来用Java编程实战案例,做一个动态调色板.案例界面会出现三个滑动组块以及对应的数值,通过移动滑块可以改变颜色区域的显示.通过实 ...

  6. Java21 + SpringBoot3集成七牛云对象存储OSS,实现文件上传

    目录 前言 实现步骤 引入maven依赖 修改配置文件 创建七牛云配置类 创建文件操作服务类 创建文件操作控制器 前端实现 运行效果 总结 前言 近日心血来潮想做一个开源项目,目标是做一款可以适配多端 ...

  7. ZR 七连 Day 1 游记

    ZR 七连 Day 1 游记 游记篇 赛前搞笑事件 今天是第一场正睿,还是要 好好对待 的 $ 17:59:58 $ 还在吃饭 $ 17:59:59 $ 做出重要决定,先打着比赛,有空就吃一口包子 $ ...

  8. NC15434 wyh的迷宫

    题目链接 题目 题目描述 给你一个n*m的迷宫,这个迷宫中有以下几个标识: s代表起点 t代表终点 x代表障碍物 .代表空地 现在你们涵哥想知道能不能从起点走到终点不碰到障碍物(只能上下左右进行移动, ...

  9. ARM 中SP,LR,PC寄存器的作用

    ARM中所有寄存器都是32位的.这里以cortex-a7内核的MX6ULL处理器为例,按照功能可以分为两类:运行需要寄存器(程序正常运行所需要的,比如变量暂存,pc制作等,总共43个),系统管理控制寄 ...

  10. STM32F401的PWM输出

    PWM的说明 PWM有三个关键指标: PWM频率, 占空比, 区分度 对于同一个时钟频率下工作的单片机, 区分度是和PWM工作频率相关的, 因为总频率是固定的, PWM工作频率越高, 留下给区分度的部 ...