¿Cómo implementar el procesamiento de datos elásticos integrado en flujo por lotes basado en Apache Pulsar y Spark?

Estado actual del flujo de lotes

En el campo del análisis de datos masivamente paralelo, "Una pila para gobernarlos a todos" de AMPLab propone usar Apache Spark como un motor unificado para soportar escenarios de procesamiento de datos comunes como procesamiento por lotes, procesamiento de flujo, consulta interactiva y aprendizaje automático. En julio de 2017, la transmisión estructurada de Spark, que fue lanzada oficialmente por la versión Spark 2.2.0, usa Spark SQL como el motor de ejecución unificado subyacente para el procesamiento de transmisión y el procesamiento por lotes. Optimice la consulta de la tabla de límites (datos históricos estáticos) y brinde a los usuarios API de Dataset / DataFrame para el procesamiento conjunto de datos por lotes, lo que desdibuja aún más el límite del procesamiento de datos por lotes.

Por otro lado, Apache Flink entró en el ojo público alrededor de 2016. Con su mejor motor de procesamiento de flujo en ese momento, la marca de agua nativa respalda la garantía de consistencia de datos de "Exaclty Once" y el soporte de varios escenarios, como la computación integrada por lotes y de flujo. Conviértete en un fuerte rival de Spark. Ya sea que usen Spark o Flink, lo que realmente les importa a los usuarios es cómo usar mejor los datos y aprovechar el valor de los datos más rápido. Los datos de transmisión y los datos estáticos ya no son individuos separados, sino dos representaciones diferentes de un fragmento de datos.

Sin embargo, en la práctica, la creación de una plataforma de datos integrada en el flujo por lotes no es solo una tarea de la capa del motor informático. Porque en las soluciones tradicionales, los datos de eventos y flujos casi en tiempo real generalmente se almacenan en colas de mensajes (como RabbitMQ) y canalizaciones de datos en tiempo real (como Apache Kafka), mientras que los datos estáticos necesarios para el procesamiento por lotes generalmente se almacenan en sistemas de archivos y almacenamiento de objetos. Esto significa que, por un lado, en el proceso de análisis de datos, con el fin de asegurar la corrección y rendimiento en tiempo real de los resultados, es necesario realizar consultas conjuntas sobre los datos almacenados en los dos tipos de sistemas; Por otro lado, en el proceso de operación y mantenimiento, es necesario volcar periódicamente datos de flujo al almacenamiento de archivos / objetos y garantizar el rendimiento de las colas de mensajes y las canalizaciones de datos manteniendo la cantidad total de datos en forma de flujo por debajo del umbral ( porque el diseño de la arquitectura basada en particiones de este tipo de sistema combina estrechamente el servicio de mensajes y el almacenamiento de mensajes, y la mayoría de ellos dependen demasiado del sistema de archivos. A medida que aumenta la cantidad de datos, el rendimiento del sistema se reducirá drásticamente), pero los datos artificiales La reubicación no solo aumentará los costos de operación y mantenimiento del sistema, sino también la limpieza y lectura de datos durante el proceso de reubicación La recuperación y la carga también son un gran consumo de recursos del clúster.

Al mismo tiempo, desde la popularidad de Mesos y YARN, el surgimiento de Docker, hasta la adopción generalizada de Kubernetes, toda la infraestructura se está desarrollando hacia la contenedorización. El servicio de mensajes tradicional y la arquitectura de computación de mensajes estrechamente acoplados no se pueden adaptar bien. Arquitectura en contenedores. Tomemos a Kafka como ejemplo: su arquitectura centrada en particiones combina estrechamente el servicio de mensajes y el almacenamiento de mensajes. Las particiones de Kafka están fuertemente ligadas a una o un grupo de máquinas físicas, lo que genera el problema del costoso y prolongado proceso de reequilibrio de datos de la partición durante la falla de la máquina o la expansión del clúster; su diseño de almacenamiento con la partición como granularidad Tampoco puede hacer un buen uso de lo existente. recursos de almacenamiento en la nube; además, el diseño demasiado simple conduce a la necesidad de resolver muchas fallas arquitectónicas en la administración de múltiples inquilinos, el aislamiento de E / S, etc. para implementar la contenedorización.

Introducción a Pulsar

