《Java架构师的第一性原理》31分布式计算之微服务RPC(Dubbo)

发布时间 2023-12-21 14:08:43作者: 沙漏哟

1 互联网架构,究竟为啥要做服务化

互联网架构,究竟为啥要做服务化?

2 微服务架构,多“微”才合适?

微服务架构,多“微”才合适? 

3 离不开的微服务架构,脱不开的RPC细节

离不开的微服务架构,脱不开的RPC细节

3.1 服务化解决的问题

1)服务化需要解决的问题:

一套序列化、反序列化、网络框架、连接池、收发线程、超时处理、状态机等“业务之外”的统一实现。

2)RPC框架能够让调用方“像调用本地函数一样调用远端的函数(服务)”。

3)RPC框架的职责

  • client端:序列化、反序列化、连接池管理、负载均衡、故障转移、队列管理,超时管理、异步管理等等

  • server端:服务端组件、服务端收发包队列、io线程、工作线程、序列化反序列化等

3.2 序列化

1)序列化

需要对数据进行存储或者传输时,往往需要把数据转化成连续空间的“二进制字节流”,一些典型的场景是:

  • 数据库索引的磁盘存储:数据库的索引在内存里是b+树,但这个格式是不能够直接存储到磁盘上的,所以需要把b+树转化为连续空间的二进制字节流,才能存储到磁盘上

  • 缓存的KV存储:redis/memcache是KV类型的缓存,缓存存储的value必须是连续空间的二进制字节流,而不能够是User对象

  • 数据的网络传输:socket发送的数据必须是连续空间的二进制字节流,也不能是对象

所谓序列化(Serialization),就是将“对象”形态的数据转化为“连续空间二进制字节流”形态数据的过程。这个过程的逆过程叫做反序列化

2)怎么进行序列化?

很容易想到的一个方法是xml(或者json)这类具有自描述特性的标记性语言。

第二个方法是自己实现二进制协议来进行序列化。

3)序列化协议要考虑什么因素?

不管使用成熟协议xml/json,还是自定义二进制协议来序列化对象,序列化协议设计时都需要考虑以下这些因素。

  • 解析效率:这个应该是序列化协议应该首要考虑的因素,像xml/json解析起来比较耗时,需要解析doom树,二进制自定义协议解析起来效率就很高

  • 压缩率,传输有效性:同样一个对象,xml/json传输起来有大量的xml标签,信息有效性低,二进制自定义协议占用的空间相对来说就小多了

  • 扩展性与兼容性:是否能够方便的增加字段,增加字段后旧版客户端是否需要强制升级,都是需要考虑的问题,xml/json和上面的二进制协议都能够方便的扩展

  • 可读性与可调试性:这个很好理解,xml/json的可读性就比二进制协议好很多

  • 跨语言:上面的两个协议都是跨语言的,有些序列化协议是与开发语言紧密相关的,例如dubbo的序列化协议就只能支持Java的RPC调用

  • 通用性:xml/json非常通用,都有很好的第三方解析库,各个语言解析起来都十分方便,上面自定义的二进制协议虽然能够跨语言,但每个语言都要写一个简易的协议客户端

7)有哪些常见的序列化方式?

  • xml/json:解析效率,压缩率都较差,扩展性、可读性、通用性较好

  • thrift

  • protobuf:Google出品,必属精品,各方面都不错,强烈推荐,属于二进制协议,可读性差了点,但也有类似的to-string协议帮助调试问题

  • Avro

  • CORBA

  • mc_pack:懂的同学就懂,不懂的就不懂了,09年用过,传说各方面都超越protobuf,懂行的同学可以说一下现状

3.2 同步调用和异步调用

