Ceph在写数据的时候,是先写主PG,然后去写副本PG,而读取的时候,实际上只有主PG能够提供服务,这对于磁盘的整体带宽来说,并没有充分地发挥其性能,所以能够读取副本当然是会有很大好处的。
Ceph客户端是基于crush规则定义的地址去进行文件的读取,这样从读取的客户端角度,理论上可自行选择地址去读取,当然这个区域没有的时候就按正常读取就可以了。
此设计最早应用于ceph-fuse,在参数中加上--localize-reads,即可根据crush规则,就近读取,主要使用于hadoop场景,解决其并发读能力问题。
Ceph曾在F版librbd实现了类似的功能,通过配置项rbd_localize_parent_reads可实现就近读取,但因测试情况不佳,后被弃用。
Ceph在O版再次在librbd中实现了此功能。通过配置项rbd_read_from_replica_policy可以选择副本读策略,默认配置项default为现有的从主PG所在的OSD读取,而配置项localize可实现就近读取,此外配置项balance可实现从PG所在的各个OSD负载均衡读取。
读策略体现在将请求发往哪个OSD,在osdc/Objecter.cc的_calc_target方法中实现。首先根据Object找到PG,再通过PG找到OSD集合。在OSD集合中再根据策略选择需要访问的OSD,策略只对读请求有效。对于localize策略,使用get_common_ancestor_distance计算距离,选择距离最小的OSD(距离相等时选择集合中的第一个OSD,即主PG所在的OSD)。对于balance策略,则随机选择OSD。除此以外的请求都将发给主PG所在的OSD。
int osd;
bool read = is_read && !is_write;
if (read && (t->flags & CEPH_OSD_FLAG_BALANCE_READS)) {
int p = rand() % acting.size();
if (p)
t->used_replica = true;
osd = acting[p];
ldout(cct, 10) << " chose random osd." << osd << " of " << acting
<< dendl;
} else if (read && (t->flags & CEPH_OSD_FLAG_LOCALIZE_READS) &&
acting.size() > 1) {
// look for a local replica. prefer the primary if the
// distance is the same.
int best = -1;
int best_locality = 0;
for (unsigned i = 0; i < acting.size(); ++i) {
int locality = osdmap->crush->get_common_ancestor_distance(
cct, acting[i], crush_location);
ldout(cct, 20) << __func__ << " localize: rank " << i
<< " osd." << acting[i]
<< " locality " << locality << dendl;
if (i == 0 ||
(locality >= 0 && best_locality >= 0 &&
locality < best_locality) ||
(best_locality < 0 && locality >= 0)) {
best = i;
best_locality = locality;
if (i)
t->used_replica = true;
}
}
ceph_assert(best >= 0);
osd = acting[best];
} else {
osd = acting_primary;
}
在OSD侧,会对请求是否正确发给自己做判断。假如OSD处理的请求所属的PG不是主PG,且请求中不含有localize或balance标签,将不予执行(osd/PrimaryLogPG.cc的do_op方法)。
if ((m->get_flags() & (CEPH_OSD_FLAG_BALANCE_READS |
CEPH_OSD_FLAG_LOCALIZE_READS)) &&i
op->may_read() &&
!(op->may_write() || op->may_cache())) {
// balanced reads; any replica will do
if (!(is_primary() || is_nonprimary())) {
osd->handle_misdirected_op(this, op);
return;
}
} else {
// normal case; must be primary
if (!is_primary()) {
osd->handle_misdirected_op(this, op);
return;
}
}
在Librbd中,只需根据配置对读请求打下对应标签即可。
librados::Rados rados(md_ctx);
int8_t require_osd_release;
int r = rados.get_min_compatible_osd(&require_osd_release);
if (r == 0 && require_osd_release >= CEPH_RELEASE_OCTOPUS) {
read_flags = 0;
auto read_policy = config.get_val<std::string>("rbd_read_from_replica_policy");
if (read_policy == "balance") {
read_flags |= CEPH_OSD_FLAG_BALANCE_READS;
} else if (read_policy == "localize") {
read_flags |= CEPH_OSD_FLAG_LOCALIZE_READS;
}
}