Mogilefs的默认复制策略的类名叫做“MultipleHosts”,文件位置在lib/MogileFS/ReplicationPolicy/MultipleHosts.pm。
本次分析的Mogilefs版本是2.34。
MultipleHosts.pm中最重要的函数是replicate_to,主要功能是根据文件所属类的devcount,即副本目标数量,文件副本当前所在设备列表、所有设备列表、失效的设备列表等参数来决定当前文件是否需要复制,如果需要复制,返回应该复制待选的设备列表。
MultipleHosts主要的复制策略如下:
- 如果文件所在设备的独立主机数量已经大于最少复制份数,返回TOO_GOOD(过复制,多复制了,因此不用再复制了);
- 如果文件所在设备的个数大于最少复制份数,返回TOO_GOOD;
- 如果文件所在设备的独立主机数量等于最小复制份数,返回ALL_GOOD(正好,不多也不少,不用再复制)
- 如果文件所在设备的独立主机数量大于目前所有设备的独立主机数量,同时文件所在设备的个数大于等于最少复制份数,返回ALL_GOOD
- 如果没有满足上述条件,Mogilefs在所有可用设备中选择合适的设备来复制文件副本,选择的方式如下:
- 首先选择和目前所有副本所在主机不同的主机上的设备,这些设备的列表放置到ideal列表中;
- 如果找不到A中所述的设备,就找和目前所有副本所在设备不同的设备,这些设备的列表放置到desp列表中。
最后replicate_to返回一个ReplicationRequest对象,并将ideal列表和desp列表分别复制给ReplicationRequest对象的ideal成员属性和desperate成员属性。接下来Mogilefs会根据replicate_to返回的信息采取相应的文件复制动作。
上述策略中,第2条是存在缺陷的,看看如下场景:
- 假设文件目标副本数量为2,有两台存储主机A和B,每台主机都有两个设备devA1,devA2,devB1,devB2,如果其中主机B出现故障,devB1和devB2被标记为down,这时候,有一个新的文件上传,MogileFS首先找到A主机上的某个设备存下来,假设这个设备是devA1,然后MogileFS运行MultipleHosts,replicate_to将按照上述第5条,得到一个空的ideal列表和一个有devB2的desp列表,MogileFS因此会将文件复制到devB2,这是文件副本数量为2。但是因为MogileFS使用的是desp列表复制文件,不够可靠,因此复制任务不会删除,MogileFS将在以后为该文件找到更可靠的复制方案。
- 主机B的故障解除,这时devB1和devB2被重新标记为alive,MogileFS再次运行复制任务,这一次,因为副本数量已经是2了,MultipleHosts的replicate_to会根据第2条规则返回TOO_GOOD,这就出现了问题,显然不是期待的结果。
预期的结果应该是MogileFS找到主机B上的devB1和devB2作为ideal列表,然后在两个设备之间挑选一个来复制文件,以达到完美可靠性,然后删除复制任务。
下列是MultipleHosts.pm中的replicate_to函数:
# MutipleHosts类的核心函数,主要功能是根据当前文件所在设备列表、所有设备列表、失效的设备列表等参数来决定当前文件是否需要复制,如果需要
# 复制,应该复制到那个设备上。
# 参数:
# fid: 当前文件的fid
# on_devs: 当前文件所在的设备列表
# all_devs: 所有设备哈希列表,key是设备编号devid,value是设备对象
# failed:失效设备的哈希列表,key是设备编号devid,value是1
# min: 指定的文件复制份数,如果该参数为空或0,则使用文件所属类的默认复制份数,否者就是用该复制份数
sub replicate_to {
my ($self, %args) = @_;
# 将参数复制到函数的本地变量
# copy all paramters to local variables
my $fid = delete $args{fid}; # fid scalar to copy
my $on_devs = delete $args{on_devs}; # arrayref of device objects
my $all_devs = delete $args{all_devs}; # hashref of { devid => MogileFS::Device }
my $failed = delete $args{failed}; # hashref of { devid => 1 } of failed attempts this round
# 如果该参数为空或0,则使用文件所属类的默认复制份数,否者就是用该复制份数
# this is the per-class mindevcount (the old way), which is passed in automatically
# from the replication worker. but if we have our own configured mindevcount
# in class.replpolicy, like "MultipleHosts(3)", then we use the explicit one. otherwise,
# if blank, or zero, like "MultipleHosts()", then we use the builtin on
my $min = delete $args{min};
$min = $self->{mindevcount} || $min;
# 参数不能过多也不能过少
# parameters should not too many or be lack
warn "Unknown parameters: " . join(", ", sort keys %args) if %args;
die "Missing parameters" unless $on_devs && $all_devs && $failed && $fid;
# 文件所在设备的个数
# number of devices we currently live on
my $already_on = @$on_devs;
# 如果最少复制份数为1,并且文件所在设备的个数已经不为0,则返回ALL_GOOD(无需复制)
# a silly special case, bail out early.
return ALL_GOOD if $min == 1 && $already_on;
# 所有设备中,可能被选择来复制文件的设备数量
# total disks available which are candidates for having files on them
my $total_disks = scalar grep { $_->dstate->should_have_files } values %$all_devs;
# 分析文件所在设备分布在哪些独立主机上
# see which and how many unique hosts we're already on.
my %on_dev;
my %on_host;
foreach my $dev (@$on_devs) {
$on_host{$dev->hostid} = 1;
$on_dev{$dev->id} = 1;
}
# 文件所在设备的独立主机数量
my $uniq_hosts_on = scalar keys %on_host;
# 目前所有设备的独立主机数量
my $total_uniq_hosts = unique_hosts($all_devs);
# if we are on two hosts but 10 devices, you want to weaken the number of
# devices you're on until you're on the right number of hosts with the
# right number of devices.
return TOO_GOOD if $uniq_hosts_on > $min; # 如果文件所在设备的独立主机数量已经大于最少复制份数,返回TOO_GOOD(过复制)
return TOO_GOOD if $already_on > $min; # 如果文件所在设备的个数大于最少复制份数,返回TOO_GOOD(过复制)(好像有问题)
return ALL_GOOD if $uniq_hosts_on == $min; # 如果文件所在设备的独立主机数量等于最小复制份数,返回ALL_GOOD(无需复制)
# 如果文件所在设备的独立主机数量大于目前所有设备的独立主机数量,同时文件所在设备的个数大于等于最少复制份数,返回ALL_GOOD(无需复制)
return ALL_GOOD if $uniq_hosts_on >= $total_uniq_hosts && $already_on >= $min;
# if we have two copies and that's all the disks there are
# anywhere, be happy enough, even if mindevcount is higher. in
# that case, when they add more disks later, they'll need to fsck
# to make files replicate more.
# this is here instead of above in case an over replication error causes
# the file to be on all disks (where more than necessary)
return ALL_GOOD if $already_on >= 2 && $already_on == $total_disks;
# if there are more hosts we're not on yet, we want to exclude devices we're already
# on from our applicable host search.
my %skip_host; # hostid => 1
if ($uniq_hosts_on < $total_uniq_hosts) {
%skip_host = %on_host;
}
my @all_dests = weighted_list map {
[$_, 100 * $_->percent_free]
} grep {
! $on_dev{$_->devid} &&
! $failed->{$_->devid} &&
$_->should_get_replicated_files
} values %$all_devs;
return TEMP_NO_ANSWER unless @all_dests;
my @ideal = grep { ! $skip_host{$_->hostid} } @all_dests;
my @desp = grep { $skip_host{$_->hostid} } @all_dests;
return TEMP_NO_ANSWER if $already_on >= $min && @ideal == 0;
return MogileFS::ReplicationRequest->new(
ideal => \@ideal,
desperate => \@desp,
);
}