1)RPC-clien同步调用架构

  • 1)业务代码发起RPC调用:Result=Add(Obj1,Obj2)
  • 2)序列化组件,将对象调用序列化成二进制字节流,可理解为一个待发送的包packet1;
  • 3)通过连接池组件拿到一个可用的连接connection;
  • 4)通过连接connection将包packet1发送给RPC-server;
  • 5)发送包在网络传输,发给RPC-server;
  • 6)响应包在网络传输,发回给RPC-client;
  • 7)通过连接connection从RPC-server收取响应包packet2;
  • 8)通过连接池组件,将conneciont放回连接池;
  • 9)序列化组件,将packet2范序列化为Result对象返回给调用方;
  • 10)业务代码获取Result结果,工作线程继续往下走;

2)RPC-client异步回调架构

  • 1)业务代码发起异步RPC调用;Add(Obj1,Obj2, callback)
  • 2)上下文管理器,将请求,回调,上下文存储起来;
  • 3)序列化组件,将对象调用序列化成二进制字节流,可理解为一个待发送的包packet1;
  • 4)下游收发队列,将报文放入“待发送队列”,此时调用返回,不会阻塞工作线程;
  • 5)下游收发线程,将报文从“待发送队列”中取出,通过连接池组件拿到一个可用的连接connection;
  • 6)通过连接connection将包packet1发送给RPC-server;
  • 7)发送包在网络传输,发给RPC-server;
  • 8)响应包在网络传输,发回给RPC-client;
  • 9)通过连接connection从RPC-server收取响应包packet2;
  • 10)下游收发线程,将报文放入“已接受队列”,通过连接池组件,将conneciont放回连接池;
  • 11)下游收发队列里,报文被取出,此时回调将要开始,不会阻塞工作线程;
  • 12)序列化组件,将packet2范序列化为Result对象;
  • 13)上下文管理器,将结果,回调,上下文取出;
  • 14)通过callback回调业务代码,返回Result结果,工作线程继续往下走;

如果请求长时间不返回,处理流程是:

  • 15)上下文管理器,请求长时间没有返回;
  • 16)超时管理器拿到超时的上下文;
  • 17)通过timeout_cb回调业务代码,工作线程继续往下走;

画外音:请配合架构图仔细看几遍这个流程。

为什么需要上下文管理器?

由于请求包的发送,响应包的回调都是异步的,甚至不在同一个工作线程中完成,需要一个组件来记录一个请求的上下文,把请求-响应-回调等一些信息匹配起来。

如何将请求-响应-回调这些信息匹配起来?

这是一个很有意思的问题,通过一条连接往下游服务发送了a,b,c三个请求包,异步的收到了x,y,z三个响应包:

  • 怎么知道哪个请求包与哪个响应包对应?
  • 怎么知道哪个响应包与哪个回调函数对应?

可以通过“请求id”来实现请求-响应-回调的串联。

整个处理流程如上,通过请求id,上下文管理器来对应请求-响应-callback之间的映射关系:

  • 1)生成请求id;
  • 2)生成请求上下文context,上下文中包含发送时间time,回调函数callback等信息;
  • 3)上下文管理器记录req-id与上下文context的映射关系;
  • 4)将req-id打在请求包里发给RPC-server;
  • 5)RPC-server将req-id打在响应包里返回;
  • 6)由响应包中的req-id,通过上下文管理器找到原来的上下文context;
  • 7)从上下文context中拿到回调函数callback;
  • 8)callback将Result带回,推动业务的进一步执行;

如何实现负载均衡,故障转移?

与同步的连接池思路类似,不同之处在于:

  • 同步连接池使用阻塞方式收发,需要与一个服务的一个ip建立多条连接

  • 异步收发,一个服务的一个ip只需要建立少量的连接(例如,一条tcp连接)

如何实现超时发送与接收?

超时收发,与同步阻塞收发的实现就不一样了:

  • 同步阻塞超时,可以直接使用带超时的send/recv来实现

  • 异步非阻塞的nio的网络报文收发,由于连接不会一直等待回包,超时是由超时管理器实现的

超时管理器如何实现超时管理?

 

 

 

超时管理器,用于实现请求回包超时回调处理。

每一个请求发送给下游RPC-server,会在上下文管理器中保存req-id与上下文的信息,上下文中保存了请求很多相关信息,例如req-id,回包回调,超时回调,发送时间等。

