前言

上一篇我们知道了feign调用实现负载均衡是通过集成ribbon实现的。也较为详细的了解到了集成的过程。现在我们看一下ribbo是如何实现负载均衡的。写到这里我尚未去阅读源代码,我在这里盲猜一下: 他肯定是有一个从注册中心拉取配置的模块,一个选择调用服务的模块。然后我们就带着这样的指导思想去看源码。

一、ribbo是何时从eurake加载的服务列表?

从上一篇文章我们知道,feign调用实际上调用的是AbstractLoadBalancerAwareClient.executeWithLoadBalancer(...)方法,我们看一下该方法做了那些事情:


public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig); try {
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
// 省略
} }

可以看到上述代码主要是创建了一个负载均衡执行命令类,然后执行请求。我们看看提交请求后执行的具体逻辑:


public Observable<T> submit(final ServerOperation<T> operation) {
final ExecutionInfoContext context = new ExecutionInfoContext(); // Use the load balancer
Observable<T> o =
(server == null ? selectServer() : Observable.just(server))
.concatMap(...); // 省略部分代码
}

这里可以看到它使用了RXjava,rxjava主要是利用观察者思想实现的链式调用,其源码的实现稍显复杂,自己使用需要谨慎,借用Uncle Ben的一句话 ”with great power comes great responsibility “。我们关心一下上面的selectServers()方法,从方法名我们可以大致猜出,它是在选择调用的服务。我们看一下此方法的实现:


/**
* Return an Observable that either emits only the single requested server
* or queries the load balancer for the next server on each subscription
*/
private Observable<Server> selectServer() {
return Observable.create(new OnSubscribe<Server>() {
@Override
public void call(Subscriber<? super Server> next) {
try {
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
next.onNext(server);
next.onCompleted();
} catch (Exception e) {
next.onError(e);
}
}
});
}

显然核心逻辑在loadBalancerContext.getServerFromLoadBalancer(...)里面,我们继续向下看:


public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException { // 省略部分代码
ILoadBalancer lb = getLoadBalancer();
if (host == null) {
// Partial URI or no URI Case
// well we have to just get the right instances from lb - or we fall back
if (lb != null){
Server svc = lb.chooseServer(loadBalancerKey);
}
} // 省略部分代码
return new Server(host, port);
}

非常戏剧性的是,我们看到他选择一个服务是通过ILoadBalancer进行的,那我们的看一下ILoadBalancer是怎么创建的。首先ILoadBalancer是属于Ribbon提供的类,其创建我们先看Ribbon自身的ILoadbalance创建过程,发现其是通过RibbonClientConfiguration的下述方法创建:


@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}

可以看到上述方法先判断name对应的ILoadBalancer是否存在,不存在就创建了一个。我们看一下这个name代表的是什么:


@RibbonClientName
private String name = "client";

可以看到它取得的是客户端名称,其实就是@FeignClient的name属性的值,这里可以直接告诉你怎么实现动态给这里的name赋值的:在生成feign的代理对象过程的实现,会将feign通过@FeingClient的contentId属性进行Context隔离,在加载配置的时候会将@RibbonClientName属性的@Value属性的环境变量的值设置成当前@FeignClient的name值,具体可以直看NamedContextFactory.createContext()方法。言归正转,ZoneAwareLoadBalancer的构建显然是持有了Config,Rule,Ping,ServerList,ServerListFilter,ServerListUpdater。

现在我们看一下这四个类的分工与职责。

  • Config

config 主要是配置了服务名称,服务读取的一些配置比如刷新服务间隔等

  • Rule

选用服务的规则,他决定了选用哪个服务。

  • Ping

主要用于探测服务列表中的服务是否可用

  • ServerList

它主要是提供最新服务列表:包括上线、下线的服务(默认实现DiscoveryEnabledNIWSServerList)

  • ServerListFilter

用于过滤服务,do what you want to do.

  • ServerListUpdater

用于更新服务列表(当前的默认实现是启动定时器,然后调用ServerList获取服务列表,将当前负载均衡可用服务列表全覆盖)。

