变化
- object id不再直接指向数据,而是数据所对应的blockID列表。
- blockID列表由一个protobuf进行压缩。(为了便于后续的扩展,我们把数据表的表名也放在pb结构中,这样可以使用一张或多张数据表)
- 通过blockid可以在相应的数据表中,查找到每个block所对应的数据。
- blockId和objectID均为int64类型。
写入流程
- 接入层接受到数据之后,将数据切割为若干个block,每个block分配一个id(先不管哪里来的)。以blockid为主键,将数据写入到data表中
- 分配一个object_id(先忽略从哪里分配),将步骤1中的blockid和数据表的表名压缩到一段pb中,然后以object_id为主键,将这段pb写入到object表中
- 以bucketName和s3文件名组成主键,将object_id写入到name表中
读取流程
- 读取流程和写入流程相反
- 以bucketName和s3文件名为key,从name表中查找到对应的objectid
- 以objectid为key,从object表中查找到对应的block列表
- 根据block列表中的长度信息,根据用户读请求的区间,计算出需要读取哪些blockid
- 以blockid为key,从data表中读取对应的数据,并进行截断和拼装,返回结果。(假设上传一个10MB的文件,1MB一个block,指定读取区间 [1MB 1B, 2M 100KB], 则需要读取第1和第2个block,并进行截断)
新的架构
如上图所示,我们实现了数据存储和元数据存储的分离,并将元数据分为两块,用于处理大文件的场景。
objectId和blockID 哪里来?
回顾上文对objectId和blockID的使用,我们发现这两个ID的用途在于"唯一标识一段数据,不能有重复"。我们可以有几种产生ID的方法:
- mysql的自增ID
- value的CRC
- 其他外部的ID分配器
这里我们先使用mysql的自增ID作为object id。使用CRC的问题在于,同一个文件两次上传(使用不同的文件名),以block的CRC和整个的CRC分别作为blockid和objectid,会导致在data表和object表中,只有一条记录(CRC相同),删除的时候会导致实际的数据被删除(当然我们可以使用CRC做去重,这里先不讨论)。Day2工作完成,我们已经实现了元数据和数据分离存储。
Day3目前我们接入层直接访问MySQL来进行数据和元数据的读写操作,和MySQL耦合比较严重。今天我们在MySQL和网关之间加一层,用于屏蔽存储层的具体实现。
新架构
网关与S3元数据服务和IO服务之间走RPC进行通信(比如gprc/brpc),S3元数据服务和IO服务通过SQL访问后端MySQL, 用于屏蔽后端具体的存储实现。
Day4回顾过去两天的工作,我们已经实现了元数据和数据的分离存储。但是作为一个分布式存储系统,我们离高可用、高可靠、水平扩展能力,差距很大,需要继续改进。数据存储部分通常采用sharding的方式进行集群化(也就是进行分片)。而sharding的方式, 又分为一次映射和二次映射的方式。
- 一次映射: 指将对应的key(比如我们这里的object id)直接取模,然后落到对应的后端服务器上(物理存储节点,比如某个MySQL中)。
- 二次映射: 可以理解为先将用户的key 映射到一段抽象(虚拟)的结构上,然后再将这个抽象的数据结构映射到实际的存储节点上。
一次映射过程
- 当路由层收到PUT请求时, 此时输入为(key, value),其中key为int64类型(blockid)。
- 假设后端有4台MySQL服务器, key % 4, 得到对应MySQL服务器的下标。
- 将请求发送给对应的MySQL服务器即可。
一次映射的缺点
- 没有办法做扩容和缩容操作(比如4台MySQL变成5台之后,按照5进行取模操作,MySQL server对应的下标会发生变化)
- 替换节点(比如MySQL所在物理机坏了),必须做一一对应,否则下标无法对应。
一个简单的改进