Notas de estudio de KafkaStreams-03

Capítulo 3 Desarrollando flujos de Kafka

Una gran cantidad de código en el libro usa expresiones lambda, así que primero complete este conocimiento.

expresión lambda

Java es un lenguaje orientado a objetos y no puede pasar directamente bloques de código o métodos de llamada. Si necesita reutilizar un bloque de código, debe construir una instancia de una clase o interfaz para encapsular este método, que es engorroso, por lo que se introducen expresiones lambda. Las expresiones lambda parecen llamar a los métodos directamente, un poco sesgados hacia la programación funcional.
Formato:
(Parámetro) -> Expresión La
expresión puede expandirse y escribirse en el bloque de código {} llaves.
Si no hay parámetros, las llaves no pueden omitirse a menos que el compilador pueda derivar el tipo de parámetro
. El valor de retorno de la expresión no necesita especificar el tipo

Algunas interfaces en Java solo encapsulan un método (interfaz funcional), y cuando usa este método, a menudo necesita implementar una instancia de la interfaz para llamar al método [implementación de clase interna anónima usada anteriormente]. En este caso, también puede usar expresiones lambda, que se ven más Conciso

Thread thread1 = new Thread(new Runnable{
	@override
	public void run(){
		System.out.println("this is a java thread running");
		}
	}
);

Se puede reescribir como expresión lambda

Thread thread2 = new Thread(()->System.out.println("this is a java thread-lambda running"));

El formulario de expresión lambda no declara la interfaz Runnable, porque el compilador puede inferir del contexto que la llamada es la instancia de la interfaz Runnable. Debido a que los parámetros requeridos por la clase Thread durante la construcción son instancias de la interfaz Runnable.

Me da la sensación de que el tipo de expresión lambda es una instancia de una interfaz funcional, y omite la declaración instanciada de la interfaz que encapsula el método según el contexto. Entonces, en otras ocasiones, Java puede llamar a una declaración lambda, que parece llamar a un método directamente, o puede asignar una expresión lambda a una interfaz.

La referencia del método abrevia aún más la expresión lambda
Operador de dos puntos dobles ::
nombre de clase :: nombre del método
A menudo combinado con la expresión lambda, el parámetro es un objeto debajo del nombre de la clase, se llama al método y se devuelve el valor de retorno del método.

En general, el compilador de actualización JDK se está volviendo más inteligente, y se pueden inferir más y más cosas, por lo que hay menos necesidad de escribir claramente la fuente del programa. Se puede abreviar. La expresión lambda es un ejemplo de una persona.

Comprender los pasos de desarrollo

El núcleo de la API Kafka Stream es el objeto KStream. Muchos métodos usan una interfaz coherente. Muchas interfaces son interfaces funcionales y se pueden usar expresiones lambda.
El núcleo de la interfaz fluida es que el objeto que llama al método y el objeto devuelto por el método son los mismos. Es muy conveniente procesar objetos con programación en cadena. Creo que es más intuitivo modificar y cambiar objetos de diferentes maneras.
Pero la diferencia entre la API Kafka Stream es que cada vez que un KStream llama a un método, devuelve una copia del KStream, no el objeto original. [¿Por qué? ¿Cuáles son las ventajas de este diseño? ¿A dónde fue el objeto original? , ¿Cómo sé que este objeto es una copia en lugar del objeto original en uso]

Pasos generales de desarrollo

  1. Definir la configuración de Kafka Streams
  2. Cree una instancia de Serde, que se puede personalizar o usar por defecto
  3. Crear una topología de procesador
  4. Crea un mundo y comienza KStream

El libro utiliza la aplicación Yelling como ejemplo, pero el orden de explicación es 3-1-2-4. Lo leí dos veces y entendí el significado, y preparé las notas para que se escribieran en orden, de modo que cuando lo revisé yo mismo, la idea fue más clara.

Despliegue de Kafka Streams

El programa Kafka Streams es altamente configurable y se requieren dos elementos de configuración. Utilice el método props.put para configurar de la siguiente manera

props.put(StreamsConfig.APPLICATION_ID_CONFIG,"yelling_app_id");
props.put(StreamsConfig,BOOTSTRAP_SERVERS_CONFIG,"localhost:9092");

El método props.put que se encuentra en estas dos declaraciones asigna directamente el valor a la variable final en StreamsConfig, donde StreamsConfig debería ser una clase final, y el valor de la variable no se modifica en el programa. application_id es el identificador único del programa. Este ID debe ser único en el clúster. El nombre de host detrás: el nombre del puerto configura la posición de la aplicación actual en el clúster

Creación de instancias de Serde

