나는이 질문이 매우 흥미롭고 시도해 볼 생각을했다.
추가로 평가했듯이 다음을 제외하고는 시도 자체가 좋습니다.
위도의 5-6 첫 번째 숫자로 구분되고 경도의 5-6 첫 번째 숫자에 연결됨
위도와 경도를 기준으로 도로 섹션 ID / 이름을 가져 오는 방법이 이미있는 경우 먼저 해당 메소드를 호출하고 도로 섹션 id / name을 사용하여 데이터를 먼저 분할하십시오.
그 후 모든 것이 매우 쉬워서 토폴로지가
Merge all four streams ->
Select key as the road section id/name ->
Group the stream by Key ->
Use time windowed aggregation for the given time ->
Materialize it to a store.
(자세한 설명은 아래 코드의 주석에서 찾을 수 있습니다. 불분명 한 것이 있으면 문의하십시오)
이 답변의 끝에 코드를 추가했습니다. 평균 대신 시연하기 쉬운 합계를 사용했습니다. 추가 데이터를 저장하여 평균을 계산할 수 있습니다.
의견에 대한 답변을 자세히 설명했습니다. 다음은 코드에서 생성 된 토폴로지 다이어그램입니다 ( https://zz85.github.io/kafka-streams-viz/ 덕분에 )
토폴로지 :
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.KafkaStreams;
import org.apache.kafka.streams.StreamsBuilder;
import org.apache.kafka.streams.StreamsConfig;
import org.apache.kafka.streams.Topology;
import org.apache.kafka.streams.kstream.KStream;
import org.apache.kafka.streams.kstream.Materialized;
import org.apache.kafka.streams.kstream.TimeWindows;
import org.apache.kafka.streams.state.Stores;
import org.apache.kafka.streams.state.WindowBytesStoreSupplier;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
public class VehicleStream {
// 5 minutes aggregation window
private static final long AGGREGATION_WINDOW = 5 * 50 * 1000L;
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
// Setting configs, change accordingly
properties.put(StreamsConfig.APPLICATION_ID_CONFIG, "vehicle.stream.app");
properties.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092,kafka2:19092");
properties.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
properties.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
// initializing a streambuilder for building topology.
final StreamsBuilder builder = new StreamsBuilder();
// Our initial 4 streams.
List<String> streamInputTopics = Arrays.asList(
"vehicle.stream1", "vehicle.stream2",
"vehicle.stream3", "vehicle.stream4"
);
/*
* Since there is no connection between a specific stream
* to a specific road or vehicle or anything else,
* we can take all four streams as a single stream
*/
KStream<String, String> source = builder.stream(streamInputTopics);
/*
* The initial key is unimportant (which can be ignored),
* Instead, we will be using the section name/id as key.
* Data will contain comma separated values in following format.
* VehicleId,Speed,Latitude,Longitude
*/
WindowBytesStoreSupplier windowSpeedStore = Stores.persistentWindowStore(
"windowSpeedStore",
AGGREGATION_WINDOW,
2, 10, true
);
source
.peek((k, v) -> printValues("Initial", k, v))
// First, we rekey the stream based on the road section.
.selectKey(VehicleStream::selectKeyAsRoadSection)
.peek((k, v) -> printValues("After rekey", k, v))
.groupByKey()
.windowedBy(TimeWindows.of(AGGREGATION_WINDOW))
.aggregate(
() -> "0.0", // Initialize
/*
* I'm using summing here for the aggregation as that's easier.
* It can be converted to average by storing extra details on number of records, etc..
*/
(k, v, previousSpeed) -> // Aggregator (summing speed)
String.valueOf(
Double.parseDouble(previousSpeed) +
VehicleSpeed.getVehicleSpeed(v).speed
),
Materialized.as(windowSpeedStore)
);
// generating the topology
final Topology topology = builder.build();
System.out.print(topology.describe());
// constructing a streams client with the properties and topology
final KafkaStreams streams = new KafkaStreams(topology, properties);
final CountDownLatch latch = new CountDownLatch(1);
// attaching shutdown handler
Runtime.getRuntime().addShutdownHook(new Thread("streams-shutdown-hook") {
@Override
public void run() {
streams.close();
latch.countDown();
}
});
try {
streams.start();
latch.await();
} catch (Throwable e) {
System.exit(1);
}
System.exit(0);
}
private static void printValues(String message, String key, Object value) {
System.out.printf("===%s=== key: %s value: %s%n", message, key, value.toString());
}
private static String selectKeyAsRoadSection(String key, String speedValue) {
// Would make more sense when it's the section id, rather than a name.
return coordinateToRoadSection(
VehicleSpeed.getVehicleSpeed(speedValue).latitude,
VehicleSpeed.getVehicleSpeed(speedValue).longitude
);
}
private static String coordinateToRoadSection(String latitude, String longitude) {
// Dummy function
return "Area 51";
}
public static class VehicleSpeed {
public String vehicleId;
public double speed;
public String latitude;
public String longitude;
public static VehicleSpeed getVehicleSpeed(String data) {
return new VehicleSpeed(data);
}
public VehicleSpeed(String data) {
String[] dataArray = data.split(",");
this.vehicleId = dataArray[0];
this.speed = Double.parseDouble(dataArray[1]);
this.latitude = dataArray[2];
this.longitude = dataArray[3];
}
@Override
public String toString() {
return String.format("veh: %s, speed: %f, latlong : %s,%s", vehicleId, speed, latitude, longitude);
}
}
}