【深入理解SpringCloud微服务】Ribbon源码解析

【深入理解SpringCloud微服务】Ribbon源码解析

  • Ribbon的原理
    • RestTemplate中的拦截器链
    • Ribbon的拦截器
    • 如何将拦截器放入到RestTemplate中
  • Ribbon中的核心类
    • LoadBalancerAutoConfiguration
    • LoadBalancerInterceptor
    • LoadBalancerClient
    • ILoadBalancer
    • ServerList
    • IRule
    • IPing
  • Ribbon核心源码解析
    • LoadBalancerAutoConfiguration
    • LoadBalancerInterceptor
    • RibbonLoadBalancerClient
    • ILoadBalancer
    • request.apply(serviceInstance)

Ribbon的原理

RestTemplate中的拦截器链

在深入理解Ribbon之前,我们先要研究一下RestTemplate。RestTemplate是Spring提供的一个用于发送http请求的工具类。而在RestTemplate的父类InterceptingHttpAccessor里面有这么个东西:

public abstract class InterceptingHttpAccessor extends HttpAccessor {private final List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();...
}	

在这里插入图片描述

interceptors是RestTemplate里面的拦截器,拦截器类型是ClientHttpRequestInterceptor。当我们使用RestTemplate时,在RestTemplate真正发出http请求之前,请求会经过interceptors这个拦截器链处理。于是,我们可以通过给RestTemplate添加拦截器,对RestTemplate即将发送的请求做各种拦截处理。

在这里插入图片描述

Ribbon的拦截器

比如Ribbon的拦截器做的处理就是url重构,url中的域名部分原先是某微服务的服务名,重构后替换成真正的ip端口。

在这里插入图片描述

当然完整的流程应该是先通过负载均衡策略选出一个服务实例,然后再根据该实例的ip和port重构url。

在这里插入图片描述

如何将拦截器放入到RestTemplate中

在RestTemplate的父类InterceptingHttpAccessor中有一个getInterceptors() 方法:

	public List<ClientHttpRequestInterceptor> getInterceptors() {return this.interceptors;}

可以直接获取到它的拦截器链,于是我们可以调用restTemplate.getInterceptors()获取到拦截器链,然后调用list.add(interceptor)把我们的拦截器interceptor添加到RestTemplate的拦截器链中。

在这里插入图片描述

经过上面这一套动作,就可以把我们的拦截器添加到RestTemplate,但是这一套需要有一个时机以及地点去触发它。而Ribbon就利用了Spring提供的SmartInitializingSingleton这个扩展点去触发。

public interface SmartInitializingSingleton {void afterSingletonsInstantiated();}

Spring会在完成了所有非懒加载单例bean的初始化之后调用注册到Spring容器中的所有SmartInitializingSingleton的afterSingletonsInstantiated()方法。于是Ribbon实现了一个SmartInitializingSingleton并注册到Spring中,在afterSingletonsInstantiated()方法中把Ribbon自己的拦截器设置到RestTemplate中。

在这里插入图片描述

Ribbon中的核心类

Ribbon中的核心类一共就以下那么几个:
在这里插入图片描述

LoadBalancerAutoConfiguration

LoadBalancerAutoConfiguration是Ribbon的自动配置类,会被SpringBoot的自动装配机制加载。LoadBalancerAutoConfiguration利用Spring的扩展点SmartInitializingSingleton设置Ribbon自己的拦截器LoadBalancerInterceptor到RestTemplate的拦截器链中。

在这里插入图片描述

LoadBalancerInterceptor

LoadBalancerInterceptor是Ribbon的拦截器,会被添加到RestTemplate的拦截器链中。当调用RestTemplate发起http请求时,请求会经过该拦截器进行负载均衡以及url重写的处理。

在这里插入图片描述

LoadBalancerClient

LoadBalancerClient是Ribbon的负载均衡客户端,会被LoadBalancerInterceptor调用,负载均衡以及url重写的工作其实是由LoadBalancerClient完成的。

在这里插入图片描述

当LoadBalancerInterceptor拿到原始url时,会从url中取出服务名serviceName(也就是域名部分),然后把serviceName传给LoadBalancerClient。LoadBalancerClient根据serviceName取得对应的ILoadBalancer,调用ILoadBalancer进行负载均衡处理,ILoadBalancer返回负载均衡选出的一个服务实例并返回。LoadBalancerClient拿到返回的服务实例,会把url中的域名(也就是服务名serviceName)改写为该实例的ip和port。

