PHP Academy · Oppitunti

Miksi asynkroninen viestintä

Nähkää, miten jonot irrottavat tuottajat kuluttajista.

Oppitunti 1/413 vaihetta

Miksi asynkroninen viestintä on ilmainen PHP Academy-oppitunti CoddyKitissä. Tämä on oppitunti 1/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu PHP Academy-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. PHP Academy-kurssilla on yhteensä 4 oppituntia.

Miksi asynkronista viestinvälitystä käytetään

Synkronisessa pyynnössä PHP-prosessi blokkaa ollessaan yhteydessä sähköpostiyhdyskäytäviin, maksupalveluihin tai downstream-palveluihin. Kuormituksen kasvaessa tämä sitoo viiveen ja käytettävyyden jokaiseen kutsuttuun riippuvuuteen. Asynkroninen viestinvälitys katkaisee tämän ketjun: tuottaja lisää viestin jonoon ja palaa heti, minkä jälkeen kuluttajat käsittelevät viestin myöhemmin omassa tahdissaan.

Tässä oppitunnissa käsitellään miksi näin tehdään — irtikytkentää, puskurointia ja toimitustakuiden kompromisseja, jotka on ymmärrettävä ennen kuin RabbitMQ:ta tai Kafkaa käytetään.

Ajallinen ja sijainnillinen irtikytkentä

Jono irtikytkee tuottajat ja kuluttajat toisistaan kahdella tavalla:

  • Sijainnin suhteen — kumpikaan osapuoli ei tarvitse toisen osoitetta; molemmat tuntevat vain brokerin.
  • Ajallisesti — kuluttaja voi olla poissa käytöstä, kun tuottaja julkaisee viestin; viesti odottaa jonossa.

Alla oleva synkroninen versio sitoo verkkopyynnön sähköpostipalvelimen käytettävyyteen ja nopeuteen.

<?php
// Synchronous: the HTTP request blocks on SMTP
function registerUser(string $email): void {
    saveUser($email);
    // If SMTP is slow or down, the user waits or the request fails
    sendWelcomeEmail($email); // 800ms blocking call
    echo "Registered\n";
}

function saveUser(string $e): void { /* ... */ }
function sendWelcomeEmail(string $e): void { usleep(1); }

registerUser('dev@example.com');

Julkaise ja jatka

Asynkroninen versio tallentaa käyttäjän, julkaisee UserRegistered-viestin ja palauttaa vastauksen. Erillinen worker lähettää sähköpostin. HTTP-pyynnön viive riippuu nyt vain nopeasta paikallisesta brokerille julkaisemisesta, ei SMTP-edestakaisesta viestinnästä.

<?php
function registerUser(string $email): void {
    saveUser($email);
    // Publish a lightweight event; worker handles email later
    publish('user.registered', json_encode(['email' => $email]));
    echo "Registered (email queued)\n";
}

function saveUser(string $e): void { /* ... */ }
function publish(string $routingKey, string $payload): void {
    echo "-> queued $routingKey: $payload\n";
}

registerUser('dev@example.com');

Kuormantasaus (puskurointi)

Liikennepiikit ovat epätasaisia, mutta käsittelykapasiteetti on rajallinen. Jono toimii puskurina: se ottaa vastaan 10 000 viestin piikin ja antaa worker-poolin käsitellä viestit kestävällä nopeudella. Ilman jonoa piikki kuormittaisi tietokantaa tai downstream-rajapintaa suoraan liikaa.

Tätä kutsutaan kuormantasaukseksi — viiveestä tingitään hieman, koska viestit voivat odottaa hetken, mutta vakaus paranee eikä mikään kaadu.

Toimitustakuut

Jokainen viestintäjärjestelmä lupaa jonkin toimitustason. Selvittäkää, mikä taso on käytössä:

  • Enintään kerran — lähetä ja unohda; viestejä voi kadota, mutta niitä ei toimiteta koskaan kahdesti.
  • Vähintään kerran — viesti toimitetaan uudelleen kuittaukseen asti; kaksoiskappaleet ovat mahdollisia. Tämä on yleinen oletus.
  • Täsmälleen kerran — ei häviämisiä eikä kaksoiskappaleita; ratkaisu on kallis ja usein vain osittainen illuusio sovellustasolla.

