只需一次交付即可通过spring-cloud-stream-binder-kafka或spring-kafka使用哪个

时间:2018-07-05 02:32:51

标签: spring-kafka manual

我正在尝试在Spring Boot应用程序中使用spring-cloud-stream-binder-kafka实现一次交付。 我使用的版本是:

  • spring-cloud-stream-binder-kafka-core-1.2.1.Rosease
  • spring-cloud-stream-binder-kafka-1.2.1.Rosease
  • spring-cloud-stream-codec-1.2.2.RELEASE spring-kafka-1.1.6.RELEASE
  • spring-integration-kafka-2.1.0.RELEASE
  • spring-integration-core-4.3.10.RELEASE
  • zookeeper-3.4.8
  • Kafka版本:0.10.1.1

这是我的配置(cloud-config):

    spring:
      autoconfigure:
        exclude: org.springframework.cloud.netflix.metrics.servo.ServoMetricsAutoConfiguration
      kafka:
        consumer:
          enable-auto-commit: false
      cloud:
        stream:
          kafka:
            binder:
              brokers: "${BROKER_HOST:xyz-aws.local:9092}"
              headers:
                - X-B3-TraceId
                - X-B3-SpanId
                - X-B3-Sampled
                - X-B3-ParentSpanId
                - X-Span-Name
                - X-Process-Id
              zkNodes: "${ZOOKEEPER_HOST:120.211.316.261:2181,120.211.317.252:2181}"
            bindings:
              feed_platform_events_input:
                consumer:
                  autoCommitOffset: false
          binders:
            xyzkafka:
              type: kafka
          bindings:
            feed_platform_events_input:
              binder: xyzkafka
              destination: platform-events
              group: br-platform-events

我有两个主要班级: FeedSink界面:

package au.com.xyz.proxy.interfaces;
import org.springframework.cloud.stream.annotation.Input; 
import org.springframework.messaging.MessageChannel;

public interface FeedSink {

String FEED_PLATFORM_EVENTS_INPUT = "feed_platform_events_input";

@Input(FeedSink.FEED_PLATFORM_EVENTS_INPUT)
MessageChannel feedlatformEventsInput();
} 

EventConsumer

package au.com.xyz.proxy.consumer;

@Slf4j
@EnableBinding(FeedSink.class)
public class EventConsumer {

    public static final String SUCCESS_MESSAGE =
            "SEND-SUCCESS : Successfully sent message to platform.";
    public static final String FAULT_MESSAGE = "SOAP-FAULT Code: {}, Description: {}";
    public static final String CONNECT_ERROR_MESSAGE = "CONNECT-ERROR Error Details: {}";
    public static final String EMPTY_NOTIFICATION_ERROR_MESSAGE =
            "EMPTY-NOTIFICATION-ERROR Empty Event Received from platform";

    @Autowired
    private CapPointService service;

    @StreamListener(FeedSink.FEED_PLATFORM_EVENTS_INPUT)
    /**
     * method associated with stream to process message.
     */
    public void message(final @Payload EventNotification eventNotification,
                        final @Header(KafkaHeaders.ACKNOWLEDGMENT) Acknowledgment acknowledgment) {

        String caseMilestone = "UNKNOWN";
        if (!ObjectUtils.isEmpty(eventNotification)) {
            SysMessage sysMessage = processPayload(eventNotification);
            caseMilestone = sysMessage.getCaseMilestone();
            try {
                ClientResponse response = service.sendPayload(sysMessage);
                if (response.hasFault()) {
                    Fault faultDetails = response.getFaultDetails();
                    log.error(FAULT_MESSAGE, faultDetails.getCode(), faultDetails.getDescription());
                } else {
                    log.info(SUCCESS_MESSAGE);
                }
                acknowledgment.acknowledge();
            } catch (Exception e) {
                log.error(CONNECT_ERROR_MESSAGE, e.getMessage());
            }
        } else {
            log.error(EMPTY_NOTIFICATION_ERROR_MESSAGE);
            acknowledgment.acknowledge();
        }
    }



    private SysMessage processPayload(final EventNotification eventNotification) {
        Gson gson = new Gson();
        String jsonString =  gson.toJson(eventNotification.getData());
        log.info("Consumed message for platform events with payload : {} ", jsonString);
        SysMessage sysMessage = gson.fromJson(jsonString, SysMessage.class);
        return sysMessage;
    }
    }

