DataHub es totalmente compatible con el protocolo de Apache Kafka. Puede utilizar clientes nativos de Kafka para leer datos de DataHub y escribir datos en él.
Mapeo de Kafka a DataHub
Tipos de temas
Kafka y DataHub tienen mecanismos diferentes para escalar temas. Para garantizar la compatibilidad con el comportamiento de Kafka, debe establecer el modo de escalado en ONLY_EXTEND cuando cree un tema de DataHub. En este modo, solo puede agregar nuevos shards a un tema. Este modo no permite dividir ni fusionar shards, y aún no se admite la eliminación de shards.
Nomenclatura de temas
El nombre de un tema de Kafka se asigna a un proyecto y un tema de DataHub, separados por un punto (.). El mapeo sigue estas reglas:
La parte anterior al primer
.corresponde al proyecto de DataHub, y la parte posterior corresponde al tema de DataHub. Por ejemplo,test_project.test_topicse asigna al proyectotest_projecty al tematest_topic.Si un nombre contiene varios caracteres
., solo el primer.actúa como separador. Todos los caracteres.y-restantes se reemplazan por_.
Particiones
Cada shard activo en DataHub corresponde a una partición en Kafka. Por ejemplo, si un tema tiene cinco shards activos, equivale a un tema de Kafka con cinco particiones. Al escribir datos, puede especificar un ID de partición en el rango [0, 4]. Si no especifica una partición, el cliente de Kafka asigna una automáticamente.
Temas Tuple
Cuando escribe datos desde Kafka hacia un tema Tuple, el esquema del tema debe tener una o dos columnas, ambas de tipo STRING. De lo contrario, la operación de escritura fallará.
Si el esquema tiene una sola columna, solo se escribe el valor y se descarta la clave.
Si el esquema tiene dos columnas, la primera y la segunda columna corresponden a la clave y al valor, respectivamente.
Además, no escriba datos binarios en un tema Tuple, ya que esto provoca caracteres ilegibles. Para almacenar datos binarios, utilice un tema Blob.
Temas Blob
Cuando escribe datos desde Kafka hacia un tema Blob, el valor del mensaje de Kafka se escribe en el campo Blob. Si la clave del mensaje no es NULL, se escribe como un atributo de DataHub. El nombre del atributo es __kafka_key__ y su valor es la clave del mensaje de Kafka.
Encabezados
Los encabezados de Kafka se asignan a atributos de DataHub. Si el valor de un encabezado es NULL, se ignora y no se escribe como atributo. Se recomienda no utilizar __kafka_key__ como clave de encabezado para evitar conflictos con el nombre de atributo integrado en los temas Blob.
Grupos de consumidores
En DataHub, un ID de suscripción actúa como un grupo de consumidores, pero solo puede suscribirse a un único tema. En cambio, un grupo de consumidores de Kafka puede suscribirse a varios temas simultáneamente. Para ofrecer compatibilidad con el modelo de suscripción de Kafka, DataHub ofrece una función de grupos. Puede crear un grupo dentro de un proyecto y vincularlo a varios temas, lo que le permite suscribirse a todos ellos bajo un mismo grupo.
Un grupo gestiona internamente múltiples suscripciones de DataHub en el servidor. Después de vincular un tema, el grupo crea automáticamente una suscripción, la cual aparece en la lista de suscripciones de la página de detalles del tema. No elimine esta suscripción manualmente. Hacerlo impedirá que el grupo se suscriba al tema y provocará la pérdida de todos los desplazamientos de consumo existentes.
Un solo grupo puede suscribirse a un máximo de 50 temas. Para suscribirse a más temas, envíe un ticket de soporte.
Parámetros de Kafka
C=Consumidor, P=Productor, S=Streams
Parámetro | C/P/S | Valor | Obligatorio | Descripción |
bootstrap.servers | * | Consulte la sección de endpoints de Kafka. | Sí | |
security.protocol | * | SASL_SSL | Sí | Para garantizar la seguridad de los datos, las conexiones de Kafka a DataHub utilizan cifrado SSL de forma predeterminada. |
sasl.mechanism | * | PLAIN | Sí | El mecanismo de autenticación para credenciales AccessKey. Solo se admite PLAIN. |
compression.type | P | LZ4 | No | Especifica el tipo de compresión para los mensajes. Actualmente, solo se admite LZ4. |
group.id | C | project.topic:subId o bien project.group | Sí | Si utiliza el formato |
partition.assignment.strategy | C | org.apache.kafka.clients.consumer.RangeAssignor | No | La estrategia de asignación de particiones predeterminada en Kafka es |
session.timeout.ms | C/S | [60000, 180000] | No | El valor predeterminado en Kafka es 10,000 ms. Sin embargo, dado que DataHub requiere un mínimo de 60,000 ms, este valor se ajusta automáticamente a 60,000 ms. |
heartbeat.interval.ms | C/S | Recomendado: 2/3 de session.timeout.ms | No | El valor predeterminado de Kafka es 3,000 ms. Dado que |
application.id | S | project.topic:subId o bien project.group | Sí | Si utiliza el formato |
Esta tabla enumera los parámetros clave que deben revisarse al utilizar un cliente de Kafka con DataHub. Otros parámetros del lado del cliente, como retries y batch.size, se comportan igual que en Kafka nativo. Los parámetros del lado del servidor no modifican el comportamiento real de DataHub. Por ejemplo, independientemente del valor de acks, DataHub devuelve una confirmación únicamente después de que los datos se han escrito por completo.
Endpoints de Kafka
|
Región |
ID de región |
Endpoint público |
Endpoint de ECS (red clásica) |
Endpoint de ECS (VPC) |
|
China (Hangzhou) |
cn-hangzhou |
dh-cn-hangzhou.aliyuncs.com:9092 |
dh-cn-hangzhou.aliyun-inc.com:9093 |
dh-cn-hangzhou-int-vpc.aliyuncs.com:9094 |
|
China (Shanghai) |
cn-shanghai |
dh-cn-shanghai.aliyuncs.com:9092 |
dh-cn-shanghai.aliyun-inc.com:9093 |
dh-cn-shanghai-int-vpc.aliyuncs.com:9094 |
|
China (Beijing) |
cn-beijing |
dh-cn-beijing.aliyuncs.com:9092 |
dh-cn-beijing.aliyun-inc.com:9093 |
dh-cn-beijing-int-vpc.aliyuncs.com:9094 |
|
China (Zhangjiakou) |
cn-zhangjiakou |
dh-cn-zhangjiakou.aliyuncs.com:9092 |
dh-cn-zhangjiakou.aliyun-inc.com:9093 |
dh-cn-zhangjiakou-int-vpc.aliyuncs.com:9094 |
|
China (Shenzhen) |
cn-shenzhen |
dh-cn-shenzhen.aliyuncs.com:9092 |
dh-cn-shenzhen.aliyun-inc.com:9093 |
dh-cn-shenzhen-int-vpc.aliyuncs.com:9094 |
|
Singapore |
ap-southeast-1 |
dh-ap-southeast-1.aliyuncs.com:9092 |
dh-ap-southeast-1.aliyun-inc.com:9093 |
dh-ap-southeast-1-int-vpc.aliyuncs.com:9094 |
|
Malaysia (Kuala Lumpur) |
ap-southeast-3 |
dh-ap-southeast-3.aliyuncs.com:9092 |
dh-ap-southeast-3.aliyun-inc.com:9093 |
dh-ap-southeast-3-int-vpc.aliyuncs.com:9094 |
|
Germany (Frankfurt) |
eu-central-1 |
dh-eu-central-1.aliyuncs.com:9092 |
dh-eu-central-1.aliyun-inc.com:9093 |
dh-eu-central-1-int-vpc.aliyuncs.com:9094 |
|
China East 2 Finance |
cn-shanghai-finance-1 |
dh-cn-shanghai-finance-1.aliyuncs.com:9092 |
dh-cn-shanghai-finance-1.aliyun-inc.com:9093 |
dh-cn-shanghai-finance-1-int-vpc.aliyuncs.com:9094 |
|
China (Hong Kong) |
cn-hongkong |
dh-cn-hongkong.aliyuncs.com:9092 |
dh-cn-hongkong.aliyun-inc.com:9093 |
dh-cn-hongkong-int-vpc.aliyuncs.com:9094 |
Crear un tema
-
Crear un tema en la consola
Al crear un tema, habilite el modo de expansión de shards (Shard Expand Mode).
-
Crear un tema mediante el SDK
No es posible crear temas mediante la API de Kafka. Debe utilizar el SDK de DataHub y establecer
ExpandModeenONLY_EXTEND. La versión de dependencia de Maven requerida es la 2.19.0 o posterior.Se recomienda utilizar variables de entorno para configurar su AccessKey ID y AccessKey Secret, y evitar codificarlos directamente en el código de su proyecto. El par de AccessKey de una cuenta de Alibaba Cloud tiene permisos para todas las operaciones de la API. Para mayor seguridad, utilice un par de AccessKey de un usuario de RAM para el acceso a la API o las operaciones diarias, con el fin de reducir el riesgo de fuga de credenciales.
datahub.endpoint=<yourEndpoint> datahub.accessId=<yourAccessKeyId> datahub.accessKey=<yourAccessKeySecret><dependency> <groupId>com.aliyun.datahub</groupId> <artifactId>aliyun-sdk-datahub</artifactId> <version>2.19.0-public</version> </dependency>@Value("${datahub.endpoint}") String endpoint ; @Value("${datahub.accessId}") String accessId; @Value("${datahub.accessKey}") String accessKey; public class CreateTopic { public static void main(String[] args) { DatahubClient datahubClient = DatahubClientBuilder.newBuilder() .setDatahubConfig( new DatahubConfig(endpoint, new AliyunAccount(accessId, accessKey))) .build(); int shardCount = 1; int lifeCycle = 7; try { datahubClient.createTopic("test_project", "test_topic", shardCount, lifeCycle, RecordType.BLOB, "comment", ExpandMode.ONLY_EXTEND); } catch (DatahubClientException e) { e.printStackTrace(); } } }
Crear un grupo
-
Crear un grupo en la consola
Haga clic en Create Group y agregue los temas a los que desea suscribirse desde la lista de la derecha. Puede modificar los temas vinculados después de crear el grupo. El grupo crea automáticamente una suscripción, la cual aparece en la página de lista de suscripciones del tema.
-
Crear un grupo mediante el SDK
La versión de dependencia de Maven debe ser la 2.21.6-public o posterior.
<dependency> <groupId>com.aliyun.datahub</groupId> <artifactId>aliyun-sdk-datahub</artifactId> <version>2.21.6-public</version> </dependency>@Value("${datahub.endpoint}") String endpoint ; @Value("${datahub.accessId}") String accessId; @Value("${datahub.accessKey}") String accessKey; public class CreateGroup { public static void main(String[] args) { DatahubClient datahubClient = DatahubClientBuilder.newBuilder() .setDatahubConfig( new DatahubConfig(endpoint, new AliyunAccount(accessId, accessKey))) .build(); List<String> topicList = new ArrayList<>(); topicList.add("test_project.topic1"); topicList.add("test_project.topic2"); topicList.add("test_project.topic3"); try { // Create a Kafka group. datahubClient.createKafkaGroup("test_project", "test_topic", "test comment"); // Bind the topics to the group for subscription. datahubClient.updateTopicsForKafkaGroup("test_project", "test_topic", topicList, UpdateKafkaGroupMode.ADD); } catch (DatahubClientException e) { e.printStackTrace(); } } }
Ejemplo de productor
El archivo kafka_client_producer_jaas.conf
Cree un archivo llamado kafka_client_producer_jaas.conf en cualquier directorio y agregue el siguiente contenido.
KafkaClient {
org.apache.kafka.common.security.plain.PlainLoginModule required
username="yourAccessKeyId"
password="yourAccessKeySecret";
};
Dependencia de Maven
La versión del cliente de Kafka debe ser la 0.10.0.0 o posterior. Se recomienda la versión 2.4.0.
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.4.0</version>
</dependency>
Código de ejemplo
public class ProducerExample {
static {
System.setProperty("java.security.auth.login.config", "src/main/resources/kafka_client_producer_jaas.conf");
}
public static void main(String[] args) {
Properties properties = new Properties();
properties.put("bootstrap.servers", "dh-cn-hangzhou.aliyuncs.com:9092");
properties.put("security.protocol", "SASL_SSL");
properties.put("sasl.mechanism", "PLAIN");
properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
properties.put("compression.type", "lz4");
String KafkaTopicName = "test_project.test_topic";
Producer<String, String> producer = new KafkaProducer<String, String>(properties);
try {
List<Header> headers = new ArrayList<>();
RecordHeader header1 = new RecordHeader("key1", "value1".getBytes());
RecordHeader header2 = new RecordHeader("key2", "value2".getBytes());
headers.add(header1);
headers.add(header2);
ProducerRecord<String, String> record = new ProducerRecord<>(KafkaTopicName, 0, "key", "Hello DataHub!", headers);
// Sync send
producer.send(record).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} finally {
producer.close();
}
}
}
Resultado
Una vez que el código se ejecute correctamente, puede muestrear datos para verificar el resultado.
Ejemplo de consumidor
Para obtener información sobre cómo generar el archivo kafka_client_producer_jaas.conf y agregar la dependencia de Maven, consulte el ejemplo del productor.
Cuando se une un nuevo consumidor, la asignación de shards tarda entre 10 y 20 segundos. Una vez completada la asignación, el consumidor puede comenzar a consumir datos.
Código de ejemplo
Uso de un grupo de Kafka (recomendado)
package com.aliyun.datahub.kafka.demo;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
public class ConsumerExample2 {
static {
System.setProperty("java.security.auth.login.config", "src/main/resources/kafka_client_producer_jaas.conf");
}
public static void main(String[] args) {
Properties properties = new Properties();
properties.put("bootstrap.servers", "dh-cn-hangzhou.aliyuncs.com:9092");
properties.put("security.protocol", "SASL_SSL");
properties.put("sasl.mechanism", "PLAIN");
// Set group.id to the project.group format.
properties.put("group.id", "test_project.test_kafka_group");
properties.put("auto.offset.reset", "earliest");
properties.put("session.timeout.ms", "60000");
properties.put("heartbeat.interval.ms", "40000");
properties.put("ssl.endpoint.identification.algorithm", "");
properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<String, String>(properties);
List<String> topicList = new ArrayList<>();
topicList.add("test_project.test_topic1");
topicList.add("test_project.test_topic2");
topicList.add("test_project.test_topic3");
// By using a Kafka group, you can subscribe to multiple topics.
kafkaConsumer.subscribe(topicList);
while (true) {
ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofSeconds(5));
for (ConsumerRecord<String, String> record : records) {
System.out.println(record.toString());
}
}
}
}
Uso de project.topic:subId
package com.aliyun.datahub.kafka.demo;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import java.time.Duration;
import java.util.Collections;
import java.util.Properties;
public class ConsumerExample {
static {
System.setProperty("java.security.auth.login.config", "src/main/resources/kafka_client_producer_jaas.conf");
}
public static void main(String[] args) {
Properties properties = new Properties();
properties.put("bootstrap.servers", "dh-cn-hangzhou.aliyuncs.com:9092");
properties.put("security.protocol", "SASL_SSL");
properties.put("sasl.mechanism", "PLAIN");
// Set group.id to the project.topic:subId format.
properties.put("group.id", "test_project.test_topic:1611039998153N71KM");
properties.put("auto.offset.reset", "earliest");
properties.put("session.timeout.ms", "60000");
properties.put("heartbeat.interval.ms", "40000");
properties.put("ssl.endpoint.identification.algorithm", "");
properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<String, String>(properties);
// When using the project.topic:subId format, you can only subscribe to a single topic.
kafkaConsumer.subscribe(Collections.singletonList("test_project.test_topic"));
while (true) {
ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofSeconds(5));
for (ConsumerRecord<String, String> record : records) {
System.out.println(record.toString());
}
}
}
}
Resultado
Después de que el código se ejecute correctamente, podrá ver los datos consumidos en su terminal.
ConsumerRecord(topic = test_project.test_topic, partition = 0, leaderEpoch = 0, offset = 0, LogAppendTime = 1611040892661, serialized key size = 3, serialized value size = 14, headers = RecordHeaders(headers = [RecordHeader(key = key1, value = [118, 97, 108, 117, 101, 49]), RecordHeader(key = key2, value = [118, 97, 108, 117, 101, 50])], isReadOnly = false), key = key, value = Hello DataHub!)
En este ejemplo, todos los registros de datos devueltos en una sola solicitud tienen el mismo LogAppendTime, que corresponde a la marca de tiempo más reciente entre todos los registros de ese lote.
Ejemplo de Streams
Dependencia de Maven
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-streams</artifactId>
<version>2.4.0</version>
</dependency>
Código de ejemplo
Este ejemplo lee datos de un tema de entrada dentro de test_project, convierte las cadenas de clave y valor a minúsculas, y escribe el resultado en un tema de salida.
public class StreamExample {
static {
System.setProperty("java.security.auth.login.config", "src/main/resources/kafka_client_producer_jaas.conf");
}
public static void main(final String[] args) {
final String input = "test_project.input";
final String output = "test_project.output";
final Properties properties = new Properties();
properties.put("bootstrap.servers", "dh-cn-hangzhou.aliyuncs.com:9092");
properties.put("application.id", "test_project.input:1611293595417QH0WL");
properties.put("security.protocol", "SASL_SSL");
properties.put("sasl.mechanism", "PLAIN");
properties.put("session.timeout.ms", "60000");
properties.put("heartbeat.interval.ms", "40000");
properties.put("auto.offset.reset", "earliest");
final StreamsBuilder builder = new StreamsBuilder();
TestMapper testMapper = new TestMapper();
builder.stream(input, Consumed.with(Serdes.String(), Serdes.String()))
.map(testMapper)
.to(output, Produced.with(Serdes.String(), Serdes.String()));
final KafkaStreams streams = new KafkaStreams(builder.build(), properties);
final CountDownLatch latch = new CountDownLatch(1);
Runtime.getRuntime().addShutdownHook(new Thread("streams-shutdown-hook") {
@Override
public void run() {
streams.close();
latch.countDown();
}
});
try {
streams.start();
latch.await();
} catch (final Throwable e) {
System.exit(1);
}
System.exit(0);
}
static class TestMapper implements KeyValueMapper<String, String, KeyValue<String, String>> {
@Override
public KeyValue<String, String> apply(String s, String s2) {
return new KeyValue<>(StringUtils.lowerCase(s), StringUtils.lowerCase(s2));
}
}
}
Resultado
Después de iniciar la tarea de Streams, la asignación de shards tarda aproximadamente un minuto. Posteriormente, podrá ver el número de tareas actuales en la consola. El número de tareas coincide con el número de shards del tema de entrada. En este ejemplo, el tema de entrada tiene tres shards.
currently assigned active tasks: [0_0, 0_1, 0_2]
currently assigned standby tasks: []
revoked active tasks: []
revoked standby tasks: []
Una vez asignados los shards, puede escribir datos de prueba como (AAAA,BBBB),(CCCC,DDDD),(EEEE,FFFF) en el tema de entrada. Luego, muestree datos del tema de salida para verificar que se escribieron correctamente.
Notas de uso
No se admiten transacciones ni idempotencia.
Los clientes de Kafka no pueden crear temas automáticamente en DataHub. Debe crear el tema antes de escribir datos en él.
Al utilizar un ID de suscripción (
project.topic:subid) comogroup.id, un consumidor solo puede suscribirse a un tema. Para suscribirse a varios temas, utilice un grupo de DataHub.La marca de tiempo de los datos leídos por un consumidor siempre es el LogAppendTime, que indica cuándo se escribieron los datos en DataHub. Todos los registros en una sola solicitud de obtención comparten la misma marca de tiempo: la más reciente de ese lote. Esto significa que la marca de tiempo de lectura podría ser posterior a la hora real de escritura.
Una aplicación de Streams admite solo un tema de entrada, pero puede tener varios temas de salida.
Solo se admiten tareas de Streams sin estado.
Las versiones compatibles de Kafka van desde la 0.10.0 hasta la 2.4.0.
Preguntas frecuentes
Desconexión al escribir datos
Selector - [Producer clientId=producer-1] Connection with dh-cn-shenzhen.aliyuncs.com disconnected
java.io.EOFException
at org.apache.kafka.common.network.SslTransportLayer.read(SslTransportLayer.java:573)
...
Las solicitudes de metadatos de Kafka y las solicitudes de escritura de datos utilizan conexiones diferentes.
El cliente establece primero una conexión para obtener metadatos. Luego utiliza la información del broker devuelta para establecer una segunda conexión destinada a la escritura de datos. Todas las solicitudes posteriores se envían a través de esta segunda conexión.
La primera conexión, ahora inactiva, se cierra automáticamente por el servidor tras un tiempo de espera. Esto puede generar un error de desconexión en los registros. Puede ignorar este error si los datos se están escribiendo correctamente.
Fallo al iniciar el cliente de Kafka
Caused by: org.apache.kafka.common.errors.SslAuthenticationException: SSL handshake failed
Caused by: javax.net.ssl.SSLHandshakeException: No subject alternative names matching IP address 100.67.134.161 found
Agregue la siguiente propiedad a su configuración: properties.put("ssl.endpoint.identification.algorithm", "");.
DisconnectException durante el consumo
[INFO][Consumer clientId=client-id, groupId=consumer-project.topic:subid] Error sending fetch request (sessionId=INVALID, epoch=INITIAL) to node 1: {}.
org.apache.kafka.common.errors.DisconnectException
El cliente de Kafka debe mantener una conexión TCP persistente con el servidor. Esta excepción suele deberse a inestabilidad en la red. El cliente cuenta con una lógica de reintento integrada, por lo que este error generalmente no afecta el consumo.