当前位置:   article > 正文

PHP下kafka的实践_php使用kafka详细

php使用kafka详细

kafka

简介

Kafka 是一种高吞吐量的分布式发布订阅消息系统

kafka角色必知

  1. producer:生产者。
  2. consumer:消费者。
  3. topic: 消息以topic为类别记录,Kafka将消息种子(Feed)分类, 每一类的消息称之为一个主题(Topic)。
  4. broker:以集群的方式运行,可以由一个或多个服务组成,每个服务叫做一个broker;消费者可以订阅一个或多个主题(topic), 并从Broker拉数据,从而消费这些已发布的消息。

经典模型

  1. 1. 一个主题下的分区不能小于消费者数量,即一个主题下消费者数量不能大于分区属,大了就浪费了空闲了
  2. 2. 一个主题下的一个分区可以同时被不同消费组其中某一个消费者消费
  3. 3. 一个主题下的一个分区只能被同一个消费组的一个消费者消费

clipboard.png

常用参数说明

request.required.acks

  1. Kafka producer的ack有3中机制,初始化producer时的producerconfig可以通过配置request.required.acks不同的值来实现。
  2. 0:这意味着生产者producer不等待来自broker同步完成的确认继续发送下一条(批)消息。此选项提供最低的延迟但最弱的耐久性保证(当服务器发生故障时某些数据会丢失,如leader已死,但producer并不知情,发出去的信息broker就收不到)。
  3. 1:这意味着producer在leader已成功收到的数据并得到确认后发送下一条message。此选项提供了更好的耐久性为客户等待服务器确认请求成功(被写入死亡leader但尚未复制将失去了唯一的消息)。
  4. -1:这意味着producer在follower副本确认接收到数据后才算一次发送完成。
  5. 此选项提供最好的耐久性,我们保证没有信息将丢失,只要至少一个同步副本保持存活。
  6. 三种机制,性能依次递减 (producer吞吐量降低),数据健壮性则依次递增。

auto.offset.reset

  1. 1. earliest:自动将偏移重置为最早的偏移量
  2. 2. latest:自动将偏移量重置为最新的偏移量(默认)
  3. 3. none:如果consumer group没有发现先前的偏移量,则向consumer抛出异常。
  4. 4. 其他的参数:向consumer抛出异常(无效参数)

kafka安装和简单测试

安装kafka(不需要安装,解包即可)

  1. # 官方下载地址:http://kafka.apache.org/downloads
  2. # wget https://www.apache.org/dyn/closer.cgi?path=/kafka/1.1.1/kafka_2.12-1.1.1.tgz
  3. tar -xzf kafka_2.12-1.1.1.tgz
  4. cd kafka_2.12-1.1.0

启动kafka server

  1. # 需先启动zookeeper
  2. # -daemon 可启动后台守护模式
  3. bin/zookeeper-server-start.sh config/zookeeper.properties
  4. bin/kafka-server-start.sh config/server.properties

启动kafka客户端测试

  1. # 创建一个话题,test话题2个分区
  2. bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 2 --topic test
  3. Created topic "test".
  4. # 显示所有话题
  5. bin/kafka-topics.sh --list --zookeeper localhost:2181
  6. test
  7. # 显示话题信息
  8. bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic test
  9. Topic:test PartitionCount:2 ReplicationFactor:1 Configs:
  10. Topic: test Partition: 0 Leader: 0 Replicas: 0 Isr: 0
  11. Topic: test Partition: 1 Leader: 0 Replicas: 0 Isr: 0
  12. # 启动一个生产者(输入消息)
  13. bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test
  14. [等待输入自己的内容 出现>输入即可]
  15. >i am a new msg !
  16. >i am a good msg ?
  17. # 启动一个消费者(等待消息)
  18. # 注意这里的--from-beginning,每次都会从头开始读取,你可以尝试去掉和不去掉看下效果
  19. bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning
  20. [等待消息]
  21. i am a new msg !
  22. i am a good msg ?

