【Código fuente de ClickHouse】Introducción a ReadIndirectBufferFromRemoteFS

La traducción literal de ReadIndirectBufferFromRemoteFS es el ReadBuffer indirecto creado para el sistema de archivos remoto, esto se debe a que el sistema de archivos remoto no puede operar archivos directamente como el sistema de archivos local, por lo que la interfaz necesaria se abstrae a través de ReadBuffer, de modo que se pueden implementar varios ReadBuffers. ReadIndirectBufferFromRemoteFS es uno de ellos.

El archivo de encabezado es el siguiente:

class ReadIndirectBufferFromRemoteFS : public ReadBufferFromFileBase
{

public:
    explicit ReadIndirectBufferFromRemoteFS(std::shared_ptr<ReadBufferFromRemoteFSGather> impl_);

    off_t seek(off_t offset_, int whence) override;

    off_t getPosition() override;

    String getFileName() const override;

    void setReadUntilPosition(size_t position) override;

    void setReadUntilEnd() override;

private:
    bool nextImpl() override;

    std::shared_ptr<ReadBufferFromRemoteFSGather> impl;

    size_t file_offset_of_buffer_end = 0;
};

Las funciones marcadas con anulación son las funciones que deben reescribirse.

Para este tipo de empaquetado, ReadBuffer contendrá un ReadBuffer correspondiente que puede leer archivos remotos, como su variable miembro impl, cuyo tipo es ReadBufferFromRemoteFSGather. Eche un vistazo a la función principal nextImpl:

bool ReadIndirectBufferFromRemoteFS::nextImpl()
{
    /// Transfer current position and working_buffer to actual ReadBuffer
    swap(*impl);

    assert(!impl->hasPendingData());
    /// Position and working_buffer will be updated in next() call
    auto result = impl->next();
    /// and assigned to current buffer.
    swap(*impl);

    if (result)
    {
        file_offset_of_buffer_end += available();
        BufferBase::set(working_buffer.begin() + offset(), available(), 0);
    }

    assert(file_offset_of_buffer_end == impl->file_offset_of_buffer_end);

    return result;
}

La primera es intercambiar el búfer de Impl a ReadIndirectBufferFromRemoteFS mediante intercambio para garantizar que el búfer de ReadIndirectBufferFromRemoteFS sea el estado actual del búfer de Impl. Luego lea los datos en el búfer Impl a través de impl->next(), y luego intercambie el estado del búfer Impl al búfer de ReadIndirectBufferFromRemoteFS después de que se complete la lectura, y actualice file_offset_of_buffer_end para completar la siguiente lectura. De hecho, el siguiente proceso general es más o menos el mismo.

El siguiente de ReadIndirectBufferFromRemoteFS en realidad llama al siguiente de Impl. ReadBufferFromRemoteFSGather es una capa de encapsulación de proxy para archivos remotos, porque un archivo de datos de ClickHouse puede ser muy grande. Para el sistema de archivos remoto, este archivo puede corresponder a múltiples archivos remotos. , entonces ReadBufferFromRemoteFSGather encapsula la lectura de un archivo de datos de ClickHouse, protege los detalles de lectura reales y hace que la capa superior sienta que está leyendo un archivo remoto.

LeerBufferFromRemoteFSGather

El propósito de ReadBufferFromRemoteFSGather es leer los archivos remotos correspondientes uno por uno.

Constructor

ReadBufferFromRemoteFSGather(
    const std::string & common_path_prefix_,
    const BlobsPathToSize & blobs_to_read_,
    const ReadSettings & settings_);

Mirando su constructor, los parámetros de entrada incluyen common_path_prefix_, blobs_to_read_ y settings_. common_path_prefix_ es el prefijo de ruta correspondiente al sistema de archivos remoto. Para S3, common_path_prefix_ es {bucket}/{path_pre}/. BlobsPathToSize es un vector que almacena todos los archivos remotos y sus tamaños. Este vector también está ordenado. Si usa la analogía de S3, aquí hay varios objetos y tamaño_objeto.

crearImplementationBufferImpl

Esta es la función virtual proporcionada en ReadBufferFromRemoteFSGather, debido a que el sistema de archivos remoto no es solo S3, sino también HDFS, BLOB, etc., por lo que ImplementationBuffer debe interactuar directamente con el sistema de archivos remoto y otros búferes deben tener implementaciones diferentes, como ya que S3 tiene su clase de implementación ReadBufferFromS3Gather. Mira el código específicamente:

SeekableReadBufferPtr ReadBufferFromS3Gather::createImplementationBufferImpl(const String & path, size_t file_size)
{
    auto remote_path = fs::path(common_path_prefix) / path;
    auto remote_file_reader_creator = [=, this]()
    {
        return std::make_unique<ReadBufferFromS3>(
            client_ptr, bucket, remote_path, version_id, max_single_read_retries,
            settings, /* use_external_buffer */true, /* offset */ 0, read_until_position, /* restricted_seek */true);
    };

    if (with_cache)
    {
        return std::make_shared<CachedReadBufferFromRemoteFS>(
            remote_path, settings.remote_fs_cache, remote_file_reader_creator, settings, query_id, read_until_position ? read_until_position : file_size);
    }

    return remote_file_reader_creator();
}

Hay dos partes principales de la lógica:

Si el caché está habilitado, se usará CachedReadBufferFromRemoteFS; si no se usa el caché, se usará ReadBufferFromS3 directamente. Aunque aquí se usan dos implementaciones, cuando se usa CachedReadBufferFromRemoteFS, si no hay un caché local, ReadBufferFromS3 aún se usará para solicitar datos de archivos remotos.