超时管理器启动timer对上下文管理器中的context进行扫描,看上下文中请求发送时间是否过长,如果过长,就不再等待回包,直接超时回调,推动业务流程继续往下走,并将上下文删除掉。

如果超时回调执行后,正常的回包又到达,通过req-id在上下文管理器里找不到上下文,就直接将请求丢弃。

画外音:因为已经超时处理了,无法恢复上下文。

无论如何,异步回调和同步回调相比,除了序列化组件和连接池组件,会多出上下文管理器,超时管理器,下游收发队列,下游收发线程等组件,并且对调用方的调用习惯有影响。

画外音:编程习惯,由同步变为了回调。

 

异步回调能提高系统整体的吞吐量,具体使用哪种方式实现RPC-client,可以结合业务场景来选取。

3.3 连接池组件

连接池组件有什么作用?

RPC框架锁支持的负载均衡、故障转移、发送超时等特性,都是通过连接池组件去实现的。

 

 

1)维护连接

典型连接池组件对外提供的接口为:

int ConnectionPool::init(…);

Connection ConnectionPool::getConnection();

int ConnectionPool::putConnection(Connection t);

init做了些什么?

和下游RPC-server(一般是一个集群),建立N个tcp长连接,即所谓的连接“池”。

getConnection做了些什么?

从连接“池”中拿一个连接,加锁(置一个标志位),返回给调用方。

putConnection做了些什么?

将一个分配出去的连接放回连接“池”中,解锁(也是置一个标志位)。

2)如何实现负载均衡?

连接池中建立了与一个RPC-server集群的连接,连接池在返回连接的时候,需要具备随机性。

3)如何实现故障转移?

连接池中建立了与一个RPC-server集群的连接,当连接池发现某一个机器的连接异常后,需要将这个机器的连接排除掉,返回正常的连接,在机器恢复后,再将连接加回来。

4)如何实现发送超时?

因为是同步阻塞调用,拿到一个连接后,使用带超时的send/recv即可实现带超时的发送和接收。

总的来说,同步的RPC-client的实现是相对比较容易的,序列化组件、连接池组件配合多工作线程数,就能够实现。

4 工作线程数究竟要设置为多少

【修正版】QPS、TPS、RT、并发数、吞吐量理解和性能优化深入思考

工作线程数究竟要设置为多少 | 架构师之路

4.1 需求缘起

Web-Server通常有个配置,最大工作线程数,后端服务一般也有个配置,工作线程池的线程数量,这个线程数的配置不同的业务架构师有不同的经验值。

有些业务设置为CPU核数的2倍,有些业务设置为CPU核数的8倍,有些业务设置为CPU核数的32倍。

“工作线程数”的设置依据是什么,到底设置为多少能够最大化CPU性能,是本文要讨论的问题。

4.2 共性认知

在进行进一步深入讨论之前,先以提问的方式就一些共性认知达成一致。

1)问:工作线程数是不是设置的越大越好?

答:肯定不是的

  • 服务器CPU核数有限,能够同时并发的线程数有限,单核CPU设置10000个工作线程没有意义

  • 线程切换是有开销的,如果线程切换过于频繁,反而会使性能降低

2)问:调用sleep()函数的时候,线程是否一直占用CPU

答:不占用,等待时会把CPU让出来,给其他需要CPU资源的线程使用。

不止sleep()函数,在进行一些阻塞调用时,例如网络编程中的:

  • 阻塞accept(),等待客户端连接

  • 阻塞recv(),等待下游回包

都不占用CPU资源。

4)问:单核CPU,设置多线程有意义么,是否能提高并发性能?

答:即使是单核,使用多线程也是有意义的,大多数情况也能提高并发

  • 多线程编码可以让代码更加清晰,例如:IO线程收发包,Worker线程进行任务处理,Timeout线程进行超时检测

  • 如果有一个任务一直占用CPU资源在进行计算,此时增加线程并不能增加并发,例如以下代码会一直占用CPU,并使得CPU占用率达到100%:

     while(1){ i++; }

  • 通常来说,Worker线程一般不会一直占用CPU进行计算,此时即使CPU是单核,增加Worker线程也能够提高并发,因为这个线程在休息的时候,其他的线程可以继续工作