我已将Kafka和spring容器的autocommit属性设置为false。 如果在EventConsumer类中看到,则在service.sendPayload成功且没有异常的情况下,我已使用Acknowledge。我希望容器移动偏移量并轮询下一条记录。 我观察到的是:

  • 场景1-如果抛出Exception并且在kafka上没有新消息发布。没有重试来处理该消息,并且似乎没有活动。即使根本问题已解决。我要指的问题是下游服务器不可用。有没有一种方法可以重试n次然后放弃。请注意,这是从上次提交的偏移量重试处理或查询。这与Kafka实例不可用有关。 如果我重新启动服务(EC2实例),则处理将从上一次成功确认的偏移量开始。

  • 场景2-如果发生异常,然后将后续消息推送到kafka。我看到新消息已处理且偏移量已移动。这意味着我丢失了未被确认的消息。所以问题是我是否已经处理过确认书。如何控制从上一次提交中读取内容,而不仅仅是最新消息并进行处理。我假设内部正在进行一次轮询,并且没有考虑或不知道最后一条消息未被确认。我认为从kafka读取的线程不多。我不知道如何控制@Input和@StreamListener批注。我假设线程由属性Consumer.concurrency控制,该属性控制线程,默认情况下它设置为1。

因此,我已经进行了研究,发现了很多链接,但不幸的是,它们都没有回答我的特定问题。 我看着(https://github.com/spring-cloud/spring-cloud-stream/issues/575) 其中有Marius(https://stackoverflow.com/users/809122/marius-bogoevici)的评论:

  

请注意,Kafka不提供单独的邮件确认,   意味着确认转化为更新最新消费   与已确认消息的偏移量的偏移量(按主题/分区)。那   意味着如果您要确认来自同一个主题的消息,则将其分区   按顺序,一条消息可以“确认”所有消息。

不确定有一个线程时是否是订单问题。

道歉,但我想提供足够的信息。最主要的是,我试图避免从kafka消费时丢失消息,并且试图查看spring-cloud-stream-binder-kafka是否可以胜任这项工作,或者我必须研究替代方法。

更新2018年7月6日

我看到了这篇帖子https://github.com/spring-projects/spring-kafka/issues/431 这是解决我的问题的更好方法吗?我可以尝试使用最新版本的spring-kafka

@KafkaListener(id = "qux", topics = "annotated4", containerFactory = "kafkaManualAckListenerContainerFactory",
                containerGroup = "quxGroup")
public void listen4(@Payload String foo, Acknowledgment ack, Consumer<?, ?> consumer) {
  • 这将有助于控制将偏移量设置为最后一个偏移量的位置 成功处理记录?我该如何从聆听中做到这一点 方法。 Consumer.seekToEnd();然后如何重置监听方法以获取该记录?
  • 将消费者置于签名中是否提供支持以获取 处理消费者?还是我需要做更多事情?
  • 我应该使用Acknowledge还是Consumer.commitSyncy()
  • containerFactory的意义是什么。我必须定义它吗 作为豆子。
  • 我需要@EnableKafka和@Configuration才能实现上述方法吗? 请记住,该应用程序是Spring Boot应用程序。
  • 通过添加Consumer来监听方法,我不需要实现 ConsumerAware接口?

最后但并非最不重要,如果可行,是否可以提供上述方法的一些示例。

更新2018年7月12日

感谢Gary(https://stackoverflow.com/users/1240763/gary-russell)提供了使用maxAttempts的技巧。我曾经使用过这种方法。而且我能够实现一次准确的传递并保留邮件的顺序。

我更新的云配置:

    spring:
      autoconfigure:
        exclude: org.springframework.cloud.netflix.metrics.servo.ServoMetricsAutoConfiguration
      kafka:
        consumer:
          enable-auto-commit: false
      cloud:
        stream:
          kafka:
            binder:
              brokers: "${BROKER_HOST:xyz-aws.local:9092}"
              headers:
                - X-B3-TraceId
                - X-B3-SpanId
                - X-B3-Sampled
                - X-B3-ParentSpanId
                - X-Span-Name
                - X-Process-Id
              zkNodes: "${ZOOKEEPER_HOST:120.211.316.261:2181,120.211.317.252:2181}"
            bindings:
              feed_platform_events_input:
                consumer:
                  autoCommitOffset: false
          binders:
            xyzkafka:
              type: kafka
          bindings:
            feed_platform_events_input:
              binder: xyzkafka
              destination: platform-events
              group: br-platform-events
              consumer:
                maxAttempts: 2147483647
                backOffInitialInterval: 1000
                backOffMaxInterval: 300000
                backOffMultiplier: 2.0

事件使用者与我的最初实现相同。除了将错误重新抛出以使容器知道处理失败之外。如果您只是捕获它,那么容器就不可能知道消息处理失败。通过执行acknoweldgement.acknowledge,您仅在控制偏移量提交。为了重试,您必须抛出异常。不要忘记将kafka客户端的autocommit属性和spring(容器级别)的autocommitOffset属性设置为false。就是这样。

1 个答案:

答案 0 :(得分:1)

正如Marius解释的那样,Kafka仅在日志中保留偏移量。如果处理下一条消息,并更新偏移量;失败的消息丢失。

您可以将失败的消息发送到死信主题(将enableDlq设置为true)。

Spring Kafka(2.1.x)的最新版本具有特殊的错误处理程序ContainerStoppingErrorHandler,该错误处理程序在发生异常时停止容器,而SeekToCurrentErrorHandler则将导致重新发送失败的消息。