Apache Pulsar es un sistema de suscripción y publicación de mensajes a nivel empresarial de alto rendimiento y multiinquilino. Fue desarrollado originalmente por Yahoo. Se graduó de la incubadora Apache en septiembre de 2018 y se convirtió en el principal proyecto de código abierto de la Fundación Apache. Pulsar se basa en el modelo de publicación-suscripción (pub-sub). Los productores publican mensajes en los temas. Los consumidores pueden suscribirse a los temas, procesar los mensajes recibidos y enviar la confirmación (Ack) una vez que se completa el procesamiento del mensaje). Pulsar ofrece cuatro tipos de suscripciones, que pueden coexistir sobre el mismo tema, distinguidos por el nombre de la suscripción:

  • Suscripción exclusiva: solo puede haber un consumidor a la vez con un nombre de suscripción.

  • Suscripción compartida (compartida): puede ser suscrita por varios consumidores, cada consumidor recibe una parte del mensaje.

  • Suscripción de conmutación por error: permite que varios consumidores se conecten al mismo tema, pero solo un consumidor puede recibir mensajes. Solo cuando el consumidor actual falla, otros consumidores comienzan a recibir mensajes.

  • Suscripción de clave compartida (función beta): varios consumidores conectados al mismo tema, la misma clave siempre se enviará al mismo consumidor.

Pulsar apoya el concepto de arrendamiento múltiple desde el comienzo de su diseño. Los inquilinos pueden abarcar varios clústeres. Cada inquilino tiene sus propios métodos de autenticación y autenticación. Los inquilinos también tienen cuotas de almacenamiento y supervivencia de mensajes. Tiempo (TTL) y unidad de administración de la estrategia de aislamiento . La función de múltiples inquilinos de Pulsar se puede reflejar completamente en la URL del tema, y ​​su estructura es  persistent://tenant/namespace/topic. El espacio de nombres es la unidad de administración más básica de Pulsar. Podemos establecer permisos, ajustar las opciones de replicación, administrar la replicación de datos entre clústeres, controlar el tiempo de vencimiento de los mensajes o realizar otras tareas clave.

Arquitectura única de Pulsar

La diferencia más fundamental entre Pulsar y otros sistemas de mensajería es que utiliza una arquitectura en capas que separa la informática y el almacenamiento. El clúster Pulsar consta de dos capas: la capa de servicio sin estado, que está compuesta por un grupo de corredores que aceptan y entregan mensajes; la capa de almacenamiento distribuido, que está compuesta por un grupo de nodos de almacenamiento Apache BookKeeper llamados corredores de apuestas, con alta disponibilidad, fuerte consistencia, características de baja latencia.

Al igual que Kafka, Pulsar también almacena datos de temas basados ​​en el concepto lógico de partición de temas. La diferencia es que el almacenamiento físico de Kafka también se basa en particiones, cada partición debe almacenarse como un todo (un directorio) en un intermediario, y cada partición temática de Pulsar es esencialmente un almacenamiento distribuido en BookKeeper Log, cada registro se divide en segmentos (Segmento). Cada segmento actúa como un libro mayor en BookKeeper, distribuido uniformemente y almacenado en múltiples casas de apuestas. La arquitectura en capas de almacenamiento y el almacenamiento fragmentado centrado en el segmento son dos conceptos clave de diseño de Pulsar. En base a esto, Pulsar ofrece muchas ventajas importantes: particiones temáticas ilimitadas, expansión del almacenamiento instantáneo, sin migración de datos, recuperación de fallas de intermediarios sin problemas, expansión de clústeres sin problemas, recuperación de fallas de almacenamiento (Bookie) sin problemas y escalabilidad independiente.

El sistema de mensajes disocia al productor y al consumidor, pero el mensaje real sigue siendo de naturaleza estructurada. Por lo tanto, se necesita un mecanismo de coordinación entre el productor y el consumidor para llegar a un consenso sobre la estructura del mensaje en el proceso de producción y consumo. Para lograr el propósito de la seguridad de tipos. Pulsar tiene un método de registro de esquema incorporado para proporcionar una forma de transferir el acuerdo del tipo de mensaje en el lado del sistema de mensajes. El cliente puede acordar la información del tipo de mensaje a nivel de tema cargando el esquema, y ​​Pulsar es responsable de la verificación del tipo de mensaje y la serialización automática. de mensajes mecanografiados, deserialización, reduciendo así el costo del desarrollo y mantenimiento repetidos del código de análisis de mensajes entre múltiples aplicaciones. Por supuesto, la definición de esquema y la seguridad de tipos son un mecanismo opcional y no causarán ninguna sobrecarga de rendimiento para la publicación y el consumo de mensajes sin tipo.

Leer y escribir datos de Pulsar en Spark  

自 Spark 2.2 版本 Structured Streaming 正式发布,Spark 只保留了 SparkSession 作为主程序入口,你只需编写 DataSet/DataFrame API 程序,以声明形式对数据的操作,而将具体的查询优化与批流处理执行的细节交由 Spark SQL 引擎进行处理。对于一个数据处理作业,需要定义 DataFrame 的产生、变换和写出三个部分,而将 Pulsar 作为流数据平台与 Spark 进行集成正是要解决如何从 Pulsar 中读取数据(Source)和如何向 Pulsar 写出运算结果(Sink)两个问题。

为了实现以 Pulsar 为源读取批流数据与支持批流数据向 Pulsar 的写入,我们构建了 Spark Pulsar Connector。

对 Structured Streaming 的支持

上图展示了 Structured Streaming(以下简称 SS )的主要组件:

  • 输入和输出——为了提供细粒度的容错,SS 要求输入数据源(Source)是可重放(replayable)的;为了提供端到端的 Exactly-Once 的语义,需要输出(Sink)支持幂等写出(一条消息被多次写入与一次写入效果一致,可由 DBMS、KV 系统通过键约束的方式支持)。

  • API——用户通过编写 Spark SQL 的 batch API(SQL 或 DataFrame)指定对一个或多个流、表的查询,并定义一个输出表保存所有的输出结果,而引擎内部决定如何将结果增量地写到 Sink 中。为了支持流处理,SS 在原有的 Spark SQL API 上添加了一些接口:

    • 触发器(Trigger)——控制引擎触发流处理执行、在 Sink 中更新结果的频率。

    • 水印机制(Watermark policy)——用户通过指定字段做 event time,来决定对晚到数据的处理。

    • 有状态算子(Stateful operator)——用户可以根据 Key 跟踪和更新算子内部的可变状态,完成复杂的业务需求(例如,基于会话的窗口)。

  • 执行层——当收到一个查询时,SS 决定它的增量执行方式,进行优化、并开始执行。SS 有两种可选的执行模型:

    • Microbatch model(微批处理模式)——默认的执行方式,与 Spark Streaming 的 DStream 类似,将流切成 micro batch,对每个 batch 分别处理。这种模式支持动态负载均衡、故障恢复等机制,适合将吞吐率作为主要性能指标的应用。

    • Continuous mode(持续模式)——在集群上启动长时间运行的算子,适合处理较为简单、延迟敏感类应用。

  • Log 和 State Store —— SS 利用两种持久化存储来提供容错保障:一个 Write-ahead-Log(WAL),记录被成功消费且持久化写出的每个数据源中的位置;一个大规模的 state store, 存储长期运行的聚集算子内部的状态快照。当故障发生时,SS 会根据快照的位置,通过重放之后的消息完成流处理状态的恢复。

具体到源码层面,Source 接口定义了可重放数据源需要提供的功能。

trait Source {
 def schema: StructType
 def getOffset: Option[Offset]
 def getBatch(start: Option[Offset], end: Offset): DataFrame
 def commit(end: Offset): Unit
 def stop(): Unit
}

trait Sink {
 def addBatch(batchId: Long, data: DataFrame): Unit
}

以 microbatch 执行模式为例:

  1. 在每个 microbatch 的最开始,SS 会向 source 询问当前的最新进度(getOffset),并将其持久化到 WAL 中。

  2. 随后,source 根据 SS 提供的 start end 偏移量,提供区间范围的数据(getBatch)。

  3. SS 触发计算逻辑的优化和编译,把计算结果写出给 sink(addBatch),这时才触发实际的取数据操作以及计算过程。

  4. 在数据完整写出到 sink 后,SS 通知 source 可以废弃数据(commit),并将成功执行的batchId 写入内部维护的 commitLog 中。

