如何设计一个类似Dubbo的RPC框架?——RPC项目中可能会遇到的一些问题(4)

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
img

正文

  1. 动态代理

生成Client Stub(客户端存根)和Server Stub(服务端存根)的时候需要用到java动态代理技术。
2. 序列化
在网络中,所有的数据都将会被转化为字节进行传送,需要对这些参数进行序列化和反序列化操作。

目前主流高效的开源序列化框架有Kryo、fastjson、Hessian、Protobuf等。
3. NIO通信

Java 提供了 NIO 的解决方案,Java 7 也提供了更优秀的 NIO.2 支持。可以采用Netty或者mina框架来解决NIO数据传输的问题。开源的RPC框架Dubbo就是采用NIO通信,集成支持netty、mina、grizzly。
4. 服务注册中心

通过注册中心,让客户端连接调用服务端所发布的服务。主流的注册中心组件:Redis、Nacos、Zookeeper、Consul 、Etcd。Dubbo采用的是ZooKeeper提供服务注册与发现功能。
5. 负载均衡

在高并发的场景下,需要多个节点或集群来提升整体吞吐能力。
6. 健康检查

健康检查包括,客户端心跳和服务端主动探测两种方式。

3、项目在面试中可能遇到的问题

3.1  在RPC项目中使用短链接有什么问题?如何实现长连接

由于采用TCP作为传输协议,所以若采用短连接,则每次传输都要建立连接和断开连接,有很多网络通信消耗,这和开发预期的高性能RPC不符合。

利用Zookeeper分布式服务器,将服务端注册入Zookeeper服务器中,而客户端每次都从zookeeper服务器中获取服务端地址,连接后将返回的ChannelFuture存进一个set里,每次发送请求都从set从取出ChannelFuture发送信息,并且发送消息完毕后并不关闭连接,这就保证了客户端和服务端的长连接。

3.2 项目是基于TCP还是HTTP来做的,为什么用TCP实现呢?

要做长连接的话,是不能用http协议来做的,因为http协议已经是应用层协议了,并且http协议是无状态的,而我们要做长连接,肯定是需要在应用层封装自己的业务,所以就需要基于TCP协议来做。

同时TCP协议可以保证到达应用层的数据是正确无误的,是可靠的网络传输协议,这对于RPC通信框架来说是不可缺少的。

3.3 同步阻塞调用性能瓶颈:有什么瓶颈?你是怎么解决的,怎么实现异步调用的?

同步阻塞的缺点是调用时线程会阻塞在请求中,cpu此时并不会切换到其他线程,若服务器不返回消息,则客户端会一直被阻塞,无法处理其他请求。

Netty是基于NIO开发的,采用了异步非阻塞通信方式,本项目采用了netty服务框架实现了客户端服务端开发。

3.4 为什么需要心跳检测,客户端发送请求服务端没回应会造成什么?心跳检测机制你是怎么实现的?

在系统空闲时,例如凌晨,往往没有业务消息。如果此时链路被防火墙Hang住,或者遭遇网络闪断、网络单通等,通信双方无法识别出这类链路异常。等到第二天业务高峰期到来时,瞬间的海量业务冲击会导致消息积压无法发送给对方,由于链路的重建需要时间,这期间业务会大量失败(集群或者分布式组网情况会好一些)。

为了解决这个问题,需要周期性的心跳对链路进行有效性检测,一旦发生问题,可以及时关闭链路,重建TCP连接。

当有业务消息时,无须心跳检测,可以由业务消息进行链路可用性检测。所以心跳消息往往是在链路空闲时发送的。

使用IdleStateHandler实现心跳检测机制,IdleStateHandler提供了3种心跳检测方法:

● 读空闲超时机制:当连续周期T没有消息可读时,触发超时Handler, 用户可以基于读空闲超时发送心跳消息,进行链路检测;如果连续N个周期仍然没有读取到心跳消息,可以主动关闭链路。

● 写空闲超时机制: 当连续周期T没有消息要发送时,触发超时Handler,用户可以基于写空闲超时发送心跳消息,进行链路检测;如果连续N个周期仍然没有接收到对方的心跳消息,可以主动关闭链路。

● 读写空闲超时机制:以上两者的结合

重写userEventTriggered方法,根据用于业务需要,判断event触发事件,当产生读空闲、写空闲、读写空闲时所需要进行的操作,如在发生读写空闲时,服务端发送“pong”,客户端发送“ping”做回应。

3.5  什么叫做序列化?Java原生序列化有什么问题?为什么用JSON,怎么实现的JSON序列化,和JAVA序列化速度的差异?

序列化就是将类对象编码成可在网络中传输的二进制数据,jdk原生序列化有下面三个问题:

  1. Java序列化机制是Java内部的一种对象编解码技术,无法跨语言使用。例如对于异构系统之间的对接,Java序列化后的码流需要能够通过其他语言反序列化成原始对象(副本),目前很难支持。

  2. 相比于其他开源的序列化框架,Java序列化后的码流太大,无论是网络传输还是持久化到磁盘,都会导致额外的资源占用。

  3. 序列化性能差,资源占用率高(主要是CPU资源占用高)。

使用fastjson的原因是这种序列化方式简单明了,且相比jdk序列化编解码速度快,序列化后码流较小。

3.6 为什么选择使用JSON,应该有其他更快的序列化方式吧?

