1.目标

在这个Kafka Schema Registry教程中,我们将了解Schema Registry是什么以及为什么我们应该将它与Apache Kafka一起使用此外,我们将看到Avro架构演变的概念,并使用Kafka Avro Serializers设置和使用Schema Registry。此外,我们将学习使用Schema Registry的REST接口管理Avro Schemas。 
那么,让我们讨论一下Apache Kafka Schema Registry。

Kafka Schema Registry | 学习Avro Schema

Apache Kafka架构注册表

2.什么是Kafka Schema Registry?

基本上,对于Kafka ProducersKafka ConsumerKafka的 Schema Registry都存储Avro Schemas。

  • 它提供了一个用于管理Avro架构的RESTful界面。
  • 它允许存储版本化模式的历史记录。
  • 此外,它还支持检查Kafka的架构兼容性。
  • 使用Avro Schema,我们可以配置兼容性设置以支持Kafka模式的发展。

学习Apache Kafka用例和应用程序

基本上,Kafka Avro序列化项目提供了序列化程序。在Avro和Kafka Schema Registry的帮助下,使用Kafka Avro序列化的Kafka Producers和Kafka Consumer都处理模式管理以及记录的序列化。

Kafka Schema Registry | 学习Avro Schema

Kafka Schema Registry简介

此外,生产者在使用Kafka中的Confluent Schema Registry时不必发送模式,只需要唯一的模式ID。因此,为了从Confluent模式注册表中查找完整模式(如果尚未缓存),使用者使用模式ID。这意味着不必使用每组记录发送模式,这样也可以节省时间。 

但是,Kafka制作人也创建了一条记录/消息,即Avro记录。该记录包含模式ID和数据。此外,如果需要,还会注册架构,然后使用Kafka Avro Serializer序列化数据和架构ID。 
我们来讨论Apache Kafka架构及其基本概念

3.为什么在Kafka中使用Schema Registry? 

消费者的架构可能与生产者的架构不同。在定义消费者模式时,消费者期望记录/消息符合要求。执行检查时,如果两个模式不匹配但兼容,则通过Avma Schema Evolution和Schema Registry进行有效负载转换。此外,Kafka记录可以有一个键和一个值,两者都可以有一个模式。

4. Kafka Schema注册局运营

但是,对于Kafka记录的键和值,Schema Registry可以存储模式。此外,它按主题列出模式。此外,它可以列出主题(模式)的所有版本。此外,它可以按版本或ID检索架构。并且可以获得最新版本的模式。
Apache Kafka架构注册表的一些兼容级别是向后,向前,完整,无。此外,我们可以通过REST API使用Schema注册表管理模式。
修改Apache Kafka操作和命令。

5. Kafka Schema兼容性设置

让我们了解所有兼容性级别。基本上,向后兼容性是指使用较旧模式编写的数据,可以使用较新的模式进行读取。此外,向前兼容性是指使用旧模式可读取使用较新模式编写的数据。此外,完全兼容性是指新版本的模式是向后兼容的。并且,“无”状态,意味着它禁用模式验证,不建议这样做。因此,Schema Registry只存储模式,如果我们将级别设置为“none”,它将不会验证兼容性。

一个。架构注册表配置

无论是全球还是每个主题。
兼容性值为:
A。无
表示不检查架构兼容性。
B.转发
说,检查以确保最后一个模式版本与新模式向前兼容。
C.向后(默认)
这意味着确保新模式向后兼容最新模式。
D. Full 
“Full”表示确保新模式从最新到最新以及从最新到最新都是向前和向后兼容的。
阅读Storm Kafka与配置和代码的集成

6.架构演变

虽然,如果使用该架构的旧版本,在将数据写入存储后会更改Avro架构,那么当我们尝试读取该数据时,Avro可能会进行架构演变。
但是,从Kafka的角度来看,模式演变只发生在消费者的反序列化(阅读)中。并且,如果可能,则在反序列化期间自动修改值或键,以便在消费者的模式与生产者的模式不同时符合消费者的读取模式。
简而言之,它是消费者模式版本与生产者放入Kafka日志的模式之间的Avro模式的自动转换。但是,当消费者模式与用于序列化Kafka记录的生产者模式不同时,将对Kafka记录的键或值执行数据转换。虽然,如果模式匹配,则无需进行转换。

一个。模式演变过程中允许的修改