El formato de almacenamiento del tema de Kafka es una matriz de bytes, el formato de transmisión de datos en las secuencias de kafka es json y los objetos utilizados en el procesamiento son objetos. Esto requiere que el mensaje (matriz de bytes) se convierta en json y luego en un objeto para que el procesador lo procese. Este proceso requiere que los objetos serde se serialicen o deserialicen. Los tipos básicos de serialización o deserialización se implementan mediante métodos de fábrica de Serds, como:

Serde<String> stringSerde = Serdes.String();

La interfaz serde es genérica, debe ser el tipo de objeto que necesita ser serializado o deserializado, y luego crear una instancia correspondiente, que puede ser devuelta por el método en la clase Serdes. Aquí se encuentra que Serdes es una clase que debe contener tipos comunes de métodos de serialización o deserialización. Otras clases personalizadas necesitan crear clases de Serde personalizadas. Serde es una interfaz para recibir dicha instancia de Serde, que es el contenedor del serializador y deserializador del objeto.

Aplicación de topología de gritos

Toda la información del tema se convierte en letras mayúsculas:
dos temas se utilizan como archivos de almacenamiento de datos y
tres procesadores: procesador de origen, convertido a letras mayúsculas y procesador receptor. Estos tres son la
topología completa que debe construirse con API . Encadenado

procesador fuente

KStream<String, String > simpleFirstStream = builder.stream("src-topic", Comsumed.with(stringSerde, stringSerde));

Se puede encontrar en esta declaración que el objeto KStream es genérico [¿es un par clave-valor? ], Creado por builder.stream (), requiere dos parámetros, el primer parámetro es Tema, el segundo parámetro es el objeto Serde. El objeto Serde se determina mediante el método Combinado con. Los parámetros requeridos por este último también son objetos Serde, que también se ajustan a los principios de las interfaces fluidas. Tenga en cuenta que el objeto Serde se usa para serializar o deserializar el mensaje. La clase Serde proporciona algunos tipos básicos de métodos de serialización o deserialización. Es necesario personalizar tipos especiales (métodos de anulación).
Entonces, las dos clases de Comsumed y Producido pueden entenderse como entrada y salida en la secuencia de E / S, o leer y escribir. Cuando se lee el mensaje (flujo de entrada) corresponde al mensaje de consumo. Cuando se escribe el mensaje (flujo de salida) corresponde al mensaje de producción.

Procesador de caracteres en mayúscula

KStream<String, String> upperCasedStream = simpleFirstStream.mapValues(String::toUpperCase);