JSON简单通用,使用广泛,有较好的扩展性和可读性。与其他的二进制序列化方式对比,JSON序列化的可读性会好很多,而且JSON可以跨多语言进行使用,通用性好。

3.7 你还知道哪些序列化的方式?

以上几个问题

速度方面:

序列化后大小方面:

其他序列化方式主要有:protobuf和protostuff,hessian等

Hessian比JSON性能要高,但对java一些常见的对象类型不支持,如Linked系列、Locale类等等

Protobuf是由谷歌公司开发的数据语言,支持java、C++、python等多平台,序列化后体积较小,转化性能较高。但需要预编译IDL。

Protostuff 不需要依赖 IDL 文件,可以直接对 Java 领域对象进行反/序列化操作,在效率上跟 Protobuf 差不多,生成的二进制格式和 Protobuf 是完全相同的,可以说是一个 Java版本的 Protobuf 序列化框架。但不支持null,也不支持单纯的 Map、List 集合对象,需要包在对象里面。

3.8  你是怎么基于动态代理进行的请求处理?用的哪一种?为什么使用这种?

使用SpringCGLIB进行动态代理,在intercept方法里对方法进行横切扩展,主要是将请求参数包装成ClientRequest类,并使用Netty客户端将请求发送出去

使用SpringCGLIB的优点就是可以对类进行代理,而jdk的动态代理只能针对接口。

3.9 你的项目哪里遇到进程通信问题了?

当大量客户端同时向服务器发送请求时,服务器响应可能会产生并发问题,导致出现进程通信问题。

3.10 怎么解决RPC项目中的进程通信问题?

通过引入wait和notify机制,使用ReentryLock和Condition进行控制。

① 创建锁

② 在主线程获取数据前,先等待结果

③ 在获取结果时,上锁,防止多线程同时获得结果

④ 如果没有获得结果,则进入Condition进行等待,并在finally释放锁

⑤ 获得结果,则调用signal唤醒锁。

3.11 还有哪些解决进程通信问题的方式?

Synchronized:重量级锁;volatile乐观锁;CAS机制

3.12 TCP/IP 拆包粘包能简单介绍下么?你怎么解决的呢?有没有更好的解决方式?

由于TCP协议是基于流的协议,没有消息边界,客户端发送数据时,由于TCP的neagle算法,为了减少通信消耗,会将多次发送的数据组合在一起发送,而在发送过程中有可能会对数据包进行分片,而服务端收到消息时,由于没有指定消息边界,因此多条消息的数据有可能连在一起,这就是粘包现象,还有一种可能是一条消息过长,因此被分片后服务器得到的数据只有消息的部分,这就是半包。

这种现象发生的原因有以下几种:应用程序write写入的字节大于套接字发送缓冲区;TCP进行MSS分片;以太网帧payload大于MTU。

我采用分隔符的方法解决TCP的粘包半包问题,使用DelimiterBasedFrameDecoder这一handler,指定分隔符为”/r/n”,每次发送消息时都会在消息后面加上这一分隔符,这样服务器就可以在应用层分辨出消息边界。

更好的解决方法可以采用“消息头+消息体”的方法,每条请求的消息头表明该消息的长度,可以使用LengthFieldBasedFrameDecoder这一handler标明数据长度。其他方法还有固定长度:FixedLengthFrameDecoder,或者短连接方式(但这不符合开发目的)。

3.13 你是怎么基于BeanPostProcessor 机制和 ApplicationListener 机制实现客户端的自启动与基于注解的服务调用?

这个问题比较复杂,

首先客户端类TcpClient的静态代码块会初始化一个netty客户端,因此在BeanPostProcessor中完成请求的包装后,调用TcpClient.send()方法就实现了客户端的初始化自启动。

而服务端则将初始化代码放在一个start()方法里,同时服务端实现了ApplicationListener,并在onApplicationEvent方法里调用服务端的start方法,这样当Spring启动完成后,监听器监听到ContextRefreshedEvent,就会启动netty server。

基于注解的服务调用方面,我将service的实现类标注了@Remote注解,这是一个自定义注解,同时在Spring启动过程中会有一个BeanPostProcessor,对@Remote注解标注的类进行处理,主要是将service接口的各种方法与接口进行映射;而客户端则对service的接口标注了@RemoteInvoke,在BeanPostProcessor的实现类中对被@RemoteInvoke标注的接口完成方法与接口的映射,这样服务器的map和客户端的map里关于方法和类名都是一致的,因此服务端可以知道该执行什么处理。

3.14 注册中心你是怎么实现的?用的什么?

对每个服务器设置一个序列号,每次启动一个服务器都会将该服务器注册同一路径下,保存该服务器地址和端口号。

用的是基于zookeeper的服务注册。

3.15 为什么RPC项目用Zookeeper作为注册中心?

zookeeper中curator中的watcher机制,可以实现客户端连接的动态管理、监听和发现功能,并实现了服务器注册功能,扩展项目功能。

3.16 你的服务结点结构是怎样的?

路径/+IP地址+“#”+端口号+“#”(+权重+“#”)

3.17 那么当有新的服务上线或者旧服务下线的时候,你怎么保证得到最新结果?客户端怎么发现服务?怎么动态监听连接?给我讲一讲机制?

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
[外链图片转存中…(img-PrbVzxlB-1713188484763)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 17
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值