可以将具有默认值的字段添加到架构。我们可以删除具有默认值的字段。此外,我们可以更改字段的订单属性。此外,我们可以将字段的默认值更改为其他值,或者将默认值添加到没有字段的字段中。
但是,可以删除或添加字段别名,但这可能会导致某些依赖别名的使用者中断。此外,我们可以将类型更改为包含原始类型的联合。上述更改将导致我们的架构在使用旧架构读取时可以使用Avro的架构演变。

湾 修改架构的道路规则

如果我们想让我们的架构可以进化,我们必须遵循这些准则。首先,我们需要为模式中的字段提供默认值,因为这允许我们稍后删除该字段。请记住,永远不要更改字段的数据类型。此外,在向架构添加新字段时,我们必须为字段提供默认值。并且,请确保不要重命名现有字段(而是使用别名)。
我们来讨论Apache Kafka Streams | 流处理拓扑
例如:

  • 员工示例Avro架构:
  1. {"namespace": "com.dataflair.phonebook",
     "type": "record",
     "name": "Employee",
     "doc" : "Represents an Employee at a company",
     "fields": [
       {"name": "firstName", "type": "string", "doc": "The persons given name"},
       {"name": "nickName", "type": ["null", "string"], "default" : null},
       {"name": "lastName", "type": "string"},
       {"name": "age",  "type": "int", "default": -1},
       {"name": "emails", "default":[], "type":{"type": "array", "items": "string"}},
       {"name": "phoneNumber",  "type":
       [ "null",
         { "type": "record",   "name": "PhoneNumber",
           "fields": [
             {"name": "areaCode", "type": "string"},
             {"name": "countryCode", "type": "string", "default" : ""},
             {"name": "prefix", "type": "string"},
             {"name": "number", "type": "string"}
           ]
         }
       ]
       },
       {"name":"status", "default" :"SALARY", "type": { "type": "enum", "name": "Status",
         "symbols" : ["RETIRED", "SALARY", "HOURLY", "PART_TIME"]}
       }
     ]
    }

     

7. Avro Schema Evolution场景

假设在模式的版本1中,我们的员工记录没有年龄因素。但现在我们要添加一个默认值为-1的年龄字段。所以,假设我们有一个消费者使用版本1没有年龄,生产者使用模式的版本2与年龄
现在,通过使用Employee模式的版本2生产者,创建一个com.dataflair.Employee记录设置年龄字段到42,然后将其发送给Kafka主题new-Employees。之后,使用版本1,使用者将使用Employee架构的新员工记录。因此,在反序列化期间删除age字段只是因为使用者正在使用模式的版本1。

您是否知道Kafka和RabbitMQ之间的区别
此外,同一个消费者修改了一些记录,然后将记录写入NoSQL商店。因此,它写入NoSQL存储的记录中缺少age字段。现在,使用模式的第2版,另一个具有年龄的客户端从NoSQL存储中读取记录。因此,由于Consumer使用版本1编写它,因此记录中缺少age字段,因此客户端读取记录并将age设置为默认值-1。
因此,Schema Registry可以拒绝该模式,并且生产者永远不会将其添加到Kafka日志中,如果我们添加了年龄并且它不是可选的,即age字段没有默认值。

8.使用Schema Registry REST API

此外,通过使用以下操作,Kafka中的Schema Registry允许我们管理模式:

  1. 存储Kafka记录的键和值的模式
  2. 按主题列出模式
  3. 列出主题的所有版本(架构)
  4. 按版本检索架构
  5. 按ID检索架构
  6. 检索最新版本的架构
  7. 执行兼容性检查
  8. 全局设置兼容级别

您是否知道Apache Kafka职业范围及其薪资趋势
然而,所有这些都可通过REST API与Kafka中的Schema Registry一起获得。
我们可以执行以下操作,以便发布新架构:

一个。发布新架构

  1. curl -X POST -H "Content-Type:
    application/vnd.schemaregistry.v1+json" \
       --data '{"schema": "{\"type\": …}’ \
       http://localhost:8081/subjects/Employee/versions

     

湾 列出所有模式

curl -X GET http:// localhost:8081 / subject
我们基本上可以通过Schema Registry的REST接口执行上述所有操作,只有你有一个好的HTTP客户端。例如,Schema Registry使用Square中的OkHttp客户端(com.squareup.okhttp3:okhttp:3.7.0+)稍微好一点,如下所示:

  • 使用REST端点尝试所有Schema Registry选项:

  1. package com.dataflair.kafka.schema;
    import okhttp3.*;
    import java.io.IOException;
    public class SchemaMain {
       private final static MediaType SCHEMA_CONTENT =
               MediaType.parse("application/vnd.schemaregistry.v1+json");
       private final static String Employee_SCHEMA = "{\n" +
               " \"schema\": \"" +
               " {" +
               " \\\"namespace\\\": \\\"com.dataflair.phonebook\\\"," +
               " \\\"type\\\": \\\"record\\\"," +
               " \\\"name\\\": \\\"Employee\\\"," +
               " \\\"fields\\\": [" +
               " {\\\"name\\\": \\\"fName\\\", \\\"type\\\": \\\"string\\\"}," +
               " {\\\"name\\\": \\\"lName\\\", \\\"type\\\": \\\"string\\\"}," +
               " {\\\"name\\\": \\\"age\\\",  \\\"type\\\": \\\"int\\\"}," +
               " {\\\"name\\\": \\\"phoneNumber\\\",  \\\"type\\\": \\\"string\\\"}" +
               " ]" +
               " }\"" +
               "}";
       public static void main(String... args) throws IOException {
           System.out.println(Employee_SCHEMA);
           final OkHttpClient client = new OkHttpClient();
           //POST A NEW SCHEMA
           Request request = new Request.Builder()
                   .post(RequestBody.create(SCHEMA_CONTENT, Employee_SCHEMA))
                   .url("http://localhost:8081/subjects/Employee/versions")
                   .build();
           String output = client.newCall(request).execute().body().string();
           System.out.println(output);
           //LIST ALL SCHEMAS
           request = new Request.Builder()
                   .url("http://localhost:8081/subjects")
                   .build();
           output = client.newCall(request).execute().body().string();
           System.out.println(output);
           //SHOW ALL VERSIONS OF Employee
           request = new Request.Builder()
                   .url("http://localhost:8081/subjects/Employee/versions/")
                   .build();
           output = client.newCall(request).execute().body().string();
           System.out.println(output);
           //SHOW VERSION 2 OF Employee
           request = new Request.Builder()
                   .url("http://localhost:8081/subjects/Employee/versions/2")
                   .build();
           output = client.newCall(request).execute().body().string();
           System.out.println(output);
           //SHOW THE SCHEMA WITH ID 3
           request = new Request.Builder()
                   .url("http://localhost:8081/schemas/ids/3")
                   .build();
           output = client.newCall(request).execute().body().string();
           System.out.println(output);
           //SHOW THE LATEST VERSION OF Employee 2
           request = new Request.Builder()
                   .url("http://localhost:8081/subjects/Employee/versions/latest")
                   .build();
           output = client.newCall(request).execute().body().string();
           System.out.println(output);
           //CHECK IF SCHEMA IS REGISTERED
           request = new Request.Builder()
                   .post(RequestBody.create(SCHEMA_CONTENT, Employee_SCHEMA))
                   .url("http://localhost:8081/subjects/Employee")
                   .build();
           output = client.newCall(request).execute().body().string();
           System.out.println(output);
           //TEST COMPATIBILITY
           request = new Request.Builder()
                   .post(RequestBody.create(SCHEMA_CONTENT, Employee_SCHEMA))
                   .url("http://localhost:8081/compatibility/subjects/Employee/versions/latest")
                   .build();
           output = client.newCall(request).execute().body().string();
           System.out.println(output);
           // TOP LEVEL CONFIG
           request = new Request.Builder()
                   .url("http://localhost:8081/config")
                   .build();
           output = client.newCall(request).execute().body().string();
           System.out.println(output);
           // SET TOP LEVEL CONFIG
           // VALUES are none, backward, forward and full
           request = new Request.Builder()
                   .put(RequestBody.create(SCHEMA_CONTENT, "{\"compatibility\": \"none\"}"))
                   .url("http://localhost:8081/config")
                   .build();
           output = client.newCall(request).execute().body().string();
           System.out.println(output);
           // SET CONFIG FOR Employee
           // VALUES are none, backward, forward and full
           request = new Request.Builder()
                   .put(RequestBody.create(SCHEMA_CONTENT, "{\"compatibility\": \"backward\"}"))
                   .url("http://localhost:8081/config/Employee")
                  .build();
           output = client.newCall(request).execute().body().string();
          System.out.println(output);
       }
    }

     

我们建议运行该示例以尝试强制不兼容的架构到架构注册表,并且还要注意各种兼容性设置的行为。

让我们修改Kafka vs Storm

C。运行Kafka架构注册表:

$ cat~ / tools / confluent-3.2.1 / etc / schema-registry / schema-registry.properties 
listeners = http://0.0.0.0:8081 
kafkastore.connection.url = localhost:2181 
kafkastore.topic = _schemas 
debug = false 
~ / tools / confluent-3.2.1 / bin / schema-registry-start~ / tools / confluent-3.2.1 / etc / schema-registry / schema-registry.properties

9.撰写消费者和生产者 

在这里,我们将要求启动指向ZooKeeper集群的Schema Registry服务器。此外,我们可能需要将Kafka Avro Serializer和Avro JAR导入我们的Gradle项目。之后,我们将要求配置生产者使用Schema Registry和KafkaAvroSerializer。此外,我们将要求将其配置为使用Schema Registry并使用KafkaAvroDeserializer来编写使用者。
因此,此构建文件显示了我们需要的Avro JAR文件。
阅读Apache Kafka Security | Kafka的需求和组成部分

  • Kafka Avro Serializer示例的Gradle构建文件:
  1. plugins {
       id "com.commercehub.gradle.plugin.avro" version "0.9.0"
    }
    group 'dataflair'
    version '1.0-SNAPSHOT'
    apply plugin: 'java'
    sourceCompatibility = 1.8
    dependencies {
       compile "org.apache.avro:avro:1.8.1"
       compile 'com.squareup.okhttp3:okhttp:3.7.0'
       testCompile 'junit:junit:4.11'
       compile 'org.apache.kafka:kafka-clients:0.10.2.0'
       compile 'io.confluent:kafka-avro-serializer:3.2.1'
    }
    repositories {
       jcenter()
       mavenCentral()
       maven {
           url "http://packages.confluent.io/maven/"
       }
    }
    avro {
       createSetters = false
       fieldVisibility = "PRIVATE"
    }

     

请记住包括Kafka Avro Serializer lib(io.confluent:kafka-avro-serializer:3.2.1)和Avro lib(org.apache.avro:avro:1.8.1)。

一个。写一个制片人

让我们按如下方式编写制作人。

  • 使用Kafka Avro Serialization和Kafka Registry的制作人:
package com.dataflair.kafka.schema;
import com.dataflair.phonebook.Employee;
import com.dataflair.phonebook.PhoneNumber;
import io.confluent.kafka.serializers.KafkaAvroSerializerConfig;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.LongSerializer;
import io.confluent.kafka.serializers.KafkaAvroSerializer;
import java.util.Properties;
import java.util.stream.IntStream;

 

public class AvroProducer {
   private static Producer<Long, Employee> createProducer() {
       Properties props = new Properties();
       props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
       props.put(ProducerConfig.CLIENT_ID_CONFIG, "AvroProducer");
       props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
               LongSerializer.class.getName());
       // Configure the KafkaAvroSerializer.
      props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
               KafkaAvroSerializer.class.getName());
       // Schema Registry location.
       props.put(KafkaAvroSerializerConfig.SCHEMA_REGISTRY_URL_CONFIG,
               "http://localhost:8081");
       return new KafkaProducer<>(props);
   }
   private final static String TOPIC = "new-Employees";
   public static void main(String... args) {
       Producer<Long, Employee> producer = createProducer();
        Employee bob = Employee.newBuilder().setAge(35)
               .setFirstName("Bob")
               .setLastName("Jones")
               .setPhoneNumber(
                       PhoneNumber.newBuilder()
                               .setAreaCode("301")
                               .setCountryCode("1")
                               .setPrefix("555")
                               .setNumber("1234")
                              .build())
               .build();
       IntStream.range(1, 100).forEach(index->{
           producer.send(new ProducerRecord<>(TOPIC, 1L * index, bob));
       });
       producer.flush();
       producer.close();
   }
}

 

 

