消息量非超级多不建议使用,可以使用redis或Beanstalkd 使用简单
Beanstalkd 客户端建议用:
composer require pda/pheanstalk
如果无JAVA JDK 先下载JAVA JDK 并安装
添加.profile变量:JAVA_HOME
export JAVA_HOME=/usr/java/jdk1.8.0_31
下载:
http://gradle.org/downloads
gradle-2.3-all.zip (binaries, sources and documentation)
解压到:
/usr/local/gradle-2.3/
添加.profile变量:GRADLE_HOME(在PATH=**下)
export GRADLE_HOME=/usr/local/gradle-2.3 PATH=$PATH:$GRADLE_HOME/bin
下载:
http://kafka.apache.org/downloads.html
Source download:
Source download: kafka-0.8.2.0-src.tgz (asc, md5)
解压到:
/usr/local/kafka/
执行:
gradle
./gradlew
#如果无独立 zookeeper 执行 下载 zookeeper,如果想使用zookeeper集群,请独立安装zookeeper
./gradlew jar
#启动 zookeeper
bin/zookeeper-server-start.sh config/zookeeper.properties &
配置:config/server.properties
启动 kafka
bin/kafka-server-start.sh config/server.properties &
#测试
创建topic :test
bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
--replication-factor 表示副本的数量(kafka服务的数量)
--partitions 分区的数量
备注:一个topic 可以有多个分区.产生消息可以随机或用指定算法写入到指定分区.
查看topic列表:
bin/kafka-topics.sh --list --zookeeper localhost:2181
查看topic状态:
bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic test
往topic添加消息(Leader kafka):
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test this is message ^C
启动topic观察者:
bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic test --from-beginning #这里会输出 ^C
#集群.配置等其他资料查阅:http://kafka.apache.org/documentation.html#quickstart
#备注: zookeeper为外部服务提供统一接口,内部自动选择Leader等. kafka 也依赖zookeeper 来实现部分功能
安装管理软件:https://github.com/yahoo/kafka-manager
下载,解压,编译
修改 config/application.conf
./sbt clean dist
编译完成进入 ./
解压 并移动到/usr/local/下
启动:
./bin/kafka-manager -Dconfig.file=./config/application.conf -Dhttp.port=9009
配置NGINX代理到此服务(提供用户验证)
在NGINX配置目录/conf 下执行
htpasswd ./pwd username
proxy_buffering off; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_http_version 1.1; upstream my-backend { server 127.0.0.1:9009; } server { listen 192.168.4.200:8080; charset utf-8; auth_basic "kafka panel!!"; auth_basic_user_file pwd; location / { proxy_pass http://my-backend; } }
PHP 接口:
https://github.com/nmred/kafka-php
#Produce.php
for ($i=0;$i<10;$i++) { $produce = KafkaProduce::getInstance('localhost:2181',300); $partitions = $produce->getAvailablePartitions('testp'); if(count($partitions)==1) $partition=array_pop($partitions); else $partition=rand(0,count($partitions)-1); $produce->setRequireAck(-1); //参数: // topic 主题 // partition 分组 // message 消息数组 $produce->setMessages('testp',$partition, array('test'.$i)); $result = $produce->send(); var_dump($result); }
#Consumer.php
$consumer = KafkaConsumer::getInstance('localhost:2181'); $group = 'testgroup';//代表消费的组,一组的成员数据消费一次 $consumer->setGroup($group); //$consumer->setPartition('testp', 0);//设置具体topic分组 $consumer->setTopic('testp');//遍历全部分组 $result = $consumer->fetch(); foreach ($result as $topicName => $partition) { foreach ($partition as $partId => $messageSet) { var_dump($partition->getHighOffset());//最大值 var_dump($partition->getMessageOffset());//当前分组的最大偏移 foreach ($messageSet as $k=>$message) { var_dump($message); flush(); } } }
#其他example 参考即可 ,为 Produce Consumer 的分拆测试部分
关于其他hadoop 的参考:http://hadoop.apache.org/ 可以先看: HDFS MapReduce Hive 实现
PHP得到通知解决办法:
用C实现一个后台程序监听kafka,当有消息时候,启动PHP执行消息处理
C接口:
https://github.com/edenhill/librdkafka
简单实现:
#include <ctype.h> #include <signal.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <syslog.h> #include <sys/time.h> #include <errno.h> #include <sys/types.h> #include <sys/wait.h> /* Typical include path would be <librdkafka/rdkafka.h>, but this program * is builtin from within the librdkafka source tree and thus differs. */ #include "rdkafka.h" /* for Kafka driver */ #include "rdkafka_int.h" #include <zookeeper.h> #include <zookeeper.jute.h> #include <jansson.h> #define BROKER_PATH "/brokers/ids" static int run = 1; static rd_kafka_t *rk; static int exit_eof = 0; static int quiet = 0; //signal static void sig_stop (int sig) { run = 0; fclose(stdin); /* abort fgets() */ } static void sig_usr1 (int sig) { rd_kafka_dump(stdout, rk); } static void sig_child_stop(int sig){ pid_t t; while((t=waitpid(-1,NULL,WNOHANG)>0)){ if(quiet) printf("stop child:%d ",t); } } /** * Kafka logger callback (optional) */ static void logger (const rd_kafka_t *rk, int level, const char *fac, const char *buf) { struct timeval tv; gettimeofday(&tv, NULL); fprintf(stderr, "%u.%03u RDKAFKA-%i-%s: %s: %s ", (int)tv.tv_sec, (int)(tv.tv_usec / 1000), level, fac, rd_kafka_name(rk), buf); } static void notify_php( const char *site_dir,const char *php,const char *bootstarp, const char * topic,int partition,rd_kafka_message_t *rkmessage ){ if (rkmessage->err) { if (rkmessage->err == RD_KAFKA_RESP_ERR__PARTITION_EOF) { fprintf(stderr, "%% Consumer reached end of %s [%"PRId32"] " "message queue at offset %"PRId64" ", rd_kafka_topic_name(rkmessage->rkt), rkmessage->partition, rkmessage->offset); if (exit_eof) run = 0; return; } fprintf(stderr, "%% Consume error for topic "%s" [%"PRId32"] " "offset %"PRId64": %s ", rd_kafka_topic_name(rkmessage->rkt), rkmessage->partition, rkmessage->offset, rd_kafka_message_errstr(rkmessage)); return; } int pid=fork(); if(pid==0){ chdir(site_dir); char _topic[120]; char _partition[20]; sprintf(_topic,"--topic=%s",topic); sprintf(_partition,"--partition=%d",partition); execl(php,"php",bootstarp,"--task=tips",_topic,_partition,NULL); exit(errno); } } static void set_brokerlist_from_zookeeper(zhandle_t *zzh, char *brokers) { if (zzh) { struct String_vector brokerlist; if (zoo_get_children(zzh, BROKER_PATH, 1, &brokerlist) != ZOK) { fprintf(stderr, "No brokers found on path %s ", BROKER_PATH); return; } int i; char *brokerptr = brokers; for (i = 0; i < brokerlist.count; i++) { char path[255], cfg[1024]; sprintf(path, "/brokers/ids/%s", brokerlist.data[i]); int len = sizeof(cfg); zoo_get(zzh, path, 0, cfg, &len, NULL); if (len > 0) { cfg[len] = '