El ozono DataNode ContainerStateMachine lograr semántica

prefacio


Parte artículo el autor describe el paquete de lograr consistencia interna en el uso de ozono Apache Ratis realización del principio de coherencia, gracias a la parte inferior de los niveles de ozono, y sólo necesita llamar a esta biblioteca para poner en práctica un método StateMachine personalizado. En ozono en DataNode, hemos personalizado la ContainerStateMachine operaciones de contenedores para lograr el control coherencia entre varias copias. En este artículo hablamos de la aplicación de ozono interna DataNode ContainerStateMachine, por lo que podemos obtener una mayor comprensión del proceso de operación solicitada de contenedores.

Para ContainerStateMachine StateMachine / semántica RaftLog implementan


Dado que el ozono utiliza interna Apache aplicación Ratis, habrá muchas veces involucra los dos conceptos siguientes:

  • RaftLog
  • Máquina estatal

Así que tenemos que entender claramente estos dos conceptos en ozono es una especie de definición de variables.

Primero RaftLog, RaftLog en ozono se entiende simplemente que cada operación de petición de usuario, donde las manifestaciones de TransactionContext. Este proceso es el siguiente:

En primer lugar los niveles de ozono se empaquetan como una petición de usuario TransactionContext objetos,

El método de funcionamiento ContainerStateMachine startTransaction,

  public TransactionContext startTransaction(RaftClientRequest request)
      throws IOException {
    long startTime = Time.monotonicNowNanos();
    final ContainerCommandRequestProto proto =
        message2ContainerCommandRequestProto(request.getMessage());
    Preconditions.checkArgument(request.getRaftGroupId().equals(gid));
    try {
      dispatcher.validateContainerCommand(proto);
    } catch (IOException ioe) {
      if (ioe instanceof ContainerNotOpenException) {
        metrics.incNumContainerNotOpenVerifyFailures();
      } else {
        metrics.incNumStartTransactionVerifyFailures();
        LOG.error("startTransaction validation failed on leader", ioe);
      }
      TransactionContext ctxt = TransactionContext.newBuilder()
          .setClientRequest(request)
          .setStateMachine(this)
          .setServerRole(RaftPeerRole.LEADER)
          .build();
      ctxt.setException(ioe);
      return ctxt;
    }
    ...
}

A continuación, el escrito recibido DataNode TransactionContext RaftLog, se transformará en la entrada del registro,

  @Override
  public LogEntryProto initLogEntry(long term, long index) {
    Preconditions.assertTrue(serverRole == RaftPeerRole.LEADER);
    Preconditions.assertNull(logEntry, "logEntry");
    Objects.requireNonNull(smLogEntryProto, "smLogEntryProto == null");
    return logEntry = ServerProtoUtils.toLogEntryProto(smLogEntryProto, term, index);
  }

A continuación, estos estarán dentro DataNode RaftServer TransactionContext convierte de la entrada de registro se escribe en la RaftLog local.

Durante la redacción de la entrada de registro, todavía tiene que ser dividido en los dos casos siguientes:

En primer lugar, el caso de una solicitud con los datos del usuario, una solicitud de operación de escritura de datos Italia, por ejemplo, la solicitud writeChunk, tenemos que separar los datos escritos en el StateMachine, RaftLog Transacción retener sólo la información en sí ozono. StateMachine aquí puede entenderse como DataNode del estado actual de metadatos.

Debido a que el usuario está obligado a escribir los datos en tiempo real, por lo DataNode ContainerStateMachine en que para lograr la forma caché interna para guardar las solicitudes de datos de usuario, a continuación, escribir asíncrono esta parte del bloque de datos, pero con el fin de guardar los archivos temporales tmp estado.

  /*
   * writeStateMachineData calls are not synchronized with each other
   * and also with applyTransaction.
   */
  @Override
  public CompletableFuture<Message> writeStateMachineData(LogEntryProto entry) {
    try {
      metrics.incNumWriteStateMachineOps();
      long writeStateMachineStartTime = Time.monotonicNowNanos();
      ContainerCommandRequestProto requestProto =
          getContainerCommandRequestProto(
              entry.getStateMachineLogEntry().getLogData());
      //1) 构造write chunk请求操作 
      WriteChunkRequestProto writeChunk =
          WriteChunkRequestProto.newBuilder(requestProto.getWriteChunk())
              .setData(getStateMachineData(entry.getStateMachineLogEntry()))
              .build();
      requestProto = ContainerCommandRequestProto.newBuilder(requestProto)
          .setWriteChunk(writeChunk).build();
      Type cmdType = requestProto.getCmdType();

      // For only writeChunk, there will be writeStateMachineData call.
      // CreateContainer will happen as a part of writeChunk only.
      switch (cmdType) {
      case WriteChunk:
        return handleWriteChunk(requestProto, entry.getIndex(),
            entry.getTerm(), writeStateMachineStartTime);
      default:
        throw new IllegalStateException("Cmd Type:" + cmdType
            + " should not have state machine data");
      }
    } catch (IOException e) {
      metrics.incNumWriteStateMachineFails();
      return completeExceptionally(e);
    }
  }

  private CompletableFuture<Message> handleWriteChunk(
      ContainerCommandRequestProto requestProto, long entryIndex, long term,
      long startTime) {
    final WriteChunkRequestProto write = requestProto.getWriteChunk();
    RaftServer server = ratisServer.getServer();
    Preconditions.checkState(server instanceof RaftServerProxy);
    try {
      // 2) 如果是Leader服务,将chunk数据写入cache中,leader服务将从此cache中读chunk数据,
      // 包装为raft log请求发送给Follower
      if (((RaftServerProxy) server).getImpl(gid).isLeader()) {
        stateMachineDataCache.put(entryIndex, write.getData());
      }
    } catch (IOException | InterruptedException ioe) {
      return completeExceptionally(ioe);
    }
    DispatcherContext context =
        new DispatcherContext.Builder()
            .setTerm(term)
            .setLogIndex(entryIndex)
            // 标明此阶段为写数据阶段
            .setStage(DispatcherContext.WriteChunkStage.WRITE_DATA)
            .setContainer2BCSIDMap(container2BCSIDMap)
            .build();
    CompletableFuture<Message> raftFuture = new CompletableFuture<>();
    // ensure the write chunk happens asynchronously in writeChunkExecutor pool
    // thread.
    ...
    return raftFuture;
  }

