以PHP視角探尋Kafka - 實現簡易生產者功能

老衲愛飲酒發表於2021-11-18

程式碼先行

public function actionProducer()
{
  $conf = new \RdKafka\Conf();
  $conf->set('metadata.broker.list', 'broker地址');

  /**
  * kafka投遞迴調
  * RdKafka\Message Object
  * (
  * [err] => 0      //  等於0時說明投遞成功
  * [topic_name] => topic_name    // 投遞的topic名稱
  * [timestamp] => 1637121418036    // 投遞的時間
  * [partition] => 8            // 投遞的分割槽
  * [payload] => Message 100    // 投遞的訊息
  * [len] => 11     // 訊息的長度
  * [key] =>        // 每個訊息的key 特殊用途(譬如通過key來跑不同的業務  key1建立 key2更改等等)
  * [offset] => 1   // 偏移量 
  * [headers] =>
  * [opaque] =>
  * )
  */
  $conf->setDrMsgCb(function ($kafka, $message) {
    if ($message->err) {
      echo '訊息永久失敗'."\n";
    } else {
      echo '訊息傳送成功'."\n";
    }
  });
  $producer = new \RdKafka\Producer($conf);
  $topic = $producer->newTopic("topic_name");
  for ($i = 0; $i < 10; $i++) {
    // RD_KAFKA_PARTITION_UA = -1
    $topic->produce(RD_KAFKA_PARTITION_UA, 0, "Message {$i}");
  }
  while ($producer->getOutQLen() > 0) {
    $producer->poll(1000);
  }

  for ($flushRetries = 0; $flushRetries < 10; $flushRetries++) {
    $result = $producer->flush(500);
    // RD_KAFKA_RESP_ERR_NO_ERROR = 0
    if (RD_KAFKA_RESP_ERR_NO_ERROR === $result) {
      break;
    }
  }

  if (RD_KAFKA_RESP_ERR_NO_ERROR !== $result) {
    throw new \RuntimeException('無法重新整理, 訊息可能會丟失!');
  }
}

程式碼分解

第一模組 - 配置

$conf = new \RdKafka\Conf();
$conf->set('metadata.broker.list', 'broker地址');
$conf->setDrMsgCb(function ($kafka, $message) {
  if ($message->err) {
    echo '訊息永久失敗'."\n";
  } else {
    echo '訊息傳送成功'."\n";
  }
});
  • 第一部分 new一個kafka配置的例項
  • 第二部分 設定kafka的broker地址 非必須 後續也可以用\Rdkafka\Producer::addBrokers()方法實現broker地址的新增
  • 第三部分 如果需要知道投遞的結果,這個方法一定要實現,此方法是投遞任務後的回撥事件,==需要配合pol()方法使用==。返回值如下
RdKafka\Message Object
(
[err] => 0      //  等於0時說明投遞成功
[topic_name] => topic_name    // 投遞的topic名稱
[timestamp] => 1637121418036    // 投遞的時間
[partition] => 8            // 投遞的分割槽
[payload] => Message 100    // 投遞的訊息
[len] => 11     // 訊息的長度
[key] =>        // 每個訊息的key 特殊用途(譬如通過key來跑不同的業務  key1建立 key2更改等等)
[offset] => 1   // 偏移量 
[headers] =>
[opaque] =>
)

第二模組 - 生成(可能會推送)

$producer = new \RdKafka\Producer($conf);
// $producer->addBrokers('broker地址');
$topic = $producer->newTopic("topic_name");
for ($i = 0; $i < 10; $i++) {
  // RD_KAFKA_PARTITION_UA = -1
  $topic->produce(RD_KAFKA_PARTITION_UA, 0, "Message {$i}");
}
  • 第一部分 new一個producer例項,引數是一個Conf物件,也就是【第一模組】裡面的\Rdkafka\Conf()
  • 第二部分 註釋掉的程式碼和【第一模組】中的【第二部分】的set方式二選一即可
  • 第三部分 建立一個topic

官方解釋如下 中文為百度翻譯

Creates a new topic instance for topic_name.

為主題名稱建立新的主題例項

  • 第四部分 生成並投遞資料 ==這個方法很重要==

官方解釋如下 中文為百度翻譯

Produce and send a single message

生產併傳送一條訊息

Note:
Since producing is asynchronous, you should call flush before you destroy the producer. Otherwise, any outstanding messages will be silently discarded.

由於生產是非同步的,您應該在銷燬生產者之前呼叫flush。否則,任何未完成的訊息將被靜默丟棄。

==如果程式碼到此結束的話,訊息99%可能會丟失,因為生成是非同步,可能會出現程式執行完了,訊息還未投遞的情況,所以可能會導致訊息的丟失==

實驗 將程式睡眠2秒鐘,訊息其實是可以推送的,但是如果訊息特別多的話,睡眠2S可能推送不完,這種情況沒有實驗

$producer = new \RdKafka\Producer($conf);
// $producer->addBrokers('broker地址');
$topic = $producer->newTopic("topic_name");
for ($i = 0; $i < 10; $i++) {
  // RD_KAFKA_PARTITION_UA = -1
  $topic->produce(RD_KAFKA_PARTITION_UA, 0, "Message {$i}");
}
sleep(2);    // 程式休眠

第三模組 - 確認成功機制

while ($producer->getOutQLen() > 0) {
  $producer->poll(1000);
}

for ($flushRetries = 0; $flushRetries < 10; $flushRetries++) {
  $result = $producer->flush(500);
  // RD_KAFKA_RESP_ERR_NO_ERROR = 0
  if (RD_KAFKA_RESP_ERR_NO_ERROR === $result) {
    break;
  }
}
if (RD_KAFKA_RESP_ERR_NO_ERROR !== $result) {
  throw new \RuntimeException('無法重新整理, 訊息可能會丟失!');
}
  • 第一部分

    • while迴圈條件$producer->getOutQLen()

    官方解釋如下 中文為百度翻譯

    Polls for events, cause application provided callbacks to be called.

    事件輪詢,導致應用程式提供的回撥被呼叫。

    Note:

    An application using a sub-class of RdKafka should make sure to call poll() at regular intervals to serve any queued callbacks waiting to be called.

    使用RdKafka子類的應用程式應確保定期呼叫poll(),定期為等待呼叫的任何排隊回撥提供服務。

    Returns the number of messages in the out queue.

    返回輸出佇列中的訊息數

    • 迴圈體 poll() 如果不執行這個方法,【第一模組】的回撥事件將無法執行

    ==關於poll()的引數,個人理解是等待回撥的阻塞時間,譬如1秒鐘時間回撥還沒有執行成功則放棄回撥,如有不對的還望指正==

    官方解釋如下 中文為百度翻譯

    Returns the number of events served.

    返回服務的事件數.

  • 第二部分

    • for迴圈次數,個人建議是通過$producer->getOutQLen()方法來決定次數
    • 迴圈體中plush()方法

    ==關於flush()方法,個人理解是:==這個方法相當於一個兜底的poll()方法,如果前面沒有執行poll()方法,那麼flush()方法也會幫我們去執行poll(),從而確保訊息全部推送,返回值如果是0,則說明全部推送成功,反之訊息可能會丟失

    官方解釋如下 中文為百度翻譯

    Wait until all outstanding produce requests, et.al, are completed. This should typically be done prior to destroying a producer instance to make sure all queued and in-flight produce requests are completed before terminating. This function will call poll() and thus trigger callbacks.

    等到所有未完成的產品請求等完成。這通常應該在銷燬生產者例項之前完成,以確保在終止之前完成所有排隊和進行中的生產請求。此函式將呼叫 poll() 並因此觸發回撥。

    In case of success returns RD_KAFKA_RESP_ERR_NO_ERROR, in case of timeout RD_KAFKA_RESP_ERR__TIMED_OUT and if not called on a producer instance RD_KAFKA_RESP_ERR__NOT_IMPLEMENTED.

    在成功的情況下返回RD_KAFKA_RESP_ERR_NO_ERROR,在超時的情況下,RD_KAFKA_RESP_ERR__TIMED_OUT如果沒有在生產者例項上呼叫RD_KAFKA_RESP_ERR__NOT_IMPLEMENTED

以上是本人結合官方文件整理的一份最基礎的生產者程式碼分析,如果不對還望大家指正!!!!!!

Rdkafka文件:arnaud.le-blanc.net/php-rdkafka-do...

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章