知道了上面的职责划分,我们看到ZoneAwareLoadBalancer最终初始化服务列表的方法:


public void updateListOfServers() {
List<T> servers = new ArrayList<T>();
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);
}

基于上述我们可以清晰的了解到,获取服务列表发生在ServerList的实现中(serverListImpl.getUpdatedListOfServers())。

二、ribbo配合Eurake的ServerList实现

上面serverListImpl 实现类就是DiscoveryEnabledNIWSServerList,当调用getUpdatedListOfServers()最终调用到了如下所述方法,我们看一下他干了些什么:

// 省略了部分代码
private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
List<DiscoveryEnabledServer> serverList = new ArrayList<DiscoveryEnabledServer>();
EurekaClient eurekaClient = eurekaClientProvider.get();
if (vipAddresses!=null){
for (String vipAddress : vipAddresses.split(",")) {
List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
for (InstanceInfo ii : listOfInstanceInfo) {
if (ii.getStatus().equals(InstanceStatus.UP)) {
DiscoveryEnabledServer des = createServer(ii, isSecure, shouldUseIpAddr);
serverList.add(des);
}
}
}
}
return serverList;
}

上述代码主要是通过EurakeClient 获取对应服务实例,然后将服务实例向DiscoveryEnabledServer进行映射。至此从服务列表的获取过程就完成了。

三、 Rule 服务的选择

知道了服务列表的来源,接下来就是最关键的一步了,选择一个服务调用。我们先看一下默认情况使用的是什么规则(RibbonClientConfiguration):


@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
if (this.propertiesFactory.isSet(IRule.class, name)) {
return this.propertiesFactory.get(IRule.class, config, name);
}
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}

显然默认使用的是ZoneAvoidanceRule。 这里我们看一下IRule都有哪些实现,然后阐述一下各个算法实现(这里没有再看源码的必要了,算法就那些):

  • ZoneAvoidanceRule

    基于可用性与区域的复合预测规则
  • RoundRobinRule

    轮询
  • WeightedResponseTimeRule

    根据响应时间选择
  • RandomRule

    随机规则

小结

总结起来,ribbon的核心组成就是ZoneAwareLoadBalancer的组成。其分工明确,服务列表的维护、服务的可用性、服务的选择都有专门的类去负责。下一篇将会着重讲一下Feign、Ribbon如何做到基于服务的配置隔离的,会适当配合实战,比如怎么自定义一个全局生效的路由规则,自定义一个基于服务名的生效的路由规则等等。

微服务通信之ribbon实现原理的更多相关文章

  1. 2021升级版微服务教程6—Ribbon使用+原理+整合Nacos权重+实战优化 一篇搞定

    2021升级版SpringCloud教程从入门到实战精通「H版&alibaba&链路追踪&日志&事务&锁」 教程全目录「含视频」:https://gitee.c ...

  2. 如何将 Redis 用于微服务通信的事件存储

    来源:Redislabs作者:Martin Forstner 翻译:Kevin (公众号:中间件小哥) 以我的经验,将某些应用拆分成更小的.松耦合的.可协同工作的独立逻辑业务服务会更易于构建和维护.这 ...

  3. 【一起学源码-微服务】Eureka+Ribbon+Feign阶段性总结

    前言 想说的话 这里已经梳理完Eureka.Ribbon.Feign三大组件的基本原理了,今天做一个总结,里面会有一个比较详细的调用关系流程图. 说明 原创不易,如若转载 请标明来源! 博客地址:一枝 ...

  4. 【微服务】之四:轻松搞定SpringCloud微服务-负载均衡Ribbon

    对于任何一个高可用高负载的系统来说,负载均衡是一个必不可少的名称.在大型分布式计算体系中,某个服务在单例的情况下,很难应对各种突发情况.因此,负载均衡是为了让系统在性能出现瓶颈或者其中一些出现状态下可 ...

  5. Spring Cloud 微服务:Eureka+Zuul+Ribbon+Hystrix+SpringConfig实现流程图

    相信现在已经有很多小伙伴已经或者准备使用springcloud微服务了,接下来为大家搭建一个微服务框架,后期可以自己进行扩展.会提供一个小案例: 服务提供者和服务消费者 ,消费者会调用提供者的服务,新 ...

  6. java架构之路-(微服务专题)ribbon的基本使用和内部算法的自我实现

    上次回归: 上次我们主要说了,我们的注册中心nacos的使用,如我们的命名空间.分组.集群.版本等是如何使用的,如果是这样呢?我们现在有三个用户服务和三个订单服务,我们应该如何分发这些请求呢?都请求到 ...

  7. 微服务通信之feign的注册、发现过程

    前言 feign 是目前微服务间通信的主流方式,是springCloud中一个非常重要的组件.他涉及到了负载均衡.限流等组件.真正意义上掌握了feign可以说就掌握了微服务. 一.feign的使用 f ...

  8. 微服务负载均衡 —— ribbon

    负载均衡是系统高可用.缓解网络流量和处理能力扩容的重要手段,广义的负载均衡指的是服务端负载均衡,如硬件负载均衡(F5)和软件负载均衡(Nginx).负载均衡设备会维护一份可用的服务器的信息,当客户端请 ...

  9. SpringCloud微服务(02):Ribbon和Feign组件,实现服务调用的负载均衡

    本文源码:GitHub·点这里 || GitEE·点这里 一.Ribbon简介 1.基本概念 Ribbon是一个客户端的负载均衡(Load Balancer,简称LB)器,它提供对大量的HTTP和TC ...