安装kafka的php扩展

  1. # 先安装rdkfka库文件
  2. git clone https://github.com/edenhill/librdkafka.git
  3. cd librdkafka/
  4. ./configure
  5. make
  6. sudo make install
  7. git clone https://github.com/arnaud-lb/php-rdkafka.git
  8. cd php-rdkafka
  9. phpize
  10. ./configure
  11. make all -j 5
  12. sudo make install
  13. vim [php]/php.ini
  14. extension=rdkafka.so

php代码实践

生产者

  1. <?php
  2. $conf = new RdKafka\Conf();
  3. $conf->setDrMsgCb(function ($kafka, $message) {
  4. file_put_contents("./dr_cb.log", var_export($message, true).PHP_EOL, FILE_APPEND);
  5. });
  6. $conf->setErrorCb(function ($kafka, $err, $reason) {
  7. file_put_contents("./err_cb.log", sprintf("Kafka error: %s (reason: %s)", rd_kafka_err2str($err), $reason).PHP_EOL, FILE_APPEND);
  8. });
  9. $rk = new RdKafka\Producer($conf);
  10. $rk->setLogLevel(LOG_DEBUG);
  11. $rk->addBrokers("127.0.0.1");
  12. $cf = new RdKafka\TopicConf();
  13. // -1必须等所有brokers同步完成的确认 1当前服务器确认 0不确认,这里如果是0回调里的offset无返回,如果是1和-1会返回offset
  14. // 我们可以利用该机制做消息生产的确认,不过还不是100%,因为有可能会中途kafka服务器挂掉
  15. $cf->set('request.required.acks', 0);
  16. $topic = $rk->newTopic("test", $cf);
  17. $option = 'qkl';
  18. for ($i = 0; $i < 20; $i++) {
  19. //RD_KAFKA_PARTITION_UA自动选择分区
  20. //$option可选
  21. $topic->produce(RD_KAFKA_PARTITION_UA, 0, "qkl . $i", $option);
  22. }
  23. $len = $rk->getOutQLen();
  24. while ($len > 0) {
  25. $len = $rk->getOutQLen();
  26. var_dump($len);
  27. $rk->poll(50);
  28. }

运行生产者

  1. php producer.php
  2. # output
  3. int(20)
  4. int(20)
  5. int(20)
  6. int(20)
  7. int(0)
  8. # 你可以查看你刚才上面启动的消费者shell应该会输出消息
  9. qkl . 0
  10. qkl . 1
  11. qkl . 2
  12. qkl . 3
  13. qkl . 4
  14. qkl . 5
  15. qkl . 6
  16. qkl . 7
  17. qkl . 8
  18. qkl . 9
  19. qkl . 10
  20. qkl . 11
  21. qkl . 12
  22. qkl . 13
  23. qkl . 14
  24. qkl . 15
  25. qkl . 16
  26. qkl . 17
  27. qkl . 18
  28. qkl . 19

