【远程调用框架】如何实现一个简单的RPC框架(一)想法与设计

【如何实现一个简单的RPC框架】系列文章:

【远程调用框架】如何实现一个简单的RPC框架(一)想法与设计
【远程调用框架】如何实现一个简单的RPC框架(二)实现与使用
【远程调用框架】如何实现一个简单的RPC框架(三)优化一:利用动态代理改变用户服务调用方式
【远程调用框架】如何实现一个简单的RPC框架(四)优化二:改变底层通信框架
【远程调用框架】如何实现一个简单的RPC框架(五)优化三:软负载中心设计与实现
第一个优化以及第二个优化修改后的工程代码可下载资源 如何实现一个简单的RPC框架


前言

 最近在读《大型网站系统与Java中间件实践》这本书,第一次系统的简单接触分布式的东西,了解到从单机应用到分布式集群大型应用的道路上,应用的拆分与服务化对于系统的维护、各模块的解藕等都非常重要。这本书的第四章详细介绍了服务框架的各个部分的实现原理。他分别从客户端(服务调用者)以及服务端(服务发布者)的角度介绍了服务框架基本的工作原理,又在基本工作原理的基础上,介绍了服务治理等更加生产实际化的部分。
  研究生学习期间,接触过一些rpc远程调用框架的知识,例如webservice等等,关于webservice的基本知识、发布调用等等进行过简单基本的学习与实验。16年暑假在淘宝实习,接触了阿里非常重要的一个远程调用框架的中间件——HSF,当时简单的看了下源码,明白了HSF基本的工作原理(其实就是所有远程调用框架的基础原理),又在实习转正答辩的时候,了解了下webservice与hsf的不同。但有一点可以肯定的是,他们都是远程调用服务框架,基础的实现原理都是一致的。因此我想对这个一般rpc框架都会实现的原理亲身实现一波,于是便有了这篇博客。整个系列目前分为三篇博客,分别是想法与设计、实现、提升。
 其实很惭愧的是我既没有看过webservice的源码、也没有深入仔细推敲过HSF的源码,因此准备在完成这个基础的不能再基础的RPC服务框架之后,认真分析一下HSF的源码,也为今年入职淘宝做准备。
 本人实现的这个“基础的不能再基础”的RPC服务框架就暂且命名为LCRPC吧。

1、什么是RPC

RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。
-------《百度百科》

2、webservice

Web service是一个平台独立的,低耦合的,自包含的、基于可编程的web的应用程序,可使用开放的XML(标准通用标记语言下的一个子集)标准来描述、发布、发现、协调和配置这些应用程序,用于开发分布式的互操作的应用程序。[1]
Web Service技术, 能使得运行在不同机器上的不同应用无须借助附加的、专门的第三方软件或硬件, 就可相互交换数据或集成。依据Web Service规范实施的应用之间, 无论它们所使用的语言、 平台或内部协议是什么, 都可以相互交换数据。Web Service是自描述、 自包含的可用网络模块, 可以执行具体的业务功能。Web Service也很容易部署, 因为它们基于一些常规的产业标准以及已有的一些技术,诸如标准通用标记语言下的子集XML、HTTP。Web Service减少了应用接口的花费。Web Service为整个企业甚至多个组织之间的业务流程的集成提供了一个通用机制。
-------《百度百科》

可以参考笔者总结的webservice的相关博客:
【webservice】关于WSDL
【webservice】Java 发布webservice 步骤
【webservice】如何远程调用Websevice服务
【webservice】Java调用WebService

3、阿里中间件HSF

https://wenku.baidu.com/view/1002761a6bd97f192279e908.html 可以参考这篇文章

4、如何实现一个简单的RPC框架

4.1 LCRPC服务框架包含的内容

LCRPC服务框架主要包含两个部分,一个是提供给用户使用的jar包,用于服务调用以及发布。另一个是服务注册查找中心,不与用户直接打交道,用于服务信息的注册与查找。

  • 1、一个应用依赖包
    服务框架的核心部分,是服务框架开发的主要产物--应用依赖jar包。为服务调用者以及服务提供者提供:注册服务、发布服务、调用服务等功能。完成服务框架的核心功能。

