目录

一、PHP代码实现

  1. kafka连接-设置初始参数
  2. 生产者生产消息
  3. 利用进程持续监听kafka,实时消费

二、逻辑思路

三、redis与kafka的区别

 

一、PHP代码实现

1. kafka连接-设置初始参数

<?php
/**
 * Created by PhpStorm.
 * User: wuliZs_
 */

/**
 * 业务Kafka单件
 */

class App_Kafka
{
    private static $producer = null;//生产者

    /**
     * 设置初始参数
     * @return type
     * @throws \Exception
     */
    public static function getInstance()
    {
        $broker_list = '';                // kakfa连接
        if (!isset(self::$producer)) {
            try {
                if (empty($broker_list)) {
                    throw new \Exception("broker_list is null", 1);
                }
                $rk = new \RdKafka\Producer();//创建生产者
                if (!isset($rk)) {
                    throw new \Exception("create producer error", 1);
                }

                $rk->setLogLevel(LOG_DEBUG);//设置日志级别7
                if (!$rk->addBrokers($broker_list)) {//设置kafka服务器
                    throw new \Exception("add producer error", 1);
                }
                self::$producer = $rk;
            } catch (Exception $e) {
                // 抛出异常,写入日志
                App_Log::save('kafka_send_error', 'Message: ' . $e->getMessage());
            }
            return self::$producer;
        } else {
            return self::$producer;
        }
    }
}

2. 生产者生产消息

<?php
/**
 * Created by PhpStorm.
 * User: wuliZs_
 */

Class Lib_Kafka{
    public static $partition = 0;   //topic物理上的分区
    /**
     * 生产者生产消息
     * @param array $message
     * @return string
     */
    public static function produce($topic, $message=''){
        $producer = App_Kafka::getInstance();
        $topic = $producer->newTopic($topic);//创建主题topic
        if(strlen($message) > 1000000){//kafka限制了最大长度为1M
            return false;
            App_Log::save('kafka_send_error', '超过1m: ' . $message);
        }
        return $topic->produce(RD_KAFKA_PARTITION_UA, self::$partition, $message);//向指定的topic物理地址发消息
    }
}

3. 利用进程持续监听kafka,实时消费【写入一个daemon,实时监听消费】

<?php

/**
 * kafka监听消费
 */
require dirname(__DIR__) . '/script/shell.php';

class KafkaSendMsg{

    public static function daemon(){

        ini_set('default_socket_timeout', -1);  //不超时

        $kfk_conf = new Rdkafka\Conf();
        $kfk_conf->set("group.id","");									// 存放的分组名
        $kfk_conf->set('metadata.broker.list','');						// kafka连接
        $kfk_conf->set('topic.metadata.refresh.interval.ms',60000);		// Topic metadata 刷新间隔,毫秒。metadata 自动刷新错误和连接。设置为 -1 关闭刷新间隔。
        $kfk_conf->set('socket.keepalive.enable',true);					// Broker sockets 允许 TCP 保持活力 
        $kfk_conf->setRebalanceCb(function (RdKafka\KafkaConsumer $kafka, $err, array $partitions = null) {
            switch ($err) {
                case RD_KAFKA_RESP_ERR__ASSIGN_PARTITIONS:
                    $kafka->assign($partitions);
                    break;

                case RD_KAFKA_RESP_ERR__REVOKE_PARTITIONS:
                    $kafka->assign(NULL);
                    break;

                default:
                    throw new \Exception($err);
            }
        });

        $topicConf = new RdKafka\TopicConf();
        $topicConf->set('auto.commit.enable', true);
        $kfk_conf->setDefaultTopicConf($topicConf);
        $consumer = new RdKafka\KafkaConsumer($kfk_conf);
        $consumer->subscribe(['kafka']);									// kafka通道,进行监听
        while (true) {
            
            try {
                $msg_kfk = $consumer->consume(10000);           			//获取队列并往下执行消息,设置timeout
                if ($msg_kfk && !$msg_kfk->err) {
                    $message    = json_decode($msg_kfk->payload, true);		// 收到消息,进行处理
                    // 收到消息,调用处理方法
                    if(!empty($message)){
                    	self::sendMsg($message);
                    }
                }
            }catch(Exception $e){
                // 抛出异常,写入日志
            }
        }
    }
    /**
     * 消息处理
     * @param $message
     */
    private static function sendMsg($message = null{
    	// 收到消息后处理逻辑的方法
    }
}
KafkaSendMsg::daemon();

二、逻辑思路【用法跟redis的消费订阅差不多】:

  1. 连接kafka
  2. 生产者生产消息,推送到指定的kafka通道
  3. 开启一个进程监听kafka通道的动态
  4. 消费者监听到通道有消息过来后,进行逻辑处理

三、redis与kafka的区别

Kafka与Redis PUB/SUB之间较大的区别在于Kafka是一个完整的系统,而Redis PUB/SUB只是一个套件(utility)——没有冒犯Redis的意思,毕竟它的主要功能并不是PUB/SUB。
先说Redis吧,它首先是一个内存数据库,其提供的PUB/SUB功能把消息保存在内存中(基于channel),因此如果你的消息的持久性需求并不高且后端应用的消费能力超强的话,使用Redis PUB/SUB是比较合适的使用场景。比如官网说提供的一个网络聊天室的例子:模拟IRC,因为channel就是IRC中的服务器。用户发起连接,发布消息到channel,接收其他用户的消息。这些对于持久性的要求并不高,使用Redis PUB/SUB来做足矣。

 

而Kafka是一个完整的系统,它提供了一个高吞吐量、分布式的提交日志(由于提供了Kafka Connect和Kafka Streams,目前Kafka官网已经将自己修正为一个分布式的流式处理平台,这里也可以看出Kafka的野心:-)。除了p2p的消息队列,它当然提供PUB/SUB方式的消息模型。而且,Kafka默认提供了消息的持久化,确保消息的不丢失性(至少是大部分情况下)。另外,由于消费元数据是保存在consumer端的,所以对于消费而言consumer被赋予极大的自由度。consumer可以顺序地消费消息,也可以重新消费之前处理过的消息。这些都是Redis PUB/SUB无法做到的。

Redis PUB/SUB使用场景:

    消息持久性需求不高
    吞吐量要求不高
    可以忍受数据丢失
    数据量不大

Kafka使用场景:
上面以外的其他场景:)

    高可靠性
    高吞吐量
    持久性高
    多样化的消费处理模型
 

希望可以帮助到有需要的同学!