另外,请确保我们将Schema Registry和KafkaAvroSerializer配置为生成器设置的一部分。

我们来讨论Kafka主题

  1. // Configure the KafkaAvroSerializer.
    props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
                   KafkaAvroSerializer.class.getName());
    // Schema Registry location.        props.put(KafkaAvroSerializerConfig.SCHEMA_REGISTRY_URL_CONFIG,
                   "http://localhost:8081");

     

此外,我们按预期使用生产者

写一个消费者

之后,我们将写信给消费者。

  • 使用Kafka Avro序列化和架构注册表的Kafka Consumer:
  1. package com.dataflair.kafka.schema;
    import com.dataflair.phonebook.Employee;
    import io.confluent.kafka.serializers.KafkaAvroDeserializer;
    import io.confluent.kafka.serializers.KafkaAvroDeserializerConfig;
    import org.apache.kafka.clients.consumer.Consumer;
    import org.apache.kafka.clients.consumer.ConsumerConfig;
    import org.apache.kafka.clients.consumer.ConsumerRecords;
    import org.apache.kafka.clients.consumer.KafkaConsumer;
    import org.apache.kafka.common.serialization.LongDeserializer;
    import java.util.Collections;
    import java.util.Properties;
    import java.util.stream.IntStream;
    public class AvroConsumer {
       private final static String BOOTSTRAP_SERVERS = "localhost:9092";
       private final static String TOPIC = "new-Employee";
       private static Consumer<Long, Employee> createConsumer() {
           Properties props = new Properties();
           props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, BOOTSTRAP_SERVERS);
           props.put(ConsumerConfig.GROUP_ID_CONFIG, "KafkaExampleAvroConsumer");
           props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
                   LongDeserializer.class.getName());
           //Use Kafka Avro Deserializer.
           props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
                   KafkaAvroDeserializer.class.getName());  //<----------------------
           //Use Specific Record or else you get Avro GenericRecord.
           props.put(KafkaAvroDeserializerConfig.SPECIFIC_AVRO_READER_CONFIG, "true");
           //Schema registry location.
           props.put(KafkaAvroDeserializerConfig.SCHEMA_REGISTRY_URL_CONFIG,
                   "http://localhost:8081"); //<----- Run Schema Registry on 8081
           return new KafkaConsumer<>(props);
       }
       public static void main(String... args) {
           final Consumer<Long, Employee> consumer = createConsumer();
           consumer.subscribe(Collections.singletonList(TOPIC));
           IntStream.range(1, 100).forEach(index -> {
               final ConsumerRecords<Long, Employee> records =
                       consumer.poll(100);
               if (records.count() == 0) {
                   System.out.println("None found");
               } else records.forEach(record -> {
                   Employee EmployeeRecord = record.value();
                   System.out.printf("%s %d %d %s \n", record.topic(),
                           record.partition(), record.offset(), EmployeeRecord);
               });
           });
       }
    }

     