Low Level 消费者

  1. <?php
  2. $conf = new RdKafka\Conf();
  3. $conf->setDrMsgCb(function ($kafka, $message) {
  4. file_put_contents("./c_dr_cb.log", var_export($message, true), FILE_APPEND);
  5. });
  6. $conf->setErrorCb(function ($kafka, $err, $reason) {
  7. file_put_contents("./err_cb.log", sprintf("Kafka error: %s (reason: %s)", rd_kafka_err2str($err), $reason).PHP_EOL, FILE_APPEND);
  8. });
  9. //设置消费组
  10. $conf->set('group.id', 'myConsumerGroup');
  11. $rk = new RdKafka\Consumer($conf);
  12. $rk->addBrokers("127.0.0.1");
  13. $topicConf = new RdKafka\TopicConf();
  14. $topicConf->set('request.required.acks', 1);
  15. //在interval.ms的时间内自动提交确认、建议不要启动
  16. //$topicConf->set('auto.commit.enable', 1);
  17. $topicConf->set('auto.commit.enable', 0);
  18. $topicConf->set('auto.commit.interval.ms', 100);
  19. // 设置offset的存储为file
  20. //$topicConf->set('offset.store.method', 'file');
  21. // 设置offset的存储为broker
  22. $topicConf->set('offset.store.method', 'broker');
  23. //$topicConf->set('offset.store.path', __DIR__);
  24. //smallest:简单理解为从头开始消费,其实等价于上面的 earliest
  25. //largest:简单理解为从最新的开始消费,其实等价于上面的 latest
  26. //$topicConf->set('auto.offset.reset', 'smallest');
  27. $topic = $rk->newTopic("test", $topicConf);
  28. // 参数1消费分区0
  29. // RD_KAFKA_OFFSET_BEGINNING 重头开始消费
  30. // RD_KAFKA_OFFSET_STORED 最后一条消费的offset记录开始消费
  31. // RD_KAFKA_OFFSET_END 最后一条消费
  32. $topic->consumeStart(0, RD_KAFKA_OFFSET_BEGINNING);
  33. //$topic->consumeStart(0, RD_KAFKA_OFFSET_END); //
  34. //$topic->consumeStart(0, RD_KAFKA_OFFSET_STORED);
  35. while (true) {
  36. //参数1表示消费分区,这里是分区0
  37. //参数2表示同步阻塞多久
  38. $message = $topic->consume(0, 12 * 1000);
  39. if (is_null($message)) {
  40. sleep(1);
  41. echo "No more messages\n";
  42. continue;
  43. }
  44. switch ($message->err) {
  45. case RD_KAFKA_RESP_ERR_NO_ERROR:
  46. var_dump($message);
  47. break;
  48. case RD_KAFKA_RESP_ERR__PARTITION_EOF:
  49. echo "No more messages; will wait for more\n";
  50. break;
  51. case RD_KAFKA_RESP_ERR__TIMED_OUT:
  52. echo "Timed out\n";
  53. break;
  54. default:
  55. throw new \Exception($message->errstr(), $message->err);
  56. break;
  57. }
  58. }

High LEVEL消费者

  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: qkl
  5. * Date: 2018/8/22
  6. * Time: 17:58
  7. */
  8. $conf = new \RdKafka\Conf();
  9. function rebalance(\RdKafka\KafkaConsumer $kafka, $err, array $partitions = null) {
  10. global $offset;
  11. switch ($err) {
  12. case RD_KAFKA_RESP_ERR__ASSIGN_PARTITIONS:
  13. echo "Assign: ";
  14. var_dump($partitions);
  15. $kafka->assign();
  16. // $kafka->assign([new RdKafka\TopicPartition("qkl01", 0, 0)]);
  17. break;
  18. case RD_KAFKA_RESP_ERR__REVOKE_PARTITIONS:
  19. echo "Revoke: ";
  20. var_dump($partitions);
  21. $kafka->assign(NULL);
  22. break;
  23. default:
  24. throw new \Exception($err);
  25. }
  26. }
  27. // Set a rebalance callback to log partition assignments (optional)
  28. $conf->setRebalanceCb(function(\RdKafka\KafkaConsumer $kafka, $err, array $partitions = null) {
  29. rebalance($kafka, $err, $partitions);
  30. });
  31. // Configure the group.id. All consumer with the same group.id will consume
  32. // different partitions.
  33. $conf->set('group.id', 'test-110-g100');
  34. // Initial list of Kafka brokers
  35. $conf->set('metadata.broker.list', '192.168.216.122');
  36. $topicConf = new \RdKafka\TopicConf();
  37. $topicConf->set('request.required.acks', -1);
  38. //在interval.ms的时间内自动提交确认、建议不要启动
  39. $topicConf->set('auto.commit.enable', 0);
  40. //$topicConf->set('auto.commit.enable', 0);
  41. $topicConf->set('auto.commit.interval.ms', 100);
  42. // 设置offset的存储为file
  43. $topicConf->set('offset.store.method', 'file');
  44. $topicConf->set('offset.store.path', __DIR__);
  45. // 设置offset的存储为broker
  46. // $topicConf->set('offset.store.method', 'broker');
  47. // Set where to start consuming messages when there is no initial offset in
  48. // offset store or the desired offset is out of range.
  49. // 'smallest': start from the beginning
  50. $topicConf->set('auto.offset.reset', 'smallest');
  51. // Set the configuration to use for subscribed/assigned topics
  52. $conf->setDefaultTopicConf($topicConf);
  53. $consumer = new \RdKafka\KafkaConsumer($conf);
  54. //$KafkaConsumerTopic = $consumer->newTopic('qkl01', $topicConf);
  55. // Subscribe to topic 'test'
  56. $consumer->subscribe(['qkl01']);
  57. echo "Waiting for partition assignment... (make take some time when\n";
  58. echo "quickly re-joining the group after leaving it.)\n";
  59. while (true) {
  60. $message = $consumer->consume(120*1000);
  61. switch ($message->err) {
  62. case RD_KAFKA_RESP_ERR_NO_ERROR:
  63. var_dump($message);
  64. // $consumer->commit($message);
  65. // $KafkaConsumerTopic->offsetStore(0, 20);
  66. break;
  67. case RD_KAFKA_RESP_ERR__PARTITION_EOF:
  68. echo "No more messages; will wait for more\n";
  69. break;
  70. case RD_KAFKA_RESP_ERR__TIMED_OUT:
  71. echo "Timed out\n";
  72. break;
  73. default:
  74. throw new \Exception($message->errstr(), $message->err);
  75. break;
  76. }
  77. }

