分布式之RPC的协议以及错误处理

来源:互联网 发布:淘宝介入后对卖家影响 编辑:程序博客网 时间:2024/05/01 00:58

本文主要内容来自于分布式系统:概念与设计
用惯了Web Services,但是很少知道RPC和RMI,更不用说它们之间的区别了,所以先总结一下RPC以及RMI到底是什么东西!!

在分布式系统通信中有两个最基本的远程调用技术,分别是:

  • RPC:一个对远程节点上的过程的调用就像调用本地节点那样完成;
  • RMI:与特定的Java RMI要进行区分,与RPC最大区别在于使用了面向对象的概念,那么在远程调用时可以把对象应用作为参数传递,它的实现最典型的就是Java RMI,但不限于Java RMI;

1、交互协议

远程调用允许客户端透明地调用在服务器程序中的过程,而这些服务器程序在不同的进程中,并且通常在不同于客户端的计算机中。如果要完成远程调用,必然要设计出一种协议让客户端和服务端来遵守,这样才能让调用者和提供调用过程者协同完成工作。其实我们对这种“协议”也不陌生,比如熟知的TCP/IP协议和Http协议等等。在远程调用过程中有多种交互协议,如下:

  • 请求协议:可以用在不需要从远程操作中返回值或者客户端不需要得到远程操作执行确认的场景中,请求发送后客户端不需要等待应答消息而可以继续执行;
  • 请求-应答协议:这种方式使我们最常见的,服务器的应答消息可以看做是客户端请求消息的一个确认;
  • 请求-应答-确认应答协议

这三种协议传递消息的方式如下:

协议 客户端 服务端 客户端 请求协议 请求 XX XX 请求-应答协议 请求 应答 XX 请求-应答-确认协议 请求 应答 确认应答

这里着重了解一下请求-应答协议。在请求-应答协议中消息可以是同步的也可以是异步的。

  • 同步:来自服务器的应答到达客户端之前客户端的进程是阻塞的,如果写过socket程序的人一定深有体会;
  • 异步:可以用在客户端对服务端返回的结果允许延迟的情况下,例如执行该过程需要消耗很长的时间,这样就避免了客户端进程的堵塞,但是如果服务端执行完则可以通过回调的方式来通知客户端执行结果;

在通信过程中涉及到三个原语,关于原语可以理解为不可分割的部分,在执行原语过程中不可以被打断,可以类似理解为一个事务。该通信同步过程如下:

  • doOperation:调用远程操作,方法参数包括远程服务器的地址、端口以及待调用的操作和需要的参数,返回的结果可以是一个字节数组,然后根据协议使用的编码方式解码该数组得出结果,发送请求之后,进程开始阻塞来接收应答消息。
  • getRequest:服务器进程通过该方法获取请求信息,当确定操作后开始执行,完成之后通过sendReply方法向客户端发送应答消息,这是客户端接收消息后才解除阻塞继续执行。

下面的伪码可以大致描述上述的三个方法:

//doOperation()public byte[] doOperation(Remote remote, int operationId, byte[] arguments)

在该方法的参数中,Remote描述了远程的服务器,里面可以包含服务器的地址和端口以及对应的一些处理的方法;第二个参数operationId用来标识要调用哪个操作,用来确定操作过程;第三个参数就是用来传递参数。

//getRequest()public void getRequest()

该方法只是用来从服务器的一个端口上获取客户端的请求。

//sendReply()public void sendReply(byte[] reply, Remote client)

在该方法的参数中,client参数和remote参数类似是用来描述客户端的,包括地址和端口号,reply是需要返回给客户端的数据。
就像在发送Http请求和接收响应时的Headers一样,请求消息和应答消息中也有传递信息的格式,如下所示:

信息头 数据形式 messageType int requestId int remoteRef 远程对象引用 operationId int/String arguments byte[]
  • messageType表明该消息是请求消息还是应答消息;
  • requestId是用来标明一个请求消息的,在每个doOperation()方法中都会为一条请求消息生成一个RequestId,这样在该请求消息传递到服务器端时,服务器端的程序就会将该Id复制到对应的应答消息上,这样在客户端才能检查返回的应答信息是否和发出的请求信息相对应;
  • remoteRef用来定位客户端或者服务器,包括ip和端口号等;
  • operationId用来标识要调用哪项操作,可以通过索引将所有的操作编号;
  • arguments则是要使用到的参数;

其实在上述的requestId是有缺陷的,由于发送请求消息的进程可能不止一个,而且这些进程可能不在同一台电脑上,那么这些消息的requestId不会重复吗?当然会了,那么怎样解决这个问题?可以加上该进程所在的主机的ip地址和相应的端口号就能区分出这些requestId是从哪些进程发送出来的。
对于请求-应答协议可以从Http协议中深入了解,因为这就是一个典型的请求-应答协议的例子。

2、故障处理

在远程过程调用时不可避免的会出现各种各样的问题,比如由于网络传输的问题导致的信息传递中断,或者当服务器出现故障时不能及时返回应答信息等等。想想如果在发送Http请求时出现这样的问题我们会看到什么,网络错误!!Time Out!!通常就是几种方式,如果使用了浏览器,不同的浏览器可能会建议我们去检查什么东西。那么在远程调用时一样要考虑这种问题,这里使用超时来暗示这种问题的存在。但是当超出我们设定的时间之后会发生什么,其实是由我们来处理的,比如:

  • 最简单的但是也是最懒的方法,直接将这种超时的信息抛给客户端调用者,由他来决定下一步干什么,但是这就显得我们的方法太不“智能”了;
  • 另外一种方法可以通过编程来实现,如果这次超时,可以进行重试,重试的次数可以编程决定,多长时间重试一次亦可以编程决定;
  • 或者在客户端通过异步的方式来调用该过程,那么我们在客户端就不用考虑超时的问题了,这样请求消息和应答消息就有足够的时间进行传递,而当应答消息返回时,可以通过回调的方法来通知客户端,这种可以设计成基于事件的,类似于AngularJs中的onSuccess()和onError()等,但是这样做的前提是客户端对该应答消息的延迟有相当的容忍程度;

上面提到可以通过重试来解决超时的问题,但是别的问题又来了,当在服务器端对客户端调用的操作的处理的时间和返回结果的时间总和大于我们在客户端设置的超时时间,就会产生请求消息重复发送的问题,但是本来执行该项请求就很费时间,所以绝不能够再让服务器重复执行该操作,所以在服务端应该能够识别requestId连续且来自同一个客户端的消息并且过滤掉重复的消息。

对于重复的请求消息,其中还有一些细节可以体会一下。

  • 如果服务端接收到重复消息时还没有发回应答消息,这样就比较简单了,等待服务端执行完毕然后返回该应答消息即可;
  • 但是如果接到重复消息之后发现应答消息已经发送出去,不可否认,这种情况是存在的,这时服务端就不能区分该重复的请求信息是客户端故意重新发的,还有由于超时程序自己重试的。
    • 我的想法是,如果客户端和服务端可以沟通就好了,可以彼此设定超时时间是多少,这样在服务端那就可以根据超时时间来判断自己识别重复请求消息的策略。
    • 但是如果彼此不能协商,那么必须要做的就是每一个请求消息都要做出应答,如果对于不耗时的操作可以这么做,是没有问题的,这可以看做是一个幂等操作,这种操作执行一次的效果与执行多次的效果是相同的;但是对于耗时的操作,可以通过在服务端使用缓存保存上次操作的结果来说减少执行的时间,但是这也有问题,如果保存在缓存中的数据与实际存储在服务器后台的数据不一致时就会返回错误的信息,这种问题可以通过在更新服务器的数据时也同时更新缓存或者清除相关的缓存来保持数据的一致性;

相关文章:

  • RPC的概念模型与实现解析
  • REST之中的幂等指的是什么?
  • 你应该知道的 RPC 原理
  • Difference Between RPC and Web Service
  • What is the difference between remote procedure call and web service
0 0
原创粉丝点击