本篇概要:
- 1. 消息队列相关概念;
- 2. Kafka 消息队列;
- 3. 安装 Kafka 服务;
- 4. 安装PHP的 Kafka 扩展 rdkafka;
- 5. 编写 Kafka 的生产者方法;
- 6. 编写 Kafka 的异步消费者方法。
1. 消息队列相关概念;
-
相关概念:
-
消息队列中间件是分布式系统中重要的组件,主要解决
应用耦合,
异步消息,
流量削峰等问题。实现
高性能,
高可用,
可伸缩和
最终一致性架构。是大型分布式系统不可缺少的中间件。
-
使用场景:异步处理
-
场景说明:用户注册成功后,发送注册邮件,再发送注册短信。
-
串行方式:将注册信息写入数据库成功后,向用户发送邮件,再发送注册短信,将结果返回客户端。
-
并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信,以上三个任务完成后,返回给客户端。
-
消息队列:将注册信息写入数据库成功后,注册信息写入消息队列,就将结果返回给客户端。然后发送邮件和短信的消费者异步读取消息队列。
-
使用场景:应用解耦
-
场景说明:用户下单后,订单系统需要通知库存系统。
-
传统方式:订单系统调用库存系统接口(并不可靠,访问接口的时候网络可能挂了,库存系统挂了,用户下单会失败)
-
消息队列:
-
‐ 订单系统:在用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功
-
‐ 库存系统:订阅下单的消息,采用拉 / 推的方式,获取下单信息,库存系统根据下单信息,进行库存操作。这样用户在下单的时候,不会去访问库存系统相关内容
-
使用场景:流量削峰
-
场景说明:秒杀活动,一般会因为流量过大,导致流量暴增。
-
传统方式:服务端突然接收来自前端的大量订单请求。
-
消息队列:在应用的前端加入消息队列。
-
‐ 用户的请求,服务端接收后,首先写入消息队列。假如消息队列长度超过最大数量,则字节抛弃用户请求或跳转到错误页面
-
‐ 秒杀业务根据消息队列中的请求信息,再做后续处理
-
使用场景:日志处理
-
解决大量日志传输的问题
-
日志采集客户端(比如 Yii2 框架的 log 组件),负责日志数据采集,写入 Kafka 队列
-
Kafka 消息队列负责日志数据的接收,存储和转发
-
日志处理应用:订阅并消费 Kafka 队列中的日志数据
-
使用场景:消息通讯
-
点对点消息队列,或者聊天室
-
客户端 A 和客户端 B 使用同一队列,进行消息通讯
-
客户端 A、客户端 B、客户端 N 订阅同一主题,进行消息发布和接收
-
消息队列组要产品
-
目前在生产环境,使用较多的消息队列有 ActiveMQ、RabbitMQ、ZeroMQ、
Kafka、MetaMQ、RocketMQ 等
2. Kafka 消息队列;
-
相关概念:
-
Kafka 是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模较大的网站中的所有动作流数据。
-
Kafka 官网: http://kafka.apache.org
-
优势:
-
高吞吐量:非常普通的硬件 Kafka 也可以支持每秒数百万的消息
-
支持通过 Kafka 服务器和消费机集群来区分消息。可以对消息进行分类,可以使用不同分类的服务器、不同分类的消费机去消费不同分类的消息
-
支持 Hadoop 并行数据加载
-
关键概念:
-
Broker:Kafka 集群中的一台或多台服务器统称为 Broker (服务器)
-
Topic:Kafka 处理的消息源(feeds of message)的不同分类(消息分类)
-
Partition:Topic 物理上的分组,一个 Topic 可以分为多个 partition,每个 partition 是一个有序的队列。partition 中的每条消息都会被分配一个有序的 id(offset)可以把 topic 理解为群名称,消费 topic 的时候可以进行物理上的分组。比如一个 partition 不够用,可以分给多个 partition
-
Message:消息,是通信的基本单位,每个 producer 可以向一个 topic(主题)发布一些消息
-
Producers:消息和数据生产者,向 Kafka 的一个 topic 发布消息的过程叫做 producers
-
Consumers:消息和数据消费者,订阅 topics 并处理其发布的消息的过程叫做 consumers
3. 安装 Kafka 服务;
- ZooKeeper 依赖 java 虚拟机,所以需要安装 java 环境
yum -y update
yum list java*
yum -y install java-1.8.0-openjdk*
java -version
- 打开 http://kafka.apache.org,点击 Download

- 下载
cd /usr/local/src
wget http://mirrors.tuna.tsinghua.edu.cn/apache/kafka/2.3.0/kafka_2.11-2.3.0.tgz
tar zxvf kafka_2.11-2.3.0.tgz -C /usr/local/
cd /usr/local/
mv kafka_2.11-2.3.0/ kafka/
cd kafka/config/
vim server.properties
listeners=PLAINTEXT://192.168.2.214:9092
zookeeper.connect=192.168.2.214:2181
cd ../bin/
/usr/local/kafka/bin/zookeeper-server-start.sh /usr/local/kafka/config/zookeeper.properties
/usr/local/kafka/bin/kafka-server-start.sh /usr/local/kafka/config/server.properties
netstat -tunpl | grep 9092
tcp6 0 0 192.168.2.214:9092 :::* LISTEN 25675/java
/usr/local/kafka/bin/kafka-console-producer.sh \
--broker-list 192.168.2.214:9092 --topic test

