0Pricing
PHP Academy · Lezione

Apache Kafka con PHP

Elabori eventi ad alta velocità con Kafka

Apache Kafka con PHP è una lezione PHP Academy gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento PHP Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso PHP Academy include 4 lezioni in totale.

Kafka con PHP

Kafka non è una coda di attività: è un commit log distribuito e append-only. I producer aggiungono record ai topic; i consumer leggono dal proprio offset e possono rileggere la cronologia. Questo rende Kafka ideale per flussi di eventi ad alto throughput, event sourcing e alimentazione di più gruppi di consumer indipendenti a partire da un unico stream.

In PHP si comunica con Kafka tramite ext-rdkafka, un binding sulla libreria C collaudata librdkafka.

Log, non coda

Il cambio di prospettiva mentale da RabbitMQ a Kafka:

  • I messaggi non vengono eliminati quando sono consumati; scadono in base alla politica di conservazione (tempo o dimensione).
  • Ogni consumer tiene traccia del proprio offset, cioè della sua posizione nel log.
  • Un topic è suddiviso in partizioni; l'ordinamento è garantito solo all'interno di una partizione.
  • Più gruppi di consumer leggono lo stesso topic in modo indipendente.

Se servono il replay, la distribuzione a molti lettori o un throughput enorme, Kafka è adatto. Se servono il routing per messaggio e i TTL, è adatto RabbitMQ.

Installazione di ext-rdkafka

Installi prima la libreria nativa, poi l'estensione PECL e, facoltativamente, un wrapper di livello superiore.

# Debian/Ubuntu
apt-get install -y librdkafka-dev
pecl install rdkafka
echo "extension=rdkafka.so" >> php.ini

# Optional ergonomic wrapper
composer require longlang/phpkafka  # or arnaud-lb/php-rdkafka-stubs for IDE

Produzione dei record

Crei un RdKafka\Producer, ottenga un handle per il topic e chiami produce(). La chiave determina in quale partizione finirà un record: stessa chiave, stessa partizione e ordine preservato. Esegua sempre flush() prima di uscire, altrimenti i record bufferizzati andranno persi.

<?php
$conf = new RdKafka\Conf();
$conf->set('bootstrap.servers', 'localhost:9092');
$producer = new RdKafka\Producer($conf);

$topic = $producer->newTopic('orders');
// RD_KAFKA_PARTITION_UA = let the partitioner choose by key
$key = 'order-42';
$topic->produce(RD_KAFKA_PARTITION_UA, 0, json_encode(['id' => 42]), $key);

$producer->poll(0);
$result = $producer->flush(10000); // wait up to 10s
if ($result !== RD_KAFKA_RESP_ERR_NO_ERROR) {
    throw new RuntimeException('Failed to flush');
}

Partizionamento per chiave

Il partizionamento è il cuore della scalabilità e dell'ordinamento di Kafka. Il partizionatore predefinito calcola l'hash della chiave del record: partition = hash(key) % numPartitions. La scelta di una buona chiave è importante:

  • Chiave basata su customerId → tutti gli eventi di un cliente restano ordinati e in un'unica partizione.
  • Chiave nulla → distribuzione round-robin tra le partizioni (throughput massimo, nessun ordinamento).

Non è mai possibile ridurre il numero di partizioni di un topic e l'aggiunta di partizioni rimescola la mappatura dell'hash: dimensioni quindi le partizioni in anticipo per il massimo parallelismo.

Gruppi di consumer e offset

Utilizzi il KafkaConsumer di alto livello con group.id. Kafka assegna le partizioni ai membri del gruppo ed esegue un ribilanciamento quando i membri entrano o escono. Ogni membro legge solo le partizioni assegnate, ottenendo così scalabilità orizzontale senza costi aggiuntivi.

<?php
$conf = new RdKafka\Conf();
$conf->set('bootstrap.servers', 'localhost:9092');
$conf->set('group.id', 'order-emailers');
$conf->set('auto.offset.reset', 'earliest'); // start of log if no offset
$conf->set('enable.auto.commit', 'false');   // we commit manually

$consumer = new RdKafka\KafkaConsumer($conf);
$consumer->subscribe(['orders']);

while (true) {
    $msg = $consumer->consume(5000);
    if ($msg->err === RD_KAFKA_RESP_ERR_NO_ERROR) {
        handle($msg->payload);
        $consumer->commit($msg); // commit offset AFTER work
    }
}
function handle(string $p): void {}

Quando eseguire il commit

Il commit dell'offset definisce la semantica di consegna:

  • Commit dopo l'elaborazione → at-least-once (un arresto anomalo prima del commit riproduce il record).
  • Commit prima dell'elaborazione → at-most-once (un arresto anomalo lo fa perdere).

