Kafka Streams-如何扩展Kafka商店生成的变更日志主题

时间:2018-06-19 20:36:44

标签: apache-kafka apache-kafka-streams

我有多个冗余应用程序实例,这些实例要消耗主题的所有事件并独立存储它们以进行磁盘查找(通过rocksdb)。

为了论证,让我们假设这些多余的使用者正在服务无状态的http请求;因此不会使用kafka共享负载,而是使用kafka将数据从生产者复制到每个实例本地存储中。

在查看生成的主题时,每个使用应用的应用都会创建3个额外的主题:

  • {topicname} STATE-STORE-0000000000-changelog
  • {application-name}-{storename} -changelog
  • {application-name}-{storename}-分区

但是,每个生成的主题都与原始主题的压缩视图一样大。这意味着每个消费商店都将原始主题(已经压缩)的大小乘以3。

  1. 为什么卡夫卡商店需要这3个主题。协调磁盘上存储时,我们不能简单地将流配置为从上次消耗的偏移量重新加载吗?
  2. 是冗余消费应用程序的每个实例都有其唯一的3组“商店生成的主题”的想法,还是应该将它们配置为共享同一组变更日志主题?那么,由于它们需要消耗所有分区的所有事件,因此它们应该共享相同的applicationId还是宁愿不共享?

简而言之,我对存储可伸缩性感到担忧,因为我们使用的应用程序越来越多,这些应用程序会产生更多的更改日志主题...

这是创建商店的代码

public class ProgramMappingEventStoreFactory {
  private static final Logger logger = Logger.getLogger(ProgramMappingEventStoreFactory.class.getName());
  private final static String STORE_NAME = "program-mapping-store";
  private final static String APPLICATION_NAME = "epg-mapping-catalog_program-mapping";

  public static ReadOnlyKeyValueStore<ProgramMappingEventKey, ProgramMappingEvent> newInstance(String kafkaBootstrapServerUrl,
                                                                                               String avroRegistryUrl,
                                                                                               String topic,
                                                                                               String storeDirectory)
  {
    Properties kafkaConfig = new KafkaConfigBuilder().withBootstrapServers(kafkaBootstrapServerUrl)
                                                     .withSchemaRegistryUrl(avroRegistryUrl)
                                                     .withApplicationId(createApplicationId(APPLICATION_NAME))
                                                     .withGroupId(UUID.randomUUID().toString())
                                                     .withClientId(UUID.randomUUID().toString())
                                                     .withDefaultKeySerdeClass(SpecificAvroSerde.class)
                                                     .withDefaultValueSerdeClass(SpecificAvroSerde.class)
                                                     .withStoreDirectory(storeDirectory)
                                                     .build();

    StreamsBuilder streamBuilder = new StreamsBuilder();
    bootstrapStore(streamBuilder, topic);
    KafkaStreams streams = new KafkaStreams(streamBuilder.build(), kafkaConfig);
    streams.start();
    try {
      return getStoreAndBlockUntilQueryable(STORE_NAME,
                                            QueryableStoreTypes.keyValueStore(),
                                            streams);
    } catch (InterruptedException e) {
      throw new IllegalStateException("Failed to create the LiveMediaPolicyIdStore", e);
    }
  }

  private static <T> T getStoreAndBlockUntilQueryable(String storeName,
                                                      QueryableStoreType<T> queryableStoreType,
                                                      KafkaStreams streams)
    throws InterruptedException
  {
    while (true) {
      try {
        return streams.store(storeName, queryableStoreType);
      } catch (InvalidStateStoreException ignored) {
        Thread.sleep(100);
      }
    }
  }

  private static void bootstrapStore(StreamsBuilder builder, String topic) {
    KTable<ProgramMappingEventKey, ProgramMappingEvent> table = builder.table(topic);

    table.groupBy((k, v) -> KeyValue.pair(k, v)).reduce((newValue, aggValue) -> newValue,
                                                        (newValue, aggValue) -> null,
                                                        Materialized.as(STORE_NAME));

  }

  private static String createApplicationId(String applicationName) {
    try {
      return String.format("%s-%s", applicationName, InetAddress.getLocalHost().getHostName());
    } catch (UnknownHostException e) {
      logger.warning(() -> "Failed to find the hostname, generating a uique applicationId");
      return String.format("%s-%s", applicationName, UUID.randomUUID());
    }
  }

}

1 个答案:

答案 0 :(得分:2)

如果要将同一状态加载到多个实例中,则应在所有实例(GlobalKTable)上使用application.id和唯一的builder.globalTable()

如果您使用KTable,则数据已分区,迫使您对每个实例使用不同的application.id。这可以被视为反模式。

我也不确定,为什么要groupBy((k, v) -> KeyValue.pair(k, v)).reduce()-这样会导致不必要的分区主题。

对于为table()运算符生成的changelog主题,如果使用1.0,则1.1StreamsBuilder版本中存在一个已知错误(不使用KStreamBuilder受影响)。它已在2.0版本(https://issues.apache.org/jira/browse/KAFKA-6729)中修复