Para papel DataNode RaftLeader, que necesita para implementar el método readStateMachineData lee estructura de datos de usuario balsa de troncos, que envió a DataNode Balsa Seguidor de StateMachine en sí mismo.

ContainerStateMachine 的 方法 readStateMachineData,

  /*
   * This api is used by the leader while appending logs to the follower
   * This allows the leader to read the state machine data from the
   * state machine implementation in case cached state machine data has been
   * evicted.
   */
  @Override
  public CompletableFuture<ByteString> readStateMachineData(
      LogEntryProto entry) {
    ...
    try {
      final ContainerCommandRequestProto requestProto =
          getContainerCommandRequestProto(
              entry.getStateMachineLogEntry().getLogData());
      // readStateMachineData should only be called for "write" to Ratis.
      Preconditions.checkArgument(!HddsUtils.isReadOnly(requestProto));
      // 目前readStateMachineData只会被write chunk请求调用
      if (requestProto.getCmdType() == Type.WriteChunk) {
        final CompletableFuture<ByteString> future = new CompletableFuture<>();
        CompletableFuture.supplyAsync(() -> {
          try {
            future.complete(
                getCachedStateMachineData(entry.getIndex(), entry.getTerm(),
                    requestProto));
          } catch (IOException e) {
            metrics.incNumReadStateMachineFails();
            future.completeExceptionally(e);
          }
          return future;
        }, chunkExecutor);
        return future;
      } else {
        throw new IllegalStateException("Cmd type:" + requestProto.getCmdType()
            + " cannot have state machine data");
      }
    } catch (Exception e) {
      metrics.incNumReadStateMachineFails();
      LOG.error("{} unable to read stateMachineData:", gid, e);
      return completeExceptionally(e);
    }
  }

  /**
   * Reads the Entry from the Cache or loads it back by reading from disk.
   */
  private ByteString getCachedStateMachineData(Long logIndex, long term,
      ContainerCommandRequestProto requestProto)
      throws IOException {
    // 从本地cache中快速读取,如果cache中已不存在了,从本地tmp chunk文件中读取数据
    ByteString data = stateMachineDataCache.get(logIndex);
    if (data == null) {
      data = readStateMachineData(requestProto, term, logIndex);
    }
    return data;
  }

Esta parte de la lógica de procesamiento de petición final ChunkManagerImpl como sigue:

  public void writeChunk(Container container, BlockID blockID, ChunkInfo info,
      ChunkBuffer data, DispatcherContext dispatcherContext)
      throws StorageContainerException {
    Preconditions.checkNotNull(dispatcherContext);
    DispatcherContext.WriteChunkStage stage = dispatcherContext.getStage();
    try {
      ...

      switch (stage) {
      case WRITE_DATA:
        //...
        if (tmpChunkFile.exists()) {
          // If the tmp chunk file already exists it means the raft log got
          // appended, but later on the log entry got truncated in Ratis leaving
          // behind garbage.
          // TODO: once the checksum support for data chunks gets plugged in,
          // instead of rewriting the chunk here, let's compare the checkSums
          LOG.warn(
              "tmpChunkFile already exists" + tmpChunkFile + "Overwriting it.");
        }
        // 写入的是临时tmp文件中,如果后续发送Raft log truncate操作,此tmp数据也可以被重新覆盖掉
        // 此过程发生在ContainerStateMachine的writeStateMachineData阶段
        ChunkUtils
            .writeData(tmpChunkFile, info, data, volumeIOStats, doSyncWrite);
        // No need to increment container stats here, as still data is not
        // committed here.
        break;
      case COMMIT_DATA:
        ...
        // 提交chunk数据阶段,rename tmp chunk文件为正式文件名,
        // 此过程发生在ContainerStateMachine的applyTransaction阶段
        commitChunk(tmpChunkFile, chunkFile);
        // Increment container stats here, as we commit the data.
        updateContainerWriteStats(container, info, isOverwrite);
        break;
      case COMBINED:
        // directly write to the chunk file
        ChunkUtils.writeData(chunkFile, info, data, volumeIOStats, doSyncWrite);
        updateContainerWriteStats(container, info, isOverwrite);
        break;
      default:
        throw new IOException("Can not identify write operation.");
      }
    } catch (StorageContainerException ex) {
      ...
    }
  }

