你知道的越多,不知道的就越多,业余的像一棵小草!

你来,我们一起精进!你不来,我和你的竞争对手一起精进!

编辑:业余草

来源:blog.csdn.net/LO_YUN/article/details/107961556

推荐:https://www.xttblog.com/?p=5097

ribbon的作用是负载均衡,但是根据我面试他人的情况来看,很多人只忙于业务,而不清楚具体的底层原理,在面试中是很容易吃亏的。基于此,本文就来分析一下这里面的请求流程,里面贴的源码会比较多,如果看不惯的话,可以直接看最后的总结或者阅读原文免费学习 150 集的视频教程。

一般来说,使用原生ribbon而不搭配feign的话,使用的都是RestTemplate,通过这个RestTemplate 来访问其他的服务,看起来是这样的!

@LoadBalanced@Beanpublic RestTemplate getRestTemplate() {    return new RestTemplate();}

RestTemplate本身并没有负载均衡的功能,只是一个单纯的http请求组件而已,通过上面的代码,我们可以发现多了一个@LoadBalanced注解,这个注解就是ribbon实现负载均衡的一个入口,我们就从这里开始看。

/** * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient * @author Spencer Gibb */@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Qualifierpublic @interface LoadBalanced {}

乍一眼看过去,这个注解好像是没啥东西,这个时候就需要一些技巧了,一般的Spring Boot项目都会有一个XXXAutoConfigration类作为自动配置类,这里面都会提供一些有用的信息,在同一个包下稍微找找就能发现一个类叫做LoadBalancerAutoConfiguration,我们接着往里面看。

在这个类里面,最重要的就是给RestTemplate 添加了一个拦截器,那么这个拦截器的作用是什么呢?其实这个拦截器就是将请求交给了ribbon来处理,之后的负载均衡就由ribbon全权负责了。

@Configuration@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")static class LoadBalancerInterceptorConfig { @Bean public LoadBalancerInterceptor ribbonInterceptor(   LoadBalancerClient loadBalancerClient,   LoadBalancerRequestFactory requestFactory) {  return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); }

 @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {  return new RestTemplateCustomizer() {   @Override   public void customize(RestTemplate restTemplate) {    List list = new ArrayList<>(      restTemplate.getInterceptors());    list.add(loadBalancerInterceptor);// 将拦截器加入到restTemplate中    restTemplate.setInterceptors(list);   }  }; }}

接下来就是看看这个拦截器具体在做些什么,首先进入这个拦截器的类,发现里面有一个intercept方法。

@Overridepublic ClientHttpResponse intercept(final HttpRequest request, final byte[] body,final ClientHttpRequestExecution execution) throws IOException { // 获取请求url final URI originalUri = request.getURI(); // 获取服务名称 String serviceName = originalUri.getHost(); Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));}

拦截器最后又调用了loadBalancer的execute方法,那就接着往下看吧。

@Overridepublic  T execute(String serviceId, LoadBalancerRequest request) throws IOException { ILoadBalancer loadBalancer = getLoadBalancer(serviceId); Server server = getServer(loadBalancer);if (server == null) {throw new IllegalStateException("No instances available for " + serviceId); } RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,   serviceId), serverIntrospector(serviceId).getMetadata(server));return execute(serviceId, ribbonServer, request);}

这里的loadBalancer默认是ZoneAwareLoadBalancer,下面的方法就是getServer方法,光从方法名也可以猜出来这个方法就会根据多个服务实例负载均衡出来一个机器出来,那么在此之前就有一个问题了,我们是如何取到所有服务实例的信息的呢?

这就得依靠服务注册中心了,因为服务实例的信息都注册到了服务注册中心中了,这里以Eureka为例,那么ribbon是如何从Eureka中获取到服务实例信息呢?

这里的奥秘就在ZoneAwareLoadBalancer中。

public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,                             IPing ping, ServerList serverList, ServerListFilter filter,                             ServerListUpdater serverListUpdater) {    super(clientConfig, rule, ping, serverList, filter, serverListUpdater);}

在ZoneAwareLoadBalancer的构造函数中,我们发现其实就是调用了父类(DynamicServerListLoadBalancer)的构造方法,接着往下走。

public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,                                         ServerList serverList, ServerListFilter filter,                                         ServerListUpdater serverListUpdater) {    super(clientConfig, rule, ping);    this.serverListImpl = serverList;    this.filter = filter;    this.serverListUpdater = serverListUpdater;    if (filter instanceof AbstractServerListFilter) {        ((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());    }    restOfInit(clientConfig);}

重点在restOfInit方法中。

void restOfInit(IClientConfig clientConfig) {    boolean primeConnection = this.isEnablePrimingConnections();    // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()    this.setEnablePrimingConnections(false);    enableAndInitLearnNewServersFeature();

    updateListOfServers();    if (primeConnection && this.getPrimeConnections() != null) {        this.getPrimeConnections()                .primeConnections(getReachableServers());    }    this.setEnablePrimingConnections(primeConnection);    LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());}

enableAndInitLearnNewServersFeature方法我们之后再说,先来看updateListOfServers方法,很明显这个方法就是在更新服务实例列表的信息,可以直接理解为从Eureka中获取服务实例注册表中的信息。

@VisibleForTestingpublic void updateListOfServers() {    List servers = new ArrayList();if (serverListImpl != null) {        servers = serverListImpl.getUpdatedListOfServers();        LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",                getIdentifier(), servers);if (filter != null) {            servers = filter.getFilteredListOfServers(servers);            LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",                    getIdentifier(), servers);        }    }    updateAllServerList(servers);}

serverListImpl.getUpdatedListOfServers()这段代码就是从Eureka中获取服务注册信息,走得是DiscoveryEnabledNIWSServerList的getUpdatedListOfServers方法,具体这边就不再展开细讲了,反正这里就获取到了所有的服务实例信息,以供后面的负载均衡算法来进行选择

回过头再看看之前跳过的enableAndInitLearnNewServersFeature方法。

public void enableAndInitLearnNewServersFeature() {    LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());    serverListUpdater.start(updateAction);}

protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {    @Override    public void doUpdate() {        updateListOfServers();    }};

发现这个方法里面还是调用的updateListOfServers方法,这里其实就是一个线程,每隔30秒再去Eureka同步一下最新的服务注册信息。

如果你还有印象的话,我们之前就是分析到了获取负载均衡的算法的地方,也就是getServer方法。

