Ceph 源代码分析 ---- 纠错码解码
Ceph版本:14.2.22
第一部分 总体框架
第二部分 源码分析
2.1 ECBackend::objects_read_and_reconstruct 方法分析
osd在读数据时,最终通过ECBackend::objects_read_and_reconstruc方法,在该方法中调用ECBackend::start_read_op方法读数据。
文件路径:ceph/src/osd/ECBackend.cc
void ECBackend::objects_read_and_reconstruct(...)
{
...
for (auto &&to_read : reads)
{
CallClientContexts *c = new CallClientContexts(to_read.first, this, &(in_progress_client_reads.back()), to_read.second);
//初始化for_read_op
for_read_op.insert(make_pair(to_read.first, read_request_t(to_read.second, shards, false, c)));
//初始化obj_want_to_read
obj_want_to_read.insert(make_pair(to_read.first, want_to_read));
}
//开始读
start_read_op(CEPH_MSG_PRIO_DEFAULT, obj_want_to_read, for_read_op, OpRequestRef(), fast_read, false);
return;
}
2.2 CallClientContexts::finish 方法分析
上面分析到,ECBackend::start_read_op读完数据之后,CallClientContexts类对象会自动调用回调方法finish;finish方法会调用ECUtil::decode对读的数据进行解码。
文件路径:ceph/src/osd/ECBackend.cc
void finish(...)
{
...
for (auto &&read : to_read)
{
...
int r = ECUtil::decode(ec->sinfo, ec->ec_impl, to_decode, &bl);
}
}
2.3 ECUtil::decode 方法分析
上面分析到,ECUtil::decode方法是纠删码解码的入口,该方法中主要思路为:依次读取一个条带的数据(数据块+校验块),对该条带数据进行解码,将解码之后的条带数据依次追加到bufferlist中。
文件路径:ceph/src/osd/ECUtil.cc
int ECUtil::decode(...)
{
//获取单个分片的数据总大小
uint64_t total_data_size = to_decode.begin()->second.length();
//for:此处是以某个分片为基点,以当前分片上块为单元,寻找其他分片对应的块
//for:集齐k个块数据,拼接成一个条带,以条带为单元进行解码
for (uint64_t i = 0; i < total_data_size; i += sinfo.get_chunk_size())
{
map<int, bufferlist> chunks;//存放解码之前单个条带中所有的块数据
//for:依次获取不同分片上的块数据,此处获取一个条带的数据
for (map<int, bufferlist>::iterator j = to_decode.begin(); j != to_decode.end(); ++j)
{
chunks[j->first].substr_of(j->second, i, sinfo.get_chunk_size());
}
bufferlist bl;//存放解码之后单个条带的数据
int r = ec_impl->decode_concat(chunks, &bl); //对单个条带数据进行解码
out->claim_append(bl);//将解码之后的单个条带数据追加到bufferlist中
}
}
2.4 ErasureCode::decode_concat 方法分析
上面分析到,ECUtil::decode方法最终会调用ec_impl->decode_concat对单个条带数据解码,decode_concat是类ErasureCode的方法。该方法会创建一个新的bufferlist对象,该对象用于存放解码之后的条带数据,该条带数据是完整的,没有任何缺失块。然后调用ErasureCode::_decode方法做进一步处理,最后将完整的条带数据追加到主调方法ECUtil::decode的bl参数中。
文件路径:ceph/src/erasure-code/ErasureCode.cc
int ErasureCode::decode_concat(...)
{
set<int> want_to_read;//保存块数据的编号
//for:初始化want_to_read
for (unsigned int i = 0; i < get_data_chunk_count(); i++)
{
want_to_read.insert(chunk_index(i));
}
map<int, bufferlist> decoded_map;//存放解码之后完整有序的单个条带数据
//对chunks做进一步处理
int r = _decode(want_to_read, chunks, &decoded_map);
if (r == 0)
{
for (unsigned int i = 0; i < get_data_chunk_count(); i++)
{
//将解码之后的块数据按顺序添加到decoded中,凑成一个条带数据
decoded->claim_append(decoded_map[chunk_index(i)]);
}
}
return r;
}
2.5 ErasureCode::_decode 方法分析
上面分析到,ErasureCode::decode_concat方法最终会调用_decode方法对chunks做进一步处理,_decode是类ErasureCode的方法。在该方法中会对chunks进行检查,查看是否有缺失块,如果没有缺失块,就不需要解码;如果有缺失块,则构造一个空的块,凑成一个完整的条带,然后再对该完整的条带做进一步处理。
文件路径:ceph/src/erasure-code/ErasureCode.cc
int ErasureCode::_decode(...)
{
vector<int> have;
have.reserve(chunks.size());//分配空间
//for:初始化have数据
for (map<int, bufferlist>::const_iterator i = chunks.begin(); i != chunks.end(); ++i)
{
have.push_back(i->first);
}
//查看数据块是否缺失,如果不缺失,直接将chunks中数据给decoded,然后直接返回
if (includes(have.begin(), have.end(), want_to_read.begin(), want_to_read.end()))
{
for (set<int>::iterator i = want_to_read.begin(); i != want_to_read.end(); ++i)
{
(*decoded)[*i] = chunks.find(*i)->second;
}
return 0;
}
//如果数据块丢失,则执行解码
unsigned int k = get_data_chunk_count();//获取数据块个数
unsigned int m = get_chunk_count() - k;//获取校验快个数
unsigned blocksize = (*chunks.begin()).second.length();//获取块大小
//for:构造出完整的单条带数据
for (unsigned int i = 0; i < k + m; i++)
{
//如果数据块丢失,构造出一个空的数据块,凑齐完整的一个条带
if (chunks.find(i) == chunks.end())
{
//构造缺失的块数据,默认数据为空
bufferlist tmp;
bufferptr ptr(buffer::create_aligned(blocksize, SIMD_ALIGN));
tmp.push_back(ptr);
tmp.claim_append((*decoded)[i]);
(*decoded)[i].swap(tmp);
}
else
{
//如果不缺,直接取出数据块中的数据
(*decoded)[i] = chunks.find(i)->second;
(*decoded)[i].rebuild_aligned(SIMD_ALIGN);
}
}
//对decoded数据进一步处理
return decode_chunks(want_to_read, chunks, decoded);
}
注意:have和want_to_read区别:have中保存的chunks中块的编号,可能有缺失的块。want_to_read是逻辑上块的编号,一定是完整的。
2.6 ErasureCodeJerasure::decode_chunks 方法分析
上面分析到,ErasureCode::_decode最终会调用decode_chunks方法对decoded进一步处理。decoded是类ErasureCodeJerasure的方法。在该方法中会对单条带数据进行分离,分离出数据块和校验块,然后调用接口做进一步处理。
文件路径:ceph/src/erasure-code/jerasure/ErasureCodeJerasure.cc
int ErasureCodeJerasure::decode_chunks(...)
{
unsigned blocksize = (*chunks.begin()).second.length();//统计块数据的大小
int erasures[k + m + 1];//记录丢失块编号
int erasures_count = 0;//统计丢失块数量
char *data[k];//数据块
char *coding[m];//校验快
for (int i = 0; i < k + m; i++)
{
//查找缺失块
if (chunks.find(i) == chunks.end())
{
erasures[erasures_count] = i;//记录丢失块编号
erasures_count++;//统计丢失块数量
}
if (i < k)
data[i] = (*decoded)[i].c_str();//筛选出数据块的数据
else
coding[i - k] = (*decoded)[i].c_str();//筛选出校验块的数据
}
//结束标志
erasures[erasures_count] = -1;
//调用范德蒙矩阵解码接口
return jerasure_decode(erasures, data, coding, blocksize);
}
2.7 ErasureCodeJerasureReedSolomonVandermonde::jerasure_decode 方法分析
上面分析到,ErasureCodeJerasure::decode_chunks最终会调用jerasure_decode方法做进一步处理,jerasure_decode是ErasureCodeJerasureReedSolomonVandermonde方法。该方法调用纠删码解码C语言接口,纠删码解码方法有很多,目前使用的是范德蒙行列式解码。
文件路径:ceph/src/erasure-code/jerasure/ErasureCodeJerasure.cc
int ErasureCodeJerasureReedSolomonVandermonde::jerasure_decode(...)
{
//调用jerasure解码接口
return jerasure_matrix_decode(k, m, w, matrix, 1, erasures, data, coding, blocksize);
}