radosgw Restful 并发读写时的一致性实现机制

radosgw Restful 并发读写时的一致性实现机制

问题

  1. atomic PUT并发写问题:多个客户端同时写同一个对象时,如何保证写完后对象的一致性?
  2. atomic GET并发读写问题:一个客户端读对象时,另一个客户端正在写同一个对象,如何保证读取对象的一致性(即读取的内容要么是写之前的,要么是写之后的)?
根本原因:radosgw读写一个对象时,需对rados存储集群进行多次读写操作。

思索

针对读写并发问题,我们最容易想到是办法是加读写锁处理,但其会破坏radosgw的水平扩展能力,并增加radosgw的实现难度。(cephfs分布式文件系统在元数据服务器中实现锁定,作为POSIX文件锁定支持的一部分,如果在网关中引入锁机制,需要在每个对象上保持状态并在不同网关实例之间同步的状态。)

解决方案

  • atomic PUT并发写问题

写对象时,先把对象写到一个临时的对象中,当临时对象全部写完后,再把整个临时对象原子性拷贝到目标对象(类似于重命名),最后删除临时对象。

由于RADOS底层存储是分布式的,要保证原子性的拷贝,就要求临时对象和目标对象保存在同一个PG上;通常我们根据对象的名称,通过hash算法计算具体的后端存储位置;故我们使用目标对象的名称来计算临时对象的存储位置,以保证它们最终落到同一个PG上。

具体步骤:

  1. 把对象写入到一个临时对象中,但这个临时对象需跟目标对象处于同一个PG上;
  2. 把临时对象原子性拷贝到目标对象;
  3. 删除临时对象;

atomic GET并发读写问题

RADOS底层存储支持原子性批量操作,类似于数据库的事务操作,即下发的一系列操作,要么全部执行成功,要么一个也未执行。其可以保证在PUT对象时,原子性的更新元数据和数据。

为了保证开始读的对象后,正在被读取的对象不会被重新写入。每一次PUT写对象时,都会产生一个随机值tag,并保存到对象属性中。当写一个对象时,需先检查这个对象是否已存在及获取其tag值;如已经存在,则克隆此对象,新克隆出来的对象名称为“对象名称_tag”(原子性操作)。具体写入原子操作流程如下:

  • check to see if object <name> tag attribute is <tag>
  • clone to <name>_<tag>

同理,客户端在读取对象前,也需要获取其tag值,每次读操作都需要检查其tag值,如tag值相同,则正常读取此对象;如tag值不同,说明其对象被重写了,则需要读取“对象名称_tag”的对象。具体的读取过程如下:

  • read object foo tag -> 123
  • verify object foo tag is “123″; read object foo (offset = 0, size = 512K) -> ok, read 512K
  • check object foo tag is “123″; read object foo (offset = 512K, size = 512K) -> not ok, object was replaced
  • read object foo_123 (offset = 512K, size = 512K) -> ok, read 512K

至此读写并发问题解决了,但细心的读者会发现一个问题,即同一个对象,可能会有多个不同名字的实例,占用空间。故需要一种在合理的时间后把多余的对象清理的机制,我们添加了一个日志对象,用于记录每个需要被删除的对象;在一段时间后,清理进程会把这些多余的对象清理干净。

相关资料

官方文档

相关推荐