具体到 Pulsar 的 connector 实现中:

  1. 在所有批次开始执行前,SS 会调用 schema 方法返回消息的结构信息,在 schema 方法内部,我们从 Pulsar 的 Schema Registry 提取出所有主题的 Schema,并进行一致性检查。

  2. 随后,我们为每个主题分区创建一个消费者,按照 (start, end] 返回主题分区中的数据。

  3. 当收到 SS 的 commit 通知时,通过 topics 中的 resetCursor 向 Pulsar 标志消息消费的完成。Sink 中构建的生产者则将 addBatch 中获取的实际数据以消息形式追加写入相应的主题中。

    对批处理作业的支持

在某个时间点执行的批作业,可以看作是对 Pulsar 平台中的流数据在一个时间点的快照进行的数据分析。Spark 对历史数据的查询是以 Relation 为单位,Spark Pulsar Connector 提供createRelation 方法的实现根据用户指定的多个主题分区构建表,并返回包含 Schema 信息的 DataSet。在查询计划阶段,Connector 的功能分成两步:首先,根据用户提供的一个或多个主题,在 Pulsar Schema Registry 中查找主题 Schema,并检查多个主题 Schema 的一致性;其次,将用户指定的所有主题分区进行任务划分(Partition),得到的分片即是 Spark source task 的执行粒度。

Pulsar 提供了两层的接口对其中的数据进行访问,基于主题分区的 Consumer/Reader 接口,以传统消息接收为语义的顺序数据读取;Segment 级的读接口,提供对 Segment 数据的直接读取。因此,相应地从 Pulsar 读数据执行批作业可以分成两种粒度(即读取数据的并行度)进行:以主题分区为粒度(每个主题分区作为一个分片);以 Segment 为粒度(将一个主题分区的多个 Segment 组织成一个分片,因此一个主题分区会有多个对应的分片)。你可以按照批作业的并行度需求和可分配计算资源选择合适的消息读取的并行粒度。另一方面,将批作业的执行存储到 Pulsar 也很直观,你只需指定写入的主题和消息路由规则(RoundRobin 或者按 Key 划分),在 Sink task 中创建的每个生产者会将待写出的消息送至对应的主题分区。

如何使用 Spark Pulsar Connector

根据一个或多个主题创建流处理 Source。

val df = spark
 .readStream
 .format("pulsar")
 .option("service.url", "pulsar://localhost:6650")
 .option("admin.url", "http://localhost:8080")
 .option("topicsPattern", "topic.*") // Subscribe to a pattern
 // .option("topics", "topic1,topic2")    // Subscribe to multiple topics
 // .option("topic", "topic1"). //subscribe to a single topic
 .option("startingOffsets", startingOffsets)
 .load()
df.selectExpr("CAST(__key AS STRING)", "CAST(value AS STRING)")
 .as[(String, String)]


构建批处理 Source。

val df = spark
 .read
 .format("pulsar")
 .option("service.url", "pulsar://localhost:6650")
 .option("admin.url", "http://localhost:8080")
 .option("topicsPattern", "topic.*")
 .option("startingOffsets", "earliest")
 .option("endingOffsets", "latest")
 .load()
df.selectExpr("CAST(__key AS STRING)", "CAST(value AS STRING)")
 .as[(String, String)]


使用数据中本身的 topic 字段向多个主题进行持续 Sink。

val ds = df
 .selectExpr("topic", "CAST(__key AS STRING)", "CAST(value AS STRING)")
 .writeStream
 .format("pulsar")
 .option("service.url", "pulsar://localhost:6650")
 .start()


将批处理结果写回 Pulsar。

df.selectExpr("CAST(__key AS STRING)", "CAST(value AS STRING)")
 .write
 .format("pulsar")
 .option("service.url", "pulsar://localhost:6650")
 .option("topic", "topic1")
 .save()

注意

由于 Spark Pulsar Connector 支持结构化消息的消费和写入,为了避免消息负载中字段和消息元数据(event time、publish time、key 和 messageId)的潜在命名冲突,消息元数据字段在 Spark schema 中以双下划线做为前缀(例如,__eventTime)。


Supongo que te gusta

Origin blog.51cto.com/15060462/2678362
Recomendado
Clasificación