说起 RPC (远程过程调用),大家应该不陌生。随着微服务、分布式越来越流行,RPC 应用越来越普遍。常见的 RPC 框架如:Dubbo、gRPC、Thrift 等。本篇文章不是介绍各种 RPC 的使用和对比。而是深入剖析一个 RPC 包含哪些内容。我最近在 Hadoop 的源码,正好把 Hadoop RPC 看完了。感觉 Hadoop 的 RPC 框架设计的还是比价优秀的。Hadoop 作为大数据技术的基石,如果没有一个高性能、高可靠的 RPC 框架,很难支撑上千台服务器规模的集群。因此,本篇文章就以 Hadoop RPC 为例,介绍一个 RPC 框架会涉及的技术。
架构设计
RPC 的架构涉及客户端、网络、服务端三大组件。网络一般使用 socket ,更多的是基于现有的网络框架进行参数的设置达到最优的目的。但是客户端和服务端需要我们自己设计,并且对于分布式框架来说,设计的架构应该有高性能、高可用以及可扩展的特点。
- 高性能:由于客户端同时发起多个请求,这就要求系统能够快速处理,降低响应延迟。也就是高吞吐、低延迟。从客户端角度来说,由于创建客户端到服务端的连接成本较高。因此可以缓存连接资源,从而实现多个客户端复用相同的连接资源,避免每个客户端都来创建而降低性能;从服务端角度来说,可以启动多线程来并发处理客户端请求。除了多线程,可以采用 Reactor 编程模式,提高多线程并发的性能。
- 高可用:当我们的服务端挂了,能不能有备用节点继续提供服务。Hadoop 2.x 实现了 NameNode 的高可用。当客户端需要通过 RPC 调用 NameNode 服务的过程中,如果主 NameNode 宕机,那么备用 NameNode 会升级成活动节点。同时会将 RPC 的请求发送的当前活跃的 NameNode,从而继续提供可用的服务,而这个过程对客户端来说是透明的。
- 可扩展性:一个框架需要不断地优化、不断升级。需要在架构设计时明确不变的需求点,以及可变的需求点,对于可变的需求需要能够有良好的可扩展性。以 RPC 涉及的序列化为例。由于不同序列化框架适用场景不同,因此这需要被当成可变的需求点,应该将其设计成可扩展的,能够容易地支持不同的序列化框架。目前,Hadoop RPC 支持自身的序列化框架(Writable)和 Protoc Buffer。
设计模式
设计模式更多地与上面提到的可扩展性相呼应。良好的设计模式可以提高代码复用性、增强可扩展性,同时能够降低 BUG 数量。Hadoop RPC 中涉及的设计模式比较多,大概包括:工厂模式、代理模式、适配器模式、装饰者模式和命令模式等。以代理模式为例,当客户端调用远程方法时,实际上是通过代理,将方法名和参数通过网络发送到服务端。但这个过程对客户端是透明的,对于客户端来说就像调用本地方法一样。
除了设计模式,在工程实践中还应该注意遵循常见的设计原则。
多线程
在任何一个系统中多线程都比较常见。通过多线程并发处理,提高系统的吞吐量。在 Hadoop RPC 中,客户端与服务端都用到了多线程技术。客户端开启多线程,每个线程处理一类请求,并且缓存连接资源。服务端也是多线程并发处理客户端的请求,使用 Reactor 编程模式提高并发性能。
谈到多线程就不得不提另一个话题 —— 线程安全。Hadoop RPC 中用了不少的技术来保证线程安全,包括:synchronized、concurrent并发包、atomic并发包和 nio 工具包。从优秀框架中学习线程安全,对我们以后并发编程有不少好处。
序列化与反序列化
由于 RPC 涉及数据在网络上传输,因此需要一个优秀的序列化框架,既能够高效的编码与解码,且编码后的数据大小又尽可能小。不同的序列化框架主要是在编解码效率和编码大小两个主要方面做权衡。Hadoop RPC 目前支持两种序列化框架,一个是 Hadoop 自己实现的 Writable 框架,另一个是 Protocol Buffer。Hadoop RPC 虽然支持 Writable 序列化框架,但还是以 Protocol Buffer 为主。因为 Protocol Buffer 从编解码效率和编码大小方便都是比较优秀的。当然常见的序列化包括 Avro、Kryo 等,有兴趣的读者可以查一下它们之间的性能对比。
其他
一个 RPC 框架,除了包含上面提到比较主要的方面。还有一些其他的方面
- 语言层面:利用好 Java 语言的继承、组合、封装、多态等特性。甚至包括泛型、注解等。
- 代码规范:良好的工程实现应该有一个良好的代码规范。在 Hadoop 中,代码风格比较统一,且每个重要的类都有详细的注释,在关键的方法或者属性上也有明确的注释。我在自己的工程中会使用阿里的 Java 代码规约插件,也会为了让自己的代码更规范。
- 异常处理:对于一个优秀的框架异常处理很关键,什么时候需要抛出异常、抛出什么样的异常以及什么时候需要处理异常。在 RPC 中除了需要处理本地异常还要处理远程服务的异常。因此,在程序中如何优雅的处理异常也是体现一个程序员能力的地方。
- 网络编程:RCP 中涉及的网络编程一般用 socket,Hadoop RPC 使用的 Reactor 模式的网络编程,并且 Netty 也在使用这种框架。我们有必要会用并且掌握它。
这一段写的比较杂,想到哪写到哪。最近有跟朋友聊过在看 RPC 相关的东西,朋友说:“一个 RPC 能够涉及多少东西?值得研究?”。其实我一开始也是这样想的,无非就是客户端将请求序列化,通过网络发给服务端,服务端反序列化调用函数后再返回。但是看了 Hadoop RPC 代码后,我发现这样框架涉及的知识还是特别多的,并且还比较系统,基本上包含了我们平时编程涉及的方方面面。同时它不再是一个单机程序,而是一个 C/S 架构的程序。如果我们有兴趣还可以继续研究他的高可用,从而对分布式应用有更深入的了解。
我觉得 RPC 是麻雀虽小五脏俱全。由于它涉及了我们编程的方方面面,所以我想基于 Hadoop RPC 做一个详细的教程,把它涉及的每个重要部分都进行详细的分析,上面提到的内容基本都会涵盖。对于想了解 RPC 的读者,能够感受到一个 RPC 框架更清晰的面貌。对于仅有 Java 基础的读者来说,能够学到编写一个框架所涉及的具体编程技术,同时能够从世界顶级开源项目学到优秀设计和工程经验。
小结
本篇文章主要介绍了 RPC 框架涉及的知识。包括:架构设计、设计模式以及设计原则、多线程并发以及线程安全、序列化框架和一些其他的内容。我觉得学习最好的方式就是从优秀的框架中学习、模仿。好比我们练书法基本都要经过临摹这一步。当然直接看别人的代码确实需求花费更多的时间和经历,并且有时候投入与产出并不成正比。所以,我想把我在 Hadoop RPC 框架中学到的优秀的设计和实现能够整理成教程,以便有兴趣的读者学习。如果有任何建议欢迎与我交流。公众号有福利
公众号「渡码」