searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

RGW 上传对象的并发控制分析(一)—— 数据写入流程的并发控制

2023-06-29 01:31:34
511
0

1. RGW对象布局

在Ceph RGW上传对象时,若对象大于4M(通过rgw_max_chunk_size可以指定),则将对象分为两个部分,即前4M作为Head对象,剩余部分的数据按照4M(通过)划分为多个Shadow对象。当对象大小小于等于4M时,则只会存在一个Head对象,没有Shadow对象。Head对象中存储了整个对象的元数据,其中Manifest作为最重要的元数据项,用作对象数据索引。

在RGW中,Head对象是作为整个对象存在的唯一确定标志,其oid如下。Head对象是整个对象在集群中存在的唯一标志,读操作会从寻找Head对象开始,写操作会以写Head对象结束。由其oid结构可知,只要对象同bucket且名,则只会存在一个Head对象。

{bucket_id}_{object_name}

Shadow对象采用下面的格式生成oid,其中{random_num}是在对象上传时,为避免碰撞,为每个客户端会话生成不同的随机数。该随机数会存储于Head对象的Manifest中,用于对象索引。{tail_id}是shadow对象的编号,按顺序从0开始。由其oid结构可知,因为随机数的存在,即使对象同bucket且名,其Shadow对象也互不冲突

{bucket_id}_shadow_{random_num}_{tail_id}

2. RGW对象的写入流程

RGW在写入对象的时候可以分为三个步骤,按照先后顺序为shadow对象写入,head对象写入以及索引数据写入。因为index数据写入流程,要详细分析并发控制,还需要牵扯到bilog、multisite数据一致性等复杂内容,所以,在本篇中不对index数据写入做分析。

2.1. Shadow对象写入

在RGW接受到客户端的对象数据流后,会首先将前4M数据缓存在内存中,不做处理,其优先将4M后的Shadow数据写入Rados中。在写入Shadow对象之前,RGW会为每个PUT会话生成{random_num}作为Shadow对象OID的一部分,保证多个会话之间,即使PUT同名对象,在Shadow对象上传的过程中也不会产生冲突。同时,也保证了在集群中存在同名对象时,也不会和现有同名对象的Shadow对象产生冲突。由于这一特性,确保了在Shadow对象写入完成之后,Head对象写入之前,客户端如果来进行读取操作,要么因为无同名对象存在导致无法找到Head对象,要么读到现有同名对象的Head对象并根据Manifest读取现有对象的剩余数据,新上传的数据不会对老数据产生影响,保证了数据修改的原子性。通过上述逻辑,RGW将并发的碰撞推迟到了上传Head对象阶段。

但在实际情况中,生成的随机数可能会重复,一旦随机数重复,同样会产生碰撞。RGW的解决方案是:先同步写入第一个Shadow对象,确定没有重复(能成功写入)后,再并发写入其他Shadow对象。若这个过程遇到随机数重复的情况,则立即更换随机数重试。

2.2. Head对象写入

通过上一节的分析可以知道,在Shadow对象上传过程中通过oid中的会话随机数避免了上传冲突,也不会产生数据一致性问题。所以,RGW保证并发数据一致性的核心流程在于对Head对象上传的处理上。Head对象的并发处理用到了Rados的事务机制以及乐观锁实现。

2.2.1. Rados事务

Rados操作事务是通过使用Rados库提供的事务对象(Transaction)来实现的。事务对象是一个用于构建和执行一系列操作的容器,可以将多个读取和写入操作打包成一个原子操作。原子操作指的是事务中的所有操作要么全部成功执行,要么全部回滚,不会出现部分执行的情况。对于写事务,RGW封装了ObjectWriteOperation类进行处理。ObjectWriteOperation类中可以包含针对某个对象的一组OP(Operation)以及每个OP对应的参数,在提交事务时RGW将这些OP打包成原子操作交由OSD执行。OP的所有类型可以参考rados.h中__CEPH_FORALL_OSD_OPS的宏定义。

在Head对象写入流程中比较关键的OP如下:

