FaRM是一个探索性质的系统。⽬标是在于探索这些新的RDMA⾼速⽹络硬件的潜⼒。
FaRM的特点:
- 所有的replica都在同一个数据中心。
- 使用NVRAM(⾮易失性RAM)⽅案,消除了持久化操作的性能瓶颈。
- 使用了RDMA技术在机器与机器之间传输数据,消除了CPU和网络的性能瓶颈。
- 在52 ms内即可完成简单事务,⽐Spanner快上100倍。
- Client充当了事务协调器的作用。
架构
- 使用了配置管理器,用于决定每个数据分片中哪台服务器是primary,哪台服务器是backup。使用了Zookeeper实现。
- 所有的replica都在一个数据中心,
- 更新数据时,primary和backup的数据都需要更新。因此容错能力为 N - 1。
- 读取数据时,必须通过primary进行读取。
- 数据通过Key进行分片。
1,NVRAM
FaRM中所有的数据都是存放在RAM中的。因此不需要将数据写入到持久化存储中,节约了大量时间。
因为数据写⼊RAM只需要花200纳秒,写⼊固态硬盘100微秒。机械硬盘10毫秒了。
当发生停电故障时,电池系统会接手工作,然后像服务器发送信息,要求服务器写入RAM的数据到固态硬盘中。
因此当出现了硬件或软件故障丢失数据当没有办法恢复数据,这也是需要多个replica同步的原因。
2,RDMA 概况
在传统的架构中,当服务器需要发送RPC信息时,需要进入内核态中,经历Socket、TCP、NIC(网络接口卡)发送信息。接收信息也是类似的。
其中存在着⼤量的软件,⼤量的处理以及很多⼗分消耗CPU资源的操作,⽐如:系统调⽤, interrupt中断以及复制数据。
FaRM通过RDMA减少推送数据包的成本。简单来讲就是,在不对服务器发出中断信号的情况下,他们通过NIC接收数据包并通过指令直接对服务器内存中的数据进⾏读写。
具体实现分为两种方案:
- kernel bypass
- 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。
- 若数据被上锁了,则返回No
- 若数据没上锁,但版本号不一致,返回No
- 若数据没上锁且版本号一致,表明数据从被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接收后会删除与这个事务相关的日志条目。