4.3 常见服务线程模型

了解常见的服务线程模型,有助于理解服务并发的原理,一般来说互联网常见的服务线程模型有两种:

  • IO线程与工作线程通过任务队列解耦

  • 纯异步

1)IO线程与工作线程通过队列解耦类模型

如上图,大部分Web-Server与服务框架都是使用这样的一种“IO线程与Worker线程通过队列解耦”类线程模型:

  • 有少数几个IO线程监听上游发过来的请求,并进行收发包(生产者)

  • 有一个或者多个任务队列,作为IO线程与Worker线程异步解耦的数据传输通道(临界资源)

  • 有多个工作线程执行正真的任务(消费者)

这个线程模型应用很广,符合大部分场景,这个线程模型的特点是,工作线程内部是同步阻塞执行任务的(回想一下tomcat线程中是怎么执行Java程序的,dubbo工作线程中是怎么执行任务的),因此可以通过增加Worker线程数来增加并发能力,今天要讨论的重点是“该模型Worker线程数设置为多少能达到最大的并发”。

2)纯异步线程模型

没有阻塞,这种线程模型只需要设置很少的线程数就能够做到很高的吞吐量,该模型的缺点是:

  • 如果使用单线程模式,难以利用多CPU多核的优势

  • 程序员更习惯写同步代码,callback的方式对代码的可读性有冲击,对程序员的要求也更高

  • 框架更复杂,往往需要server端收发组件,server端队列,client端收发组件,client端队列,上下文管理组件,有限状态机组件,超时管理组件的支持

4.4 工作线程的工作模式

了解工作线程的工作模式,对量化分析线程数的设置非常有帮助:

  

上图是一个典型的工作线程的处理过程,从开始处理start到结束处理end,该任务的处理共有7个步骤:

  • 从工作队列里拿出任务,进行一些本地初始化计算,例如http协议分析、参数解析、参数校验等

  • 访问cache拿一些数据

  • 拿到cache里的数据后,再进行一些本地计算,这些计算和业务逻辑相关

  • 通过RPC调用下游service再拿一些数据,或者让下游service去处理一些相关的任务

  • RPC调用结束后,再进行一些本地计算,怎么计算和业务逻辑相关

  • 访问DB进行一些数据操作

  • 操作完数据库之后做一些收尾工作,同样这些收尾工作也是本地计算,和业务逻辑相关

分析整个处理的时间轴,会发现:

  • 其中1,3,5,7步骤中(上图中粉色时间轴),线程进行本地业务逻辑计算时需要占用CPU

  • 而2,4,6步骤中(上图中橙色时间轴),访问cache、service、DB过程中线程处于一个等待结果的状态,不需要占用CPU,进一步的分解,这个“等待结果”的时间共分为三部分:

    2.1)请求在网络上传输到下游的cache、service、DB

    2.2)下游cache、service、DB进行任务处理

    2.3)cache、service、DB将报文在网络上传回工作线程

4.5 量化分析并合理设置工作线程数

最后一起来回答工作线程数设置为多少合理的问题。

通过上面的分析,Worker线程在执行的过程中,有一部计算时间需要占用CPU,另一部分等待时间不需要占用CPU,通过量化分析,例如打日志进行统计,可以统计出整个Worker线程执行过程中这两部分时间的比例,例如:

  • 执行计算,占用CPU的时间(粉色时间轴)是100ms

  • 等待时间,不占用CPU的时间(橙色时间轴)也是100ms

得到的结果是,这个线程计算和等待的时间是1:1,即有50%的时间在计算(占用CPU),50%的时间在等待(不占用CPU):

  • 假设此时是单核则设置为2个工作线程就可以把CPU充分利用起来,让CPU跑到100%

  • 假设此时是N核则设置为2N个工作现场就可以把CPU充分利用起来,让CPU跑到N*100%