Koska useimmat todelliset järjestelmät tarjoavat vähintään kerran -toimituksen, kuluttajien on kestettävä saman viestin näkeminen kahdesti.

Idempotentit kuluttajat

Vähintään kerran -toimituksen kaksoiskappaleet ratkaistaan idempotenssilla: viestin käsittely kahdesti tuottaa saman tuloksen kuin sen käsittely kerran. Tavallinen tekniikka on viestin tunnisteen sisältävä deduplikaatioavain, joka tallennetaan yksikäsitteiseen indeksiin.

<?php
function handle(array $msg, PDO $pdo): void {
    $pdo->beginTransaction();
    try {
        // Unique constraint on message_id makes the insert the dedup gate
        $stmt = $pdo->prepare(
            'INSERT INTO processed_messages (id) VALUES (?)'
        );
        $stmt->execute([$msg['id']]);
    } catch (PDOException $e) {
        $pdo->rollBack();
        echo "Duplicate {$msg['id']} skipped\n";
        return; // already handled
    }
    chargeCustomer($msg['amount']);
    $pdo->commit();
}
function chargeCustomer(int $a): void {}

Kuittaukset ja uudelleentoimitus

Kuluttaja ilmoittaa onnistumisesta ack-kuittauksella. Jos se kaatuu ennen kuittausta, brokeri toimittaa viestin uudelleen toiselle kuluttajalle. Näin vähintään kerran -toimitus toimii — mutta siksi ack-kuittauksen on tultava sen jälkeen, kun sivuvaikutus on commitattu, ei koskaan ennen.

Jos kuittaatte liian aikaisin ja prosessi kaatuu, viesti katoaa. Jos kuittaatte liian myöhään ja prosessi kaatuu, syntyy kaksoiskappale — jonka idempotenssikerros käsittelee.

<?php
// Pseudocode of the consumer contract
function consumeLoop($channel): void {
    while ($msg = $channel->get()) {
        try {
            processSideEffect($msg);   // commit DB write first
            $channel->ack($msg);       // only then ack
        } catch (\Throwable $e) {
            $channel->nack($msg, requeue: true); // let it redeliver
        }
    }
}
function processSideEffect($m): void {}

Järjestys ei tule ilmaiseksi

Jonot eivät takaa yleistä järjestystä, kun kuluttajia skaalataan useita. Kaksi samasta jonosta viestejä hakevaa workeria käsittelee niitä samanaikaisesti, joten viesti B voi valmistua ennen viestiä A.

Jos järjestyksellä on merkitystä, esimerkiksi tilin saldomuutoksissa, viestit on osioitava avaimen perusteella, jotta kaikki toisiinsa liittyvät viestit menevät yhdelle kuluttajalle järjestyksessä. Kafka tekee tämän natiivisti osioilla; RabbitMQ:ssa viestit reititetään yhtenäisen hajautuksen avulla avainkohtaisiin jonoihin.

Dead letter -jonot

Jotkin viestit eivät voi koskaan onnistua — esimerkiksi hyötykuorma voi olla virheellinen tai viitata poistettuun riviin. Niiden ikuinen uudelleenyritys jumittaa jonon (myrkyllinen viesti). Ratkaisu on dead letter queue (DLQ): N epäonnistuneen yrityksen jälkeen viesti siirretään sivuun tutkittavaksi sen sijaan, että sitä toimitettaisiin uudelleen.

<?php
function consume(array $msg, $channel): void {
    $attempts = ($msg['headers']['x-attempt'] ?? 0) + 1;
    try {
        process($msg);
        $channel->ack($msg);
    } catch (\Throwable $e) {
        if ($attempts >= 5) {
            $channel->deadLetter($msg); // park in DLQ
        } else {
            $channel->republish($msg, ['x-attempt' => $attempts]);
        }
    }
}
function process(array $m): void {}

Milloin jonoa ei pidä käyttää