OP 描述 参数
CEPH_OSD_CMPXATTR_MODE_STRING 读取指定的xattr,并将其作为字符串类型于给定的v进行比较

name:
string类型,xattr名称

op:
枚举类型,指定比较操作 (> , <, ==, !=......)

v:
bufferlist类型,要比较的目标字符串二进制数据

CEPH_OSD_OP_CREATE 创建Rados对象 exclusive:
bool类型,是否是排它的。若exclusive为true时,且有同名Rados对象存在,则返回错误。为false时直接创建新Rados对象,覆盖现有对象。
CEPH_OSD_OP_WRITEFULL 写入参数中指定的所有数据到Rados对象中 bl:
bufferlist类型,要写入的数据
CEPH_OSD_OP_SETXATTR 设置对象xattr

name:
string类型,xattr名称

val:
bufferlist类型,xattr的二进制数据

2.2.2. Head对象写入流程

Head对象的总体写入流程可以参考代码实现RGWRados::Object::Write::_do_write_meta()。其关键操作,按照顺序如下:

  • get_state
    在执行上传之前,RGW首先会在Rados中查找是否已经存在同名的Head对象存在。这一步操作通过RGWRados::get_state流程实现。若存在同名的Head对象,则会读取该对象的xattr数据。
  • prepare_atomic_modification
    在该过程中,首先检查get_state的结果,如果不存在同名Rados对象,则会在事务中插入CEPH_OSD_OP_CREATE(exclusive: true)操作指定非覆盖写。若已经存在同名对象,则会在事务中插入CEPH_OSD_CMPXATTR_MODE_STRING(name: RGW_ATTR_ID_TAG, op: ==, v: {idtag})操作,其中{idtag}从上一步get_state中获取到的xattr数据中得到。同时还会插入CEPH_OSD_OP_CREATE(exclusive: false)操作用于指定覆盖写。
    完成上述操作后,再为新的对象生成随机idtag:{new_idtag},并在事务中插入CEPH_OSD_OP_SETXATTR(name: RGW_ATTR_ID_TAG, val: {new_idtag})。

  • 准备新对象
    在事务中插入CEPH_OSD_OP_WRITEFULL操作用于写入对象数据,再插入数个CEPH_OSD_OP_SETXATTR操作用于写入RGW所需要的xattr,如ACL,Manifest等。由于该部分不涉及并发控制相关的逻辑,所以过程略过。

  • operate
    在该过程中,调用RGWRados::rgw_rados_operate()方法提交之前流程准备好的事务。若事务执行出错,则会执行done_cancel流程,向GC列表中添加之前上传的Shadow对象以避免产生碎片数据。

  • complete_atomic_modification
    若get_state发现有同名Head对象存在,则根据get_state中获取到的Manifest回收对应的Shadow对象。具体做法为将这些Shadow对象添加至RGW的GC列表中。

在整个流程中,除了OSD对事务处理外,最重要的就是prepare_atomic_modification过程中,插入到事务当中的CEPH_OSD_CMPXATTR_MODE_STRING操作,该操作通过对Head对象版本——idtag的比较操作,实现了乐观锁的机制。

3. 并发场景分析

根据上述的RGW上传数据流程,在Shadow对象上传过程中,并不会产生冲突,其并发控制的关键流程在于Head对象上传阶段,我们取两种情况来着重分析一下该阶段如何实现的并发控制。在下面的例子中,只取上一章的get_state、operate两个关键时间点进行说明。

3.1. 并发上传同名对象,集群中无同名对象存在

如图所示,Client 1与Client 2同时上传对象。在t1和t2的get_state阶段,Client 1与Client 2均发现Rados中无同名的Head对象存在。所以,根据之前提到的上传流程,在prepare_atomic_modification阶段,都只会在事务中插入CEPH_OSD_OP_CREATE(exclusive: true)以及CEPH_OSD_OP_SETXATTR操作,我们假定Client 1与Client 2生成的idtag分别为a与b。再根据之前的上传流程,在operate阶段,Client 1与Client 2的事务分别如下表:

Client 1 Client 2

CEPH_OSD_OP_CREATE(exclusive: true)

CEPH_OSD_OP_SETXATTR(idtag: a)

CEPH_OSD_OP_WRITEFULL

CEPH_OSD_OP_SETXATTR

CEPH_OSD_OP_SETXATTR

         .

         .

         .

CEPH_OSD_OP_CREATE(exclusive: true)

CEPH_OSD_OP_SETXATTR(idtag: b)

CEPH_OSD_OP_WRITEFULL

CEPH_OSD_OP_SETXATTR

CEPH_OSD_OP_SETXATTR

         .

         .

         .

在t3时刻时,Client 2先提交Head对象事务至Rados中,此时因为Rados中无同名对象,整个写入事务可以顺利进行,Client 2写入成功并返回。在t4时刻时,Client 1提交事务,由于Client 2已经在t3时刻写入成功,同名Head对象存在,Client 1事务的CEPH_OSD_OP_CREATE(exclusive: true)操作会返回失败。则Client 1的流程终止,并将之前上传的Shadow对象回收,并返回失败。

3.2. 并发上传同名对象,集群中有同名对象存在

如图所示,Client 1与Client 2同时上传对象。在t1和t2的get_state阶段,Client 1与Client 2均发现Rados中存在同名Head对象。所以,根据之前提到的上传流程,在prepare_atomic_modification阶段,都会在事务中插入CEPH_OSD_CMPXATTR_MODE_STRING(v: c)、CEPH_OSD_OP_CREATE(exclusive: false)以及CEPH_OSD_OP_SETXATTR操作,我们假定Client 1与Client 2生成的idtag分别为a与b。再根据之前的上传流程,在operate阶段,Client 1与Client 2的事务分别如下表:

Client 1 Client 2

CEPH_OSD_CMPXATTR_MODE_STRING(v: c)

CEPH_OSD_OP_CREATE(exclusive: false)

CEPH_OSD_OP_SETXATTR(idtag: a)

CEPH_OSD_OP_WRITEFULL

CEPH_OSD_OP_SETXATTR

CEPH_OSD_OP_SETXATTR

         .

         .

         .

CEPH_OSD_CMPXATTR_MODE_STRING(v: c)

CEPH_OSD_OP_CREATE(exclusive: false)

CEPH_OSD_OP_SETXATTR(idtag: b)

CEPH_OSD_OP_WRITEFULL

CEPH_OSD_OP_SETXATTR

CEPH_OSD_OP_SETXATTR

         .

         .

         .

在t3时刻时,Client 2先提交Head对象事务至Rados中,此时因为Rados中的对象为idtag为c的原始对象,且CEPH_OSD_OP_CREATE操作的exclusive标志为false,整个写入事务可以顺利进行,删除原始Head对象并写入新Head对象,且设置idtag为b,Client 2写入成功并返回。在t4时刻时,Client 1提交事务,由于Client 2已经在t3时刻写入成功,同名Head对象存在且idtag为b,Client 1事务的CEPH_OSD_CMPXATTR_MODE_STRING(v: c)操作会返回失败。则Client 1的流程终止,并将之前上传的Shadow对象回收,并返回失败。

总结

本文简单介绍了RGW上传并发控制的主要实现方式,并通过对两个并发写的场景进行分析,说明了有效性。当然在实际情况中,遇到的并发场景远比当前复杂,但是通过事务和乐观锁的机制,均可以有效避免数据一致性遭到破坏。各位读者不妨自己尝试列举几种特殊场景并进行分析,以更好的理解RGW的并发控制逻辑。

关于数据一致性的话题,在RGW中有非常多的巧妙、复杂的设计,是一个非常庞大的话题。后续有机会我会继续介绍Bucket Index写入、Multisite同步数据等等过程中,与数据一致性相关的设计。感谢各位阅读。

0条评论
0 / 1000
c****n
2文章数
0粉丝数
c****n
2 文章 | 0 粉丝
c****n
2文章数
0粉丝数
c****n
2 文章 | 0 粉丝
原创

