花6个月写的付费专栏,免费送|仿开源框架从零到一完整实现高性能、可扩展的RPC框架

作者

渡码,阿里巴巴码农,公众号:渡码 作者,专注大数据开发、数据分析和Python技术。

avatar

关注公众号 渡码 回复关键字 manis,可获取电子书、各章节和完整源代码,并且可加入读者群一起交流问题。

简介

19年上半年,我阅读了Hadoop RPC模块的源代码,读完后发现这个模块设计的非常好,与其他模块无耦合,完全可以独立出来当成一个独立的框架。为了总结学到的编程知识,同时也为了学习Apache顶级开源项目的代码是如何编写的,我便把它做成了电子书,共350页,从写代码到做成电子书共花了6个月的时间。本来想做成付费专栏赚点小钱,并且已经到了上架阶段了,但后来决定把它免费开放出来,让更多的人能够学习到优秀的实战项目。

当然我们这本书并不是源码分析类教程,而是强调动手能力。在这里我会带着大家按照 Hadoop RPC 源码从 0 到 1 完整敲一遍,代码量在 4600 行左右。为了让不熟悉 Hadoop 或 RPC 的朋友也能够学习,我将 Hadoop RPC 稍微做了一点改造,赋予了新的业务含义,也有自己的名字,叫 Manis。Mnias 源码相比于 Hadoop RPC源码还原度为90%。为什么不是100%呢?一方面为了突出重点,我会把不太重要、不是很核心的技术舍弃掉。另一方面为了符合新的业务定义,我会做一些改进,而不是照搬完全 Hadoop RPC。

虽然这个项目是实现 RPC 功能,但我觉得我们关注的重点不应该过多地放在 RPC 本身,而应该重点学习编写 RPC 过程中所涉及的系统设计、面向对象设计思想和原则、工程/代码规范、客户端开发、服务端开发、网络编程、多线程、并发编程、设计模式、异常处理等核心知识,可以说是麻雀虽小五脏俱全。尤其是对于刚学习 Java 还没有接触线上实战项目的朋友,这是一次很好的练兵机会。

学习开源项目的一个优势在于它是经过线上检验的,Hadoop集群规模最大达到上万台服务端,足以证明它的 RPC 模块是优秀的。另外一个好处是可以积累顶级开源项目的开发经验,大到架构设计,小到设计模式、代码规范,说不定日后就能为开源社区贡献代码了。所以,学会了 Manis 后,不但有编写实战项目的经验,同时也有能力阅读 Hadoop RPC 的源码,这也算是面试的加分项。

涉及到的核心技术

下面我们来介绍一下 Manis 中涉及的核心技术点。作为一个 RPC 框架,最关键的几个模块是客户端、网络模块和服务端

客户端

作为客户端来说,它的职责非常明确,以数据库客户端为例,它的职责就是向用户提供增删改查接口,并将相应的请求发送给服务端,接收结果并返回给用户。由于客户端职责边界是非常明确的,所以我们从设计上就要将其与网络模块、与服务端解耦,解耦的方式就要用到设计模式中的代理模式。也就说客户端只需要定义好它需要提供的功能(接口)并提供给用户,具体如何实现就交给代理,至于代理是通过网络发送给服务端还是通过其他什么方式客户端就不需要关心了,客户端只关心调用代理并拿结果。这样做的好处是客户端与其他模块解耦,提高了系统扩展性。当然,代理模式还有个容易被忽略的好处是它天然地适合用在 RPC 场景。

Manis 中支持多种序列化/反序列化方式,每种序列化方式对应一个类,它们都继承共同的基类。我们在设计时需要做到不同序列化方式之间的代码是解耦的,且序列化/反序列化模块与客户端模块、与网络模块是解耦的,这样才能做到任意地增加新的新的序列化方式以及删除老的序列化方式。为了实现客户端与序列化/反序列化模块的松耦合,我们需要用到一些设计模式,比如,用适配器模式将客户端定义的请求接口适配到不同序列化协议定义的请求接口。这样做几乎不需要修改现有的代码,符合面向对象的开闭原则

网络模块

下面再来说说网络模块。

由于客户端的请求可能来自不同的序列化协议,但的目的是相同的,都是为了通过网络模块的服务端,可以说是殊途同归。这样的话,我们就有必要在网络这一层定义一个统一的协议(接口),让不同序列化方式都遵循相同的协议(接口),那么网络模块就可以对它们“一视同仁”,编写一套代码就可以了。就好比,不管你用U盘还是硬盘,只要是 USB 接口,那都能插到电脑的同一个接口进行相同的读写逻辑。对于服务端的返回值也是采用同样的处理逻辑。

网络模块必不可少的功能就是发送网络请求,当然除了这个还有一个更核心的功能是管理网络资源。听起来有点抽象,如果用面向对象的思想来理解,其实就是创建一个类代表网络连接,比如就叫Connection类,每次创建一个网络连接其实就是创建一个Connection对象。当然,我们知道网络资源比较宝贵且创建成本较高,当系统客户端请求量非常大的时候,我们不可能为每次请求都创建一个网络连接,所以,需要建立一个网络连接池,以达到复用网络资源的目的。我们可以再定义一个类ConnectionId,每个ConnectionId对象都唯一代表Connection对象,ConnectionId的属性包含服务端地址请求网络的一些参数,所以我们可以认为客户端请求服务端的地址和参数相同的话,就可以复用同一个网络连接。当然,这里还有一个很关键的问题不容忽视,网络连接池是公共资源,为了保证线程安全,在对资源池读写时需要加锁,也是从这里开始本书加大了对并发编程的相关讲解。刚刚介绍的这部分在 Manis 中是自主实现的。

建立网络连接的过程中还会涉及发送请求头、请求上下文,装饰网络输入、输出流等功能,这些比较偏业务,这里就不再赘述了。

发送网络请求时,为了将业务代码与发送请求代码剥离,在 Manis 创建了一个建线程池,将发送发送请求的代码封装成线程,丢到线程池中等待执行。所以,这里又涉及到三部分知识

使用工厂模式创建线程池,并用单例模式保证不被重复创建

使用Java的ExecutorFuture,用来创建任务并等待返回

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwggjz.html