/usr/local/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server 192.168.2.214:9092 --topic test --from-beginning

4. 安装PHP的 Kafka 扩展 rdkafka;
- 安装 librdkafka:https://github.com/edenhill/librdkafka
cd /usr/local/src
git clone https://github.com/edenhill/librdkafka.git
cd librdkafka/
./configure
make && make install
- 安装 php-rdkafka:https://github.com/arnaud-lb/php-rdkafka
cd /usr/local/src
git clone https://github.com/arnaud-lb/php-rdkafka.git
cd php-rdkafka/
phpize
./configure --with-php-config=/usr/local/php/bin/php-config
make && make install
echo "[rdkafka]" >> /usr/local/php/etc/php.ini
echo "extension = rdkafka.so" >> /usr/local/php/etc/php.ini
systemctl restart nginx
/etc/init.d/php-fpm restart
php -m
5. 编写 Kafka 的生产者方法;
- 以 Yii2 框架为例,新建
basic/models/Kafka.php
- 参考:https://github.com/arnaud-lb/php-rdkafka
namespace app\models;
class Kafka{
public $broker_list = '192.168.2.214:9092';
public $topic = "topic";
public $partition = 0;
protected $producer = null;
protected $consumer = null;
public function __construct(){
if(empty($this->broker_list)){
throw new \yii\base\InvalidConfigException("broker not config");
}
$conf = new \RdKafka\Conf();
if (empty($conf)) {
throw new \yii\base\InvalidConfigException("Conf error");
}
$conf->set('log_level', LOG_DEBUG);
$conf->set('debug', 'all');
$rk = new \RdKafka\Producer($conf);
if (empty($rk)) {
throw new \yii\base\InvalidConfigException("rk error");
}
if (!$rk->addBrokers($this->broker_list)) {
throw new \yii\base\InvalidConfigException("addBrokers error");
}
$this->producer = $rk;
}
public function send($messages = []){
$t = $this->producer->newTopic($this->topic);
$result = $t->produce(RD_KAFKA_PARTITION_UA, $this->partition, json_encode($messages));
$this->producer->poll(50);
return $result;
}
}
- 修改配置文件
basic/config/web.php
'asyncLog' => [
'class' => '\\app\\models\\Kafka',
'broker_list' => '192.168.2.214:9092',
'topic' => 'asynclog',
],
- 新建
basic/controllers/IndexController.php
namespace app\controllers;
use Yii;
use yii\web\Controller;
class IndexController extends controller{
public function actionIndex(){
Yii::$app->asyncLog->send(['this is IndexController']);
}
}
/usr/local/kafka/bin/zookeeper-server-start.sh /usr/local/kafka/config/zookeeper.properties
/usr/local/kafka/bin/kafka-server-start.sh /usr/local/kafka/config/server.properties
/usr/local/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server 192.168.2.214:9092 --topic asynclog --from-beginning
http://192.168.2.214/yii2/basic/web/index.php?r=index/index
6. 编写 Kafka 的异步消费者方法。
- 修改
basic/models/Kafka.php
// 添加消费者方法
/**
* 消费者方法
* @param $object 对象
* @param $callback 回调方法
*/
public function consumer($object, $callback){
$conf = new \RdKafka\Conf();
$conf->set('group.id', 0); // 定义 groupid,默认为 0
$conf->set('metadata.broker.list', $this->broker_list);
// 定义 topic 相关内容
$topicConf = new \RdKafka\TopicConf();
$topicConf->set('auto.offset.reset', 'smallest'); // 从开头消费最新消息
$conf->setDefaultTopicConf($topicConf);
$consumer = new \RdKafka\KafkaConsumer($conf);
$consumer->subscribe([$this->topic]); // 可以订阅多个,所以是数组
// 开始监听消息队列
echo "waiting for messages.....\n";
while(true) {
$message = $consumer->consume(120*1000); //
switch ($message->err) { // 判断消息接收错误
case RD_KAFKA_RESP_ERR_NO_ERROR: // 如果没有错误,处理消息
echo "message payload....\n";
// Yii::info($message->payload);
$object->$callback($message->payload);
break;
}
sleep(1); // 必须加程序休眠,否则 CPU 消耗会越来越大
}
}
- 修改配置文件
basic/config/console.php
'asyncLog' => [
'class' => '\\app\\models\\Kafka',
'broker_list' => '192.168.2.214:9092',
'topic' => 'asynclog',
],
[
'class' => 'yii\log\FileTarget',
'levels' => ['info'],
'categories' => ['testkafka'],
'logVars' => [],
'exportInterval' => 1,
'logFile' =>'@app/runtime/logs/Kafka.log',
],
- 新建
basic/command/KafkaController.php
namespace app\commands;
use Yii;
use yii\console\Controller;
class KafkaController extends Controller{
public function actionConsume(){
Yii::$app->asyncLog->consumer($this, 'callback');
}
public function callback($message){
Yii::info($message, 'testkafka');
Yii::$app->log->setflushInterval(1);
}
}
cd /data/project/test/yii2/basic/
./yii
- kafka
kafka/consume
./yii kafka/consume
http://192.168.2.214/yii2/basic/web/index.php?r=index/index
message payload....
2019-10-12 15:37:04 [-][-][-][info][testkafka] ["this is IndexController"]
- 出现的问题(待解决):