确保,我们必须告诉消费者在哪里找到注册表,与生产者一样,我们必须配置Kafka Avro反序列化器。

  • 为消费者配置架构注册表:
  1. //Use Kafka Avro Deserializer.
    props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
                   KafkaAvroDeserializer.class.getName());
    //Use Specific Record or else you get Avro GenericRecord.
    props.put(KafkaAvroDeserializerConfig.SPECIFIC_AVRO_READER_CONFIG, "true");
    //Schema registry location.        props.put(KafkaAvroDeserializerConfig.SCHEMA_REGISTRY_URL_CONFIG,
                   "http://localhost:8081"); //<----- Run Schema Registry on 8081

     

此外,使用生成的Employee对象版本。因为,如果我们没有,而不是我们生成的Employee对象,那么它将使用Avro GenericRecord,这是一个SpecificRecord。
而且,我们需要启动Kafka和ZooKeeper,运行上面的例子:

  • 运行ZooKeeper和Kafka:
  1. kafka/bin/zookeeper-server-start.sh kafka/config/zookeeper.properties &
    kafka/bin/kafka-server-start.sh kafka/config/server.properties

     

所以,这完全是关于Kafka Schema Registry的。希望你喜欢我们的解释。
体验最好的Apache Kafka Quiz Part-1 | 准备迎接挑战

10.结论

因此,我们看到Kafka Schema Registry为Kafka消费者和Kafka生产商管理Avro Schemas。此外,Avro还提供模式迁移,这对于流式传输和大数据架构非常重要。因此,我们已经向Kafka Schema Registry学习了整个概念。在这里,我们讨论了Kafka中Schema注册表的需求。

此外,我们还学习了模式注册表操作和兼容性设置。最后,我们看到了Kafka Avro Schema并使用了Schema Registry Rest API。最后,我们转向使用Schema注册表和Avro Serialization编写Kafka使用者和生产者

相关文章: