kafka streams中多个window统计count

场景描述:统计某个商品(商品id)在10min,30min,1hour内的购买量。我们把商品的每一次购买事件作为流,对其加窗,统计各需求时间段内的购买次数。

1.先创建一个读取数据的topic:window-count,再创建一个统计结果输出的topic:window-count-out。

2.编写代码。

3.启动程序WindowCount。

package teststreams;

import java.util.Properties;
import java.util.concurrent.TimeUnit;

import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.KafkaStreams;
import org.apache.kafka.streams.KeyValue;
import org.apache.kafka.streams.StreamsBuilder;
import org.apache.kafka.streams.StreamsConfig;
import org.apache.kafka.streams.kstream.KGroupedStream;
import org.apache.kafka.streams.kstream.KStream;
import org.apache.kafka.streams.kstream.KeyValueMapper;
import org.apache.kafka.streams.kstream.Materialized;
import org.apache.kafka.streams.kstream.TimeWindows;

/**
 * @description count 10min,30min,1hour groupBy itemId
 * @author wyhui
 *
 */
public class WindowCount {

	public static void main(String[] args) {
		Properties prop = new Properties();
		prop.put(StreamsConfig.APPLICATION_ID_CONFIG, "mywindowcount");
		prop.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.184.128:9092");//kafka服务器IP
		prop.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
		prop.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
		prop.put(StreamsConfig.STATE_DIR_CONFIG, "C:\\IT\\tool\\kafka-state-store");//设置状态仓库的存储路径
		StreamsBuilder builder = new StreamsBuilder();
		//从topic中读入数据
		KStream<String, String> input = builder.stream("window-count");
		//使用map()将数据的Key设置为与value相同。
		KGroupedStream<String, String> groupBy = input
			.map(new KeyValueMapper<String, String, KeyValue<String, String>>(){
	
				@Override
				public KeyValue<String, String> apply(String key, String value) {
					return new KeyValue<String, String>(value, value);
				}
				
			})
			//gorupBy()可以指定任意想要group的值
			.groupBy(new KeyValueMapper<String, String, String>(){

				@Override
				public String apply(String key, String value) {
					//这里我们根据itemId来进行group,此方法要返回的就是我们要group的值,也就是group之后的key。
					//假设我们的topic读入的数据格式是:itemId="0001",itemName="华硕电脑",userId="1003"
					return value.substring(8, 12);//截取itemId
				}
				
			});
		/*
		 * 对数据进行group之后,我们该对不同的itemId加窗统计,由于这里我们是想求得同一个Id在不同时间段内的购买次数,
		 * 就需要施加多个window,所以在这里我们需要分成多个分流进行不同的加窗,然后把最终的结果都输出到window-count-out这个topic中。
		 * 要对上面的同一个group结果进行不同的操作,我们就需要将其复制多份来进行不同的操作。
		 */
		KGroupedStream<String, String> groupBy10min = groupBy;
		KGroupedStream<String, String> groupBy30min = groupBy;
		KGroupedStream<String, String> groupBy1hour = groupBy;
		groupBy10min.windowedBy(TimeWindows.of(TimeUnit.MINUTES.toMillis(1)))//以Min为单位,转换为毫秒
					.count(Materialized.as("itemGroupBy1min-state-store"))//指定存储中间状态的state-store,存储在上面配置的STATE_DIR_CONFIG路径下
					.toStream()//经过上面的count之后得到的是KTable,输出到topic中需要转为流
					//经过上面的操作之后,会自动地在key的后面加上一个时间戳,而我们在输出时并不需要这些数据,所以使用map()将key中的时间戳处理掉
					/*
					 * .map((key, count) -> KeyValue.pair(key.toString(), count.toString()+"10min"))
					 * 如果使用这样的方法,得到的输出结果就是:offset:0,key:[0001@1550313600000/1550314200000],value:110min
					 * 所以我们需要对Key进行处理
					 */
					//上面统计的count是Long类型的,此处需要转为String。为了在输出count时能区分出是哪个时间段统计的,在这里我们加上"10min"来指示
					.map((key, count) -> KeyValue.pair(key.toString().substring(1, key.toString().indexOf("@")), count.toString()+"******1min"))
					.to("window-count-out");//输出到topic中
		groupBy30min.windowedBy(TimeWindows.of(TimeUnit.MINUTES.toMillis(3)))//以Min为单位,转换为毫秒
					.count(Materialized.as("itemGroupBy3min-state-store"))//指定存储中间状态的state-store
					.toStream()
					.map((key, count) -> KeyValue.pair(key.toString().substring(1, key.toString().indexOf("@")), count.toString()+"******3min"))
					.to("window-count-out");
		groupBy1hour.windowedBy(TimeWindows.of(TimeUnit.HOURS.toMillis(1)))
					.count(Materialized.as("itemGroupBy1hour-state-store"))
					.toStream()
					.map((key, count) -> KeyValue.pair(key.toString().substring(1, key.toString().indexOf("@")), count.toString()+"*******1hour"))
					.to("window-count-out");
		KafkaStreams streams = new KafkaStreams(builder.build(), prop);
		streams.cleanUp();
		streams.start();
		Runtime.getRuntime().addShutdownHook(new Thread(streams::close));
	}

}

