सड़कों की औसत गति की गणना करें [बंद]


20

मैं एक डाटा इंजीनियर की नौकरी के लिए इंटरव्यू देने गया। साक्षात्कारकर्ता ने मुझसे एक प्रश्न पूछा। उन्होंने मुझे कुछ स्थिति दी और मुझे उस प्रणाली के लिए डेटा प्रवाह को डिजाइन करने के लिए कहा। मैंने उसे हल किया, लेकिन उसे मेरा समाधान पसंद नहीं आया और मैं असफल हो गया। मैं जानना चाहूंगा कि यदि आपके पास बेहतर विचार हैं तो उस चुनौती को कैसे हल किया जाए।

सवाल यह था:

हमारे सिस्टम को डेटा की चार धाराएँ प्राप्त होती हैं। डेटा में एक वाहन आईडी, गति और जियोलोकेशन समन्वय शामिल हैं। प्रत्येक विहिकल एक मिनट में एक बार अपना डेटा भेजता है। किसी विशिष्ट धारा का किसी विशिष्ट सड़क या विहिकल या किसी अन्य चीज से कोई संबंध नहीं है। एक फ़ंक्शन है जो समन्वय स्वीकार करता है और एक सड़क अनुभाग नाम लौटाता है। हमें प्रति सड़क अनुभाग में 5 मिनट प्रति एवरेज गति जानने की आवश्यकता है। अंत में हम काफ्का को परिणाम लिखना चाहते हैं।

यहां छवि विवरण दर्ज करें

तो मेरा समाधान था:

पहले सभी डेटा को एक काफ्का क्लस्टर में, एक विषय में, देशांतर के 5-6 पहले अंकों से विभाजित करके, देशांतर के 5-6 पहले अंकों तक पहुँचाया जाता है। फिर संरचित स्ट्रीमिंग द्वारा डेटा को पढ़ना, समन्वय द्वारा प्रत्येक पंक्ति को सड़क अनुभाग नाम के लिए जोड़ना (उस के लिए एक पूर्वनिर्धारित udf है), और फिर सड़क अनुभाग नाम से डेटा को जमा करना।

क्योंकि मैं समन्वय के 5-6 पहले अंकों के द्वारा काफ्का में डेटा का विभाजन करता हूं, समन्वय को अनुभाग नाम में अनुवाद करने के बाद, सही विभाजन में बहुत सारे डेटा को स्थानांतरित करने की कोई आवश्यकता नहीं है और इसलिए मैं कोलेस (ऑपरेशन) का लाभ उठा सकता हूं कि एक पूर्ण फेरबदल ट्रिगर नहीं करता है।

फिर प्रति निष्पादक औसत गति की गणना।

पूरी प्रक्रिया हर 5 मिनट में होगी और हम अपेंड मोड में डेटा को अंतिम काफ्का सिंक में लिखेंगे।

यहां छवि विवरण दर्ज करें

तो फिर, साक्षात्कारकर्ता को मेरा समाधान पसंद नहीं आया। क्या कोई सुझाव दे सकता है कि इसे कैसे सुधारना है या पूरी तरह से अलग और बेहतर विचार है?


क्या उस व्यक्ति से पूछना बेहतर नहीं होगा जो उसे बिल्कुल पसंद नहीं था?
गिनो पेन

मुझे लगता है कि यह सुप्त अक्षांश-लंबे समय तक विभाजन के लिए एक बुरा विचार है। प्रत्येक लेन के लिए डेटा बिंदु को थोड़ा अलग समन्वय के रूप में रिपोर्ट नहीं किया जाएगा?
वेबर

@webber इसलिए मैं केवल कुछ अंक लेती हूं, इसलिए स्थिति अद्वितीय नहीं होगी, लेकिन अपेक्षाकृत एक सड़क अनुभाग के आकार में।
अलोन

जवाबों:


6

मुझे यह सवाल बहुत दिलचस्प लगा और मैंने इस पर एक प्रयास करने की सोची।

जैसा कि मैंने आगे मूल्यांकन किया है, आपका प्रयास स्वयं को छोड़कर, निम्नलिखित को छोड़कर अच्छा है:

अक्षांश के 5-6 पहले अंकों से विभाजित होकर देशांतर के 5-6 पहले अंकों तक पहुंच गया

यदि आपके पास पहले से ही अक्षांश और देशांतर के आधार पर सड़क अनुभाग आईडी / नाम प्राप्त करने की एक विधि है, तो पहले उस पद्धति को क्यों न बुलाएं और पहली बार में डेटा को विभाजित करने के लिए सड़क अनुभाग आईडी / नाम का उपयोग करें?

और उसके बाद, सब कुछ काफी आसान है, इसलिए टोपोलॉजी होगी

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

क्या सभी धाराओं का विलय एक बुरा विचार नहीं है? यह आपके डेटा प्रवाह के लिए एक अड़चन बन सकता है। जब आपका सिस्टम बढ़ता है तो आप अधिक से अधिक इनपुट स्ट्रीम प्राप्त करना शुरू करते हैं तो क्या होता है? क्या यह स्केलेबल होगा?
विपुल

