Ortalama yol hızını hesapla [kapalı]


20

Bir veri mühendisi iş görüşmesine gittim. Görüşmeci bana bir soru sordu. Bana biraz durum verdi ve bu sistem için veri akışını tasarlamamı istedi. Bunu çözdüm ama çözümümü beğenmedi ve başarısız oldum. Bu sorunu nasıl çözeceğiniz hakkında daha iyi fikirleriniz olup olmadığını bilmek istiyorum.

Soru şuydu:

Sistemimiz dört veri akışı alır. Veriler bir araç kimliği, hız ve coğrafi konum koordinatları içerir. Her araç, verilerini dakikada bir gönderir. Belirli bir akış ile belirli bir yol veya araç veya başka bir şey arasında bağlantı yoktur. Koordinatları kabul eden ve bir yol bölümü adı döndüren bir işlev vardır. 5 dakikada bir yol bölümü başına ortalama hızı bilmemiz gerekir. Sonunda sonuçları Kafka'ya yazmak istiyoruz.

resim açıklamasını buraya girin

Benim çözümüm şuydu:

Önce tüm verileri bir Kafka kümesine, tek bir konuya yazarak, enlemin 5-6 ilk basamağıyla boylamın ilk 5-6 basamağına birleştirilir. Daha sonra verileri Yapılandırılmış Akış ile okur, her satıra yol bölümü adını koordinasyonlarla ekler (bunun için önceden tanımlanmış bir udf vardır) ve ardından verileri yol bölümü adına göre birleştirir.

Kafka'daki verileri koordinasyonların ilk 5-6 basamağına böldüğüm için, koordinatları bölüm adına çevirdikten sonra, çok sayıda veriyi doğru bölüme aktarmaya gerek yoktur ve bu nedenle colesce () işleminden yararlanabilirim tam bir karışıklığı tetiklemez.

Ardından, yürütücü başına ortalama hızı hesaplar.

Tüm süreç her 5 dakikada bir gerçekleşecek ve verileri son Kafka lavabosuna Ekleme modunda yazacağız.

resim açıklamasını buraya girin

Yine röportaj benim çözümümü beğenmedi. Herkes nasıl geliştirileceğini veya tamamen farklı ve daha iyi bir fikir önerebilir misiniz?


Kişiye tam olarak neyi beğenmediğini sormak daha iyi olmaz mıydı?
Gino Pane

Bence birleştirilmiş uzun zamana bölünmek kötü bir fikir. Her bir şerit için veri noktası biraz farklı bir koordinat olarak rapor edilmeyecek mi?
webber

@webber bu nedenle sadece birkaç basamak alıyorum, bu yüzden konum benzersiz olmayacak ancak nispeten bir yol bölümünün boyutunda olacak.
Alon

Yanıtlar:


6

Bu soruyu çok ilginç buldum ve bir girişimde bulunmayı düşündüm.

Daha fazla değerlendirdiğim gibi, aşağıdakiler dışında girişiminizin kendisi iyidir:

boylamın 5-6 ilk basamağına birleştirilen enlemin 5-6 ilk basamağına bölünür

Enlem ve boylam temelinde yol bölümü kimliğini / adını almak için zaten bir yönteminiz varsa, neden önce bu yöntemi çağırmıyorsunuz ve verileri ilk etapta bölümlemek için yol bölümü kimliğini / adını kullanmıyorsunuz?

Ve bundan sonra her şey oldukça kolay, bu yüzden topoloji

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. 

(Daha ayrıntılı açıklama aşağıdaki koddaki yorumlarda bulunabilir. Lütfen net olmayan bir şey olup olmadığını sorun)

Bu cevabın sonuna kodu ekledim, ortalama yerine, göstermek için daha kolay olduğu için toplam kullandığımı lütfen unutmayın. Bazı ekstra verileri saklayarak ortalama yapmak mümkündür.

Cevabı yorumlarda ayrıntılı olarak anlattım. Koddan oluşturulan bir topoloji diyagramı ( https://zz85.github.io/kafka-streams-viz/ sayesinde )

Topoloji:

Topoloji Diyagramı

    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);
            }
        }
    }

