fondo
En la exploración preliminar de Apache Hudi (1) (combinado con flink) , mencionamos Pipelines.hoodieStreamWrite 写hudi文件
que esta operación en realidad escribe hudi bajo Pipelines.hoodieStreamWrite
el método transform(opName("stream_write", conf), TypeInformation.of(Object.class), operatorFactory)
y analiza específicamente el proceso de escritura.
analizar
Para transform(opName("stream_write", conf), TypeInformation.of(Object.class), operatorFactory)
este fragmento de código, nos fijamos principalmente en operatorFactory
este objeto ( transform
esta operación es la operación del marco Flink):
public class StreamWriteOperator<I> extends AbstractWriteOperator<I> {
public StreamWriteOperator(Configuration conf) {
super(new StreamWriteFunction<>(conf));
}
public static <I> WriteOperatorFactory<I> getFactory(Configuration conf) {
return WriteOperatorFactory.instance(conf, new StreamWriteOperator<>(conf));
}
}
hudi
El operador más importante es StreamWriteOperator
, donde la operación más importante la StreamWriteFunction
realiza :
// StreamWriteFunction
@Override
public void initializeState(FunctionInitializationContext context) throws Exception {
this.taskID = getRuntimeContext().getIndexOfThisSubtask();
this.metaClient = StreamerUtil.createMetaClient(this.config);
this.writeClient = FlinkWriteClients.createWriteClient(this.config, getRuntimeContext());
this.writeStatuses = new ArrayList<>();
this.writeMetadataState = context.getOperatorStateStore().getListState(
new ListStateDescriptor<>(
"write-metadata-state",
TypeInformation.of(WriteMetadataEvent.class)
));
this.ckpMetadata = CkpMetadata.getInstance(this.metaClient.getFs(), this.metaClient.getBasePath());
this.currentInstant = lastPendingInstant();
if (context.isRestored()) {
restoreWriteMetadata();
} else {
sendBootstrapEvent();
}
// blocks flushing until the coordinator starts a new instant
this.confirming = true;
}
@Override
public void open(Configuration parameters) throws IOException {
this.tracer = new TotalSizeTracer(this.config);
initBuffer();
initWriteFunction();
}
@Override
public void snapshotState(FunctionSnapshotContext functionSnapshotContext) throws Exception {
if (inputEnded) {
return;
}
snapshotState();
// Reload the snapshot state as the current state.
reloadWriteMetaState();
}
@Override
public void snapshotState() {
// Based on the fact that the coordinator starts the checkpoint first,
// it would check the validity.
// wait for the buffer data flush out and request a new instant
flushRemaining(false);
}
@Override
public void processElement(I value, ProcessFunction<I, Object>.Context ctx, Collector<Object> out) throws Exception {
bufferRecord((HoodieRecord<?>) value);
}
-
initializeState
Operación, principalmente para realizar algunas operaciones de inicialización.-
this.taskID = getRuntimeContext().getIndexOfThisSubtask();
Obtenga el subíndice de índice de la tarea actual, que se utiliza paraoperator coordinator
enviar el eventooperator coordinator
, y luego StreamWriteOperatorCoordinator(operator coordinator
) lo procesará, y hablaremos de StreamWriteOperatorCoordinator más adelante. -
metaClient = StreamerUtil.createMetaClient(this.config)
writeClient = FlinkWriteClients.createWriteClient
Inicialice el cliente de metadatos de Hudi (aquíHoodieTableMetaClient
) y escriba el cliente (aquíHoodieFlinkWriteClient
) -
writeStatuses = new ArrayList<>()
Registre la información posterior escrita en el archivo hudi -
writeMetadataState = context.getOperatorStateStore().getListState
event
Registre el evento de metadatos escrito en hudi, que se empaquetará y enviará aoperator coordinator
(StreamWriteOperatorCoordinator) en operaciones posteriores. -
ckpMetadata = CkpMetadata.getInstance
La ruta de información de metadatos del punto de control de Flink,默认的路径是/${hoodie.basePath}/.hoodie/.aux/ckp_meta
-
currentInstant = lastPendingInstant()
Obtenga el compromiso que no se completó la última vez -
restoreWriteMetadata或者sendBootstrapEvent
, De acuerdo con si se recupera del punto de control para enviar mensajes diferentes, el (StreamWriteOperatorCoordinator)
aquíoperator coordinator
realizará un procesamiento unificado e inicializará una confirmación.
-
-
open
Operación
La operación previa antes de escribir en hudi, por ejemplo, seleccione la operación de seguimiento初始化TotalSizeTracer记录maxBufferSize便于flush操作
de acuerdo con el valor (el valor predeterminado es upsert) , aquí estáwrite.operation
insert或upsert或overwrite
upsert
-
processElement
Operación
Aquí, los datos entrantesHoodieRecord
se almacenan en caché, principalmente parabufferRecord
hacer cosas,- Primero, se obtendrá el ID del depósito y luego los datos se insertarán en el depósito correspondiente.
- Si excede
write.batch.size
(el valor predeterminado es 128 MB), se realizará la operación flushBucket, que consiste principalmente en escribir en hudi //TODO: escritura específica en hudi- Primero, se obtendrán las nuevas confirmaciones que deben enviarse.
- Luego realice la operación real de escritura.
- La información de metadatos del archivo escrito se envía de vuelta
operator coordinator
al
-
snapshotState
funcionar- Llame para
flushRemaining
escribir los datos restantes en el almacenamiento hudi - Vuelva a cargar la información de metadatos del archivo hudi actualmente escrita en el estado actual de flink
- Llame para
Acción hudi StreamWriteOperatorCoordinator
En general, la función de StreamWriteOperatorCoordinator es la misma que la del controlador en Spark, que es enviar información de metadatos al huid al final.
El rol específico aún depende del método específico:
@Override
public void handleEventFromOperator(int i, OperatorEvent operatorEvent) {
ValidationUtils.checkState(operatorEvent instanceof WriteMetadataEvent,
"The coordinator can only handle WriteMetaEvent");
WriteMetadataEvent event = (WriteMetadataEvent) operatorEvent;
if (event.isEndInput()) {
// handle end input event synchronously
// wrap handleEndInputEvent in executeSync to preserve the order of events
executor.executeSync(() -> handleEndInputEvent(event), "handle end input event for instant %s", this.instant);
} else {
executor.execute(
() -> {
if (event.isBootstrap()) {
handleBootstrapEvent(event);
} else {
handleWriteMetaEvent(event);
}
}, "handle write metadata event for instant %s", this.instant
);
}
}
...
@Override
public void notifyCheckpointComplete(long checkpointId) {
executor.execute(
() -> {
// The executor thread inherits the classloader of the #notifyCheckpointComplete
// caller, which is a AppClassLoader.
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
// for streaming mode, commits the ever received events anyway,
// the stream write task snapshot and flush the data buffer synchronously in sequence,
// so a successful checkpoint subsumes the old one(follows the checkpoint subsuming contract)
final boolean committed = commitInstant(this.instant, checkpointId);
if (tableState.scheduleCompaction) {
// if async compaction is on, schedule the compaction
CompactionUtil.scheduleCompaction(metaClient, writeClient, tableState.isDeltaTimeCompaction, committed);
}
if (tableState.scheduleClustering) {
// if async clustering is on, schedule the clustering
ClusteringUtil.scheduleClustering(conf, writeClient, committed);
}
if (committed) {
// start new instant.
startInstant();
// sync Hive if is enabled
syncHiveAsync();
}
}, "commits the instant %s", this.instant
);
}
-
El método handleEventFromOperator se utiliza para aceptar el mensaje enviado por la tarea.
-
Para
BootStrap
el tipo de WriteMetadataEvent (enStreamWriteFunction方法initializeState中
), es equivalente a la inicialización de la función y activará el procesamiento de
este tipo de mensaje (asumimos que cada operador de tarea ha completado la operación de inicialización), y el flujo de datos correspondiente es el siguiente:handleBootstrapEvent
initInstant || \/ reset => startInstant
startInstant aquí inicializará la información de confirmación de una operación de escritura de hudi
-
Para el evento de información de escritura general (por ejemplo, en la función flushBucket de ProcessElement), se procesa
handleWriteMetaEvent
mediante :if (this.eventBuffer[event.getTaskID()] != null) { this.eventBuffer[event.getTaskID()].mergeWith(event); } else { this.eventBuffer[event.getTaskID()] = event; }
Aquí simplemente se agrega a la matriz de tipo WriteMetadataEvent con nombre de variable eventBuffer, que se procesará más adelante.
-
Para
isEndInput
eltrue
evento, este tipo de fuente general se basa en el archivo, que no se discutirá aquí.
-
-
notifyCheckpointComplete Cuando se complete el checkpointId correspondiente, se llamará a este método
- commitInstant envía los metadatos de hudi, si ocurre una excepción, revierte la confirmación correspondiente al hudi actual
- agendaCompacción && agendaAgrupación para la
Compcation
suma de huiClustering
- Si se envía correctamente, se iniciará una nueva confirmación. Si la sincronización de Hive está habilitada (el
hive_sync.enabled
valor predeterminado es falso), la información de metadatos se sincronizará con Hive.
Resumir
Utilice una imagen para resumir el modo de interacción, de la siguiente manera: