本篇博客基于kafka集群搭建,在此基础上操作
1.新建maven工程添加依赖:
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.12</artifactId>
<version>0.10.2.1</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>jmxtools</artifactId>
<groupId>com.sun.jdmk</groupId>
</exclusion>
<exclusion>
<artifactId>jmxri</artifactId>
<groupId>com.sun.jmx</groupId>
</exclusion>
<exclusion>
<artifactId>jms</artifactId>
<groupId>javax.jms</groupId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
2.编写一个生产者:
package com.wx.kafka;
import kafka.javaapi.producer.Producer;
import kafka.producer.KeyedMessage;
import kafka.producer.ProducerConfig;
import java.util.Properties;
import java.util.UUID;
/**
* 这是一个简单的Kafka producer代码
* 包含两个功能:
* 1、数据发送
* 2、数据按照自定义的partition策略进行发送
* KafkaSpout的类
*/
public class KafkaProducer {
public static void main(String[] args)
{
/**
* 1、指定当前kafka producer生产的数据的目的地
* 创建topic可以输入以下命令,在kafka集群的任一节点进行创建。下面表示的是一个分区一个副本
* bin/kafka-topics.sh --create --zookeeper zk01:2181 --replication-factor 1 --partitions 1 --topic test
*/
String TOPIC = "kafka4Mq";
/**
* 2、读取配置文件
*/
Properties props = new Properties();
/*
* key.serializer.class默认为serializer.class
*/
props.put("serializer.class", "kafka.serializer.StringEncoder");
/*
* kafka broker对应的主机,格式为host1:port1,host2:port2
*/
props.put("metadata.broker.list", "192.168.25.130:9092,192.168.25.131:9092,192.168.25.132:9092");
/*
* request.required.acks,设置发送数据是否需要服务端的反馈,有三个值0,1,-1
* 0,意味着producer永远不会等待一个来自broker的ack,这就是0.7版本的行为。
* 这个选项提供了最低的延迟,但是持久化的保证是最弱的,当server挂掉的时候会丢失一些数据。
* 1,意味着在leader replica已经接收到数据后,producer会得到一个ack。
* 这个选项提供了更好的持久性,因为在server确认请求成功处理后,client才会返回。
* 如果刚写到leader上,还没来得及复制leader就挂了,那么消息才可能会丢失。
* -1,意味着在所有的ISR都接收到数据后,producer才得到一个ack。
* 这个选项提供了最好的持久性,只要还有一个replica存活,那么数据就不会丢失
*/
props.put("request.required.acks", "1");
//指定分区数量
// props.put("num.partitions", "4");
/*
* 可选配置,如果不配置,则使用默认的partitioner partitioner.class
* 默认值:kafka.producer.DefaultPartitioner
* 用来把消息分到各个partition中,默认行为是对key进行hash。
*/
//props.put("partitioner.class", "com.wx.kafka.MyLogPartitioner");
props.put("partitioner.class", "kafka.producer.DefaultPartitioner");
/**
* 3、通过配置文件,创建生产者
*/
Producer<String, String> producer = new Producer<String, String>(new ProducerConfig(props));
/**
* 4、通过for循环生产数据
*/
for (int messageNo = 1; messageNo < 100000; messageNo++) {
String messageStr = new String(messageNo +"注意:这里需要指定 partitionKey,用来配合自定义的MyLogPartitioner进行数据分发注意:这里需要指定 partitionKey,用来配合自定义的MyLogPartitioner进行数据分发") ;
/**
* 5、调用producer的send方法发送数据
* 注意:这里需要指定 partitionKey,用来配合自定义的MyLogPartitioner进行数据分发
*/
producer.send(new KeyedMessage<String, String>(TOPIC, messageNo + " ", "appid" + UUID.randomUUID() + messageStr));
}
}
}
编写一个消费者:
package com.wx.kafka;
import kafka.consumer.Consumer;
import kafka.consumer.ConsumerConfig;
import kafka.consumer.ConsumerIterator;
import kafka.consumer.KafkaStream;
import kafka.javaapi.consumer.ConsumerConnector;
import kafka.message.MessageAndMetadata;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class KafkaConsumerSimple implements Runnable {
public String title;
public KafkaStream<byte[], byte[]> stream;
public KafkaConsumerSimple(String title, KafkaStream<byte[], byte[]> stream) {
this.title = title;
this.stream = stream;
}
public void run() {
System.out.println("开始运行 " + title);
ConsumerIterator<byte[], byte[]> it = stream.iterator();
/**
* 不停地从stream读取新到来的消息,在等待新的消息时,hasNext()会阻塞
* 如果调用 `ConsumerConnector#shutdown`,那么`hasNext`会返回false
* */
while (it.hasNext()) {
MessageAndMetadata<byte[], byte[]> data = it.next();
String topic = data.topic();
int partition = data.partition();
long offset = data.offset();
String msg = new String(data.message());
System.out.println(String.format(
"Consumer: [%s], Topic: [%s], PartitionId: [%d], Offset: [%d], msg: [%s]",
title, topic, partition, offset, msg));
}
System.out.println(String.format("Consumer: [%s] exiting ...", title));
}
public static void main(String[] args) throws Exception{
Properties props = new Properties();
props.put("group.id", "dashujujiagoushi");
props.put("zookeeper.connect", "192.168.25.130:2181,192.168.25.131:2181,192.128.25.132:2181");
props.put("auto.offset.reset", "largest");
props.put("auto.commit.interval.ms", "1000");
props.put("partition.assignment.strategy", "roundrobin");
ConsumerConfig config = new ConsumerConfig(props);
String topic1 = "kafka4Mq"; /制定消费的主题//
String topic2 = "paymentMq";
//只要ConsumerConnector还在的话,consumer会一直等待新消息,不会自己退出
ConsumerConnector consumerConn = Consumer.createJavaConsumerConnector(config);
//定义一个map
Map<String, Integer> topicCountMap = new HashMap<String, Integer>();
topicCountMap.put(topic1, 3);
//Map<String, List<KafkaStream<byte[], byte[]>> 中String是topic, List<KafkaStream<byte[], byte[]>是对应的流
Map<String, List<KafkaStream<byte[], byte[]>>> topicStreamsMap = consumerConn.createMessageStreams(topicCountMap);
//取出 `kafkaTest` 对应的 streams
List<KafkaStream<byte[], byte[]>> streams = topicStreamsMap.get(topic1);
//创建一个容量为4的线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
//创建20个consumer threads
for (int i = 0; i < streams.size(); i++)
executor.execute(new KafkaConsumerSimple("消费者" + (i + 1), streams.get(i)));
}
}
自定义分区函数:
package com.wx.kafka;
import kafka.producer.Partitioner;
import kafka.utils.VerifiableProperties;
import org.apache.log4j.Logger;
public class MyLogPartitioner implements Partitioner {
private static Logger logger = Logger.getLogger(MyLogPartitioner.class);
public MyLogPartitioner(VerifiableProperties props) {
}
public int partition(Object obj, int numPartitions) {
//return Integer.parseInt(obj.toString())%numPartitions; //使用自定分区就要出现尝试三次失败的错误
return 1;
}
}
这里但会一个分区:返回的写死的分区1 就去看分区1日志,ok 有数据,其他分区没有
问题:
1.如果使用自定义分区则会出现:Failed to send requests for topics paymoneyMq with correlation ids in [0,8] 这样的错误
最后用的生产者默认的分区,将生产者的问题解决,可以参考这篇博客:https://blog.csdn.net/high2011/article/details/53705737
2.消费者出现:org/apache/zookeeper/Watcher这个错误,在pom依赖中将zookeeper 的以来去除即可:
3.Failed to send messages after 3 tries.
遇到该错误主要有以下几种原因
1.服务器的zookeeper和kafka未开启
2.配置文件中,没有配置advertised.host.name和advertised.port
3.服务器防火墙未关闭,或者无法连接9092端口
解决
1.运行producer java API时,首先开启服务器的zookeeper和kafka
2.找到$KAFKA_HOME/config/中的配置文件,配置advertised.host.name="ip",advertised.port="port".具体解释可以参考配置文件中的注释
3.在本地telnet ip 9092,观察是否可以连接,如不能连接,关闭服务器防火墙,或者打开9092端口。
更改服务器下启动kafka的配置文件,默认server.properties,添加下面内容
host.name=192.168.195.132
advertised.host.name=192.168.195.132
advertised.port=9092
问题这样就解决了
结果发现是代码中的topic和集群中的不一致,改成集群中之前设置的orderMq就行了,
实现自定义分区:
首先分区:
bin/kafka-topics.sh --create --zookeeper 192.168.31.130:2181 --replication-factor 2 --partitions 4 --topic orderMq
查看分区:
重写分区类的分区函数,上面的一个有误:
package com.wx.kafka;
import kafka.producer.Partitioner;
import kafka.utils.VerifiableProperties;
import org.apache.log4j.Logger;
public class MyLogPartitioner implements Partitioner {
public MyLogPartitioner() {
this(new VerifiableProperties());
}
public MyLogPartitioner(VerifiableProperties properties) {
// nothings
}
public int partition(Object key, int numPartitions) {
int num = Integer.valueOf(((String) key).replaceAll("key_", "").trim());
return num % numPartitions;
}
}
运行:可以看到消费多个分区的消息:
下一篇博客将介绍kafka整合flume....