radosgw Restful 并发读写时的一致性实现机制
radosgw Restful 并发读写时的一致性实现机制
问题
- atomic PUT并发写问题:多个客户端同时写同一个对象时,如何保证写完后对象的一致性?
- atomic GET并发读写问题:一个客户端读对象时,另一个客户端正在写同一个对象,如何保证读取对象的一致性(即读取的内容要么是写之前的,要么是写之后的)?
根本原因:radosgw读写一个对象时,需对rados存储集群进行多次读写操作。
思索
针对读写并发问题,我们最容易想到是办法是加读写锁处理,但其会破坏radosgw的水平扩展能力,并增加radosgw的实现难度。(cephfs分布式文件系统在元数据服务器中实现锁定,作为POSIX文件锁定支持的一部分,如果在网关中引入锁机制,需要在每个对象上保持状态并在不同网关实例之间同步的状态。)
解决方案
- atomic PUT并发写问题
写对象时,先把对象写到一个临时的对象中,当临时对象全部写完后,再把整个临时对象原子性拷贝到目标对象(类似于重命名),最后删除临时对象。
由于RADOS底层存储是分布式的,要保证原子性的拷贝,就要求临时对象和目标对象保存在同一个PG上;通常我们根据对象的名称,通过hash算法计算具体的后端存储位置;故我们使用目标对象的名称来计算临时对象的存储位置,以保证它们最终落到同一个PG上。
具体步骤:
- 把对象写入到一个临时对象中,但这个临时对象需跟目标对象处于同一个PG上;
- 把临时对象原子性拷贝到目标对象;
- 删除临时对象;
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
至此读写并发问题解决了,但细心的读者会发现一个问题,即同一个对象,可能会有多个不同名字的实例,占用空间。故需要一种在合理的时间后把多余的对象清理的机制,我们添加了一个日志对象,用于记录每个需要被删除的对象;在一段时间后,清理进程会把这些多余的对象清理干净。