ServerSAN系统元数据管理设计

本文首先就ServerSAN系统的元数据管理做一般的探讨,然后重点以PureFlash为例说明元数据管理的实践。

元数据,也就是数据的说明书。当一块硬盘插到电脑上,电脑就看到一个块设备,这个块设备从LBA 0开始到LBA N,顺序编址,无比平滑。然而我们知道在物理上HDD实际是按Cylinder(柱面),Header(磁头), Sector(扇区),SSD则可能更复杂,哪个Chip, 哪个die, 哪个Block, 哪个Page。记录这些物理规格信息(CHS)以及逻辑信息与之的对应关系就是元数据。

对于一个分布式存储系统,也面临着同样的问题,client清求一个volume的一个LBA数据时,这个数据位于哪个物理节点的哪个物理磁盘的哪个LBA,就是ServerSAN的元数据。

这个问题其实就是(volume_id, volume_lba) -> (store_node, store_disk, disk_LBA)这样的一个映射关系,在数学上管这个叫做一个函数:ServerSAN系统元数据管理设计,其中p表示physical location, l表示logical location。关键在于其中的映射关系f的表示。数学上表示一个函数可以是解析式,比如ServerSAN系统元数据管理设计; 也可以是一个真值表,这在布尔代数里很常见;也可以是一个分段解析函数。显然,在工程世界里单一的解析式太单纯无法达到;完全的真值表耗费太多的空间和查找时间,可实现性差;分段函数是最好的选择。下面举个例子:

ServerSAN系统元数据管理设计

上图中一个大小为2M的Volume,被分段存放在物理磁盘/dev/sda的不连续的位置上,物理位置与逻辑位置的关系就是:

ServerSAN系统元数据管理设计

这个过程在很多系统里都是常见的,将上层的输入转换成底层的操作参数。这里的问题在于,当数据量变得非常大的时候,如何高效的实现分段函数。数据量会多大呢,假定一个128节点的PureFlash系统,每个节点配置6块8T的SSD盘,那么总容量就是128*6*8=6144T。PureFlash缺省以64M为单位分配空间,那么这个分段函数就总共有约100M(1亿)个分段,这就不那么容易高效实现了。

上面我们对元数据大题有了概念,是以存储系统里最重要的元数据--位置映射为例来说明的。位置映射在所有的存储系统里都需要,磁带、硬盘、SSD、高端阵列都要处理这个问题。

下面我们以PureFlash为例,系统的说明下分布式ServerSAN系统的元数据设计。ServerSAN的元数据除了上面的位置映射数据,还包括很多其他的元数据。

1. Volume元数据

ServerSAN是一个存储系统,之所以说是一个系统是因为他能提供大量的存储,不像单个硬盘或者SSD,一个盘只给一台主机用,存储系统要提供大量的“盘”,给不同的主机同时使用。通常我们把每个主机看到的“盘”称为“卷”(Volume)。Volume在用户通过管理命令进行创建时才生成,因此不像一个硬盘,他的大小,速度再出厂时就确定了,并且固定无法改变。Volume的特性是可以根据用户需要提供,因此也被称为Software Defined Storage(SDS)。Volume的特性就需要记录在元数据里。

PureFlash使用MarriDB存储元数据,因此Volume的元数据关键部分如下:

create table t_volume(
	id bigint primary key not null,  --ID,是整个系统唯一的ID
	name varchar(96) not null,       --名字,用户open时使用的标识
	size bigint not null,            --大小,以byte为单位
	tenant_id integer not null,      --租户ID,多租系统里区别租户。不同租户可以有名字相同的volume,同一个租户的Volume名字则必须不同
	quotaset_id integer,             --当使用quotaset进行性能限制时使用,quotaset是个复杂的概念
	status varchar(16),              --Volume的状态,通常为OK, DEGRAGED,也会有ERROR状态。ERROR时处于不可访问状态,是严重错误
	meta_ver integer default 0,      --相当于很多存储系统的epoch,但是meta_ver是per volume的
	exposed integer default 0,       --是否通过非原始接口导出访问,比如iSCSI, NoF都是非原生接口
	rep_count integer default 1,     --副本数,PureFlash使用多副本机制提供高可用。副本可以是1,2,3
	snap_seq integer default 0,      --最新的snapshot sequence,用于snapshot机制
	);

