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
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;
cd /usr/local/src
git clone https://github.com/edenhill/librdkafka.git
cd librdkafka/
./configure
make && make install
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 的生产者方法;
<?php
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
<?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
<?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" ]
出现的问题(待解决):