@wypul> सभी धाराओं का विलय एक बुरा विचार नहीं है? -> मुझे लगता है कि नहीं। कफका में समानताएं धाराओं के माध्यम से प्राप्त नहीं की जाती हैं, लेकिन विभाजन (और कार्य), थ्रेडिंग, आदि धाराओं के माध्यम से डेटा को समूहीकृत करने का तरीका है। > क्या यह स्केलेबल होगा? -> हाँ। चूंकि हम सड़क वर्गों द्वारा कुंजीयन कर रहे हैं और सड़क अनुभागों को काफी वितरित कर रहे हैं, इसलिए हम इन विषयों के लिए विभाजन की संख्या को बढ़ाकर विभिन्न कंटेनरों में धारा को पार्लेली प्रक्रिया कर सकते हैं। हम प्रतिकृतियों में लोड को वितरित करने के लिए सड़क अनुभाग पर आधारित एक अच्छे विभाजन एल्गोरिथ्म का उपयोग कर सकते हैं।
इरशाद पीआई

1

इस तरह की समस्या सरल लगती है और पहले से प्रस्तुत समाधान बहुत मायने रखते हैं। मुझे आश्चर्य है कि अगर साक्षात्कारकर्ता समाधान के डिजाइन और प्रदर्शन के बारे में चिंतित थे, जिस पर आपने परिणाम की सटीकता या ध्यान केंद्रित किया है। चूंकि अन्य लोगों ने कोड, डिज़ाइन और प्रदर्शन पर ध्यान केंद्रित किया है, इसलिए मैं सटीकता पर वजन करूंगा।

स्ट्रीमिंग समाधान

जैसा कि डेटा में बह रहा है, हम एक सड़क की औसत गति का एक मोटा अनुमान प्रदान कर सकते हैं। यह अनुमान भीड़ का पता लगाने में मददगार होगा, लेकिन गति सीमा निर्धारित करने में बंद हो जाएगा।

  1. डेटा की सभी 4 धाराओं को एक साथ मिलाएं।
  2. 5 मिनट में सभी 4 धाराओं से डेटा कैप्चर करने के लिए 5 मिनट की विंडो बनाएं।
  3. सड़क का नाम और शहर का नाम पाने के लिए निर्देशांक पर UDF लागू करें। सड़कों के नाम अक्सर शहरों में डुप्लिकेट होते हैं, इसलिए हम कुंजी के रूप में शहर-नाम + सड़क-नाम का उपयोग करेंगे।
  4. एक सिंटैक्स के साथ औसत गति की गणना करें -

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

5. write the result to the Kafka Topic

बैच समाधान

यह अनुमान बंद हो जाएगा क्योंकि नमूना का आकार छोटा है। हमें गति सीमा निर्धारित करने के लिए पूरे महीने / तिमाही / वर्ष डेटा पर बैच प्रसंस्करण की आवश्यकता होगी।

  1. डेटा लेक (या काफ्का विषय) से एक वर्ष का डेटा पढ़ें

  2. सड़क का नाम और शहर का नाम पाने के लिए निर्देशांक पर UDF लागू करें।

  3. एक सिंटैक्स के साथ औसत गति की गणना करें -


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

  1. डेटा झील के लिए परिणाम लिखें।

इस अधिक सटीक गति सीमा के आधार पर हम स्ट्रीमिंग एप्लिकेशन में धीमे ट्रैफ़िक की भविष्यवाणी कर सकते हैं।


1

मुझे आपकी विभाजन रणनीति के साथ कुछ समस्याएं दिखाई देती हैं:

  • जब आप कहते हैं कि आप अपने डेटा को पहले लंबे समय के 5-6 अंकों के आधार पर विभाजित करने जा रहे हैं, तो आप पहले से कफ विभाजन की संख्या निर्धारित नहीं कर पाएंगे। आपके पास तिरछे डेटा होंगे जैसे कि कुछ सड़क वर्गों के लिए आप दूसरों की तुलना में अधिक मात्रा का निरीक्षण करेंगे।

  • और आपका मुख्य संयोजन वैसे ही विभाजन में समान सड़क अनुभाग डेटा की गारंटी नहीं देता है और इसलिए आप यह सुनिश्चित नहीं कर सकते कि इसमें फेरबदल नहीं होगा।

