Tôi thấy câu hỏi này rất thú vị và nghĩ đến việc thử nó.
Như tôi đã đánh giá thêm, bản thân nỗ lực của bạn là tốt, ngoại trừ những điều sau đây:
được phân chia bởi 5-6 chữ số đầu tiên của vĩ độ được nối với 5-6 chữ số đầu tiên của kinh độ
Nếu bạn đã có một phương pháp để lấy id / name của phần đường dựa trên vĩ độ và kinh độ, tại sao không gọi phương thức đó trước và sử dụng id / name của phần đường để phân vùng dữ liệu ở vị trí đầu tiên?
Và sau đó, mọi thứ khá dễ dàng, vì vậy cấu trúc liên kết sẽ là
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.
(Giải thích chi tiết hơn có thể được tìm thấy trong các ý kiến trong mã dưới đây. Vui lòng hỏi nếu có gì không rõ ràng)
Tôi đã thêm mã vào cuối câu trả lời này, xin lưu ý rằng thay vì trung bình, tôi đã sử dụng tổng vì dễ chứng minh hơn. Có thể làm trung bình bằng cách lưu trữ một số dữ liệu bổ sung.
Tôi đã nêu chi tiết câu trả lời trong các bình luận. Sau đây là sơ đồ cấu trúc liên kết được tạo từ mã (nhờ https://zz85.github.io/kafka-streams-viz/ )
Cấu trúc liên kết:
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);
}
}
}