消费组特别说明

特别注意,High LEVEL消费者设置的消费组,kafka服务器才会记录, Low Level消费者设置的消费组,服务器不会记录

具体查看消费组信息,你可以翻阅本篇文章

查看服务器元数据(topic/partition/broker)

  1. <?php
  2. $conf = new RdKafka\Conf();
  3. $conf->setDrMsgCb(function ($kafka, $message) {
  4. file_put_contents("./xx.log", var_export($message, true), FILE_APPEND);
  5. });
  6. $conf->setErrorCb(function ($kafka, $err, $reason) {
  7. printf("Kafka error: %s (reason: %s)\n", rd_kafka_err2str($err), $reason);
  8. });
  9. $conf->set('group.id', 'myConsumerGroup');
  10. $rk = new RdKafka\Consumer($conf);
  11. $rk->addBrokers("127.0.0.1");
  12. $allInfo = $rk->metadata(true, NULL, 60e3);
  13. $topics = $allInfo->getTopics();
  14. echo rd_kafka_offset_tail(100);
  15. echo "--";
  16. echo count($topics);
  17. echo "--";
  18. foreach ($topics as $topic) {
  19. $topicName = $topic->getTopic();
  20. if ($topicName == "__consumer_offsets") {
  21. continue ;
  22. }
  23. $partitions = $topic->getPartitions();
  24. foreach ($partitions as $partition) {
  25. // $rf = new ReflectionClass(get_class($partition));
  26. // foreach ($rf->getMethods() as $f) {
  27. // var_dump($f);
  28. // }
  29. // die();
  30. $topPartition = new RdKafka\TopicPartition($topicName, $partition->getId());
  31. echo "当前的话题:" . ($topPartition->getTopic()) . " - " . $partition->getId() . " - ";
  32. echo "offset:" . ($topPartition->getOffset()) . PHP_EOL;
  33. }
  34. }

如果需远端生产和消费

  1. vim config/server.properties
  2. advertised.listeners=PLAINTEXT://ip:9092
  3. # ip 未你kafka的外网ip即可

分享一个打包好的php-rdkafka的类库

https://github.com/qkl9527/php-rdkafka-class

clipboard.png

参考文献

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/894064
推荐阅读
相关标签
  

闽ICP备14008679号