RGW 上传对象的并发控制分析(一)—— 数据写入流程的并发控制

2023-06-29 01:31:34
511
0

1. RGW对象布局

在Ceph RGW上传对象时,若对象大于4M(通过rgw_max_chunk_size可以指定),则将对象分为两个部分,即前4M作为Head对象,剩余部分的数据按照4M(通过)划分为多个Shadow对象。当对象大小小于等于4M时,则只会存在一个Head对象,没有Shadow对象。Head对象中存储了整个对象的元数据,其中Manifest作为最重要的元数据项,用作对象数据索引。

在RGW中,Head对象是作为整个对象存在的唯一确定标志,其oid如下。Head对象是整个对象在集群中存在的唯一标志,读操作会从寻找Head对象开始,写操作会以写Head对象结束。由其oid结构可知,只要对象同bucket且名,则只会存在一个Head对象。

{bucket_id}_{object_name}

Shadow对象采用下面的格式生成oid,其中{random_num}是在对象上传时,为避免碰撞,为每个客户端会话生成不同的随机数。该随机数会存储于Head对象的Manifest中,用于对象索引。{tail_id}是shadow对象的编号,按顺序从0开始。由其oid结构可知,因为随机数的存在,即使对象同bucket且名,其Shadow对象也互不冲突

{bucket_id}_shadow_{random_num}_{tail_id}

2. RGW对象的写入流程

RGW在写入对象的时候可以分为三个步骤,按照先后顺序为shadow对象写入,head对象写入以及索引数据写入。因为index数据写入流程,要详细分析并发控制,还需要牵扯到bilog、multisite数据一致性等复杂内容,所以,在本篇中不对index数据写入做分析。

2.1. Shadow对象写入

在RGW接受到客户端的对象数据流后,会首先将前4M数据缓存在内存中,不做处理,其优先将4M后的Shadow数据写入Rados中。在写入Shadow对象之前,RGW会为每个PUT会话生成{random_num}作为Shadow对象OID的一部分,保证多个会话之间,即使PUT同名对象,在Shadow对象上传的过程中也不会产生冲突。同时,也保证了在集群中存在同名对象时,也不会和现有同名对象的Shadow对象产生冲突。由于这一特性,确保了在Shadow对象写入完成之后,Head对象写入之前,客户端如果来进行读取操作,要么因为无同名对象存在导致无法找到Head对象,要么读到现有同名对象的Head对象并根据Manifest读取现有对象的剩余数据,新上传的数据不会对老数据产生影响,保证了数据修改的原子性。通过上述逻辑,RGW将并发的碰撞推迟到了上传Head对象阶段。

但在实际情况中,生成的随机数可能会重复,一旦随机数重复,同样会产生碰撞。RGW的解决方案是:先同步写入第一个Shadow对象,确定没有重复(能成功写入)后,再并发写入其他Shadow对象。若这个过程遇到随机数重复的情况,则立即更换随机数重试。

2.2. Head对象写入

通过上一节的分析可以知道,在Shadow对象上传过程中通过oid中的会话随机数避免了上传冲突,也不会产生数据一致性问题。所以,RGW保证并发数据一致性的核心流程在于对Head对象上传的处理上。Head对象的并发处理用到了Rados的事务机制以及乐观锁实现。

2.2.1. Rados事务

Rados操作事务是通过使用Rados库提供的事务对象(Transaction)来实现的。事务对象是一个用于构建和执行一系列操作的容器,可以将多个读取和写入操作打包成一个原子操作。原子操作指的是事务中的所有操作要么全部成功执行,要么全部回滚,不会出现部分执行的情况。对于写事务,RGW封装了ObjectWriteOperation类进行处理。ObjectWriteOperation类中可以包含针对某个对象的一组OP(Operation)以及每个OP对应的参数,在提交事务时RGW将这些OP打包成原子操作交由OSD执行。OP的所有类型可以参考rados.h中__CEPH_FORALL_OSD_OPS的宏定义。

