Future und Async/Await

Max Maischein

Frankfurt.pm

English Version

https://corion.net/talks/

English Questions are OK

Überblick

  • Wird mir der Vortrag helfen?

  • Übergang von sequentiellem Programm zu asynchron mit Futures

  • Javascript / E

Wer bin ich

  • Max Maischein

  • DZ BANK Frankfurt

  • Deutsche Zentralgenossenschaftsbank

  • Data Scientist

Motivation

  • Interne Suchmaschine

  • Web Crawler / Intranet Crawler -> Windows

  • Warum linear? Weil's einfach ist.

Crawler?

Struktur

Struktur

Struktur

Struktur

Struktur

Parallelisierung

Fork und Threads

  • preemptives Multitasking

  • fork() funktioniert erstaunlich gut unter Windows.

  • Erstaunlich gut ist leider nicht gut genug

State machine / Zustandsmaschine

  • Nur verständlich, wenn man das Bild oben hat

  • bzw. das Bild zu HTTP Requests...

  • geschachtelte State Machines sind schwierig

Async / select() / NIO mit Callbacks

  • AnyEvent

  • Callback Hell

  • Javascript

Async/Await

async / await

  • Zwei neue Keywords um asynchronen Code schöner zu schreiben

  • Future::AsyncAwait

  • Perl 5.16+

Dramatis Personae

Dramatis Personae

Queue - eine Queue

 1:  our @queue;

Dramatis Personae

UA - User Agent ("Requester", "Browser"):

LWP::UserAgent

 1:    $ua->get($url);

Dramatis Personae

UA - User Agent ("Requester", "Browser"):

LWP::UserAgent

 1:    $ua->get($url);

Future

 1:    my $ua = Future::HTTP->new();
 2:    $ua->http_get($url);

Dramatis Personae

Extractor

  • z.B. HTML::Selector::XPath

  • ... oder reguläre Ausdrücke

Linearer Crawler

Linearer Crawler

Linearer Crawler

Linearer Crawler

Linearer Crawler

Linearer Crawler

 1:    our %seen;
 2:    our @queue = @ARGV;
 3:    ...
 4:    do {
 5:        my $url = shift @queue;
 6:        #sleep 10;
 7:        fetch_and_extract( $url );
 8:    } while (@queue);
 9:    print for @results;

Future als API

Idealerweise wie die synchrone API, nur mit "->get()" dahinter:

 1:  my $response = $ua->get($url);

wird zu

 1:  my $response_future = $ua->get( $url ); # starten
 2:  # irgendwas machen
 3:  my $response = $response_future->get(); # Antwort verarbeiten

Future als API

Idealerweise wie die synchrone API, nur mit "->get()" dahinter:

 1:  my $response = $ua->get($url);

wird zu

 1:  my $response_future = $ua->get( $url )->then(sub {
 2:    my ($response) = @_;
 3:    # Antwort verarbeiten
 4:  }); # starten

Modul: Future

Future und Frameworks

->get() ruft das eigentliche Framework auf

Immer noch State Machine im Hintergrund

... aber eine hübschere Fassade

"Mechanische" Umsetzung

  • "await" davor

  • "await" macht das implizite "->get()"

"Mechanische" Umsetzung

  • "await" davor

  • "await" macht das implizite "->get()"

 1:  my $response_future =       $ua->get( $url ); # starten
 2:  my $response = $response_future->get(); # Antwort verarbeiten

"Mechanische" Umsetzung

  • "await" davor

  • "await" macht das implizite "->get()"

 1:  my $response        = await $ua->get( $url );

Vorteil von async/await:

 1:  my $f = $ua->get();
 2:  $f->then( sub {
 3:      my( $response ) = @_;
 4:      print "Habe Antwort, weiter geht's\n";
 5:  });

wird zu

 1:  my $response = await $ua->get();
 2:  print "Habe Antwort, weiter geht's\n";

Vorteil von async/await:

 1:  my $f = AnyEvent::Future->timer( after => 5 );
 2:  $f->then( sub {
 3:      my( $response ) = @_;
 4:      print "Aufgewacht, weiter geht's\n";
 5:  });

wird zu

 1:  my $f = await AnyEvent::Future->timer( after => 5 );
 2:  print "Aufgewacht, weiter geht's\n";

Linearer Crawler

await/async Crawler

AsyncAwait-Crawler

 1:    our %seen;
 2:    our @queue = @ARGV;
 3:    ...
 4:    my @done;
 5:    repeat {
 6:        my $url = shift @queue;
 7:        # await sleep(10);
 8:        $pending{ $url } = 1;
 9:        fetch_and_extract( $url )->then(sub {
10:            delete $pending{ $url };
11:        });
12:
13:        my $next_action;
14:        if( @queue ) {
15:            $next_action = Future->done(1)
16:        } elsif( scalar keys %pending ) {
17:            $next_action = Future->done(1)
18:        } else {
19:            $next_action = $done;
20:            $done->done(@results)
21:        }
22:        $next_action
23:    }, while => sub { $_[0]->get });
24:    @results = $done->get;

Vorteile / Nachteile fork vs. async

 1:                 fork                async
 2:  CPU            Beliebige CPUs      1 CPU
 3:  Crash          egal                fatal
 4:  Race
 5:    conditions   Ja                  eher nein
 6:  Framework      Parallel::FM        Future / AnyEvent / Mojolicious / ...
 7:  Globale
 8:    Variablen    Nein                Ja
 9:  Implementation Trivial             Rewrite

Danke

Danke

Fragen?

Danke

Fragen?

Slides sind online:

https://corion.net/talks/

Bonus Section

Warum nicht mehrere Prozesse (ohne fork())

  • avoid IPC

  • Use Dancer and WWW::Mechanize::Chrome in the same process

Warum nicht Coro

  • async macht deutlich, wohin abgegeben werden kann

  • Nur an den direkten Aufrufer

  • Coro macht viel mehr Gymnastik

  • Kann dafür auch viel mehr

  • Möchte/brauche ich nicht

Architektur eines besseren Crawlers

  • Queue sollte persistent sein

  • Anfrage und Antwort speichern (WARC)

     1:      Queue -> In flight -> Extractor -> WARC -> Results
     2:       ^+---- retry ---+      |
     3:       ^+---- new links ------+
  • Wenn Du alle Fragen kennst, die in Zukunft gestellt werden, warum bist Du dann Programmierer?

Sleep Problem

Wir möchten die Zahl der Requests wieder reduzieren

5 Sekunden bevor der nächste Request gesendet wird

 1:  sleep 5;

Linearer Crawler + Parallel::ForkManager (Queue muss DB werden)

Aufrufe zu Futures umbauen wenn API nicht schon kann