最近项目需要进行实时读取服务端信息,在网上看到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的话也可以,这块我没写,后面有更新我再补充