maven依赖,我使用的是版本是0.8.22,scala是2.11
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.11</artifactId>
<version>0.8.2.2</version>
</dependency>
1.本地测试关闭防火器
2.在windows中配置kafka,zookeeper的host
C:\Windows\System32\drivers\etc\hosts文件
生产者
需要将producer.properties文件中的serializer.class参数由DefaultEncoder改为StringEncoder,不然会报错误
serializer.class=kafka.serializer.DefaultEncoder
serializer.class=kafka.serializer.StringEncoder
metadata.broker.list=shb01:9092,129.168.79.139:9092
package kafka;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import kafka.javaapi.producer.Producer;
import kafka.producer.KeyedMessage;
import kafka.producer.ProducerConfig;
/**
* 生产者实例
* @author think
*
*/
public class ProducerTest {
public static void main(String[] args) throws Exception {
/**
* 属性参数,设置
* 方式1:config.setProperty("producer.type", "sync"); 可以手动设置生产者参数
* 方式2:直接加载kafka集群中的producer.properties
* 在producer.properties中指定broker List
* metadata.broker.list=shb01:9092,129.168.79.139:9092
*/
Properties config = new Properties();
config.load(ProducerTest.class.getClassLoader().getResourceAsStream("producer.properties"));
ProducerConfig pConfig = new ProducerConfig(config);
/**
* 创建生产者对象
* key:为 message的key
* value:为message的value
*/
Producer<String, String> produce = new Producer<String, String>(pConfig);
/**
* 将用户参数组装成message,
* KeyedMessage<String, String>:key用来分区,value是消息本身;key可以不写
*/
String topic = "hello";
List<KeyedMessage<String, String>> messageList = new ArrayList<KeyedMessage<String, String>>();
KeyedMessage<String, String> message1 = new KeyedMessage<String, String>(topic, "key1", "value1");
KeyedMessage<String, String> message2 = new KeyedMessage<String, String>(topic, "key2", "value2");
messageList.add(message1);
messageList.add(message2);
KeyedMessage<String, String> message3 = new KeyedMessage<String, String>(topic, "value3");
/**
* 生产者生产消息
*/
produce.send(messageList);
produce.send(message3);
//关闭
produce.close();
}
}
消费者
消费者是以链接的形式存在,监听生产者生产的消息
zookeeper.connect=shb01:2181,129.168.79.139:2181
group.id=consumer-group-0
package kafka;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
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;
public class ConsumerTest {
public static void main(String[] args) throws Exception{
String topic = "hello";
/**
* 属性参数
*/
Properties prop = new Properties();
prop.load(Consumer.class.getClassLoader().getResourceAsStream("consumer.properties"));
ConsumerConfig conf = new ConsumerConfig(prop);
//创建消费者
ConsumerConnector createJavaConsumerConnector = Consumer.createJavaConsumerConnector(conf);
//执行消费
//topicCountMap:key是topic名称,value是该topic所使用的消费者的个数
Map<String, Integer> topicCountMap = new HashMap<String, Integer>();
topicCountMap.put(topic, 2);//使用2个消费者消费topic-hello
/**
* messageStreams:
* key是消费的topic的名称。
* value是消费者读取 kafka流
*/
Map<String, List<KafkaStream<byte[], byte[]>>> messageStreams = createJavaConsumerConnector.createMessageStreams(topicCountMap);
//list就是消费者读取的kafka流,有几个消费者就有几个流,此处kafka流的数量为2
List<KafkaStream<byte[], byte[]>> list = messageStreams.get(topic);
//启动多线程来分别读取
//KafkaStream<byte[], byte[]>,key是所消费的消息的key,value是所消费的消息value
for(KafkaStream<byte[], byte[]> kafkaStream : list)
{
new Thread(new Worker(kafkaStream)).start();;
}
}
/**
* 内部类,用线程接收kafka流
* @author think
*
*/
static class Worker implements Runnable
{
private KafkaStream<byte[], byte[]> kafkaStream;
public Worker(KafkaStream<byte[], byte[]> kafkaStream) {
this.kafkaStream = kafkaStream;
}
@Override
public void run() {
//使用kafkaStream的迭代器,迭代消费数据
ConsumerIterator<byte[], byte[]> iterator = kafkaStream.iterator();
while(iterator.hasNext())
{
//MessageAndMetadata就是消息本身
MessageAndMetadata<byte[], byte[]> next = iterator.next();
System.out.println(String.format("key:%s value:%s partition:%s offset:%s",
next.key() == null? "": new String(next.key()),
new String(next.message()),
next.partition(),
next.offset()));
}
}
}
}
分区器
producer.properties文件
partitioner.class=kafka.ProducerTest
package kafka;
import kafka.producer.Partitioner;
import kafka.utils.VerifiableProperties;
/**
* 分区器
* @author think
* 必须在producer.properties中指定partitioner.class=kafka.ProducerTest
*/
public class PartitionTest implements Partitioner{
/**
* 系统会将生产者的生产属性传递过来供开发者使用,就是producer.properties中的参数值
* prop.getProperty("metadata.broker.list");
*/
private kafka.utils.VerifiableProperties prop;
/**
* 必须提供一个构造方法,参数为VerifiableProperties
* @param prop
*/
public PartitionTest(VerifiableProperties prop) {
super();
this.prop = prop;
}
/**
* numberPartition:表示partition的数量
*/
@Override
public int partition(Object key, int numberPartition) {
prop.getProperty("metadata.broker.list");
//生产者的key是key1,key2
String keyStr = (String) key;
if(keyStr.contains("1"))
{
return 1;//返回1分区,
}
else
{
if(keyStr.contains("2"))
{
return 2;//返回2分区
}
}
return 0;
}
}
partition在消费者中的分配
分区会进行排序,消费者会按照其id的字典顺序排序,然后消费者线程数了会除以partition数量,以此来确定每个消费者可以消费的分区数量,如果不能整除则会给第一个消费者多分配一个分区
分区:0 1 2 3 4 5 6 7 8 9
消费者:c1 c2 c3
分配结果:c1 – 0 1 2 3; c2 – 4 5 6;c3 – 7 8 9
消费者会按照分区中消息的生产顺序来消费消息,并且是消费完成一个分区后才会去消费另一个分区,所以一个分区中消息的消费是有顺序的而多个分区之间则是无序的。
Kafka会将一些重要的信息存储在zookeeper中比如broker,consumer,offset等,消费者消费时需要配合zk才可以读取这些信息,但是kafka并不是实时向zookeeper同步信息(默认一分钟),可以在consumer.properties中进行配置auto.commit.enable=true和auto.commit.interval.ms=1000毫秒,通过这两个参数来控制kafka向zk同步的频率。
Zookeeper支持多个kafka集群,在server.properties中修改参数指定多个kafka集群的目录zookeeper.connect=shb01:2181/demo1,192.168.79.139:2181/demo1,后面在操作topic是注意对应的zk目录。