结论

N核服务器,通过执行业务的单线程分析出本地计算时间为x,等待时间为y,则工作线程数(线程池线程数)设置为 N*(x+y)/x,能让CPU的利用率最大化。

经验

一般来说,非CPU密集型的业务(加解密、压缩解压缩、搜索排序等业务是CPU密集型的业务),瓶颈都在后端数据库访问或者RPC调用,本地CPU计算的时间很少,所以设置几十或者几百个工作线程是能够提升吞吐量的。

4.6 面试八股

1)如何合理的配置java线程池?如CPU密集型的任务,基本线程池应该配置多大?IO密集型的任务,基本线程池应该配置多大?用有界队列好还是无界队列好?任务非常多的时候,使用什么阻塞能获取最好的吞吐量?

  • 配置线程池时CPU密集型任务可以少配置线程数,大概和机器的cpu核数相当,可以使得每个线程都在执行任务
  • IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数
  • 有界队列和无界队列的配置需区分业务场景,一般情况下配置有界队列,在一些可能会有爆发性增长的情况下使用无界队列
  • 任务非常多时,使用非阻塞队列,使用CAS操作替代锁可以获得好的吞吐量

4.7 总结

  • 线程数不是越多越好

  • sleep()不占用CPU

  • 单核设置多线程不但能使得代码清晰,还能提高吞吐量

  • 站点和服务最常用的线程模型是“IO线程与工作现场通过任务队列解耦”,此时设置多工作线程可以提升吞吐量

  • N核服务器,通过日志分析出任务执行过程中,本地计算时间为x等待时间为y,则工作线程数(线程池线程数)设置为 N*(x+y)/x,能让CPU的利用率最大化

5 Dubbo普普通通9问

Dubbo本身并不复杂,而且官方文档写的非常清楚详细,面试中dubbo的问题一般不会很多,从分层到工作原理、负载均衡策略、容错机制、SPI机制基本就差不多了,最大的一道大题一般就是怎么设计一个RPC框架了,但是如果你工作原理分层都搞明白了这个问题其实也就相当于回答了不是吗。

5.1 说说Dubbo的分层?

从大的范围来说,dubbo分为三层,business业务逻辑层由我们自己来提供接口和实现还有一些配置信息,RPC层就是真正的RPC调用的核心层,封装整个RPC的调用过程、负载均衡、集群容错、代理,remoting则是对网络传输协议和数据转换的封装。

划分到更细的层面,就是图中的10层模式,整个分层依赖由上至下,除开business业务逻辑之外,其他的几层都是SPI机制。

5.2 能说下Dubbo的工作原理吗?

从大的范围来说,dubbo分为三层,business业务逻辑层由我们自己来提供接口和实现还有一些配置信息,RPC层就是真正的RPC调用的核心层,封装整个RPC的调用过程、负载均衡、集群容错、代理,remoting则是对网络传输协议和数据转换的封装。

  1. 服务启动的时候,provider和consumer根据配置信息,连接到注册中心register,分别向注册中心注册和订阅服务

  2. register根据服务订阅关系,返回provider信息到consumer,同时consumer会把provider信息缓存到本地。如果信息有变更,consumer会收到来自register的推送

  3. consumer生成代理对象,同时根据负载均衡策略,选择一台provider,同时定时向monitor记录接口的调用次数和时间信息

  4. 拿到代理对象之后,consumer通过代理对象发起接口调用

  5. provider收到请求后对数据进行反序列化,然后通过代理调用具体的接口实现

 

 

 

5.3 为什么要通过代理对象通信?

主要是为了实现接口的透明代理,封装调用细节,让用户可以像调用本地方法一样调用远程方法,同时还可以通过代理实现一些其他的策略,比如:

  • 1、调用的负载均衡策略
  • 2、调用失败、超时、降级和容错机制
  • 3、做一些过滤操作,比如加入缓存、mock数据
  • 4、接口调用数据统计