Asynkroninen viestinvälitys aiheuttaa todellisia operatiivisia kustannuksia: ylläpidettävän brokerin, tuotetiimille selitettävän eventual consistency -mallin ja prosessirajat ylittävän virheenkorjauksen vaikeutumisen.

Valitkaa jono, kun työ on hidasta, piikkimäistä, uudelleen yritettävää tai fire-and-forget-tyyppistä. Pitäkää käsittely synkronisena, kun kutsuja todella tarvitsee tuloksen heti, esimerkiksi hinnan, joka käyttäjälle on näytettävä auktoritatiivisena — vastauksen vaativan kutsun siirtäminen jonoon lisää vain viivettä ja monimutkaisuutta.

Jono vai loki

Nämä mallit perustuvat kahteen yleiseen brokerirakenteeseen, joita kurssin loppuosassa käytetään molempia:

  • Tehtäväjono (RabbitMQ) poistaa viestin, kun se on kuitattu. Se sopii erinomaisesti työn jakamiseen kilpailevien kuluttajien poolille sekä viestikohtaiseen reititykseen ja TTL-arvoihin.
  • Commit-loki (Kafka) säilyttää viestit säilytysajan perusteella; kukin kuluttaja seuraa omaa offsetiaan ja voi toistaa historian, ja monet toisistaan riippumattomat kuluttajaryhmät voivat lukea samaa virtaa.

Valitkaa jono työn jakamiseen ja loki suuren läpimenon tapahtumavirtoihin sekä uudelleen toistamiseen.

<?php
$useCase = 'replay events for a new analytics service';
$broker = str_contains($useCase, 'replay') || str_contains($useCase, 'stream')
    ? 'Kafka (commit log)'
    : 'RabbitMQ (task queue)';
echo $broker . "\n";

Pikatarkistus

Toimitustakuiden päättelyä.

Kertaus

Nyt hallitsette asynkronisen viestinvälityksen taustalla olevan ajatusmallin:

  • Jonot tarjoavat sijainnillisen ja ajallisen irtikytkennän ja toimivat puskurina kuormantasauksessa.
  • Useimmat järjestelmät toimittavat viestit vähintään kerran, joten kuluttajien on oltava idempotentteja.
  • Tehkää ack-kuittaus vasta sivuvaikutuksen commitoinnin jälkeen ja antakaa virheiden käynnistää uudelleentoimitus.
  • Järjestys vaatii osioinnin avaimen perusteella; myrkylliset viestit vaativat DLQ:n.
  • Älkää siirtäkö jonoon työtä, jonka vastausta kutsuja tarvitsee synkronisesti.

Seuraavaksi sovellatte tätä käytännössä RabbitMQ:n avulla PHP:ssä.

Aloita maksutta

Opi PHP tekoälytuutorin avulla — ilmaiseksi

Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.

Kurssit
49
Oppitunnit
195

Usein kysytyt kysymykset

Onko oppitunti ”Miksi asynkroninen viestintä” ilmainen?

Kyllä – oppitunnin ”Miksi asynkroninen viestintä” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko PHP Academy-kurssin, päivitä CoddyKit PROhon. PHP Academy-kurssilla on yhteensä 4 oppituntia.

Mitä opin oppitunnilla ”Miksi asynkroninen viestintä”?

Nähkää, miten jonot irrottavat tuottajat kuluttajista. Harjoittelet PHP Academy-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.

Tarvitsenko kokemusta aloittaakseni PHP Academy-opiskelun?

Aiempi kokemus ei ole tarpeen. CoddyKitin PHP Academy-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 1/4.

Kuinka kauan ”Miksi asynkroninen viestintä”-oppitunnin suorittaminen kestää?

Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.

Voinko kirjoittaa ja suorittaa koodia tällä PHP Academy-oppitunnilla?

Kyllä. Jokainen PHP Academy-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.

Kaikki tämän kurssin oppitunnit

  1. Miksi asynkroninen viestintä
  2. RabbitMQ:n käyttö PHP:ssä
  3. Apache Kafka PHP:llä
  4. Tapahtumaohjattujen työnkulkujen rakentaminen
← Takaisin: PHP Academy