在多次运行该程序时可能会出现Exception in thread "main" org.apache.kafka.streams.errors.StreamsException: java.nio.file.DirectoryNotEmptyException,解决方案:kafka streams 中streams.errors.StreamsException: java.nio.file.DirectoryNotEmptyException_QYHuiiQ的博客-CSDN博客

4.启动cosumer:

package mykafka;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Properties;

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;

/**
 * 消息的消費者
 * @author wyhui
 *
 */
public class MyConsumer {
	public static void main(String[] args) {
		Properties consumerProp = new Properties();
		consumerProp.put("bootstrap.servers","192.168.184.128:9092");
		consumerProp.put("group.id", "wyhtest");
		consumerProp.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
		consumerProp.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
		consumerProp.put("enable.auto.commit", "true");
		consumerProp.put("auto.commit.interval.ms", "30000");
		KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(consumerProp);
		consumer.subscribe(Arrays.asList("window-count-out"));
		while(true) {
			ConsumerRecords<String, String> records = consumer.poll(1000);
			for(ConsumerRecord<String, String> record : records) {
				long offset = record.offset();
				Object key = record.key();
				Object value = record.value();
				System.out.println("offset:"+offset+",key:"+key+",value:"+value);
			}
		}
	
	}
}

5.向window-count中发送数据:

package mykafka;

import java.util.Properties;

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;

/**
 * 消息生产者实体类
 * @author wyhui
 */
public class MyProducer {
	private final static String TOPIC = "window-count";
	private static KafkaProducer<String,String> producer = null;
	public static void main(String[] args) {
		Properties producerProp = new Properties();
		producerProp.put("bootstrap.servers","192.168.184.128:9092");
		producerProp.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
		producerProp.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
		producer = new KafkaProducer<String, String>(producerProp);		
			producer.send(new ProducerRecord<>(TOPIC, null, "itemId=\"1115\",itemName=\"联想电脑\",userId=\"1003\""));//key为空
			producer.send(new ProducerRecord<>(TOPIC, null, "itemId=\"1116\",itemName=\"华硕电脑\",userId=\"1023\""));//key为空
			producer.send(new ProducerRecord<>(TOPIC, null, "itemId=\"1118\",itemName=\"惠普电脑\",userId=\"1031\""));//key为空
			producer.send(new ProducerRecord<>(TOPIC, null, "itemId=\"1117\",itemName=\"戴尔电脑\",userId=\"1026\""));//key为空
			producer.send(new ProducerRecord<>(TOPIC, null, "itemId=\"1116\",itemName=\"华硕电脑\",userId=\"1036\""));//key为空
			producer.send(new ProducerRecord<>(TOPIC, null, "itemId=\"1117\",itemName=\"戴尔电脑\",userId=\"1053\""));//key为空
			producer.send(new ProducerRecord<>(TOPIC, null, "itemId=\"1115\",itemName=\"联想电脑\",userId=\"1065\""));//key为空
			producer.send(new ProducerRecord<>(TOPIC, null, "itemId=\"1116\",itemName=\"华硕电脑\",userId=\"1077\""));//key为空
			producer.send(new ProducerRecord<>(TOPIC, null, "itemId=\"1118\",itemName=\"惠普电脑\",userId=\"1046\""));//key为空
			producer.send(new ProducerRecord<>(TOPIC, null, "itemId=\"1115\",itemName=\"联想电脑\",userId=\"1076\""));//key为空
			producer.send(new ProducerRecord<>(TOPIC, null, "itemId=\"1117\",itemName=\"戴尔电脑\",userId=\"1084\""));//key为空
			producer.send(new ProducerRecord<>(TOPIC, null, "itemId=\"1116\",itemName=\"华硕电脑\",userId=\"1027\""));//key为空
			producer.send(new ProducerRecord<>(TOPIC, null, "itemId=\"1115\",itemName=\"联想电脑\",userId=\"1099\""));//key为空	
			System.out.println("消息发送完毕");
			producer.close();
		}
	}

由于我们在测试时使用的是1min,3min,1hour,所以我们实行以下发送数据计划:

7:21分-------启动windowCount程序,启动cosumer程序。

7:22分-------启动producer程序,第一次发送数据,在每次的数据中,itemId分别出现的次数为:1115(4),1116(4),1117(3),1118(2)。

此时的消费输出结果为:

7:22分------在这一分钟内第二次发送数据,是为了验证1min的时间窗口计数是否正确,输出的结果为:

可以看到在这1min内每个id的count还是在刚才1min的基础上累加的,说明数据计时正确。

7:23分发送数据,此时已经超过1min,所有id在1min的计算窗口内都是重新计算了,而3min,1hour的计数还是在之前的结果上累加,说明数据计数正确:

7:24发送数据,此时距离WindowCount程序启动已经超过3min,所以3min的数据重新计算(在计时过程中由于我没有精确到秒,所以可能会出现误差):

以上就是对同一数据流进行不同时间的加窗统计count。

项目源码:https://github.com/wyhuiii/KafkaStreams-count

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

QYHuiiQ

听说打赏的人工资翻倍~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值