FaRM是一个探索性质的系统。⽬标是在于探索这些新的RDMA⾼速⽹络硬件的潜⼒。

FaRM的特点:

  1. 所有的replica都在同一个数据中心。
  2. 使用NVRAM(⾮易失性RAM)⽅案,消除了持久化操作的性能瓶颈。
  3. 使用了RDMA技术在机器与机器之间传输数据,消除了CPU和网络的性能瓶颈。
  4. 在52 ms内即可完成简单事务,⽐Spanner快上100倍。
  5. Client充当了事务协调器的作用。

架构

  1. 使用了配置管理器,用于决定每个数据分片中哪台服务器是primary,哪台服务器是backup。使用了Zookeeper实现。
  2. 所有的replica都在一个数据中心,
  3. 更新数据时,primary和backup的数据都需要更新。因此容错能力为 N - 1。
  4. 读取数据时,必须通过primary进行读取。
  5. 数据通过Key进行分片。

1,NVRAM

FaRM中所有的数据都是存放在RAM中的。因此不需要将数据写入到持久化存储中,节约了大量时间。

因为数据写⼊RAM只需要花200纳秒,写⼊固态硬盘100微秒。机械硬盘10毫秒了。

当发生停电故障时,电池系统会接手工作,然后像服务器发送信息,要求服务器写入RAM的数据到固态硬盘中。

因此当出现了硬件或软件故障丢失数据当没有办法恢复数据,这也是需要多个replica同步的原因。

2,RDMA 概况

在传统的架构中,当服务器需要发送RPC信息时,需要进入内核态中,经历Socket、TCP、NIC(网络接口卡)发送信息。接收信息也是类似的。

其中存在着⼤量的软件,⼤量的处理以及很多⼗分消耗CPU资源的操作,⽐如:系统调⽤, interrupt中断以及复制数据。

FaRM通过RDMA减少推送数据包的成本。简单来讲就是,在不对服务器发出中断信号的情况下,他们通过NIC接收数据包并通过指令直接对服务器内存中的数据进⾏读写。

具体实现分为两种方案:

  1. kernel bypass
  2. One-sided RDMA

FaRM使用RDMA通过RPC之类的协议来发送信息。有时候会通过one-sided RDMA来直接读取数据。写操作时一般用于给目标对象的incoming queue追加信息。目标对象再轮询queue是否为空处理信息。

通过RDMA,消除了涉及⽹络的内核代码调用,程序可以直接对内存进⾏读写,⽹络接⼝卡可以直接看到这些内容。大大提升了性能。使得每秒钟能够处理1000万次读写操作,简单的读或者写操作所导致的延迟只有5微秒。

3,kernel bypass

思路是通过对内核保护机制的配置,让用户态程序直接访问NIC。因此在不需要内核参与的情况下,可以直接看到到达NIC的字节信息。

需要发送数据时,程序会创造一些队列,NIC通过DMA读取数据,通过网线将数据发送出去。

对于可靠性等功能,kernel bypass使用了⼀个叫做DPDK的⼯具包解决。

4,One-sided RDMA

这种方案中,需要购买特殊的NIC,它们支持RDMA。可以通过RDMA系统发送特殊信息,来对其他服务器应用程序地址空间的进行内存读写操作。而被读写机器上的应用程序是不知情的。

RDMA NIC使用sequenced protocol保障可靠性。

在传输读写数据是,NIC会一致保持传送数据的状态,直到你的请求丢失或者得到⼀个响应为止。最后你会拿到⼀个确认信息。

事务

FaRM使用了乐观锁机制。通过版本号和二阶段提交实现。

为Client提供了以下API完成事务:

  • txCreate() 说明事务
  • o = texRead(OID) 根据OID拿到数据的信息
  • txWrite(OID, val) 写入新数值到数据值中,在本地完成
  • txCommit() 提交事务,若成功返回 ok。

OID是对象标识符,标识对象对应的区域编号和地址信息。

若事务失败,会进行重试(可能是随机时间段)。

1,内存布局

服务器的内存中包含了⼀个或多个数据区域。一个区域中有多个数据对象。

对象的内部有个header,包含版本号等信息。版本的高位有一个lock标志位,为了可以同时对版本号和lock进行compare-and-set操作(原子操作)。

每个服务器都存储着N份日志,表示N个服务器。可以通过RDMA方式进行追加记录。

每一对的发送和接受方都有自己独立的消息队列,用于处理RPC通信。

发送是通过RDMA在对方的接收队列中添加记录实现的。接收从接收队列中轮询是否为空实现。

2,事务执行

FaRM在执行中Client充当了事务协调器的功能。

Execute phase阶段中,Client会通过RDMA从服务器中获取数据(版本号和对应的数据),在本地修改数据。

Commit phase会判断事务是否可以执行。具体分为下述几步。

Lock阶段

对于写操作,Client把数据的新值和版本号发给primary。

  1. 若数据被上锁了,则返回No
  2. 若数据没上锁,但版本号不一致,返回No
  3. 若数据没上锁且版本号一致,表明数据从被Client接收开始没有被修改,primary对数据进行上锁,并返回Yes

Client若没有收到No,则进入下一个阶段的执行。否则Client需要通知所有的primary不执行事务,解除锁。

Validate阶段

对于读操作,Client使用one-sided read直接读取目标数据的header。验证锁和版本号是满足条件。若不满足条件,中止事务。与Lock阶段类似。

Commit backup阶段

向数据的backup发送信息。

Commit primary阶段

Client会往每个primary的日志中追加日志条目。一旦某个primary被确立追加成功,就可以表示事务执行成功。

primary收到信息会解除锁,并更新版本号和数据。

因此只要primary接收并处理了⼀条commit primary日志信息,就会将这个新数据暴露给其他事务。

Truncate阶段

Client向primary发送信息表示事务完成。primary接收后会删除与这个事务相关的日志条目。