欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

Ceph 源代码分析 ---- 纠错码解码

最编程 2024-04-06 12:46:51
...

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);
}