最近项目需要进行实时读取服务端信息,在网上看到kafka可以解决这个问题,开发完成后对kafka做一个简单的整理,希望可以帮助到刚开始学习kafka 的同学,给自己也做个笔记:

首先说一下kafka是个什么东西:

kafka是一个分布式、支持分区的(partition)、多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量数据以满足各种需求场景:比如基于hadoop的批处理系统、低延迟的实时系统、storm/Spark流式处理引擎,web/nginx日志、访问日志,消息服务等等

Kafka的特性:

- 高吞吐量、低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒,每个topic可以分多个partition, consumer group 对partition进行consume操作。

- 可扩展性:kafka集群支持热扩展

- 持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失

- 容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)

- 高并发:支持数千个客户端同时读写

Kafka的使用场景:

- 日志收集:一个公司可以用Kafka可以收集各种服务的log,通过kafka以统一接口服务的方式开放给各种consumer,例如hadoop、Hbase、Solr等。

- 消息系统:解耦和生产者和消费者、缓存消息等。

- 用户活动跟踪:Kafka经常被用来记录web用户或者app用户的各种活动,如浏览网页、搜索、点击等活动,这些活动信息被各个服务器发布到kafka的topic中,然后订阅者通过订阅这些topic来做实时的监控分析,或者装载到hadoop、数据仓库中做离线分析和挖掘。

- 运营指标:Kafka也经常用来记录运营监控数据。包括收集各种分布式应用的数据,生产各种操作的集中反馈,比如报警和报告。

- 流式处理:比如spark streaming和storm

我使用的是消费端,所以我着重说一下消费端的内容:

bootstrap.servers
连接Kafka集群的地址,多个地址以逗号分隔
key.deserializer
消息中key反序列化类,需要和Producer中key序列化类相对应
value.deserializer
消息中value的反序列化类,需要和Producer中Value序列化类相对应
group.id
消费者所属消费组的唯一标识

其他属性根据个人情况去定。

kafka总是自动关闭问题 

在我启动kafka后,总是会模型奇妙的进程掉了,我是启动命令是

 ./kafka-server-start.sh  /home/kafka/kafka_2.11-2.3.1/config/server.properties 

解决办法:以守护进程的方式启动

./kafka-server-start.sh -daemon /home/kafka/kafka_2.11-2.3.1/config/server.properties

加 -daemon 和不加 -daemon 区别在于:

bin/kafka-run-class.sh
 
# Launch mode
if [ "x$DAEMON_MODE" = "xtrue" ]; then
  #加 daemon 会使用该命令
  nohup $JAVA $KAFKA_HEAP_OPTS $KAFKA_JVM_PERFORMANCE_OPTS $KAFKA_GC_LOG_OPTS $KAFKA_JMX_OPTS $KAFKA_LOG4J_OPTS -cp $CLASSPATH $KAFKA_OPTS "$@" > "$CONSOLE_OUTPUT_FILE" 2>&1 < /dev/null &
else
  #不加时使用的命令
  exec $JAVA $KAFKA_HEAP_OPTS $KAFKA_JVM_PERFORMANCE_OPTS $KAFKA_GC_LOG_OPTS $KAFKA_JMX_OPTS $KAFKA_LOG4J_OPTS -cp $CLASSPATH $KAFKA_OPTS "$@"
fi

代码这块:

配置:

1.pom.xml文件

<!-- kafka 消息队列 -->
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>RELEASE</version>
        </dependency>

2.配置文件

配置文件可简单可复杂,最不可或缺的是 

#kafka相关配置
spring.kafka.bootstrap-servers=192.168.183.145:9092

其他可根据自己的需要配置,有些可以写死

3.消费端代码

package com.nature.kafka.util;

import com.nature.common.constant.SysParamsCache;
import org.apache.kafka.clients.consumer.*;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.Callable;

public class Consumer implements Callable<Map<String, Object>> {

    private static Properties props = null;
    private static KafkaConsumer<String, String> consumer = null;
    private String topics;
    private Map<String, Object> rtnMap;

    public Consumer(String topics, Map<String, Object> rtnMap) {
        this.topics = topics;
        this.rtnMap = rtnMap;
    }

    @Override
    public Map<String, Object> call() throws Exception {
        return  Consumer.consume(topics, rtnMap);
    }

    private static KafkaConsumer<String, String> getConsumer() {
        props = new Properties();
        String address = SysParamsCache.SYSPARA_BOOTSTRAPSERVER;
        props.put("bootstrap.servers", address);   // "localhost:9092"
        //每个消费者分配独立的组号
        props.put("group.id", "test");

        //如果value合法,则自动提交偏移量
        props.put("enable.auto.commit", "true");

        //设置多久一次更新被消费消息的偏移量
        props.put("auto.commit.interval.ms", "1000");

        //设置会话响应的时间,超过这个时间kafka可以选择放弃消费或者消费下一条消息
        props.put("session.timeout.ms", "30000");

        props.put("key.deserializer","org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer","org.apache.kafka.common.serialization.StringDeserializer");
        consumer = new KafkaConsumer<>(props);
        return consumer;
    }

    public static Map<String, Object> consume(String topics, Map<String, Object> rtnMap) {
        if (consumer == null) {
            consumer = getConsumer();
        }

        consumer.subscribe(Collections.singletonList(topics));

        StringBuffer sb = new StringBuffer();
        ConsumerRecords<String, String> records = null;

        records = consumer.poll(Duration.ofMillis(100));

        for (ConsumerRecord<String, String> record : records) {
            sb.append(record.value());
        }
        rtnMap.put("msg", sb.toString());
        return rtnMap;
    }

    /*public static void main(String[] args) {

        KafkaConsumer<String, String> consumer = null;
        if(consumer == null){
            consumer = getConsumer();
        }

        consumer.subscribe(Collections.singletonList("application_1575964334154_0154"));  //核心函数1:订阅topic

        while (true) {
            System.err.println("=========");
            //System.out.println(i++);
            //核心函数2:long poll,一次拉取回来多个消息
            *//* 读取数据,读取超时时间为100ms *//*
            ConsumerRecords<String, String> records = consumer.poll(100);
            for (ConsumerRecord<String, String> record : records)
                System.err.printf("offset = %d, key = %s, value = %s\n",
                        record.offset(), record.key(), record.value());
        }
    }*/

}

其实当初在使用这个的时候是要跟springboot集成,但是网上基本上都是自己调用自己用,不输出,所有我贴一段controll 供大家使用:

/**
     * 获取kafka消息
     * @param request
     * @return
     */
    @RequestMapping("/getKafkaDate")
    @ResponseBody
    public String getKafkaDate(HttpServletRequest request) throws FileNotFoundException {
        Map<String, Object> rtnMap = new HashMap<>();
        rtnMap.put("code", 500);
        String appId = request.getParameter("appid");
        Consumer consumer = new Consumer(appId,rtnMap);
        FutureTask<Map<String, Object>> ft=new FutureTask<Map<String, Object>>(consumer);
        new Thread(ft,"消费kafka").start();
        Map<String, Object> map = new HashMap<String, Object>();
        try {
            map = ft.get();
            rtnMap.put("msg",map.get("msg"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        rtnMap.put("code", 200);
        return JsonUtils.toJsonNoException(rtnMap);
    }

这样前台就可以查询到kafka的消息了。

这里说两个思路:

如果要前台实时看到日志,两个办法:

1.前台持续进行调接口请求

2.使用websocket进行长连接获取内容

我这种办法是方案1,用websocket的话也可以,这块我没写,后面有更新我再补充