5.4 说说服务暴露的流程?

  1. 在容器启动的时候,通过ServiceConfig解析标签,创建dubbo标签解析器来解析dubbo的标签,容器创建完成之后,触发ContextRefreshEvent事件回调开始暴露服务
  2. 通过ProxyFactory获取到invoker,invoker包含了需要执行的方法的对象信息和具体的URL地址
  3. 再通过DubboProtocol的实现把包装后的invoker转换成exporter,然后启动服务器server,监听端口
  4. 最后RegistryProtocol保存URL地址和invoker的映射关系,同时注册到服务中心

5.4 说说服务引用的流程?

服务暴露之后,客户端就要引用服务,然后才是调用的过程。

  1. 首先客户端根据配置文件信息从注册中心订阅服务

  2. 之后DubboProtocol根据订阅的得到provider地址和接口信息连接到服务端server,开启客户端client,然后创建invoker

  3. invoker创建完成之后,通过invoker为服务接口生成代理对象,这个代理对象用于远程调用provider,服务的引用就完成了

 

5.5 有哪些负载均衡策略?

  1. 加权随机:假设我们有一组服务器 servers = [A, B, C],他们对应的权重为 weights = [5, 3, 2],权重总和为10。现在把这些权重值平铺在一维坐标值上,[0, 5) 区间属于服务器 A,[5, 8) 区间属于服务器 B,[8, 10) 区间属于服务器 C。接下来通过随机数生成器生成一个范围在 [0, 10) 之间的随机数,然后计算这个随机数会落到哪个区间上就可以了。

  2. 最小活跃数:每个服务提供者对应一个活跃数 active,初始情况下,所有服务提供者活跃数均为0。每收到一个请求,活跃数加1,完成请求后则将活跃数减1。在服务运行一段时间后,性能好的服务提供者处理请求的速度更快,因此活跃数下降的也越快,此时这样的服务提供者能够优先获取到新的服务请求。

  3. 一致性hash:通过hash算法,把provider的invoke和随机节点生成hash,并将这个 hash 投射到 [0, 2^32 - 1] 的圆环上,查询的时候根据key进行md5然后进行hash,得到第一个节点的值大于等于当前hash的invoker。

  4.  加权轮询:比如服务器 A、B、C 权重比为 5:2:1,那么在8次请求中,服务器 A 将收到其中的5次请求,服务器 B 会收到其中的2次请求,服务器 C 则收到其中的1次请求。

5.6 集群容错方式有哪些

  • Failover Cluster失败自动切换:dubbo的默认容错方案,当调用失败时自动切换到其他可用的节点,具体的重试次数和间隔时间可用通过引用服务的时候配置,默认重试次数为1也就是只调用一次。

  • Failback Cluster失败自动恢复:在调用失败,记录日志和调用信息,然后返回空结果给consumer,并且通过定时任务每隔5秒对失败的调用进行重试

  • Failfast Cluster快速失败:只会调用一次,失败后立刻抛出异常

  • Failsafe Cluster失败安全:调用出现异常,记录日志不抛出,返回空结果

  • Forking Cluster并行调用多个服务提供者:通过线程池创建多个线程,并发调用多个provider,结果保存到阻塞队列,只要有一个provider成功返回了结果,就会立刻返回结果

  • Broadcast Cluster广播模式:逐个调用每个provider,如果其中一台报错,在循环调用结束后,抛出异常。

5.7 了解Dubbo SPI机制吗?

SPI 全称为 Service Provider Interface,是一种服务发现机制,本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类,这样可以在运行时,动态为接口替换实现类。

Dubbo也正是通过SPI机制实现了众多的扩展功能,而且dubbo没有使用java原生的SPI机制,而是对齐进行了增强和改进。

SPI在dubbo应用很多,包括协议扩展、集群扩展、路由扩展、序列化扩展等等。

使用方式可以在META-INF/dubbo目录下配置:

key=com.xxx.value

然后通过dubbo的ExtensionLoader按照指定的key加载对应的实现类,这样做的好处就是可以按需加载,性能上得到优化。

5.8 Dubbo线程模型