IMO दी गई जानकारी संपूर्ण डेटा पाइपलाइन को डिजाइन करने के लिए पर्याप्त नहीं है। क्योंकि पाइपलाइन डिजाइन करते समय, आप अपने डेटा को कैसे विभाजित करते हैं, यह एक महत्वपूर्ण भूमिका निभाता है। आपको उस डेटा के बारे में अधिक पूछताछ करनी चाहिए जो आपको वाहनों की संख्या, इनपुट डेटा धाराओं के आकार के रूप में प्राप्त हो रही है, क्या धाराओं की संख्या निर्धारित है या क्या यह भविष्य में बढ़ सकती है? क्या आप जो इनपुट डेटा स्ट्रीम प्राप्त कर रहे हैं, वह काफ्का स्ट्रीम हैं? 5 मिनट में आपको कितना डेटा मिलेगा?

  • अब मान लेते हैं कि आपके पास काफ़्का या 4 विभाजनों में 4 विषयों पर लिखी गई 4 धाराएँ हैं और आपके पास कोई विशिष्ट कुंजी नहीं है, लेकिन आपका डेटा कुछ डेटा केंद्र कुंजी के आधार पर विभाजित किया गया है या यह हैश विभाजन है। यदि ऐसा नहीं है, तो यह डेटा को किसी अन्य काफ़्का स्ट्रीम और विभाजन में डी-डुप्लिकेट करने के बजाय डेटा पक्ष पर किया जाना चाहिए।
  • यदि आप विभिन्न डेटा केंद्रों पर डेटा प्राप्त कर रहे हैं, तो आपको डेटा को एक क्लस्टर में लाने की आवश्यकता है और उस उद्देश्य के लिए आप काफ्का दर्पण निर्माता या कुछ इसी तरह का उपयोग कर सकते हैं।
  • आपके पास एक क्लस्टर पर सभी डेटा होने के बाद आप एक संरचित स्ट्रीमिंग नौकरी चला सकते हैं और अपनी आवश्यकता के आधार पर 5 मिनट के ट्रिगर अंतराल और वॉटरमार्क के साथ।
  • औसत की गणना करने और बहुत अधिक फेरबदल से बचने के लिए आप ग्रुपबाय के बजाय mapValuesऔर के संयोजन का उपयोग कर सकते हैं reduceByKey। देखें इस
  • आप प्रसंस्करण के बाद कफका सिंक को डेटा लिख ​​सकते हैं।

मैपवैल्यू और कमबायके निम्न-स्तरीय आरडीडी से संबंधित हैं। जब मैं ग्रुपबी और औसत की गणना करता हूं तो क्या कैटालिस्ट सबसे कुशल आरडीडी उत्पन्न करने के लिए पर्याप्त स्मार्ट नहीं है?
एलोन

@Alon उत्प्रेरक निश्चित रूप से आपकी क्वेरी को चलाने के लिए सबसे अच्छी योजना का पता लगाने में सक्षम होगा, लेकिन यदि आप groupBy का उपयोग करते हैं, तो उसी कुंजी के साथ डेटा को पहले उसी विभाजन में फेरबदल किया जाएगा और फिर उस पर समग्र ऑपरेशन लागू किया जाएगा। mapValuesऔर reduceByवास्तव में निम्न स्तर के आरडीडी से संबंधित है, लेकिन फिर भी इस स्थिति में बेहतर प्रदर्शन करेगा क्योंकि यह पहले विभाजन के अनुसार कुल जमा करेगा और फिर फेरबदल करेगा।
विपुल

0

इस समाधान के साथ मुझे जो मुख्य समस्याएं दिखाई दे रही हैं, वे हैं:

  • नक्शे के 6 अंकों वाले वर्गों के किनारे वाले सड़क अनुभागों में कई विषय विभाजन में डेटा होगा और कई औसत गति होगी।
  • आपके काफ्का विभाजन के लिए अंतर्ग्रहण डेटा का आकार असंतुलित (शहर बनाम रेगिस्तान) हो सकता है। कार आईडी पहले अंकों से विभाजन एक अच्छा विचार आईएमओ हो सकता है।
  • निश्चित नहीं है कि मैंने मोटे हिस्से का पालन किया है, लेकिन यह समस्याग्रस्त लगता है।

मैं कहता हूं कि समाधान करने की जरूरत है: काफ्का स्ट्रीम से पढ़ें -> यूडीएफ -> ग्रुपबी रोड सेक्शन -> औसत -> काफ्का स्ट्रीम को लिखें।


0

मेरा डिजाइन निर्भर करेगा

  1. सड़कों की संख्या
  2. वाहनों की संख्या
  3. निर्देशांक से सड़क की संगणना लागत

अगर मैं किसी भी संख्या के लिए स्केल करना चाहता हूं, तो डिजाइन इस तरह दिखेगा यहां छवि विवरण दर्ज करें

इस डिजाइन पर क्रॉस चिंताएं -

  1. इनपुट धाराओं की टिकाऊ स्थिति बनाए रखें (यदि इनपुट कफ़्का है, तो हम काफ्का या बाह्य रूप से ऑफ़सेट स्टोर कर सकते हैं)
  2. समय-समय पर चेकपॉइंट बाहरी सिस्टम को बताता है (मैं फ्लिंक में async चेकपॉइंट बाधाओं का उपयोग करना पसंद करता हूं )

इस डिजाइन पर कुछ व्यावहारिक वृद्धि संभव है -

  1. सड़कों के आधार पर यदि संभव हो तो कैशिंग रोड सेक्शन मैपिंग फ़ंक्शन
  2. मिस्ड पिंग को संभालना (व्यवहार में हर पिंग उपलब्ध नहीं है)
  3. सड़क की वक्रता को ध्यान में रखते हुए (असर और ऊंचाई को ध्यान में रखते हुए)
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.