MIT 6.824 学习笔记(一)--- RPC 详解

从本文开始,将记录做者学习 MIT 6.824 分布式系统的学习笔记,若是有志同道合者,欢迎一块儿交流。数据库

RPC 的定义和结构

RPC 全称为 Remote Procedure Call,他表示一种远程过程的调用,他可让用户在不知道底层网络协议的状况下,在本地的计算机就能够调用远端服务器的一些处理方法,而后服务器会把处理的结果返回到用户本地,就像这个方法写在用户本地空间中同样。缓存

RPC 的层级结构以下所示:服务器

RPC structure

  • Client 端(由上到下):
    • Application:应用层,表示和用户直接交互的部分,用户在该层肯定本身想要调用的函数,而且输入参数
    • stub:原意是烟蒂,很形象的表示这只是个函数空壳,表示用户想要调用的函数,这里能够是函数名
    • RPC lib:RPC 库,包括一系列的编码解码工做,对于用户端来讲,是编码用户的函数调用请求进入网络层,或者是解码服务端的结果成为上层结构能够理解的语言
    • Network Layer:网络层传输协议,这里再也不赘述,它可使 TCP
  • Server 端(由下到上):
    • Network Layer:网络层传输协议,同上
    • RPC lib:RPC 库,对服务端来讲,用于解码用户的调用函数请求,或者是函数结果的封装。
    • Dispatcher:用户的请求到达该层后,服务端须要经过 Dispatcher,根据请求的函数,找到服务器中对应的函数,并把任务交到这个函数上
    • Handler:具体的函数逻辑

如上图所示,RPC 的操做流程为:网络

  • 用户端写好 function call,向下传递到网络层并发送到服务端
  • 服务端上行用户请求,经过 Dispatcher 找到具体的 Handler,由 Handler 处理具体的逻辑
  • 服务端处理完毕后,经过 RPC lib 编码发还到客户端
  • 客户端通过解码后上行结果,用户得到 Application 层得到结果返回。

RPC 的关键技术点

RPC 具体实现的关键技术点以下多线程

  • Marshall / Unmarshall:即为咱们日常说的序列化/反序列化,用于将应用层的信息转化为网络层能够理解语言,抑或是将网络层的信息解码成为应用层能够理解的语言。这部分在 RPC lib 中能够完成
  • Binding:在一个大的网络环境中,如何为用户分配一个符合用户所需逻辑要求的,可用的服务器也是一大难点
  • Threads:handler 可能执行得很慢,而经过多线程则能够加强 Server 的吞吐量

RPC 的潜在失败状况和处理方案

RPC 的潜在失败状况有以下几种:并发

  • Lose Packet:网络环境差形成的丢包
  • Broken Net:网络直接断了
  • Server Crash:服务器崩了
  • Server Slow:服务器处理速度太慢

对此,为了必定要让客户端拿处处理结果,有如下几种解决方案:分布式

方案一:At Least once:

该方法的中心思想是:client 发送等待 server 回答,若是没收到,则继续发送。该方式可能形成的问题有:函数

  • 问题一:可能客户端 -> 服务端的网络一直相同,服务器处理正常,可是在回复过程出现问题,从而形成客户端持续发送请求,使得客户端一直重复处理,浪费资源。学习

    at least once1

  • 问题二:若是要处理的请求具备事务性,如操做数据库,那么使用这种方案就没法保证先后的一致性。考虑以下的状况,假设咱们要操做的对象是服务端的数据库,可能会出现以下的问题:编码

    at least once2

    如上图所示,PUT 表示咱们要去修改一个 key 对应的值,而 GET 则让咱们获取这个 key 对应的值,所以当咱们延迟的请求到达服务器时,服务器已经处理其余的请求,形成咱们最后一次 GET 方法获取的结果可能与咱们的预期不一样。

因此 At least once 会形成资源浪费,服务端出现不一致的状况,不是一个很好的方法

方案二:At Most Once

其实,方案一的问题在于服务端没法判断客户端由于各类缘由收不到而重复发出的请求,所以咱们须要让服务器能够发现以前的重复请求,而且在不执行 handler 的状况下,返回以前缓存的结果。所以咱们的请求须要带上惟一的识别码,这个识别码能够经过 client ip + 时间戳 生成,也可使用 client ip + seq(序列号)。这样,服务端就能够知道这是否是一个已经处理过的请求,并经过缓存直接返回结果。

然而,这对服务端也提出了新的问题:

  • 问题一: 何时能够删掉这些缓存?

    毕竟服务端不可能永远保存这些缓存,这会形成容量不够的问题。答案其实也很简单,那就是客户端证实本身收到了回复的时候。对此,咱们 RPC 用户端协议能够带一些附加信息,好比:

    • 对于客户端 RPC 请求,能够带上其近期已经收到回复的序列号,这样,服务端的在解码过程当中能够经过解析该字段,从而删除已经肯定收到回复的缓存

    • 能够经过使用 sequence number 的方法,即客户端的该次请求是上一次请求的序列号 + 1。这样,客户端就能够在请求中带上一个序列号 n,表示 seq < n 的请求我都已经收到了。服务端便可删除全部序列号小于 n 的缓存。以下的示意图展现了这一种方法:

      at_most_once

  • 问题二:若是一个请求仍在处理,此时"心急的"客户端又发了一个相同的请求,该如何处理?

    这个时候,服务端能够维护一个关于"处理中"的缓存,将正在处理的 request 记录在该缓存,若是有相同请求到来,则先去查询"处理中"的缓存,再去查找"已完成"的缓存,若是存在,则舍弃这条请求,返回已有结果或者等待原有的相同请求处理完成并返回。

总结

本章是对 MIT 6.824 第二课 RPC 内容的总结,主要讲了 RPC 的结构,以及一些潜在错误的处理方法,为 lab1 中 mapreduce RPC 的实现打下基础。

相关文章
相关标签/搜索