PureFlash 关于Volume完整的元数据请看这里。当Client端需要访问一个Volume时,拿到这些元数据是第一步要做的。

2. Shard元数据

PureFlash系统的目标是单个Volume容量最大64T,这个容量已经超过了当前的SSD单盘容量,因此每个Volume势必要分散到不同节点的不同盘上

前面举的例子是把一个Volume的的逻辑地址映射到物理地址,前面的例子也说了当空间很大时映射需要的分段函数非常多,也就是元数据量非常的大,大到无法实现。这里我们面临的第一个问题是 ①单个Volume的大小超过物理SSD的限制,甚至超过单个存储节点的容量限制,因此Volume的一个物理位置映射到物理地址时首先要映射到 1) 哪个存储节点, 其次2) 哪个SSD盘,最后3) 物理盘的哪个位置。单就输入输出看,问题没有本质的区别,只是把输出变量从1个(p)变成了(node, disk, physical_pos)组合。

从工程实现的角度,当问题太复杂而无法在规定时间内求解时,我们需要将问题分解,以方便将更多计算资源投入计算。在PureFlash系统中,采用了这样策略:

对于逻辑地址到物理地址的映射:ServerSAN系统元数据管理设计

1) IO位置到节点n的映射,由client端负责完成。
2) IO位置到SSD(d)和磁盘位置(p)的映射由存储端完成。

这里就引入了PureFlash的第一个子概念Shard。用过LVM的读者都了解,通过LVM可以将多个磁盘组合成一个大盘。PureFlash未来实现一个超大的Volume,也采取的了同样的措施:将一个Volume按地址空间划分为Shard,每个shard代表这个Volume的一段空间。也就是说一个Volume是由一个或者多个Shard顺次拼接而成。

 

ServerSAN系统元数据管理设计

create table t_shard (
    id bigint unsigned not null primary key,    --Shard ID,由Volume ID和shard_index构成
    volume_id bigint unsigned not null,         --这个Shard所属的volume ID
    shard_index bigint unsigned not null,       --这个shard在所属volume里的index,
    primary_rep_index bigint unsigned not null, --这个shard的主副本索引
    status char(16),                            --shard的状态,OK, DEGRADED, ERROR
);

按这里的定义看Shard仍然是逻辑划分,没有映射到物理位置。

明确一下,什么才算是物理位置呢?机房地理坐标,还是网络路由,还是磁盘磁道号?在ServerSAN系统设计里,当我们能定位到一个数据位于哪个服务器,的哪个盘,的哪个LBA,就算是到了物理位置了。因为有了这三个信息,基于目前的普遍技术,你就可以登录到这个服务器,找到这个盘,用dd命令把数据拿到了。

但是Shard其实是要映射到物理位置的,至少是映射到哪个服务器。这要看完了Replica后一起来说明。

3. Replica元数据

Replica的意思是副本,副本当然和正本要一模一样。通常我们说一个Volume是3副本的,也就是说这个Volume的数据会被保存3份,包括正本加副本总共3份。副本的目的是未来提供可靠性,当要给副本故障或者掉电而无法使用时,其他副本可以顶上继续提供服务,从而让client或者用户对故障无感知。

同一个Shard的Replica要保存在不同的故障域里。这样可以保证当一个故障发生时,避免这个Shard的全部Replica同时失效。以提供高可用保证。故障域是指受同一个故障原件影响的范围。比如同一个供电插座,同一个UPS电源影响下的所有设备。这里我们简化认为一个独立的服务器为一个故障域,在服务等级比较高的机房里这是一个合适的判定。下图是以2副本为例,每一个Shard的两个副本保存在不同的服务器上,但是不同shard的副本可以保存在同一个服务器上。

ServerSAN系统元数据管理设计

Replica除了记录保存在哪个节点上,还要记录保存在这个节点的哪个盘上。完整的Replica元数据定义如下:

