环境要求:
1、安装好docker,docker-compose
2、测试机的话可以关闭防火墙
3、个人使用的机子是centos7
docker-compose.yml文件
version: '2'
services:
zoo1:
image: wurstmeister/zookeeper
restart: unless-stopped
hostname: zoo1
ports:
- "2181:2181"
container_name: zookeeper
kafka1:
image: wurstmeister/kafka
ports:
- "9092:9092"
environment:
KAFKA_ADVERTISED_HOST_NAME: localhost
KAFKA_ZOOKEEPER_CONNECT: "zoo1:2181"
KAFKA_BROKER_ID: 1
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_CREATE_TOPICS: "stream-in:1:1,stream-out:1:1"
depends_on:
- zoo1
container_name: kafka
docker-compose up -d -V #启动两个容器
#进入容器测试如否可以生产、消费数据
#生产
docker exec -ti kafka /bin/bash
cd opt/kafka_2.12-2.5.0/bin/
kafka-console-producer.sh --bootstrap-server localhost:9092 --topic test
#再开一个窗口查看消费
docker exec -ti kafka /bin/bash
cd opt/kafka_2.12-2.5.0/bin/
kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning
结果:当生产输入值的回车的时候订阅的消费队列都会有对应的值出现
------------------------------------------------------------------------------------------------------------------------------------------------------------------
使用php操作kafka
1、php需要安装好对应的扩展 rdkafka
git clone https://github.com/edenhill/librdkafka.git
./configure
make
sudo make install
$ git clone https://github.com/arnaud-lb/php-rdkafka.git
#生成configure文件
$ /Users/shiyibo/LNMP/php/bin/phpize
#编译安装
$ ./configure --with-php-config=/Users/shiyibo/LNMP/php/bin/php-config
$ make
$ make install
#在php.ini 文件中配置 rdkafka扩展
$ vim /Users/shiyibo/LNMP/php/etc/php.ini
extension=rdkafka.so
#查看扩展是否生效
$php -m | grep kafka
#原文链接
https://www.jianshu.com/p/fd8ce54e1156
#生产
setDrmSgCb(function ($kafka, $message){
file_put_contents("/opt/dr_cb.log", var_export($message, true).PHP_EOL, FILE_APPEND);
});
$conf->setErrorCb(function ($kafka, $err, $reason){
file_put_contents("/opt/err_cb.log",sprintf("Kafka error: %s (reason: %s)", rd_kafka_err2str($err), $reason).PHP_EOL, FILE_APPEND);
});
$rk = new RdKafka\Producer($conf);
//$rk->setLogLevel(LOG_DEBUG);
$rk->addBrokers("172.20.0.3");
$cf = new RdKafka\TopicConf();
// -1必须等所有brokers同步完成的确认 1当前服务器确认 0不确认,这里如果是0回调里的offset无返回,如果是1和-1会返回offset
// 我们可以利用该机制做消息生产的确认,不过还不是100%,因为有可能会中途kafka服务器挂掉
$cf->set('request.required.acks', 0);
$topic = $rk->newTopic("test", $cf);
$option = 'qkl';
// 从终端接收输入
$oInputHandler = fopen('php://stdin', 'r');
while (true) {
echo "\nEnter messages:\n";
$sMsg = trim(fgets($oInputHandler));
// 空消息意味着退出
if (empty($sMsg)) {
break;
}
// 发送消息
$topic->produce(RD_KAFKA_PARTITION_UA, 0, $sMsg);
}
//
//for ($i = 0; $i < 10; $i++) {
// //RD_KAFKA_PARTITION_UA自动选择分区
// //$option可选
// $topic->produce(RD_KAFKA_PARTITION_UA, 0, "cgd . $i", $option);
//}
$len = $rk->getOutQLen();
var_dump('len:'.$len);
while ($len > 0) {
$len = $rk->getOutQLen();
// var_dump($len);
$rk->poll(10);
}
var_dump("finish");exit;
#消费
setRebalanceCb(function (RdKafka\KafkaConsumer $kafka, $err, array $partitions = null) {
switch ($err) {
case RD_KAFKA_RESP_ERR__ASSIGN_PARTITIONS:
// echo "Assign: ";
// var_dump($partitions);
$kafka->assign($partitions);
break;
case RD_KAFKA_RESP_ERR__REVOKE_PARTITIONS:
// echo "Revoke: ";
// var_dump($partitions);
$kafka->assign(NULL);
break;
default:
throw new \Exception($err);
}
});
// 配置groud.id 具有相同 group.id 的consumer 将会处理不同分区的消息,
// 所以同一个组内的消费者数量如果订阅了一个topic,
// 那么消费者进程的数量多于 多于这个topic 分区的数量是没有意义的。
$conf->set('group.id', $group_id);
// 添加 kafka集群服务器地址
$conf->set('metadata.broker.list', $host); //'localhost:9092,localhost:9093,localhost:9094,localhost:9095'
// 针对低延迟进行了优化的配置。这允许PHP进程/请求尽快发送消息并快速终止
$conf->set('socket.timeout.ms', 50);
//多进程和信号
if (function_exists('pcntl_sigprocmask')) {
pcntl_sigprocmask(SIG_BLOCK, array(SIGIO));
$conf->set('internal.termination.signal', SIGIO);
} else {
$conf->set('queue.buffering.max.ms', 1);
}
$topicConf = new \RdKafka\TopicConf();
// 在interval.ms的时间内自动提交确认、建议不要启动, 1是启动,0是未启动
$topicConf->set('auto.commit.enable', 1);
$topicConf->set('auto.commit.interval.ms', 100);
//smallest:简单理解为从头开始消费,largest:简单理解为从最新的开始消费
$topicConf->set('auto.offset.reset', 'smallest');
// 设置offset的存储为broker
//$topicConf->set('offset.store.method', 'broker');
// 设置offset的存储为file
//$topicConf->set('offset.store.method', 'file');
// 设置offset的存储路径
$topicConf->set('offset.store.path', 'kafka_offset.log');
//$topicConf->set('offset.store.path', __DIR__);
$conf->setDefaultTopicConf($topicConf);
$consumer = new \RdKafka\KafkaConsumer($conf);
// 更新订阅集(自动分配partitions )
$consumer->subscribe([$topic]);
// 指定topic分配partitions使用那个分区
// $consumer->assign([
// new \RdKafka\TopicPartition("zzy8", 0),
// new \RdKafka\TopicPartition("zzy8", 1),
// ]);
while (true) {
// 设置120s为超时
$message = $consumer->consume(3 * 1000);
if (!empty($message)) {
switch ($message->err) {
case RD_KAFKA_RESP_ERR_NO_ERROR:
var_dump('New message received :', $message); // 打印消息
// 拆解对象为数组,并根据业务需求处理数据
$payload = json_decode($message->payload,true);
$key = $message->key;
// 根据kafka中不同key,调用对应方法传递处理数据*(如果有必要的话)
//对该条message进行处理,比如用户数据同步, 记录日志。
// var_dump("asasasasasasasasasasasas");
break;
case RD_KAFKA_RESP_ERR__PARTITION_EOF:
echo "No more messages; will wait for more\n";
break;
case RD_KAFKA_RESP_ERR__TIMED_OUT:
// echo "Timed out\n";
// var_dump("##################");
break;
default:
var_dump("nothing");
throw new \Exception($message->errstr(), $message->err);
break;
}
} else {
var_dump('this is empty obj!!!');
}
}
#注意
此错误不影响生产消费
#tip
相同的group.id订阅同一个topic只有最后启动的那个可以收到