Tüm akışları birleştirmek kötü bir fikir değil mi? Bu, veri akışınız için bir darboğaz olabilir. Sisteminiz büyüdükçe daha fazla girdi akışı almaya başladığınızda ne olur? Bu ölçeklenebilir mi?
wypul

@wypul> tüm akışları birleştirmek kötü bir fikir değil mi? -> Bence hayır. Kafka'da paralellik akışlar yoluyla değil, bölümler (ve görevler), iplik geçirme vb. Yoluyla elde edilir. Akışlar, verileri gruplamanın yoludur. > Bu ölçeklenebilir mi? -> evet. Yol bölümlerine göre geçiş yaptığımızdan ve yol bölümlerinin oldukça dağıldığını varsaydığımızdan, akışı farklı konteynerlerde paralel olarak işlemek için bu konuların bölüm sayısını artırabiliriz. Yükü kopyalara dağıtmak için yol bölümüne dayalı iyi bir bölümleme algoritması kullanabiliriz.
Irshad PI

1

Bu şekilde sorun basit görünüyor ve sunulan çözümler zaten çok mantıklı. Görüşmecinin odaklandığınız çözümün tasarımı ve performansı ya da sonucun doğruluğu konusunda endişeli olup olmadığını merak ediyorum. Diğerleri kod, tasarım ve performansa odaklandığından, doğruluğu değerlendireceğim.

Akış Çözümü

Veri akarken, bir yolun ortalama hızının kabaca bir tahminini yapabiliriz. Bu tahmin, tıkanıklığı tespit etmede yardımcı olacaktır, ancak hız sınırını belirlemede kapalı olacaktır.

  1. Tüm 4 veri akışını bir araya getirin.
  2. 5 dakikada tüm 4 akıştan veri yakalamak için 5 dakikalık bir pencere oluşturun.
  3. Sokak adı ve şehir adı almak için koordinatlara UDF uygulayın. Sokak adları genellikle şehirler arasında çoğaltılır, bu nedenle şehir adı + sokak adı anahtar olarak kullanılacaktır.
  4. Gibi bir sözdizimi ile ortalama hızı hesaplayın -

    vehicle_street_speed
      .groupBy($"city_name_street_name")
      .agg(
        avg($"speed").as("avg_speed")
      )

5. write the result to the Kafka Topic

Toplu Çözüm

Örnek boyutu küçük olduğu için bu tahmin kapalı olacaktır. Hız sınırını daha doğru bir şekilde belirlemek için tüm ay / çeyrek / yıl verileri üzerinde toplu işleme ihtiyacımız olacak.

  1. Veri gölünden (veya Kafka Konusundan) yıllara ait verileri okuyun

  2. Sokak adı ve şehir adı almak için koordinatlara UDF uygulayın.

  3. Gibi bir sözdizimi ile ortalama hızı hesaplayın -


    vehicle_street_speed
      .groupBy($"city_name_street_name")
      .agg(
        avg($"speed").as("avg_speed")
      )

  1. Sonucu veri gölüne yazın.

Bu daha doğru hız sınırına dayanarak, akış uygulamasındaki yavaş trafiği tahmin edebiliriz.


1

Bölümleme stratejinizle ilgili birkaç sorun görüyorum:

  • Verilerinizi ilk 5-6 basamak uzunluğuna göre bölümleyeceğinizi söylediğinizde, kafka bölümlerinin sayısını önceden belirleyemezsiniz. Bazı yol bölümlerinde diğerlerinden daha yüksek bir hacim gözlemleyeceğiniz gibi eğri verilere sahip olacaksınız.

  • Ve anahtar kombinasyonunuz yine de aynı bölümdeki aynı yol bölümü verilerini garanti etmez ve bu nedenle karıştırma olmayacağından emin olamazsınız.