随机推荐

  1. Python numpy总结(3)——常用函数用法

    1,np.ceil(x, y) 限制元素范围,进一法,即向上取整. x 表示输入的数据  y float类型 表示每个元素的上限. a = np.array([-1.7, -1.5, -0.2, 0. ...

  2. vue安装教程

    Vue.js 安装教程 安装node.js https://nodejs.org/zh-cn/download/ 选择一个适合自己电脑的版本下载 下载成功, 直接安装, 全部点击下一步 然后输入 黑窗 ...

  3. Shiro入门学习---使用自定义Realm完成认证|练气中期

    写在前面 在上一篇文章<shiro认证流程源码分析--练气初期>当中,我们简单分析了一下shiro的认证流程.不难发现,如果我们需要使用其他数据源的信息完成认证操作,我们需要自定义Real ...

  4. 使用 jsDelivr 免费加速 GitHub Pages 博客的静态资源(二)

    之前写过一篇 使用 jsDelivr 免费加速 GitHub Pages 博客的静态资源,在那之后,又陆续想到并实施了几点利用 jsDelivr 进一步加速静态资源加载的措施,新起一篇作为记录和分享. ...

  5. 052 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 14 Eclipse下程序调试——debug2 多断点调试程序

    052 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 14 Eclipse下程序调试--debug2 多断点调试程序 本文知识点: Eclipse下程序调 ...

  6. 051 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 13 Eclipse下程序调试——debug入门1

    051 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 13 Eclipse下程序调试--debug入门1 本文知识点: 程序调试--debug入门1 程序 ...

  7. 05 sublime环境配置及编译运行后输出中文乱码的解决

    编译后的乱码问题 编译后的输出:中文显示异常: 编译C出现乱码问题解决 解决思路:解决办法很简单,就是先设置文件编码为GBK格式,之后再输入中文文字,运行时的中文就不是乱码了. 首先,sublime中 ...

  8. 【题解】NOIP2018 旅行

    题目戳我 \(\text{Solution:}\) 首先题目描述有一点不准确:回头是必须要走完一条路无路可走的时候才能返回. 对于树的情况:显然贪心做就完事了. 对于基环树的情况:对于一个\(n\)条 ...

  9. K-DTree入门

    \(K-D Tree\),一种用来维护\(K\)维数据的数据结构.常用于维护各种高维的数据,或者是邻近搜索等.从另一种意义上说,实际上就是高维的二叉搜索树.对于一些常见的问题,如\(k\)远点对.三位 ...

  10. linux配置定时任务cron/定时服务与自启动

    实现linux定时任务有:cron.anacron.at,使用最多的是cron任务 名词解释 cron--服务名:crond--linux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程,与 ...