关闭

java调用Kafka的Consumer

标签: Kafkahigh levelConsumer
202人阅读 评论(0) 收藏 举报
分类:

之前项目中有个需求技术上需要用到Kafka,另一部门的同事通过Kafka的Producer将数据以json的格式发送到服务器上的broker,双方约定一个topic,而我通过Consumer消费服务器上broker的约定topic的数据。Kafka是基于topic的消息生产消费模式,Producer是生产者,生产特定topic的消息,Consumer是消费者,消费特定topic的消息。Kafka的官网文档,比较详细,里面还有一些demo,有兴趣的可以去看看,点此打开

maven依赖:

<dependency>
			<groupId>org.apache.kafka</groupId>
			<artifactId>kafka_2.10</artifactId>
			<version>0.8.2.1</version>
		</dependency>
properties配置文件中定义的属性:

beauty.kafka.zookeeperServer=192.168.2.199:2182
beauty.kafka.topic=0_crawlerSuccTask99
beauty.kafka.groupId=testGroup2
beauty.kafka.sessionTimeOut=6000
beauty.kafka.intervalms=500
xml配置文件中定义的bean:

<bean id="kafkaConsumerHandler" class="com.alan.beauty.taskserver.handler.KafkaConsumerHandler" init-method="init">
		<property name="zookeeper" value="${beauty.kafka.zookeeperServer}" />
		<property name="topic" value="${beauty.kafka.topic}" />
		<property name="groupId" value="${beauty.kafka.groupId}" />
		<property name="sessionTimeOut" value="${beauty.kafka.sessionTimeOut}" />
		<property name="intervalms" value="${beauty.kafka.intervalms}" />
	</bean>
Consumer的调用代码:

import kafka.consumer.Consumer;
import kafka.consumer.ConsumerConfig;
import kafka.consumer.ConsumerIterator;
import kafka.consumer.KafkaStream;
import kafka.javaapi.consumer.ConsumerConnector;

import org.apache.log4j.Logger;

import com.alan.beauty.common.exception.BusinessException;

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 KafkaConsumerHandler {

    private static final Logger logger = Logger.getLogger(KafkaConsumerHandler.class);

//    @Autowired
//    private UserInfoAuditService userInfoAuditService;

    private String zookeeper;
    private String groupId;
    private String topic;
    private String sessionTimeOut;
    private String intervalms;

    private ConsumerConnector consumer;

    private ExecutorService executor;

    private ConsumerConfig createConsumerConfig() {
        Properties props = new Properties();
        props.put("zookeeper.connect", zookeeper);//如果有多个服务器,则配置形式为hostname1:port1,hostname2:port2,hostname3:port3
        props.put("group.id", groupId);
        props.put("zookeeper.session.timeout.ms", sessionTimeOut);
        props.put("zookeeper.sync.time.ms", intervalms);
        //props.put("auto.commit.interval.ms", "40000");//自动提交的间隔时间,自动提交为true时才生效
        props.put("auto.commit.enable", "false");//是否自动提交
        props.put("auto.offset.reset", "smallest");
        return new ConsumerConfig(props);
    }

    void init() {
        try {
            logger.info("zookeeper=" + zookeeper + " groupId=" + groupId + " topic=" + topic);
            ConsumerConfig consumerConfig = this.createConsumerConfig();
            consumer = Consumer.createJavaConsumerConnector(consumerConfig);
            int handleThreadNum = 1;//建议一个分区对应一个线程
            executor = Executors.newFixedThreadPool(handleThreadNum);
            Map<String, Integer> topicCountMap = new HashMap<String, Integer>();
            topicCountMap.put(topic, handleThreadNum);
            Map<String, List<KafkaStream<byte[], byte[]>>> consumerMap = consumer.createMessageStreams(topicCountMap);
            List<KafkaStream<byte[], byte[]>> streams = consumerMap.get(topic);
            for (final KafkaStream<byte[], byte[]> stream : streams) {
            	executor.submit(new KafkaConsumerRunnable(stream));
            }
        } catch (Exception e) {
            logger.error("连接kafka异常", e);
        }
    }

    public void setSessionTimeOut(String sessionTimeOut) {
        this.sessionTimeOut = sessionTimeOut;
    }

    public void setIntervalms(String intervalms) {
        this.intervalms = intervalms;
    }

    public void setZookeeper(String zookeeper) {
        this.zookeeper = zookeeper;
    }

    public void setGroupId(String groupId) {
        this.groupId = groupId;
    }

    public void setTopic(String topic) {
        this.topic = topic;
    }

    class KafkaConsumerRunnable implements Runnable {
    	
    	private KafkaStream<byte[], byte[]> stream;
    	
    	public KafkaConsumerRunnable(KafkaStream<byte[], byte[]> stream){
    		this.stream = stream;
    	}
    	
        @Override
        public void run() {
            ConsumerIterator<byte[], byte[]> it = stream.iterator();
            String result = null;
            while (it.hasNext()) {
                try {
                    result = new String(it.next().message(), "UTF-8");
                    System.out.println("message="+result);
                    //TODO 对获取到的消息进行业务处理
//                    userInfoAuditService.saveOrUpdateUserInfoAudit(result);
                    consumer.commitOffsets();
                } catch (BusinessException e) {
                    logger.error("解析消息格式异常", e);
                    consumer.commitOffsets();
                } catch (Exception e) {
                    logger.error("数据库操作异常,消息内容为:" + result, e);
                }
            }
        }
    }
    
}

需要说明的是:

1、Kafka有partition(分区)的概念,最好一个线程对应一个分区。因为我这服务端只设置一个分区,故代码中

int handleThreadNum = 1;
2、Kafka在获取数据时会阻塞主线程,所以必须把Kafka的连接部分和获取数据部分完全区分,获取数据部分必须放在异步线程中执行。

3、我是手动控制偏移量,根据业务场景来决定哪些要提交偏移量,哪些不提交。(比如数据库连接突然增多,导致连接池异常,那么调整参数后重启应用,这部分数据还可以重新读取,因为偏移量没有被提交到服务器。如果是自动提交,那么一段时间后(参数可配,见我代码中的注释)读取出来的这部分数据偏移量会被自动提交,重启后这种场景的数据就读不到了,丢了)

4、我是采用Kafka提供的high level consumer api,这个使用起来比较简单,但是对偏移量的控制比较弱。建议如果有多分区的情况或者对数据丢失的容忍度较低,还是使用Simple Consumer API。



0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:5652次
    • 积分:115
    • 等级:
    • 排名:千里之外
    • 原创:6篇
    • 转载:0篇
    • 译文:0篇
    • 评论:0条
    文章分类
    文章存档