在Head对象写入流程中比较关键的OP如下:

OP 描述 参数
CEPH_OSD_CMPXATTR_MODE_STRING 读取指定的xattr,并将其作为字符串类型于给定的v进行比较

name:
string类型,xattr名称

op:
枚举类型,指定比较操作 (> , <, ==, !=......)

v:
bufferlist类型,要比较的目标字符串二进制数据

CEPH_OSD_OP_CREATE 创建Rados对象 exclusive:
bool类型,是否是排它的。若exclusive为true时,且有同名Rados对象存在,则返回错误。为false时直接创建新Rados对象,覆盖现有对象。
CEPH_OSD_OP_WRITEFULL 写入参数中指定的所有数据到Rados对象中 bl:
bufferlist类型,要写入的数据
CEPH_OSD_OP_SETXATTR 设置对象xattr

name:
string类型,xattr名称

val:
bufferlist类型,xattr的二进制数据

2.2.2. Head对象写入流程

Head对象的总体写入流程可以参考代码实现RGWRados::Object::Write::_do_write_meta()。其关键操作,按照顺序如下:

  • get_state
    在执行上传之前,RGW首先会在Rados中查找是否已经存在同名的Head对象存在。这一步操作通过RGWRados::get_state流程实现。若存在同名的Head对象,则会读取该对象的xattr数据。
  • prepare_atomic_modification
    在该过程中,首先检查get_state的结果,如果不存在同名Rados对象,则会在事务中插入CEPH_OSD_OP_CREATE(exclusive: true)操作指定非覆盖写。若已经存在同名对象,则会在事务中插入CEPH_OSD_CMPXATTR_MODE_STRING(name: RGW_ATTR_ID_TAG, op: ==, v: {idtag})操作,其中{idtag}从上一步get_state中获取到的xattr数据中得到。同时还会插入CEPH_OSD_OP_CREATE(exclusive: false)操作用于指定覆盖写。
    完成上述操作后,再为新的对象生成随机idtag:{new_idtag},并在事务中插入CEPH_OSD_OP_SETXATTR(name: RGW_ATTR_ID_TAG, val: {new_idtag})。

  • 准备新对象
    在事务中插入CEPH_OSD_OP_WRITEFULL操作用于写入对象数据,再插入数个CEPH_OSD_OP_SETXATTR操作用于写入RGW所需要的xattr,如ACL,Manifest等。由于该部分不涉及并发控制相关的逻辑,所以过程略过。

  • operate
    在该过程中,调用RGWRados::rgw_rados_operate()方法提交之前流程准备好的事务。若事务执行出错,则会执行done_cancel流程,向GC列表中添加之前上传的Shadow对象以避免产生碎片数据。

  • complete_atomic_modification
    若get_state发现有同名Head对象存在,则根据get_state中获取到的Manifest回收对应的Shadow对象。具体做法为将这些Shadow对象添加至RGW的GC列表中。

在整个流程中,除了OSD对事务处理外,最重要的就是prepare_atomic_modification过程中,插入到事务当中的CEPH_OSD_CMPXATTR_MODE_STRING操作,该操作通过对Head对象版本——idtag的比较操作,实现了乐观锁的机制。

3. 并发场景分析

根据上述的RGW上传数据流程,在Shadow对象上传过程中,并不会产生冲突,其并发控制的关键流程在于Head对象上传阶段,我们取两种情况来着重分析一下该阶段如何实现的并发控制。在下面的例子中,只取上一章的get_state、operate两个关键时间点进行说明。

3.1. 并发上传同名对象,集群中无同名对象存在

如图所示,Client 1与Client 2同时上传对象。在t1和t2的get_state阶段,Client 1与Client 2均发现Rados中无同名的Head对象存在。所以,根据之前提到的上传流程,在prepare_atomic_modification阶段,都只会在事务中插入CEPH_OSD_OP_CREATE(exclusive: true)以及CEPH_OSD_OP_SETXATTR操作,我们假定Client 1与Client 2生成的idtag分别为a与b。再根据之前的上传流程,在operate阶段,Client 1与Client 2的事务分别如下表:

Client 1 Client 2

CEPH_OSD_OP_CREATE(exclusive: true)

CEPH_OSD_OP_SETXATTR(idtag: a)

CEPH_OSD_OP_WRITEFULL

CEPH_OSD_OP_SETXATTR

CEPH_OSD_OP_SETXATTR

         .

         .

         .

CEPH_OSD_OP_CREATE(exclusive: true)

CEPH_OSD_OP_SETXATTR(idtag: b)

CEPH_OSD_OP_WRITEFULL

CEPH_OSD_OP_SETXATTR

CEPH_OSD_OP_SETXATTR

         .

         .

         .

在t3时刻时,Client 2先提交Head对象事务至Rados中,此时因为Rados中无同名对象,整个写入事务可以顺利进行,Client 2写入成功并返回。在t4时刻时,Client 1提交事务,由于Client 2已经在t3时刻写入成功,同名Head对象存在,Client 1事务的CEPH_OSD_OP_CREATE(exclusive: true)操作会返回失败。则Client 1的流程终止,并将之前上传的Shadow对象回收,并返回失败。

3.2. 并发上传同名对象,集群中有同名对象存在

如图所示,Client 1与Client 2同时上传对象。在t1和t2的get_state阶段,Client 1与Client 2均发现Rados中存在同名Head对象。所以,根据之前提到的上传流程,在prepare_atomic_modification阶段,都会在事务中插入CEPH_OSD_CMPXATTR_MODE_STRING(v: c)、CEPH_OSD_OP_CREATE(exclusive: false)以及CEPH_OSD_OP_SETXATTR操作,我们假定Client 1与Client 2生成的idtag分别为a与b。再根据之前的上传流程,在operate阶段,Client 1与Client 2的事务分别如下表:

Client 1 Client 2

CEPH_OSD_CMPXATTR_MODE_STRING(v: c)

CEPH_OSD_OP_CREATE(exclusive: false)

CEPH_OSD_OP_SETXATTR(idtag: a)

CEPH_OSD_OP_WRITEFULL

CEPH_OSD_OP_SETXATTR

CEPH_OSD_OP_SETXATTR

         .

         .

         .

CEPH_OSD_CMPXATTR_MODE_STRING(v: c)

CEPH_OSD_OP_CREATE(exclusive: false)

CEPH_OSD_OP_SETXATTR(idtag: b)

CEPH_OSD_OP_WRITEFULL

CEPH_OSD_OP_SETXATTR

CEPH_OSD_OP_SETXATTR

         .

         .

         .

在t3时刻时,Client 2先提交Head对象事务至Rados中,此时因为Rados中的对象为idtag为c的原始对象,且CEPH_OSD_OP_CREATE操作的exclusive标志为false,整个写入事务可以顺利进行,删除原始Head对象并写入新Head对象,且设置idtag为b,Client 2写入成功并返回。在t4时刻时,Client 1提交事务,由于Client 2已经在t3时刻写入成功,同名Head对象存在且idtag为b,Client 1事务的CEPH_OSD_CMPXATTR_MODE_STRING(v: c)操作会返回失败。则Client 1的流程终止,并将之前上传的Shadow对象回收,并返回失败。

总结

本文简单介绍了RGW上传并发控制的主要实现方式,并通过对两个并发写的场景进行分析,说明了有效性。当然在实际情况中,遇到的并发场景远比当前复杂,但是通过事务和乐观锁的机制,均可以有效避免数据一致性遭到破坏。各位读者不妨自己尝试列举几种特殊场景并进行分析,以更好的理解RGW的并发控制逻辑。

关于数据一致性的话题,在RGW中有非常多的巧妙、复杂的设计,是一个非常庞大的话题。后续有机会我会继续介绍Bucket Index写入、Multisite同步数据等等过程中,与数据一致性相关的设计。感谢各位阅读。

文章来自个人专栏
ceph分布式存储
2 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
2
0