扒一扒Nacos、OpenFeign、Ribbon、loadbalancer组件协调工作的原理
大家好,我是三友~~
前几天有个大兄弟问了我一个问题,注册中心要集成SpringCloud,想实现SpringCloud的负载均衡,需要实现哪些接口和规范。

既然这个兄弟问到我了,而我又刚好知道,这不得好好写一篇文章来回答这个问题,虽然在后面的聊天中我已经回答过了。

接下来本文就以探究一下Nacos、OpenFeign、Ribbon、loadbalancer等组件协调工作的原理的方式,来讲一讲应该需要是实现哪些接口了。
再多说一句,本文并没有详细地深入剖析各个组件的源码,如果有感兴趣的兄弟可以从微信公众号三友的java日记后台菜单栏中的文章分类中查看我之前写的关于Nacos、OpenFeign、Ribbon源码剖析的文章。
Nacos
先从Nacos讲起。
Nacos是什么,官网中有这么一段话

这一段话说的直白点就是Nacos是一个注册中心和配置中心!
在Nacos中有客户端和服务端的这个概念

服务端需要单独部署,用来保存服务实例数据的 客户端就是用来跟服务端通信的SDK,支持不同语言
当需要向Nacos服务端注册或者获取服务实例数据的时候,只需要通过Nacos提供的客户端SDK就可以了,就像下面这样:
引入依赖
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.4.4</version>
</dependency>
示例代码
Properties properties = new Properties();
properties.setProperty("serverAddr", "localhost");
properties.setProperty("namespace", "8848");
NamingService naming = NamingFactory.createNamingService(properties);
//服务注册,注册一个order服务,order服务的ip是192.168.2.100,端口8080
naming.registerInstance("order", "192.168.2.100", 8080);
//服务发现,获取所有的order服务实例
List<Instance> instanceList = naming.selectInstances("order", true);
当服务注册到Nacos服务端的时候,在服务端内部会有一个集合去存储服务的信息

这个集合在注册中心界中有个响亮的名字,服务注册表。
如何进行服务自动注册?
用过SpringCloud的小伙伴肯定知道,在项目启动的时候服务能够自动注册到服务注册中心,并不需要手动写上面那段代码,那么服务自动注册是如何实现的呢?
服务自动注册三板斧
SpringCloud本身提供了一套服务自动注册的机制,或者说是约束,其实就是三个接口,只要注册中心实现这些接口,就能够在服务启动时自动注册到注册中心,而这三个接口我称为服务自动注册三板斧。
服务实例数据封装--Registration
Registration是SpringCloud提供的一个接口,继承了ServiceInstance接口


从ServiceInstance的接口定义可以看出,这是一个服务实例数据的封装,比如这个服务的ip是多少,端口号是多少。
所以Registration就是当前服务实例数据封装,封装了当前服务的所在的机器ip和端口号等信息。
Nacos既然要整合SpringCloud,自然而然也实现了这个接口

这样当前服务需要被注册到注册中心的信息就封装好了。
服务注册--ServiceRegistry
ServiceRegistry也是个接口,泛型就是上面提到的服务实例数据封装的接口

这个接口的作用就是把上面封装的当前服务的数据Registration注册通过register方法注册到注册中心中。
Nacos也实现了这个接口。

并且核心的注册方法的实现代码跟前面的demo几乎一样

服务自动注册--AutoServiceRegistration

AutoServiceRegistration是一个标记接口,所以本身没有实际的意义,仅仅代表了自动注册的意思。
AutoServiceRegistration有个抽象实现AbstractAutoServiceRegistration

AbstractAutoServiceRegistration实现了ApplicationListener,监听了WebServerInitializedEvent事件。
WebServerInitializedEvent这个事件是SpringBoot在项目启动时,当诸如tomcat这类Web服务启动之后就会发布,注意,只有在Web环境才会发布这个事件。

ServletWebServerInitializedEvent继承自WebServerInitializedEvent。
所以一旦当SpringBoot项目启动,tomcat等web服务器启动成功之后,就会触发AbstractAutoServiceRegistration监听器的执行。
最终就会调用ServiceRegistry注册Registration,实现服务自动注册

Nacos自然而然也继承了AbstractAutoServiceRegistration

对于Nacos而言,就将当前的服务注册的ip和端口等信息,就注册到了Nacos服务注册中心。
所以整个注册流程就可以用这么一张图概括

当然,不仅仅是Nacos是这么实现的,常见的比如Eureka,Zookeeper等注册中心在整合SpringCloud都是实现上面的三板斧。

Ribbon
讲完了SpringCloud环境底下是如何自动注册服务到注册中心的,下面来讲一讲Ribbon。
我们都知道,Ribbon是负载均衡组件,他的作用就是从众多的服务实例中根据一定的算法选择一个服务实例。
但是有个疑问,服务实例的数据都在注册中心,Ribbon是怎么知道的呢???
答案其实很简单,那就是需要注册中心去主动适配Ribbon,只要注册中心去适配了Ribbon,那么Ribbon自然而然就知道服务实例的数据了。
Ribbon提供了一个获取服务实例的接口,叫ServerList

接口中提供了两个方法,这两个方法在众多的实现中实际是一样的,并没有区别。
当Ribbon通过ServerList获取到服务实例数据之后,会基于这些数据来做负载均衡的。
Nacos自然而然也实现了ServerList接口,为Ribbon提供Nacos注册中心中的服务数据。

这样,Ribbon就能获取到了Nacos服务注册中心的数据。
同样地,除了Nacos之外,Eureka、Zookeeper等注册中心也都实现了这个接口。

到这,其实就明白了Ribbon是如何知道注册中心的数据了,需要注册中心来适配。
在这里插个个人的看法,其实我觉得Ribbon在适配SpringCloud时对获取服务实例这块支持封装的不太好。

因为SpringCloud本身就是一套约束、规范,只要遵守这套规范,那么就可以实现各个组件的替换,这就是为什么换个注册中心只需要换个依赖,改个配置文件就行。
而Ribbon本身是一个具体的负载均衡组件,注册中心要想整合SpringCloud,还得需要单独去适配Ribbon,有点违背了SpringCloud约束的意义。
就类似mybatis一样,mybatis依靠jdbc,但是mybatis根本不关心哪个数据库实现的jdbc。
真正好的做法是Ribbon去适配SpringCloud时,用SpringCloud提供的api去获取服务实例,这样不同的注册中心只需要适配这个api,无需单独适配Ribbon了。
而SpringCloud实际上是提供了这么一个获取服务实例的api,DiscoveryClient

通过DiscoveryClient就能够获取到服务实例,当然也是需要不同注册中心的适配。

随着Ribbon等组件停止维护之后,SpringCloud官方自己也搞了一个负载均衡组件loadbalancer,用来平替Ribbon。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
这个组件底层在获取服务实例的时候,就是使用的DiscoveryClient。

所以对于loadbalancer这个负载均衡组价来说,注册中心只需要实现DiscoveryClient之后就自然而然适配了loadbalancer。
OpenFeign
OpenFeign是一个rpc框架,当我们需要调用远程服务的时候,只需要声明个接口就可以远程调用了,就像下面这样

听上去很神奇,其实本质上就是后面会为接口创建一个动态代理对象,解析类上,方法上的注解。
当调用方法的时候,会根据方法上面的参数拼接一个http请求地址,这个地址的格式是这样的http://服务名/接口路径。
比如,上面的例子,当调用saveOrder方法的时候,按照这种规律拼出的地址就是这样的 http://order/order,第一个order是服务名,第二个order是PostMapping注解上面的。
但是由于只知道需要调用服务的服务名,不知道服务的ip和端口,还是无法调用远程服务,这咋办呢?
这时就轮到Ribbon登场了,因为Ribbon这个大兄弟知道服务实例的数据。
于是乎,OpenFeign就对Ribbon说,兄弟,你不是可以从注册中心获取到order服务所有服务实例数据么,帮我从这些服务实例数据中找一个给我。

于是Ribbon就会从注册中心获取到的服务实例中根据负载均衡策略选择一个服务实例返回给OpenFeign。
OpenFeign拿到了服务实例,此时就获取到了服务所在的ip和端口,接下来就会重新构建请求路径,将路径中的服务名替换成ip和端口,代码如下

Server就是服务实例信息的封装 orignal就是原始的url,就是上面提到的, http://order/order
假设获取到的orde服务所在的ip和端口分别是192.168.2.100和8080,最终重构后的路径就是http://192.168.2.100:8080/order,之后OpenFeign就可以发送http请求了。
至于前面提到的loadbalancer,其实也是一样的,他也会根据负载均衡算法,从DiscoveryClient获取到的服务实例中选择一个服务实例给OpenFeign,后面也会根据服务实例重构url,再发送http请求。

总结
到这,就把Nacos、OpenFeign、Ribbon、loadbalancer等组件协调工作的原理讲完了,其实就是各个组件会预留一些扩展接口,这也是很多开源框架都会干的事,当第三方框架去适配的,只要实现这些接口就可以了。
最后画一张图来总结一下上述组价的工作的原理。

最后再小小地说一句,Nacos、OpenFeign、Ribbon源码剖析的文章,可以从微信公众号三友的java日记后台菜单栏中的文章分类中查看。
往期热门文章推荐
扫码或者搜索关注公众号 三友的java日记 ,及时干货不错过,公众号致力于通过画图加上通俗易懂的语言讲解技术,让技术更加容易学习,回复 面试 即可获得一套面试真题。

扒一扒Nacos、OpenFeign、Ribbon、loadbalancer组件协调工作的原理的更多相关文章
- 以STM32和FPGA为核心的多组件协调工作系统
- 扒一扒Bean注入到Spring的那些姿势,你会几种?
大家好,我是三友~~ 这篇文章我准备来扒一扒Bean注入到Spring的那些姿势. 其实关于Bean注入Spring容器的方式网上也有很多相关文章,但是很多文章可能会存在以下常见的问题 注入方式总结的 ...
- 扒一扒ReentrantLock以及AQS实现原理
提到JAVA加锁,我们通常会想到synchronized关键字或者是Java Concurrent Util(后面简称JCU)包下面的Lock,今天就来扒一扒Lock是如何实现的,比如我们可以先提出一 ...
- Ribbon的主要组件与工作流程
一:Ribbon是什么? Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起.Ribbon客户端组件提供一系列完善的配置项如连接 ...
- 扒一扒@Retryable注解,很优雅,有点意思!
你好呀,我是歪歪. 前几天我 Review 代码的时候发现项目里面有一坨逻辑写的非常的不好,一眼望去简直就是丑陋之极. 我都不知道为什么会有这样的代码存在项目里面,于是我看了一眼提交记录准备叫对应的同 ...
- linux2.6.24内核源代码分析(2)——扒一扒网络数据包在链路层的流向路径之一
在2.6.24内核中链路层接收网络数据包出现了两种方法,第一种是传统方法,利用中断来接收网络数据包,适用于低速设备:第二种是New Api(简称NAPI)方法,利用了中断+轮询的方法来接收网络数据包, ...
- linux2.6.24内核源代码分析(1)——扒一扒sk_buff
最近研究了linux内核的网络子系统上的网络分组的接收与发送的流程,发现这个叫sk_buff的东西无处不在,内核利用了这个结构来管理分组,在各个层中传递这个结构,因此sk_buff可以说是linux内 ...
- View绘制详解(三),扒一扒View的测量过程
所有东西都是难者不会,会者不难,Android开发中有很多小伙伴觉得自定义View和事件分发或者Binder机制等是难点,其实不然,如果静下心来花点时间把这几个技术点都研究一遍,你会发现其实这些东西都 ...
- 扒一扒.NET Core的环境配置提供程序
很久之前,在玩Docker的时候顺便扒了扒,最近,终于下定决心花了些时间整理并成文,希望能够给大家一些帮助. 目录 .NET Core中的配置 ASP.NET Core中的配置 扒一扒环境变量提供程序 ...
- 扒一扒EOS的前世今生
扒一扒EOS的前世今生 EOS是什么? EOS可以认为是Enterprise Operation System的缩写,即商用的一款分布式区块链操作系统,EOS主要为了解决百万级用户的使用问题,为企 ...
随机推荐
- WebStorm 2021.3 的永久激活教程
关注公众号回复 webstorm 即可获取激活脚本和教程 更新时间 2022年9月2日. 不定时更新 激活码可在公众号中回复[激活码]获取.
- nodejs中router的使用
一.划分文件使用Router 创建一个routes目录,专门用于放置路由文件,通过module.exports导出供外部使用. // 引入类 const Koa =require('koa'); // ...
- 国际化vue-i18n 向语言包中传入参数
模板 <span>{{$t('el.table.total', { pagetotal: 123 })}}</span> js代码中 this.$t('el.table.num ...
- 用反证法说明List<Object>和List<String>不存在子父类关系可行吗?
看宋红康老师的Java基础视频讲解,视频中用反证法证明List
- 使用selemium被反爬解决方法
使用selenium进行自动化的时候,如csdn登录时可能会遇到检测反爬,从而需要验证 1. 反爬 有时候,我们利用 Selenium 自动化爬取某些网站时,极有可能会遭遇反爬. 实际上, ...
- springBoot的全局异常处理
GlobalException.java package com.bank.util; import com.bank.exception.ContentEmpyException; import c ...
- Must be called at the top of a `setup` function vue3使用vue-i18n时出现的报错
在某js文件中引入 import {useI18n} from "vue-i18n"; 使用:useI18n().t('APP_LOADING') 修改后: import i18n ...
- [ABC284F] ABCBAC(字符串哈希)
思路 这里我们要注意以下几点: 字符串哈希自然溢出(\(\pmod 2^64\))会被卡,会\(WA~5\)个点 注意有模数的时候不要用\(unsigned\ long \ long\)类型 代码 # ...
- 使用Wireshark查看HTTPS中TLS握手过程
通过使用Wireshark抓包分析TLS握手的过程,可以更容易理解和验证TLS协议,本文将先介绍Wireshark解密HTTPS流量的方法,然后分别验证TLS握手过程和TLS会话恢复的过程. 一.使用 ...
- ESP32 IDF V5.0 编译环境
方法:环境搭建工具一键安装: 下载链接:https://dl.espressif.com/dl/esp-idf/ 可以选择离线安装方式和在线安装方式,建议:采用离线安装的方式 下载离线安装包之后点击安 ...