create table t_replica(
	id bigint primary key AUTO_INCREMENT ,   -- Replica ID, 作为主键
	replica_index int not null,              -- 这个replica是shard的第几个副本,0 based
	volume_id bigint  not null,              -- 所属volume的ID
	shard_id bigint not null,                -- 所属shard的ID
	store_id integer not null,               -- replica保存在哪个store node上
	tray_uuid	varchar(64) not null,        -- replica保存在哪个盘上,每个盘有唯一的uuid
	status varchar(16));                     -- replica的状态,OK, ERROR, RECOVERYING

回顾前面的映射函数:ServerSAN系统元数据管理设计,有了Shard, Replica的概念后,Volume的location映射到n, d的问题就可以完成了。location首先转换成shard_index, 找到shard后再找到shard的replica。只是从一个输入l映射得到了多个(n, d)组合,每个都是正确的【这个好像违反了数学上函数的定义了:( 】

Replica的元数据里面有两个很重要的元素:store_idtray_uuid。这两个变量唯一标识了这个replica的存储位置。对于client而言,store_id还比较抽象,还需要转换成IP地址才能知道如何访问。把store_id转换成IP地址的元数据保存在t_store表和t_port表中:

create table t_store(
	id integer primary key, 
	name varchar(96) , 
	mngt_ip varchar(32) unique not null,
	status varchar(16) not null
);

create table t_port(
	ip_addr varchar(16) ,
	store_id int,
);

有了上面的这些信息,我们就可以完成这样一个映射链条:

Client IO 在Volume的位置 => Shard index => Replica => store ID => store IP

有了这样的信息,Client端在发起一个IO的时候就知道该把IO发往哪个IP地址。而且为了减少映射的环节,PureFlash在实现的时候并不会按上面的步骤依次转换,而是直接从IO位置转换成Shard index, 然后直接找到IP地址。

Client端的代码如下, 能看到Shard到Replica再到store ID再到IP的步骤,被合并成从从PfClientShardInfo直接取出IP地址。

int PfClientVolume::process_event(int event_type, int arg_i, void* arg_p)
{

    switch (event_type)
	{
	case EVT_IO_REQ:
	{
		int shard_index = (int)(io_cmd->offset >> SHARD_SIZE_ORDER); //从offset算出shard_index
        struct PfConnection* conn = get_shard_conn(shard_index); //用shard_index拿到网络连接
        rc = conn->post_send(cmd_bd); //发送IO请求
    }
    }
}

PfConnection* PfClientVolume::get_shard_conn(int shard_index)
{
    PfClientShardInfo * shard = &shards[shard_index];
    conn = conn_pool->get_conn(shard->parsed_store_ips[shard->current_ip]);
    return conn;
}

冗长的中间转换过程虽然在Client端被省略掉了,看似无用。但这些信息在Server端都是被充分使用的。

1. 当Server端收到一个IO, 需要根据replica的情况进行数据副本的复制
2. 当检查到网络故障时,需要判断是单个网卡故障还是整个节点故障,需要记录IP和store节点的关系,而不是简单记录IP地址与Replica的关系。
3. 一个Store节点可以有多个网卡以及多个IP地址
4. Client端只完成将IO地址转换成node, 一个IO的物理位置还需要有disk, position。这两个位置的转换由Server端完成,此时就需要使用这些全部的元数据。

上面这些就是PureFlash系统的最核心的元数据部分。作为一个完整的系统,元数据实际上比这些要多,包括:租户信息,快照信息,QoS设置,Volume上也会有更多其他设置。感兴趣的朋友可以完整的元数据数据库定义:https://github.com/cocalele/jconductor/blob/master/res/init_s5metadb.sql

元数据的保存

从上面PureFlash元数据的格式,大部分读者就能才猜出元数据使用了集中式的关系数据库进行保存。关系型数据库的好处是方便复杂的查询,比如当我们要为一个Replica挑选一个合适的位置时,需要很多条件的组合和限制,用关系型数据库就容易实现的多。

总体上PureFlash的元数据是集中式保存, 使用了MariaDB,为保证整个系统的高可用,需要使用带高可用机制的MariaDB,例如用Galera结合MariaDB。元数据数据库的性能要求不高,但是可靠性不能有任何折扣。

只所以说对性能要求不高,是因为元数据不参与IO的数据路径。只是在Volume 的create/open/delete/snapshot等管理操作时使用。

上一篇:容器的文件系统


下一篇:Docker的数据持久化主要有两种方式