protected Server getServer(ILoadBalancer loadBalancer) { if (loadBalancer == null) {  return null; } return loadBalancer.chooseServer("default"); // TODO: better handling of key}

chooseServer就是实际进行负载均衡的地方,这里会根据你使用的负载均衡算法从服务实例中选择一台机器来发送请求,跳过中间的代码跳转,直接来分析一下默认的RoundRobinRule,轮询算法。

public Server choose(ILoadBalancer lb, Object key) {    if (lb == null) {        log.warn("no load balancer");        return null;    }

    Server server = null;    int count = 0;    // count 在这里其实是一个重试的次数    while (server == null && count++ 10) {     // 所有启动的服务实例        List reachableServers = lb.getReachableServers();// 通过Eureka获取的服务实例        List allServers = lb.getAllServers();int upCount = reachableServers.size();int serverCount = allServers.size();// 没有可用服务实例的话返回nullif ((upCount == 0) || (serverCount == 0)) {            log.warn("No up servers available from load balancer: " + lb);return null;        }// 轮询算法的核心,也很好理解,递增并根据服务实例数量取模int nextServerIndex = incrementAndGetModulo(serverCount);// 从所有服务实例中取出选择的那台机器        server = allServers.get(nextServerIndex);if (server == null) {/* Transient. */            Thread.yield();continue;        }// 服务实例是可用的话则返回它if (server.isAlive() && (server.isReadyToServe())) {return (server);        }// Next.        server = null;    }if (count >= 10) {        log.warn("No available alive servers after 10 tries from load balancer: "                + lb);    }return server;}

到这里为止就已经分析完了整个ribbon负载均衡的流程,之后就可以根据选择的服务实例,去发送我们的请求了。


通过这张图来总结一下整个ribbon负载均衡的流程。

  1. 发送请求,被LoadBalancerInterceptor拦截器拦截,请求被交给ribbon来处理
  2. 拦截器拦截请求,交给了RibbonLoadBalancerClient的execute方法(下面的逻辑都是包含在这个方法中)
  3. 在进行负载均衡之前首先得知道有哪些服务实例信息,所以通过DynamicServerListLoadBalancer的updateListOfServers方法从注册中心(Eureka)那里获取到了所有的服务实例信息,并且会定时更新
  4. 使用负载均衡算法(默认轮询算法)从所有的服务实例信息中选择一台机器出来
  5. 将请求发送给负载均衡选择出来的服务实例上去

ribbon 默认负载均衡 是什么_面试官:说说Ribbon是如何实现负载均衡的?相关推荐

  1. mysql数据库事务有几种特性_面试官:你能说说事务的几个特性是啥?有哪几种隔离级别?...

    1.面试题事务的几个特点是什么? 数据库事务有哪些隔离级别? MySQL的默认隔离级别?2.面试官心里分析 用mysql开发的三个基本面:存储引擎.索引,然后就是事务,你必须得用事务. 因为一个业务系 ...

  2. 负载策略_面试官:讲一下什么是负载均衡,什么是轮询策略随机策略哈希策略

    什么是负载均衡? 先举个例子吧.以超市收银为例,假设现在只有一个窗口.一个收银员: 一般情况下,收银员平均 2 分钟服务一位顾客,10 分钟可以服务 5 位顾客:到周末高峰期时,收银员加快收银,平均 ...

  3. dubbo调用失败策略_面试官:dubbo负载均衡策略,集群容错策略,动态代理策略有哪些...

    面试官心理分析 继续深问吧,这些都是用 dubbo 必须知道的一些东西,你得知道基本原理,知道序列化是什么协议,还得知道具体用 dubbo 的时候,如何负载均衡,如何高可用,如何动态代理. 说白了,就 ...

  4. js轮询导致服务器瘫痪_面试官:讲一下什么是负载均衡,什么是轮询策略随机策略哈希策略...

    什么是负载均衡? 先举个例子吧.以超市收银为例,假设现在只有一个窗口.一个收银员: 一般情况下,收银员平均 2 分钟服务一位顾客,10 分钟可以服务 5 位顾客:到周末高峰期时,收银员加快收银,平均 ...

  5. 使用nginx负载均衡的webservice wsdl访问不到_面试官:关于负载均衡你了解多少

    面试官:关于负载均衡你了解多少,知道哪些常用框架? 问题分析: 工作中小编也会经常接触到 Nginx,比如美团的 Oceanus 框架,是一款 HTTP 服务治理框架,这个框架就是基于 Nginx和 ...

  6. 面试官:讲一下什么是负载均衡,什么是轮询策略、随机策略、哈希策略

    本文已收录GitHub,更有互联网大厂面试真题,面试攻略,高效学习资料等 什么是负载均衡? 先举个例子吧.以超市收银为例,假设现在只有一个窗口.一个收银员: 一般情况下,收银员平均 2 分钟服务一位顾 ...

  7. es删除数据_面试官是怎么来考察你对ES搜索引擎的理解?

    来源:http://1t.click/ZdY 一. 面试官心理分析问这个,其实面试官就是要看看你了解不了解 es 的一些基本原理,因为用 es 无非就是写入数据,搜索数据.你要是不明白你发起一个写入和 ...

  8. kafka计算机专业读法_面试官:Kafka 为什么快?

    无论 kafka 作为 MQ 也好,作为存储层也罢,无非就是两个功能(好简单的样子),一是 Producer 生产的数据存到 broker,二是 Consumer 从 broker 读取数据.那 Ka ...

  9. 500并发 一台服务器的性能_面试官绝杀:系统是如何支撑高并发的?

    作者 | 中华石杉 责编 | 伍杏玲 本文经授权转载石杉的架构笔记(ID:shishan100) 很多人面试的时候被问到一个让人特别手足无措的问题:你的系统如何支撑高并发? 大多数同学被问到这个问题压 ...

最新文章

  1. E8.Net 2005工作流平台版本发布
  2. 【MySQL高级】查询缓存、合并表、分区表
  3. vue transition动画
  4. 【UE灯光•简介】UE4光照类型和灯光参数
  5. mybatis里oracle与MySQL的insert_update
  6. instantclient使用步骤
  7. 各种计算机控制系统特点小结
  8. 【通俗理解】显著性检验,T-test,P-value
  9. 台达DVP-ES3 ModbusTCP通信案例
  10. Xposed框架的使用--简单入门
  11. 急需你的意见:句子迷图文日签投票
  12. Burp Suite使用介绍
  13. 离散数学总复习精华版(最全 最简单易懂)已完结
  14. python输出26个大小写字母_python实现生成字符串大小写字母和数字的各种组合
  15. Android软键盘遮挡问题解决
  16. OSPF报文与LSA
  17. windows搭建RN环境
  18. 使用Jetcache过程的bug之Buffer underflow
  19. nginx代理ws协议
  20. 字符集本地化(locale)与输入法系列讲座-----(3) truetype造字程序详解

热门文章

  1. PyTorch常用代码段合集
  2. SVM原理详细图文教程来了!一行代码自动选择核函数,还有模型实用工具
  3. 介绍下计算机的一些常识?
  4. 操作系统学习:Linux0.12初始化详细流程-打开文件与加载可执行程序
  5. 规格选项表管理之删除规格选项表数据
  6. html 的基本结构、标签(分类、关系)、文档类型、页面语言、字符集、语义化
  7. 轻量级语义分割网络:ENet
  8. 基于Pyhton的图像隐写术--如何隐藏图像中的数据
  9. 天际汽车牛胜福:受感知系统等影响 点对点L3将于五年后实现...
  10. java中的关键字static(静态变量)和final定义常量