L'auto-commit (enable.auto.commit=true) esegue il commit a intervalli regolari, indipendentemente dal fatto che l'elaborazione sia terminata: è comodo, ma un arresto anomalo può eliminare i record senza che ve ne accorgiate. Lo disabiliti ed esegua manualmente il commit quando la correttezza è importante.

Gestione degli errori di consumo

Non ogni valore restituito da consume() è un messaggio. È necessario distinguere in base al codice di errore: __PARTITION_EOF e __TIMED_OUT sono normali segnali di controllo, non errori.

<?php
$msg = $consumer->consume(2000);
switch ($msg->err) {
    case RD_KAFKA_RESP_ERR_NO_ERROR:
        echo "Got: {$msg->payload} @ offset {$msg->offset}\n";
        break;
    case RD_KAFKA_RESP_ERR__PARTITION_EOF:
        echo "Reached end of partition\n"; // caught up, keep polling
        break;
    case RD_KAFKA_RESP_ERR__TIMED_OUT:
        echo "No message this poll\n";
        break;
    default:
        throw new \Exception($msg->errstr(), $msg->err);
}

Ottimizzazione del throughput

Il throughput di Kafka deriva dal batching. Impostazioni chiave del producer:

  • linger.ms — attende brevemente per raggruppare più record in ogni richiesta (ad es. 5–20 ms).
  • batch.size / queue.buffering.max.messages — buffer più grandi, meno richieste avanti e indietro.
  • compression.type — lz4 o zstd riduce drasticamente il costo di rete.
  • acks — all per la durabilità, 1 per una latenza inferiore.
<?php
$conf = new RdKafka\Conf();
$conf->set('bootstrap.servers', 'localhost:9092');
$conf->set('compression.type', 'lz4');
$conf->set('linger.ms', '10');
$conf->set('batch.size', '65536');
$conf->set('acks', 'all');
$producer = new RdKafka\Producer($conf);

Callback dei rapporti di consegna

Poiché produce() è asincrona, un invio fallito non genera immediatamente un'eccezione. Registri una callback del rapporto di consegna nella configurazione del producer per conoscere l'esito di ogni record: in PHP questo è l'unico modo affidabile per rilevare gli errori silenziosi del producer.

<?php
$conf = new RdKafka\Conf();
$conf->set('bootstrap.servers', 'localhost:9092');
$conf->setDrMsgCb(function ($producer, $msg) {
    if ($msg->err) {
        fwrite(STDERR, 'Delivery FAILED: ' . rd_kafka_err2str($msg->err) . "\n");
    } else {
        echo "Delivered to partition {$msg->partition} @ offset {$msg->offset}\n";
    }
});
$producer = new RdKafka\Producer($conf);
// poll() services the callback queue; call it after producing
$producer->poll(0);

Problemi specifici di PHP

Kafka presuppone client di lunga durata; il ciclo di vita delle richieste PHP va nella direzione opposta:

  • I producer eseguono il buffering in modo asincrono: esegua sempre flush() prima che lo script termini, altrimenti i record andranno persi.
  • Esegua i consumer come worker CLI persistenti sotto un supervisore, mai all'interno di una richiesta web.
  • I ribilanciamenti sospendono il consumo; mantenga breve l'elaborazione per messaggio oppure imposti max.poll.interval.ms su un valore sufficientemente alto, così da non essere espulso dal gruppo.
  • Imposti log_level e registri la callback del rapporto di consegna del producer per rilevare gli errori silenziosi.

Verifica rapida

Garanzie sull'ordinamento in Kafka.

Riepilogo

Kafka in PHP:

  • Kafka è un log riproducibile, non una coda; i consumer tengono traccia degli offset.
  • Il partizionamento per chiave offre ordinamento per chiave e scalabilità.
  • I gruppi di consumer suddividono le partizioni ed eseguono automaticamente il ribilanciamento.
  • Esegua il commit degli offset dopo l'elaborazione per ottenere at-least-once; disabiliti l'auto-commit per avere il controllo.
  • Regoli linger.ms, batch.size e compression.type; esegua sempre flush().

Successivamente: collegheremo queste primitive per creare flussi di lavoro affidabili basati sugli eventi.

Domande Frequenti

La lezione «Apache Kafka con PHP» è gratuita?

Sì — il testo completo di «Apache Kafka con PHP» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso PHP Academy, passa a CoddyKit PRO. Il corso PHP Academy include 4 lezioni in totale.

Cosa imparerò in «Apache Kafka con PHP»?

Elabori eventi ad alta velocità con Kafka Eserciti PHP Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare PHP Academy?

Non è richiesta alcuna esperienza precedente. PHP Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.

Quanto tempo richiede la lezione «Apache Kafka con PHP»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione PHP Academy?

Sì. Ogni lezione PHP Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Perché usare la messaggistica asincrona
  2. Utilizzare RabbitMQ in PHP
  3. Apache Kafka con PHP
  4. Creare flussi di lavoro basati sugli eventi
← Torna a PHP Academy