Se puede ver en esta declaración que el método mapValues ​​es llamado por el objeto simpleFirstStream del procesador fuente, y se obtiene un nuevo objeto KStream como un procesador de caracteres en mayúscula. Este procesador es una copia del procesador fuente. Lo que hace el método mapValues ​​aquí es convertir el mensaje a mayúsculas.
Los parámetros en el método mapValues ​​usan expresiones lambda y llaman al método toUpperCase en la clase String. Esta es una expresión perezosa, que es muy clara para quienes escriben programas, y un poco vaga para quienes leen programas. De hecho, el parámetro de este método mapValues ​​es recibir una instancia de la interfaz ValueMaper <V, V1>. Hay un método de aplicación en esta interfaz, es decir, esta interfaz es una interfaz funcional y su función es procesar un valor entrante y procesarlo. Sale como otro valor (literalmente, mapa). Cuando no hay una expresión lambda, todo lo que necesita hacer es crear una instancia de esta interfaz con una clase anónima y reescribir el método de aplicación. La expresión lambda se usa directamente aquí. De hecho, la reescritura del método apply es llamar al método toUpperCase en la clase String. La expresión lambda completa puede ser (s) -> s.toUpperCase (), que utiliza la referencia del método de dos puntos.
Debido a que el método de mapValues ​​debe recibir el ejemplo anterior, las expresiones lambda no permiten escribir tantas cosas problemáticas. La interfaz y los métodos de reescritura no se reflejan en el código, pero creo que la capa inferior todavía se implementa paso a paso reescribiendo la interfaz y el método.
Probablemente porque soy Xiaobai, creo que la forma de escribir expresiones lambda sin interfaces es escribir el código por un tiempo, leer el código crematorio ... [¡Cerrar!

el procesador de sumidero escribe el mensaje procesado en el tema especificado

upperCasedStream.to("out-topic",Produed.with(stringSerde,stringSerde));

Esta oración encontró que en realidad no se creó un nuevo objeto KStream, ya que no hay una nueva topología, puede llamar al método to para escribir un mensaje. El método to requiere dos parámetros, un poco como builder.stream en el procesador fuente. Uno de los parámetros requeridos es el nombre del tema de salida, y el segundo parámetro es la instancia de Serde. Esta vez, Produced.with procesa la instancia de Serde dada.

En este punto, se crea una topología simple para una aplicación de gritos. El proceso de construcción es: crear un procesador fuente a partir del tema, llamar a mapValues ​​para devolver un procesador de caracteres en mayúsculas y llamar al método to para escribir el tema. Como cada paso devuelve una copia de KStream, puede usar la programación en cadena para ajustar el código de creación de topología anterior para:

builder.stream("src-topic, Consumed.with(stringSerde,stringSerde))
.mapValues(String::toUpperCase)
.to("out-topic",Produced.with(stringSerde, stringSerde));

Código fuente del programa

/*
 * Copyright 2016 Bill Bejeck
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package bbejeck.chapter_3;

import bbejeck.clients.producer.MockDataProducer;
import org.apache.kafka.common.serialization.Serde;
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.Consumed;
import org.apache.kafka.streams.KafkaStreams;
import org.apache.kafka.streams.StreamsBuilder;
import org.apache.kafka.streams.StreamsConfig;
import org.apache.kafka.streams.kstream.KStream;
import org.apache.kafka.streams.kstream.Printed;
import org.apache.kafka.streams.kstream.Produced;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Properties;

public class KafkaStreamsYellingApp {
	//log info
    private static final Logger LOG = LoggerFactory.getLogger(KafkaStreamsYellingApp.class);

    public static void main(String[] args) throws Exception {


        //Used only to produce data for this application, not typical usage
        MockDataProducer.produceRandomTextData();
		
		 //use properties class to configure application
        Properties props = new Properties();
        props.put(StreamsConfig.APPLICATION_ID_CONFIG, "yelling_app_id");
        props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");

        StreamsConfig streamsConfig = new StreamsConfig(props);
		 
		 //Serde instance
        Serde<String> stringSerde = Serdes.String();

		 //use builder to build topology
        StreamsBuilder builder = new StreamsBuilder();

        //source processor,read from topic
        KStream<String, String> simpleFirstStream = builder.stream("src-topic", Consumed.with(stringSerde, stringSerde));

		 //upperCase processor
        KStream<String, String> upperCasedStream = simpleFirstStream.mapValues(String::toUpperCase);

		 //sink processor, wrtie to topic
        upperCasedStream.to( "out-topic", Produced.with(stringSerde, stringSerde));
        
		 //console print?
        upperCasedStream.print(Printed.<String, String>toSysOut().withLabel("Yelling App"));

		 //build kafkaStream app
        KafkaStreams kafkaStreams = new KafkaStreams(builder.build(),streamsConfig);
        LOG.info("Hello World Yelling App Started");
		 
		 //start app
        kafkaStreams.start();
        Thread.sleep(35000);
        LOG.info("Shutting down the Yelling APP now");
  		 
  		 //close resources
        kafkaStreams.close();
        MockDataProducer.shutdown();

    }
}

Resumen

Los pasos generales de la aplicación Kafka Streams:

  1. Crear el programa de configuración de instancia StreamsConfig
  2. Crear deserializador de serialización de objetos Serde
  3. Construir el nodo de topología de procesador-KStreams
  4. Inicie la aplicación Kafka Streams

Cosas que no han sido resueltas:

  • El papel de la clase serde, cómo lograr la serialización y la deserialización
  • KStream <> Los dos tipos requeridos por este tipo genérico no entienden cuál es la relación, ¿es el valor clave correcto? ¿A qué corresponden estos dos tipos al crear un objeto? [¿Son los dos tipos de parámetros del método builder () posterior, el primero es el nombre del tema, debe ser String, el segundo es el tipo de mensaje procesado, la clase String en el ejemplo de la aplicación de gritos y la compra en el ejemplo de ZMart Clase?
  • Este simple ejemplo no me da un sentido de distribución. Está relacionado con el clúster físico y cómo el intermediario establece el procesamiento distribuido. No refleja los mensajes de otros intermediarios ni la resolución de problemas.
  • ¿Cómo cada nodo, es decir, el objeto KStream consume y produce mensajes, por ejemplo, cuando hay muchos mensajes, se leen uno por uno? ¿Crear un objeto KStream y la topología correspondiente una vez? [Obviamente, este no es el caso, pero la respuesta no se puede encontrar temporalmente en el código. Debe ser estable después de establecer una topología. Como comparo los datos normales de procesamiento de flujo de Java IO, es establecer un canal entre el programa y el archivo, y luego establecer la velocidad de procesamiento y transferir los datos. Pero aquí parece que acabo de especificar el tema, parece que no hay una declaración para leer y escribir datos, no lo entiendo aquí]
9 artículos originales publicados · Me gusta0 · Visitas 858

Supongo que te gusta

Origin blog.csdn.net/weixin_43138930/article/details/105468448
Recomendado
Clasificación