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. 【0基础学爬虫】爬虫基础之自动化工具 Pyppeteer 的使用

    大数据时代,各行各业对数据采集的需求日益增多,网络爬虫的运用也更为广泛,越来越多的人开始学习网络爬虫这项技术,K哥爬虫此前已经推出不少爬虫进阶.逆向相关文章,为实现从易到难全方位覆盖,特设[0基础学爬 ...

  2. 设计模式学习-使用go实现解释器模式

    解释器模式 定义 优点 缺点 适用范围 代码实现 参考 解释器模式 定义 解释器模式(interpreter):给定一种语言,定义它的文法的一种表示,并定一个解释器,这个解释器使用该表示来解释语言中的 ...

  3. 大规模语言LLaVA:多模态GPT-4智能助手,融合语言与视觉,满足用户复杂需求

    大规模语言LLaVA:多模态GPT-4智能助手,融合语言与视觉,满足用户复杂需求 一个面向多模式GPT-4级别能力构建的助手.它结合了自然语言处理和计算机视觉,为用户提供了强大的多模式交互和理解.LL ...

  4. C/C++ 常用开发代码片段

    由于内容较少,所以,不想把它放在我的本地博客中了,暂时保存在这里,代码有一部分来源于网络,比较经典的案例,同样收藏起来. Stack 栈容器 Stack容器适配器中的数据是以LIFO的方式组织的,它是 ...

  5. automapper 10 +autofac+asp.net web api

    automapper 不必多说 https://automapper.org autofac 这里也不多说 https://autofac.org 这里主要 说 automapper 10.0 版本+ ...

  6. Nginx负载均衡、location匹配

    nginx的日志 ``` #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$ ...

  7. 天玑9300大战骁龙8 Gen3:十余项数据实测 到底谁才是安卓之王?

    一.前言:全大核天玑9300正面硬钢骁龙8 Gen3 究竟谁才是安卓芯片之王? 今年,两家移动芯片厂商都开始放大招了,骁龙首发Cortex-X4超大核,联发科也不甘示弱,初次将"全大核&qu ...

  8. 教你轻松用上ChatGPT

    最近ChatGPT大火呀,小伙伴们是不是在网上看到各种和ChatGPT有趣聊天的截图,奈何自己实力不够,被网络拒之门外,只能眼馋别人的东西.看别人玩,肯定不如自己玩一把舒服的啊.今天小卷就给大家汇总了 ...

  9. CH59X/CH58X/CH57X sleep模式下串口唤醒收发数据

    整体程序逻辑: 下方的具体程序及使用是基于CH592进行的 SLEEP模式睡眠唤醒是由协议栈管理的,还在睡眠时,无法接收到数据. 已经通过使能HAL_SLEEP开启睡眠.如果需要在睡眠时实时接收串口传 ...

  10. 使用了未经检查或不安全的操作。 有关详细信息, 请使用 -Xlint:unchecked 重新编译