IMO tarafından verilen bilgiler, tüm veri boru hattını tasarlamak için yeterli değildir. Çünkü boru hattını tasarlarken, verilerinizi nasıl bölümlendirdiğiniz önemli bir rol oynar. Gibi araç sayısı, giriş veri akışlarının boyutu, alınan akış sayısı sabit mi yoksa gelecekte artabilir mi? Aldığınız girdi veri akışları kafka akışları mı? 5 dakikada ne kadar veri alırsınız?

  • Şimdi diyelim ki kafka veya 4 bölümdeki 4 konuya 4 akış yazdınız ve belirli bir anahtarınız yok, ancak verileriniz bazı veri merkezi anahtarlarına göre bölümlenmiş veya karma bölümlenmiş. Değilse, bu, başka bir kafka akışındaki verilerin çoğaltılması ve bölümlendirilmesi yerine veri tarafında yapılmalıdır.
  • Verileri farklı veri merkezlerinde alıyorsanız, verileri bir kümeye getirmeniz gerekir ve bu amaçla Kafka ayna yapıcısını veya benzer bir şeyi kullanabilirsiniz.
  • Bir kümedeki tüm verileri aldıktan sonra, gereksiniminize bağlı olarak 5 dakikalık tetikleme aralığı ve filigran ile yapılandırılmış bir akış işi çalıştırabilirsiniz.
  • Ortalamayı hesaplamak ve çok fazla karıştırmadan kaçınmak için groupBy yerine mapValuesve reduceByKeybunun yerine bir kombinasyon kullanabilirsiniz . Bakın bu .
  • İşlemden sonra verileri kafka lavaboya yazabilirsiniz.

mapValues ​​ve reduceByKey düşük seviyeli RDD'ye aittir. Katalizör, gruplandırıp ortalamayı hesapladığımda en verimli RDD'yi üretecek kadar akıllı değil mi?
Alon

@Alon Catalyst kesinlikle sorgunuzu çalıştırmak için en iyi planı anlayabilecektir, ancak groupBy'yi kullanırsanız, aynı anahtara sahip veriler ilk önce aynı bölüme karıştırılır ve ardından toplu işlem uygulanır. mapValuesve reduceBygerçekten de düşük seviyeli RDD'ye aittir, ancak önce bu durumda daha iyi performans gösterecektir, çünkü önce bölüm başına toplamı caculate edip karıştırma yapar.
wypul

0

Bu çözümde gördüğüm ana sorunlar:

  • Haritanın 6 basamaklı karelerinin kenarındaki yol bölümleri, birden çok konu bölümünde veri içerecek ve birden çok ortalama hıza sahip olacak.
  • Kafka bölümleriniz için alım veri boyutu dengesiz olabilir (şehir ve çöl). Araç kimliği ilk basamaklarına göre bölümleme, IMO için iyi bir fikir olabilir.
  • Birleşme kısmını takip ettiğimden emin değilim ama kulağa sorunlu geliyor.

Çözümün yapması gerektiğini söyleyebilirim: Kafka akışından okumak -> UDF -> grup yol bölümü -> ortalama -> Kafka akışına yazmak.


0

Tasarımım

  1. Yol sayısı
  2. Araç sayısı
  3. Koordinatlardan yol hesaplama maliyeti

Herhangi bir sayı için ölçeklemek istersem, tasarım şöyle görünecektir resim açıklamasını buraya girin

Bu tasarımla ilgili endişeler -

  1. Giriş akışlarının dayanıklı durumunu koruyun (giriş kafka ise, ofsetleri Kafka ile veya harici olarak saklayabiliriz)
  2. Harici sisteme düzenli olarak kontrol noktası durumları ( Flink'te zaman uyumsuz kontrol noktası bariyerleri kullanmayı tercih ederim )

Bu tasarımda bazı pratik geliştirmeler mümkün -

  1. Yollara dayalı olarak mümkünse önbellek yol haritası eşleme işlevi
  2. Cevapsız pinglerin kullanımı (pratikte her ping mevcut değildir)
  3. Yolun eğriliğini dikkate alarak (yön ve irtifa dikkate alınır)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.