Tenga en cuenta que la operación de autor writeStateMachineData antes mencionadas acaba de escribir a cabo los datos solicitados por el usuario, no significa el final de esta acción, hemos completado con éxito el RaftLog aplica a tiempo StateMachine.

Así podemos ver writechunk solicitado operación sólo cuando la operación fue declarado en applyTransaction etapa es la etapa COMMIT_DATA,

  /*
   * ApplyTransaction calls in Ratis are sequential.
   */
  @Override
  public CompletableFuture<Message> applyTransaction(TransactionContext trx) {
    long index = trx.getLogEntry().getIndex();
    // Since leader and one of the followers has written the data, it can
    // be removed from the stateMachineDataMap.
    stateMachineDataCache.remove(index);

    DispatcherContext.Builder builder =
        new DispatcherContext.Builder()
            .setTerm(trx.getLogEntry().getTerm())
            .setLogIndex(index);

    long applyTxnStartTime = Time.monotonicNowNanos();
    try {
      applyTransactionSemaphore.acquire();
      metrics.incNumApplyTransactionsOps();
      ContainerCommandRequestProto requestProto =
          getContainerCommandRequestProto(
              trx.getStateMachineLogEntry().getLogData());
      Type cmdType = requestProto.getCmdType();
      // Make sure that in write chunk, the user data is not set
      if (cmdType == Type.WriteChunk) {
        Preconditions
            .checkArgument(requestProto.getWriteChunk().getData().isEmpty());
        builder
            // apply transaction阶段为commit chunk data阶段
            .setStage(DispatcherContext.WriteChunkStage.COMMIT_DATA);
      }
      ...
}

Para otras solicitudes con no hay datos de usuario, tales como puro recipiente hecho de petición de actualización de metadatos, serán procesados ​​en applyTransaction tiempo para StateMachine RaftLog, se muestra por encima de la ContainerStateMachine applyTransaction. Esta solicitud se pasará a la sección de procesamiento HddsDispatcher de forma asíncrona.

Para Averiguar el tipo de solicitud, ya que esta parte del método de procesamiento de consultas StateMachine, de la siguiente manera:

ContainerStateMachine 的 consulta

  @Override
  public CompletableFuture<Message> query(Message request) {
    try {
      metrics.incNumQueryStateMachineOps();
      final ContainerCommandRequestProto requestProto =
          message2ContainerCommandRequestProto(request);
      // 执行runCommand方法
      return CompletableFuture
          .completedFuture(runCommand(requestProto, null)::toByteString);
    } catch (IOException e) {
      metrics.incNumQueryStateMachineFails();
      return completeExceptionally(e);
    }
  }

Como RaftLog inconsistencia puede ocurrir en el caso en que DataNode RaftFollower proceso presente, necesidad de realizar la operación de truncado. ContainerStateMachine también que hay que hacer al mismo tiempo que corresponde con el procesamiento, los datos de usuario se escriben StateMachine antes de procesar, el procesamiento como sigue:

  @Override
  public CompletableFuture<Void> truncateStateMachineData(long index) {
    // 移除指定index之后的cache数据,写出的tmp chunk data将会后续的写操作中被覆盖
    stateMachineDataCache.removeIf(k -> k >= index);
    return CompletableFuture.completedFuture(null);
  }

Para necesaria para lograr el procedimiento descrito anteriormente StateMachine ContainerStateMachine, Ozone DataNode lograr solicitud de procesamiento coherente basado en un protocolo de control de la balsa. Escribir principalmente relacionados con el procesamiento de solicitudes de datos de usuario adicional, la operación de escritura de datos necesidad StateMachine del proceso de lectura. Como la mayor parte de los términos de transacción de solicitudes de lectura y escritura, implementados en StateMachine la consulta y applyTransaction pueden ser. Apache Ratis lograr las bibliotecas subyacentes nos han permitido alcanzar un buen, partes interesantes de estos estudiantes pueden leer el entendimiento del autor del artículo: uso interno RaftLeader de ozono / sincronización mecanismo RaftFollower de coherencia .

Muestra un diagrama de flujo de este proceso se muestra a continuación, el siguiente esquema puede profundizar en la comprensión de todo el proceso descrito anteriormente, la línea continua representa la fase de registro de escritura ordinaria Raft y la línea de puntos representa un commit fase de registro.
Aquí Insertar imagen Descripción

Publicados 388 artículos originales · ganado elogios 424 · Vistas 2,07 millones +

Supongo que te gusta

Origin blog.csdn.net/Androidlushangderen/article/details/104456771
Recomendado
Clasificación