Entre ellos, la implementación nextImpl de ReadBufferFromS3 todavía es algo complicada. Aquí solo necesitamos comprender brevemente que ReadBufferFromS3 inicializa un flujo de objetos S3 a través de initailize, y una siguiente llamada obtendrá los datos del flujo y los completará en el búfer. como máximo datos de tamaño de buffer para que los use la capa superior.

siguienteImpl

Mire la función nextImpl de ReadBufferFromRemoteFSGather. El siguiente ReadIndirectBufferFromRemoteFS mencionado anteriormente es en realidad el nextImpl llamado. En nextImpl, se encapsula otra capa de readImpl por conveniencia, el código es el siguiente:

bool ReadBufferFromRemoteFSGather::readImpl()
{
    // step 1
    swap(*current_buf);

    bool result = false;

    /**
     * Lazy seek is performed here.
     * In asynchronous buffer when seeking to offset in range [pos, pos + min_bytes_for_seek]
     * we save how many bytes need to be ignored (new_offset - position() bytes).
     */
    // step 2
    if (bytes_to_ignore)
    {
        total_bytes_read_from_current_file += bytes_to_ignore;
        current_buf->ignore(bytes_to_ignore);
        result = current_buf->hasPendingData();
        file_offset_of_buffer_end += bytes_to_ignore;
        bytes_to_ignore = 0;
    }

    // step 3
    if (!result)
        result = current_buf->next();

    if (blobs_to_read.size() == 1)
    {
        file_offset_of_buffer_end = current_buf->getFileOffsetOfBufferEnd();
    }
    else
    {
        /// For log family engines there are multiple s3 files for the same clickhouse file
        file_offset_of_buffer_end += current_buf->available();
    }

    // step 4
    swap(*current_buf);

    /// Required for non-async reads.
  
    // step 5
    if (result)
    {
        assert(available());
        nextimpl_working_buffer_offset = offset();
        total_bytes_read_from_current_file += available();
    }

    return result;
}

Paso 1

Aquí se toma S3 como ejemplo para ilustrar todo el proceso, por lo que current_buf es ReadBufferFromS3Gather, que se inicializa llamando a inicializar. inicializar recorrerá el tamaño de todos los archivos remotos de acuerdo con file_offset_of_buffer_end (debido a que el tamaño se almacena en BlobsPathToSize, esta lógica no necesita acceder al sistema de archivos remoto), sabiendo desde qué archivo comenzar a leer, como se muestra en la figura:

clickhouse file:  [-------------------------------------------------]
                      file1          file2        file3       file4
remote files:    {
    
    [------------][------------][-----------][--------]}

need_to_read:                       [_________________________]
                                    ^
                                    file_offset_of_buffer_end
need_to_seek                     [__]

Como se muestra en la figura anterior, un archivo bin contendrá múltiples objetos s3, y cada objeto registra la ruta y el tamaño. Cuando se proporciona file_offset_of_buffer_end, atraviesa desde el primer objeto a través de un bucle for y va a una variable temporal offset = file_offset_of_buffer_end, si el tamaño del primer objeto es mayor que el desplazamiento, significa que el primer objeto debe leerse desde el desplazamiento para obtener datos; si el tamaño del primer objeto es menor que el desplazamiento, significa que el primer objeto necesita omitirse para juzgar si el segundo objeto se puede incluir o no, y el primer objeto se omite en este momento, entonces el desplazamiento naturalmente restará el tamaño del primer objeto, que es el desplazamiento relativo del segundo objeto. Al atravesar el archivo2, se ha encontrado el punto de partida a leer, seleccione el archivo2, busque los bytes need_to_seek, construya current_buf y regrese.

Paso 2

Continúe mirando bytes_to_ignore, aquí se usa búsqueda diferida, porque ClickHouse admite la capacidad de lectura previa de captación previa, la lectura previa utilizará un búfer externo adicional para leer, cuando llame a continuación aquí, si los datos requeridos se han leído previamente, entonces puede Tómelo directamente y úselo, pero la lectura anticipada del búfer externo no puede leer con precisión de acuerdo con el file_offset requerido, por lo que se necesita bytes_to_ignore para ajustar la posición de file_offset_of_buffer_end, por lo que aquí se usa la búsqueda diferida.

Paso 3

Llame a current_buf->next() para obtener datos de archivos remotos. La implementación específica depende de si el caché se utiliza para corresponder a CachedReadBufferFromRemoteFS o ReadBufferFromS3 respectivamente, y los detalles se presentaron anteriormente.

Etapa 4

Haz intercambio.

Paso 5

Consulte file_offset_of_buffer_end y total_bytes_read_from_current_file.

De hecho, aquí encontrará que readImpl solo realiza la lectura de un archivo remoto, porque no hay lugar para construir un nuevo búfer de archivo remoto en toda la función, razón por la cual se encapsula una función readImpl separada. En nextImpl, si llama a readImpl y descubre que todavía hay archivos que deben leerse, llamará a moveToNextBuffer. Esta función construirá un nuevo current_buf y continuará leyendo hasta que se lean todos los archivos.

En este punto, podemos saber que current_buf está construido y utilizado. También sabemos que current_buf de S3 se construye a través de la función ReadBufferFromS3Gather::createImplementationBufferImpl. ¿Cómo se construye? El código se muestra a continuación:

Hasta ahora, se ha introducido el proceso principal de ReadIndirectBufferFromRemoteFS.

Supongo que te gusta

Origin blog.csdn.net/weixin_39992480/article/details/124871572
Recomendado
Clasificación