注意:
- (1)一个服务对应一个接口,接口中包含多个方法
- (2)服务由接口的全限定名+版本号作为唯一标识

  • 2、服务注册查找中心
    服务框架通过服务注册查找中心完成服务信息的管理。LCRPC在发布服务的同时,将服务的信息(服务的唯一标识、地址等)注册到服务注册查找中心。这一,服务在调用时根据服务名称进行地址等其他信息的查找,使得调用端和服务提供端可以通过地址来直接进行交互。服务注册查找中心不与用户直接交互。

4.2 客户端与服务端工作原理

从服务调用者以及服务提供者的角度,分别说明该服务框架的基本工作原理。如下图所示:
这里写图片描述

(看不清可以访问:远程调用框架工作原理
对于服务调用者来说:
- (1)服务框架获得服务调用者提供的服务信息(服务唯一标识:接口全限定名+版本号;方法;调用参数);
- (2)框架根据服务信息通过服务注册查找中心查找到该服务提供者的地址列表;
- (3)可根据(服务、接口、方法、参数)进行路由,确定服务提供者的地址;
- (4)拼装请求参数对象Request,并序列化成二进制流;
- (5)与服务端建立连接,发送序列化二进制结果;
- (6)得到服务端响应,反序列化,得到最终调用结果

对于服务提供者来说:
- (1)发布服务,监听端口;
- (2)服务发布成功后,将服务信息(服务唯一标识:接口全限定名+版本号;服务实现类全限定名)注册到服务注册查找中心;
- (3)接收客户端请求,将请求数据反序列化为Request对象;
- (4)解析Request对象,根据服务标识从服务注册查找中心获取该服务信息,例如服务接口的实现类;
- (5)利用反射创建类实例对象,调用方法(多采用线程池的方式);
- (6)将调用结果序列化成二进制数据;
-(7)发送响应数据到客户端;

注意:服务发布者需要提供给服务调用者一个二方包,包中函数接口所有方法调用的参数以及返回类型类。其实这个二方包最大的用处应该体现在:让用户像本地调用一样使用服务框架完成远程调用,但是第一个版本我们先不实现这个功能,后面可以进行优化。

4.3 设计

4.3.1 涉及到的Java编程知识

  • (1)序列化与反序列化:请求的信息需要拼装成Request对象,并且序列化成二进制信息发送给服务发布者;服务发布者对于接收到的二进制信息,需要反序列化为Request对象,解析该对象进行服务的调用,并且将调用的结果也要序列化为二进制对象返回给服务调用者。因此,在整个服务框架中,序列化/反序列化的作用在于调用端与发布端数据的发送。
  • (2)反射:服务发布端收到调用端的请求后,根据服务唯一标识可以获得该接口的实现类信息,通过反射创建实现类实例,进行方法的调用;
  • (3)路由:由于在分布式集群环境中,同一个服务的发布者可能有很多,也就意味着从服务注册查找中心查找到的服务地址可能多于一个,我们可以根据接口/方法/参数的粒度进行路由,同时还有一些其他规则,例如同机房原则、归组规则等等。在LCRPC第一个版本的实现中,我们先采取最简单的方式获得服务的地址,例如随机选择等方式;
  • (4)网络通信实现选择:可参考博客《Java BIO NIO AIO 总结》。在LCRPC服务框架版本一中,我们先采用BIO的方式,即采用Java Socket编程的方式,对每一个连接建立一个新的线程维护;第二个版本使用Java NIO的通信方式;而后可以再用netty等现成的框架实现AIO。

4.3.2 设计

(一)服务注册查找中心
服务注册查找中心最终应是一个web应用,提供服务接口给LCRPC服务框架使用,负责服务信息的管理,例如查找与注册。
- 1、使用开发框架:springmvc+maven+tomcat
- 2、接口设计
(1)服务注册接口:对服务信息进行存储,服务对应接口全限定名+版本号作为服务的唯一标识;
(2)服务地址列表查询接口:根据服务唯一标识,返回服务地址列表;
(3)服务信息查询接口:根据服务唯一标识,返回该服务所有信息(包括地址列表)
- 3、数据结构设计
(1)服务信息描述结构:ServiceInfoDO
(2)注册服务列表单例类:ServicesSingle
(3)服务操作接口:IServiceAccess
(4)服务操作接口实现类:ServiceAccessImpl
(5)servlet:ServiceCenterServer
类结构示意图:(后面补充)

(二)LCRPC包
提供给服务调用者和服务发布者使用,应提供的功能包括:服务发布(包含服务注册)、服务调用等;
- 1、使用开发框架:maven
- 2、数据结构设计
(0)服务信息结构体:ServiceInfoDO
(1)请求调用参数结构:LCRPCRequestDO
(2)服务调用对外接口:ILCRPCConsumer LCRPCConsumerImpl 对外提供服务调用功能
(3)服务发布对外接口:ILCRPCProvider LCRPCProviderImpl 对外提供服务发布功能(包含服务注册)
(4)服务调用帮助类接口:IConsumerService ConsumerServiceImpl
(5)服务发布帮助类接口:IProviderService ProviderServiceImpl
(7)线程类一:socket服务端监听线程
(8)线程类二:socket服务端接收到连接后的处理线程
(9)自定义异常类:LCRPCRemoteCallException、LCRPCServiceIDIsIllegal、LCRPCServiceMethodIsIllegal、LCRPCServiceNotFound、LCRPCServiceInfoNotComplete、LCRPCServiceListenFailed、LCRPCServiceRegistryFailed
(10)常量类:Constant
类结构示意图:(后面补充)

(三)测试应用
利用LCRPC服务框架提供的包,进行服务发布和服务调用的测试。注意服务发布者需要提供给服务调用者一个调用二方包,包中至少含有该服务多个方法的参数以及返回类型对应的类信息。

  • 1、服务发布测试
    这里我们设计一个计算器的服务,提供加减乘除四个功能方法,同时采用自己设计的方法参数以及方法返回值类型。
  • (1)使用开发框架:spring+maven
  • (2)数据结构
    a、方法参数:MyParamDO
    b、方法返回值:MyResultDO
    c、服务对应接口:ICaculator
    d、服务对应接口的实现类:CaculatorImpl
    e、服务发布测试类(主运行类):ProviderTest
    f、常量类:Constant
    最终服务发布方需要生成一个二方包给服务调用者使用。

  • 2、服务调用测试
    这里我们利用计算器服务提供的二方包,对该服务进行远程调用

  • (1)使用开发框架:spring+maven
  • (2)数据结构
    a、服务发布者提供的二方包
    b、测试类(主运行类):ConsumerTest

4.3.3 有可能存在的问题

  • (1)连接越来越多:IO模式的改变
    在LCRPC第一个版本的实现中,我们采用的是Java中BIO的通信模式,即对每一个连接都采用一个线程进行维护,随着请求的增多,这会造成大量线程的创建维护以及资源的浪费。这一点可参考博客《Java BIO NIO AIO 总结》。因此为了解决这个问题,我们可以尝试改变通信方式,例如采用BIO的模式,以及AIO的模式。

  • (2)如何让用户像本地调用一样使用服务框架:面向接口编程+动态代理
    在第一个版本中,服务调用者能够调用服务的必要基础是:LCRPC服务框架提供的依赖、服务发布者提供的二方包、以及要调用服务的相关信息(该服务的唯一标识、该服务方法名称等等)。而其实我们是否可以帮助用户像在本地使用方法一样的去调用远程服务。用户只需要上述必要基础的前两样,利用服务发布者提供的二方包,该二方包中应当有服务对应的接口,用户直接在本地调用该接口方法,就完成远程调用的内容,不必在依靠(该服务的唯一标识、该服务方法名称)等信息进行不那么形象化的调用。而上述功能,可以采用动态代理的方式实现。

  • (3)服务注册查找中心:服务信息的持久化
    在服务框架第一个版本的实现中,服务注册查找中心对于所有已经注册的信息缓存在内存中,这在实际生产中会产生的问题是:如果服务注册查找服务需要重启,则会丢失全部已经注册服务的信息,因此需要考虑服务信息的持久化。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页