在这里插入图片描述

ILoadBalancer

ILoadBalancer是Ribbon的负载均衡器,ILoadBalancer是与服务名一一对应的,也就是每一个服务名对应一个ILoadBalancer,不同服务间的ILoadBalancer互不相干。LoadBalancerClient会通过服务名作为key从loadBalancerMap中获取对应的ILoadBalancer负载均衡器,通过ILoadBalancer的负载均衡选出一个服务实例。

在ILoadBalancer的实现类BaseLoadBalancer有四个比较重要的字段:

  • List<Server> allServerList:所有的服务实例
  • List<Server> upServerList:在线的服务实例
  • IRule rule:负载均衡策略
  • IPing ping:用于ping一个服务实例以检查是否存活的工具

ILoadBalancer使用IRule负载均衡策略从upServerList选出一个服务实例Server。然后ILoadBalancer内部有个定时任务通过IPing去ping在allServerList中的每个服务实例,得出所有在线的Server,然后更新upServerList。

在这里插入图片描述

然后在BaseLoadBalancer的子类DynamicServerListLoadBalancer中还有一个重要的字段:

  • ServerList<T> serverListImpl:向注册中心拉取服务实例列表的工具

DynamicServerListLoadBalancer有一个定时任务从调用serverListImpl拉取最新的服务实例列表,更新到allServerList中。

于是这ILoadBalancer中的这五者就是以下这样的关系:

在这里插入图片描述

ServerList

ServerList是一个用于从注册中心拉取服务实例列表的接口,不同的注册中心客户端会有不同的ServerList实现,比如nacos的NacosServerList就是通过NamingService去拉取。

在这里插入图片描述

IRule

IRule是Ribbon的负载均衡规则接口,不同的负载均衡策略有不同的IRule实现。

  • RandomRule:随机选择一个Server
  • RoundRobinRule:轮询选择
  • WeightedResponseTimeRule:根据响应时间加权的轮询策略,响应时间越长,权重越小
  • BestAvailableRule:最小并发优先策略,在众多Server中选取并发数最小的
  • ZoneAvoidanceRule:默认的负载均衡策略,在有区域的环境下,会复合判断Server所在区域的性能和Server的可用性选择Server,在没有区域的环境下,就是轮询,因此默认的负载均衡策略就是轮询

在这里插入图片描述

IPing

Iping接口就是用于判断一个Server是否可用的接口,有一个boolean isAlive(Server server)方法需要实现,判断当前这个server是否可用。

在这里插入图片描述

Ribbon核心源码解析

LoadBalancerAutoConfiguration

