我正在尝试在Spring Boot应用程序中使用spring-cloud-stream-binder-kafka实现一次交付。 我使用的版本是:
这是我的配置(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) {
最后但并非最不重要,如果可行,是否可以提供上述方法的一些示例。
更新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。就是这样。
答案 0 :(得分:1)
正如Marius解释的那样,Kafka仅在日志中保留偏移量。如果处理下一条消息,并更新偏移量;失败的消息丢失。
您可以将失败的消息发送到死信主题(将enableDlq
设置为true)。
Spring Kafka(2.1.x)的最新版本具有特殊的错误处理程序ContainerStoppingErrorHandler
,该错误处理程序在发生异常时停止容器,而SeekToCurrentErrorHandler
则将导致重新发送失败的消息。