Spring boot 项目中进行kafka Stream app 开发

1. 导入依赖

      <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-streams</artifactId>
        <version>3.6.2</version>
      </dependency>

2. 示例代码(伪代码)

这段伪代码只是为了举例设置的场景,业务场景并不一定合适

@Slf4j
@Component
public class MyKafkaStreamProcessor {

	@Value("${spring.kafka.bootstrap-servers}")
	private String bootstrapServers;

	@PostConstruct
	private void init () {
		String appId = "my-kafka-streams-app";
		myKafkaStreams(appId);
		log.info("✅ Kafka Streams:{} 初始化完成,开始监听 topic: {}", appId, "source-topic");
	}

	public void myKafkaStreams(String appId) {

		/*=======配置=======*/
		Properties config  = new Properties();

		config.put(StreamsConfig.APPLICATION_ID_CONFIG, appId);
		config.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
		StreamsBuilder builder = new StreamsBuilder();

		/*=======构建拓扑结构=======*/

		// 数据清洗
		KStream<String, Order> stream = builder
			.stream("source-topic", Consumed.with(Serdes.String(), new JsonSerde<>(Order.class)))
			.mapValues(value -> {
				// do something
				return value;
			});

		// 过滤出从app创建的订单 并进行处理
		stream.filter((k, v) -> Order.getSource.equals("app"))
			.foreach((k, v) -> {
				// do something
			});

		// 发送到第一个topic
		stream.mapValues(value -> JSON.toJSONString(value), Named.as("to-the-first-target-topic-processor"))
			.to("the-first-target-topic", Produced.with(Serdes.String(), Serdes.String()));

		// 发送到第二个topic
		stream.filter((k, v) -> {
				// filter something
			})
			.mapValues(value -> {
				// map to another object
			}, Named.as("to-the-second-target-topic-processor"))
			.to("the-second-target-topic", Produced.with(Serdes.String(), Serdes.String()));

		/*=======创建KafkaStreams=======*/
		KafkaStreams streams = new KafkaStreams(builder.build(), config);

		/*=======设置异常处理器=======*/
		streams.setUncaughtExceptionHandler(new CustomStreamsUncaughtExceptionHandler());

		/*=======启动streams=======*/
		streams.start();

		/*=======添加jvm hook 确保streams安全退出=======*/
		Runtime.getRuntime().addShutdownHook(new Thread(() -> {
			log.info("关闭 Kafka Streams:{}...", appId);
			streams.close();
			log.info("Kafka Streams:{}已经关闭!", appId);
		}));

	}

}

@Slf4j
public class CustomStreamsUncaughtExceptionHandler implements StreamsUncaughtExceptionHandler {


	/**
	 * Inspect the exception received in a stream thread and respond with an action.
	 *
	 */
	@Override
	public StreamThreadExceptionResponse handle(Throwable throwable) {
		log.error("Kafka Streams 线程发生未捕获异常: {}", ExceptionUtil.stacktraceToString(throwable));

		// 选择处理策略(以下三选一):
		// 1. 替换线程(继续运行)
		return StreamThreadExceptionResponse.REPLACE_THREAD;

		// 2. 关闭整个 Streams 应用
		// return StreamThreadExceptionResponse.SHUTDOWN_CLIENT;

		// 3. 关闭整个 JVM
		// return StreamThreadExceptionResponse.SHUTDOWN_APPLICATION;
	}
}

3. 一些注意事项和说明

  • kafka stream 的处理部分集中在构建的拓扑中,其他部分大同小异
  • 在配置部分 StreamsConfig.APPLICATION_ID_CONFIG 这个参数是必须的,且不能重复,否则会启动失败,StreamsConfig.BOOTSTRAP_SERVERS_CONFIG 是kafka的IP与端口
  • 需要注意的是kafka的 KStream 与 java 中的 stream并不相同,在java中 stream只能被消费一次,但是kstream 可以被消费多次,在上面的demo中可以看到,同一个 kstream 被多次消费,且kstream中的数据是不可变的,也就是无论在上一个处理器(processor)对数据进行了何种处理,下一个处理器从kstream 中获取的数据依旧是原来的数据
  • 在kafka stream app中应该对可能会抛出的异常进行处理,而不是全部交给UncaughtExceptionHandlerUncaughtExceptionHandler应该只处理哪些无法预料的异常
  • 如果kafka stream app 捕获未处理异常之后的处理策略也是替换线程,那么kafka stream app 中如果抛出未捕获异常,那么这个消费者组就会进入再平衡状态(PreparingRebalance),老的消费者从消费者组中剔除,新的消费者加入消费者组,然后再开始消费

4. kafka Stream 的一些方法说明

  1. stream()
    stream()方法是从源topic获取数据的方法,示例中第一个参数是字符串,也就是源topic的名称,
    第二个参数是 Consumed ,用来定义对于消息的key与value反序列化的规则,
    示例中将key序列化为string, value 序列化为order对象,需要注意的是,如果在配置的config中没有设置适用于整个stream app的序列化与反序列化规则,那么后续的 to()中必须要指定序列化规则

  2. filter()
    filter()的用法与java stream 中的filter()一致,这里不做说明

  3. mapValues()
    mapValues()的用法,是只对消息的value进行操作,比如将value转换为其他对象。这个方法不会对key进行修改

  4. map()
    与mapValues()类似,但是可以修改消息的key

  5. foreach()
    与java stream 的 foreach()类似,也是一个终结方法

  6. to()
    终结方法,用于将数据发送到另外的topic,示例中第一个参数是目标topic, 第二个参数是Produced,用于定义key和value的序列化规则

  7. 除了stream()和to()之外,其他方法基本都可以传一个参数 Named,这个参数是为每个处理器节点命名,如果不传则自动生成,但是在同一个kstream中 命名不能重复。这个名称不会影响功能,但是如果有一个名称可以在后续调试和监控中提供一点帮助

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值