@Configuration(...)
...
public class LoadBalancerAutoConfiguration {// 把被@LoadBalanced注解修饰的RestTemplate通过Spring自动注入的方式收集到这里@LoadBalanced@Autowired(required = false)private List<RestTemplate> restTemplates = Collections.emptyList();...// 通过Spring提供的SmartInitializingSingleton扩展点,// 调用RestTemplateCustomizer把LoadBalancerInterceptor放入RestTemplate的拦截器链中@Beanpublic SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {return () -> restTemplateCustomizers.ifAvailable(customizers -> {for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {for (RestTemplateCustomizer customizer : customizers) {customizer.customize(restTemplate);}}});}...// Ribbon的负载均衡拦截器,会被放入到RestTemplate的拦截器链中@Beanpublic LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient,LoadBalancerRequestFactory requestFactory) {return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);}// Ribbon通过RestTemplateCustomizer// 把LoadBalancerInterceptor放入到RestTemplate的拦截器链中@Bean@ConditionalOnMissingBeanpublic RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {return restTemplate -> {List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());list.add(loadBalancerInterceptor);restTemplate.setInterceptors(list);};}...}

LoadBalancerAutoConfiguration中配置了许多bean,但是重点就是以上三个。LoadBalancerAutoConfiguration通过Spring的扩展点SmartInitializingSingleton调用RestTemplateCustomizer,通过RestTemplateCustomizer把LoadBalancerInterceptor放入到RestTemplate的拦截器链中。

在这里插入图片描述

LoadBalancerInterceptor

RestTemplate的拦截器链中有了LoadBalancerInterceptor之后,当我们使用RestTemplate发起http请求时,就会经过LoadBalancerInterceptor的拦截处理。

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {// 负载均衡客户端private LoadBalancerClient loadBalancer;// Request对象工厂 private LoadBalancerRequestFactory requestFactory;...// RestTemplate发起http请求,会经过LoadBalancerInterceptor的intercept方法拦截处理@Overridepublic ClientHttpResponse intercept(final HttpRequest request, final byte[] body,final ClientHttpRequestExecution execution) throws IOException {final URI originalUri = request.getURI();String serviceName = originalUri.getHost();// LoadBalancerInterceptor的intercept方法,调用LoadBalancerClient的execute方法。// 传入了服务名serviceName,requestFactory创建的Request对象return this.loadBalancer.execute(serviceName,this.requestFactory.createRequest(request, body, execution));}}

RestTemplate发起的http请求会被LoadBalancerInterceptor的intercept方法拦截,LoadBalancerInterceptor的intercept方法从url中取出域名部分作为服务名serviceName,然后调用LoadBalancerClient的execute方法。

在这里插入图片描述

RibbonLoadBalancerClient

LoadBalancerClient接口的实现类是RibbonLoadBalancerClient。

public class RibbonLoadBalancerClient implements LoadBalancerClient {...public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)throws IOException {// 根据服务名(serviceId就是服务名),取得对应的ILoadBalancerILoadBalancer loadBalancer = getLoadBalancer(serviceId);// getServer方法里面通过ILoadBalancer选出一个服务实例ServerServer server = getServer(loadBalancer, hint);...// Server包装为RibbonServerRibbonServer ribbonServer = new RibbonServer(serviceId, server, ...);// 调用execute方法(重写url,发起请求)return execute(serviceId, ribbonServer, request);}...@Overridepublic <T> T execute(String serviceId, ServiceInstance serviceInstance,LoadBalancerRequest<T> request) throws IOException {...// 调用LoadBalancerRequest的apply方法(重写url,发起请求)T returnVal = request.apply(serviceInstance);...}...protected Server getServer(ILoadBalancer loadBalancer, Object hint) {// 调用ILoadBalancer的chooseServer方法选取一个服务实例return loadBalancer.chooseServer(hint != null ? hint : "default");}...}

RibbonLoadBalancerClient首先调用getLoadBalancer(serviceId),根据服务名(serviceId就是服务名),取得对应的ILoadBalancer。然后通过ILoadBalancer的chooseServer方法选取一个服务实例Server。最后选出的服务实例作为参数,调用LoadBalancerRequest的apply方法进行下一步操作(重写url,发起请求)

在这里插入图片描述

ILoadBalancer

我们看看ILoadBalancer中的chooseServer方法是如何选出实例的。默认的ILoadBalancer就是ZoneAwareLoadBalancer,由于我们一般不会配置zone(区域),所以最后还是会进入父类BaseLoadBalancer的chooseServer方法。

    public Server chooseServer(Object key) {...return rule.choose(key);...}

就是调用IRule的choose方法,默认就是轮询策略。

在这里插入图片描述

request.apply(serviceInstance)

我们再看看上面request.apply(serviceInstance)这行代码里面的逻辑。

这里的request是LoadBalancerRequestFactory的createRequest创建的。

	public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request, final byte[] body,final ClientHttpRequestExecution execution) {return instance -> {// 以选出的服务实例对象instance作为参数创建了一个ServiceRequestWrapper对象HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,this.loadBalancer);...return execution.execute(serviceRequest, body);};}

可以看到LoadBalancerRequestFactory返回的request对象的apply方法里面,以选出的服务实例对象instance作为参数创建了一个ServiceRequestWrapper对象。然后调用execution.execute(serviceRequest, body)方法。

		public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {...// request.getURI()调用到ServiceRequestWrapper的getURI()方法,里面会重写url,然后返回一个重写了url的URI对象// 然后这里的requestFactory(ClientHttpRequestFactory类型)拿着重写后的URI对象,创建一个新的ClientHttpRequest对象ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);...// 调用ClientHttpRequest的execute()方法真正发起请求,返回ClientHttpResponse(响应结果)return delegate.execute();}

我们看一下ServiceRequestWrapper的getURI()方法。

	@Overridepublic URI getURI() {// 调用LoadBalancerClient的reconstructURI方法(这里的loadBalancer是LoadBalancerClient类型)URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());return uri;}

所以总结起来request.apply(serviceInstance)里面就是调用LoadBalancerClient的reconstructURI方法重写url,然后调用ClientHttpRequestFactory的createRequest方法重新创建一个Request对象,然后调用该Request对象的execute()方法真正发起http请求。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/400973.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【高性能高易用】物联网AI开发套件----Qualcomm® RB3 Gen 2 开发套件

Qualcomm RB3 Gen 2 开发套件 专为高性能计算、高易用性而设计的物联网开发套件 Qualcomm RB3 Gen 2 开发套件拥有先进的功能和强大的性能&#xff0c;包括强大的AI运算&#xff0c;12 TOPS 算力和计算机图形处理能力&#xff0c;可轻松创造涵盖机器人、企业、工业和自动化等…

【nginx 第一篇章】认识一下 NGINX 服务器

一、简介 Nginx (engine x) 是一个高性能的 HTTP 和反向代理服务器&#xff0c;也是一个 IMAP/POP3/SMTP 代理服务器。由俄罗斯程序员 Igor Sysoev 开发&#xff0c;并在2004年首次公开发布。Nginx 以其高并发处理能力、低内存消耗、稳定性、丰富的功能集、简单的配置以及低学…

HarmonyOS应用三之组件生命周期和参数传递

目录&#xff1a; 1、生命周期的执行顺序2、页面数据传递3、图片的读取4、数据的备份和恢复5、轮播图6、页面布局图 1、生命周期的执行顺序 /** Copyright (c) 2023 Huawei Device Co., Ltd.* Licensed under the Apache License, Version 2.0 (the "License");* yo…

html+css 实现hover 换背景跳动按钮

前言:哈喽,大家好,今天给大家分享html+css 实现hover 换背景跳动按钮!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、原理解析💡这个按钮hover后,有4个变化:📝1.1…

⼆叉树选择题

⼆叉树选择题 本篇文章是初阶二叉树的收尾&#xff0c;旨在进一步加深对于二叉树性质的理解&#xff0c;祝你有一个愉快的学习之旅&#xff01; &#x1f4a1; ⼆叉树性质 1&#xff09;对任何⼀棵⼆叉树, 如果度为 0 其叶结点个数为 n0 , 度为 2 的分⽀结点个数为 n2 ,则有…

Java 阿里云视频直播开发流程

首先来看一下直播效果 推流工具有很多种&#xff08;例如OBS、阿里云直播Demo推流、等等&#xff0c;我用的是芯象导播&#xff09;阿里播放器地址 一、直播基础服务概述 官方文档说明 二、直播域名配置需要两个域名&#xff08;推流域名、播流域名&#xff09; 官方文档说…

haproxy七层代理总结

一、HAProxy概念 1.1 什么是HAProxy&#xff1f; HAProxy是一款开源、高性能的负载均衡器和代理服务器&#xff0c;专为TCP和HTTP应用而设计。它可以将客户端的请求分发到多台后端服务器&#xff0c;从而提高应用的可用性和性能。HAProxy支持多种负载均衡算法和健康检查机制&a…

Python 3 入门基础知识【3】递归函数以及参数部分

在编码过程中除了使用Python内置的函数以外&#xff0c;我们也经常需要自己定义函数。今天来回顾python中的函数。 目录 1.定义函数 2.中函数的返回值 3.递归函数 4.Python函数参数 5.Python函数使用默认参数 6.Python函数使用可变参数 7.Python函数使用可变关键字参数 …

针对thinkphp站点的漏洞挖掘和经验分享

0x1 前言 浅谈 目前在学习和研究thinkphp相关漏洞的打法&#xff0c;然后最近对于thinkphp资产的收集方面有了一个简单的认识&#xff0c;然后写一篇新手看的thinkphp相关的漏洞收集和挖掘的文章来分享下。然后后面是给师傅们分享下后台文件上传&#xff0c;然后直接打一个ge…

WPF 资源、引用命名空间格式、FrameworkElement、Binding、数据绑定

资源 对象级别独立文件 静态资源使用(StaticResource)指的是在程序载入内存时对资源的一次性使用&#xff0c;之后就不再去访问这个资源了。 动态资源使用&#xff08;DynamicResource&#xff09;使用指的是在程序运行过程中仍然会去访问资源。 显然&#xff0c;如果你确定…

欧阳坚持每周一篇高质量文章,半年后收入1380.27元

前言 大家好&#xff0c;我是欧阳&#xff0c;到目前为止欧阳已经坚持连续高质量周更文章7个多月了。在第6个月时就想写一篇半年总结&#xff0c;但是因为拖延症直到现在才写这篇半年复盘文章。 我的成果 先来说一下连续周更半年取得的成果&#xff0c;分别是收入1380.27元、…

redis模块和ioredis的注意事项

redis模块和ioredis的注意事项 文章目录 redis模块和ioredis的注意事项前言一、ioredis和redis使用zrange的比较二、出现zrange结果不同的原因总结 前言 node.js在使用redis的时候有两个库可以选择&#xff0c;一个是redis、另一个是ioredis&#xff0c;我一直以来也没有太大关…

LeetCode之回溯

1.全排列 1.1 题目 1.2 题解 LeetCode 力扣官方题解 1.3 代码 class Solution {public List<List<Integer>> permute(int[] nums) {// 创建一个空的列表 res&#xff0c;用于存储所有的排列结果List<List<Integer>> res new ArrayList<>();/…

练习题PHP5.6+变长参数 ⇒ usort回调后门 ⇒ 任意代码执行

突破长度限制 使用usort上传后门 usort — 使用用户自定义的比较函数对数组中的值进行排序 paramusort(...$GET); ...为php设置可变长参数 在url地址栏中输入[]test&1[]phpinfo();&2assert 包含了phpiinfo&#xff08;&#xff09;命令执行 结合usort使用 assert…

SpringMVC快速入门

MVC模式 MVC&#xff08;Model-View-Controller&#xff09;模式是一种设计模式&#xff0c;用于分离应用程序的不同方面&#xff0c;以提高代码的组织性和可维护性。在Spring MVC框架中&#xff0c;MVC模式的作用是将应用程序的不同职责分开&#xff0c;从而简化开发和维护过…

Datawhale X 魔搭 AI夏令营第四期 AIGC方向 task02笔记

AI工具使用 1. baseline 代码2. 使用通义千问理解代码2.1 工作流程2.2 逐行释意 3. 使用通义千问生成 Prompt3.1 生成的 Prompt3.1 根据 Prompt 生成的图片 1. baseline 代码 !pip install simple-aesthetics-predictor!pip install -v -e data-juicer!pip uninstall pytorch-…

EasyBoss ERP上线TikTok热销数据功能,助力TikTok本土店卖家快速选品上货!

想要你的TikTok本土小店销量不断增长&#xff0c;选品至关重要。只有选品问题解决了&#xff0c;后续的投放、小店定调等动作才有意义。 因此&#xff0c;今天就来分享几种TikTok本土小店的选品策略。 一、TikTok Shop选品的底层逻辑 图源&#xff1a;TikTok Shop 在选品之前…

SpringBoot 自定义 starter

1. 官方文档 SpringBoot 版本 2.6.13&#xff0c;相关链接 Developing with Spring Boot 1.1 什么是 Starter Starters are a set of convenient dependency descriptors that you can include in your application. You get a one-stop shop for all the Spring and relate…

C++哪些变量在没有显式初始化的情况下会被初始化为0

首先&#xff0c;我们需要明白C程序编译链接后会包含以下几个主要段(Section)。 代码段(.text)&#xff1a;存放程序的可执行代码&#xff0c;通常是只读的数据段(.data)&#xff1a;存放已初始化的全局变量和静态变量BSS段(.bss)&#xff1a;存放未初始化的全局变量和静态变量…

Git文件管理技巧:轻松删除与查看文件,忽略不必要的文件与文件夹!

避免文件混乱&#xff1a;Git 文件操作技巧 一、Git工作原理概述二、删除文件三、查看指定文件的修改四、指定不需要 Git 管理的文件五、总结 一、Git工作原理概述 Git是一种分布式版本控制系统&#xff0c;其核心在于其高效的快照机制、强大的分支与合并功能、本地开发的灵活…