事件处理线程说明

  • 如果事件处理的逻辑能迅速完成,并且不会发起新的IO请求,比如只是在内存中记个标识。则直接在IO线程上处理更快,因为减少了线程池调度。
  • 但如果事件处理逻辑较慢,或者需要发起新的IO请求,比如需要查询数据库,则必须派发到线程池,否则IO线程阻塞,将导致不能接收其他请求。
  • 如果用IO线程处理事件,又在事件处理过程中发起新的IO请求,比如在连接事件中发起登录请求,会报“可能引发死锁”异常,但不会真死锁。

Dispatcher

  • all:所有消息都派发到线程池,包括请求、响应、连接事件、断开事件、心跳等。
  • direct:所有消息都不派发到线程池,全部在IO线程上直接执行。
  • message:只有请求响应消息派发到线程池,其他连接断开事件、心跳等消息,直接在IO线程上执行。
  • execution:只请求消息派发到线程池,不含响应,响应和其他连接断开事件、心跳等消息,直接在IO线程上执行。
  • connection:在IO线程上,将连接断开事件放入队列,有序逐个执行,其他消息派发到线程池。

ThreadPool

  • fixed:固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省)
  • cached:缓存线程池,空闲一分钟自动删除,需要时重建。
  • limited:可伸缩线程池,但池中的线程只会增长不会收缩。(为避免收缩时突然来了大流量引起的性能问题)

配置如

  • <dubbo:protocol name="dubbo" dispatcher="all" threadpool="fixed" threads="100" /> 

5.9 如果让你实现一个RPC框架怎么设计?

  1. 首先需要一个服务注册中心,这样consumer和provider才能去注册和订阅服务
  2. 需要负载均衡的机制来决定consumer如何调用客户端,这其中还当然要包含容错和重试的机制
  3. 需要通信协议和工具框架,比如通过http或者rmi的协议通信,然后再根据协议选择使用什么框架和工具来进行通信,当然,数据的传输序列化要考虑
  4. 除了基本的要素之外,像一些监控、配置管理页面、日志是额外的优化考虑因素。

那么,本质上,只要熟悉一两个RPC框架,就很容易想明白我们自己要怎么实现一个RPC框架。

6 Dubbo官方文档

Apache Dubbo 是一款高性能、轻量级的开源 Java 服务框架。

Apache Dubbo提供了六大核心能力:

  • 面向接口代理的高性能RPC调用:提供高性能的基于代理的远程调用能力,服务以接口为粒度,为开发者屏蔽远程调用底层细节。
  • 智能容错和负载均衡:内置多种负载均衡策略,智能感知下游节点健康状况,显著减少调用延迟,提高系统吞吐量。
  • 服务自动注册和发现:支持多种注册中心服务,服务实例上下线实时感知。
  • 高度可扩展能力:遵循微内核+插件的设计原则,所有核心能力如Protocol、Transport、Serialization被设计为扩展点,平等对待内置实现和第三方实现。
  • 运行期流量调度:内置条件、脚本等路由策略,通过配置不同的路由规则,轻松实现灰度发布,同机房优先等功能。
  • 可视化的服务治理与运维:提供丰富服务治理、运维工具:随时查询服务元数据、服务健康状态及调用统计,实时下发路由策略、调整配置参数。

6.1 Dubbo架构

 

 

Dubbo 架构具有以下几个特点,分别是连通性、健壮性、伸缩性、以及向未来架构的升级性。

99 直接读这些牛人的原文

一次彻底搞透协议设计(没做过通讯底层也没有关系)

《我想进大厂》之Dubbo普普通通9问

徒手撸了一个RPC框架,理解更透彻了,代码已上传github,自取~

dubbo官方文档

一次Dubbo拥堵的分析

Dubbo调优 -- 超时TimeOut

Dubbo 是如何控制并发数和限流的?

RPC框架的技术架构和未来

Dubbo 同步、异步调用的几种方式

 

架构师之路:使用连接池实现故障转移,服务发现,负载均衡

架构师之路:过载保护+异构服务器的负载均衡,怎么设计?