SpringBoot连接Kafka与ElasticSearch实现日志管理系统
前言
ElasticSearch是一个分布式搜索和分析引擎,通常用于对分布式环境下的日志进行分析。而Kafka是一个消息队列,可以起到对消息进行缓冲的作用。两者一个用于处理日志信息,一个用于环境数据压力,结合起来就是一个基于大数据环境下的日志分析程序。
那么两者如何结合使用呢?
在本文中的使用方式是采用转换字符串格式的JSON文件,Kafka的生产者传输字符串格式的JSON文件,Kafka的消费者将JSON文件存入ElasticSearch中,实现了生产和消费的过程。
一、需要引入的Maven
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lmz</groupId>
<artifactId>springBootKafkaTest</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<!-- SpringBoot资源的引入 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot的测试包,在文章中为用到,可以不引用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- JSON格式转换包 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<!-- Kafka资源的引入 -->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.11</artifactId>
</dependency>
<!--es依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- 使用elasticsearch必须导入的三个包 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.6.1</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>7.6.1</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.6.1</version>
</dependency>
<!--谷歌的JSON格式转换API-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
</dependencies>
</project>
二、SpringBoot连接ElasticSearch
1、SpringBoot的配置类
SpringBoot在引入ElasticSearch之前可能需要配置,避免Netty启动冲突的问题,不同版本造成该现象的情况不同,一些前置版本并不需要对Netty启动进行配置,但因为该版本问题,需要先配置再启动,所以SpringBoot的启动类如下。
//原因是 netty 启动冲突问题, 需要在 CommunityApplication 编写 init()
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.annotation.PostConstruct;
/**
* @author zeyue
*/
@SpringBootApplication
public class CommunityApplication {
@PostConstruct
public void init(){
// 解决 netty 启动冲突问题
// see Netty4Utils.setAvailableProcessors()
System.setProperty("es.set.netty.runtime.available.processors", "false");
}
public static void main(String[] args) {
SpringApplication.run(CommunityApplication.class, args);
}
}
三、生产者
生产者主要的作用就是实现JSON数据的发送,此处发送的格式为字符串格式,所以生产者直接将类转换成字符串进行发送即可。
import com.alibaba.fastjson.JSON;
import com.lmz.entity.Game;
import com.lmz.util.KafkaPara;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.KafkaException;
import org.apache.kafka.common.errors.RetriableException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
* @author zeyue
*/
public class CacheLogPro {
/**
* 订阅主题名称
*/
public static final String topic = "test";
/**
* 日志类,加载日志信息
*/
private final static Logger logger = LoggerFactory.getLogger(CacheLogPro.class);
public CacheLogPro() {
}
/**
* 生产者参数配置方法
*
* @return 返回配置好生产者参数的Properties类
*/
public static Properties initConfig() {
Properties props = new Properties();
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
//Kafka的连接地址
props.put("bootstrap.servers", KafkaPara.brokerList);
props.put("client.id", "consumer.client.id.demo");
return props;
}
public static void main(String[] args) {
send();
}
public static void send() {
Producer<String, String> producer = new KafkaProducer<>(initConfig());
//此处的Game类为发送的类的信息,可以根据自己需要传输的格式进行调整。
Game game = new Game(
1L,"switch","Assain 1","It is so coll !!","I don`t know",
"zeyue",67.0,new Date(),new Date()
);
ProducerRecord record = null;
String s = JSON.toJSONString(game);
//转化为json字符串
record = new ProducerRecord<String, String>(topic, s);
producer.send(record, new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
if (e == null) {
logger.info("kafka send successful");
} else {
if (e instanceof RetriableException) {
//处理可重试异常
try {
logger.error("kafka send fail Retry sending.");
Thread.sleep(3000);
send();
} catch (InterruptedException e1) {
logger.error("kafka error :", e1);
}
} else {
throw new KafkaException("kafka server message error.");
}
}
}
});
producer.close();
}
}
四、消费者
消费者的主要作用就是接收字符串信息,并将字符串信息传输进入ElasticSearch中,在本文的数据传输中因为格式一定是正确的,所以没有对格式不正确的进行判断,如果在实际的生产项目中,还需要对JSON字符串进行一个判断,判断JSON格式是否是正确的格式,如果不是正确的格式需要返回生产者对应的数据信息。
import com.lmz.util.KafkaPara;
import org.apache.http.HttpHost;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import java.time.Duration;
import java.util.Arrays;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* 日志消费者,将日志的JSON字符进行消费,并上传到ElasticSearch中
* @author zeyue
*/
public class LogCon {
private static KafkaConsumer<String,String> consumer;
/**
* 订阅主题名称
*/
public static final String topic = "test";
public static final AtomicBoolean isRunning = new AtomicBoolean(true);
/**
* 注入restHighLevelClient的客户端参数(此处由于采用静态参数,所以不支持Spring的参数注入,所以直接写死了)
*/
public static RestHighLevelClient client= new RestHighLevelClient(
RestClient.builder(
new HttpHost("127.0.0.1",9200,"http")
)
);
public static Properties initConfig(String groupId , String clientId) {
Properties props = new Properties();
props.put("key.deserializer",
"org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer",
"org.apache.kafka.common.serialization.StringDeserializer");
//设置偏移量
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
props.put("bootstrap.servers", KafkaPara.brokerList);
//消费者所属的消费者组"test-consumer-group"
props.put("group.id", groupId );
//"consumer.client.id.demo"
props.put("client.id", clientId);
return props;
}
public static void main(String[] args) {
consumerToPoll(initConfig("test-consumer-group1","consumer.client.id.demo1"));
}
/**
* 消费者拉取消息
* > ① 创建消费者实例
* > ② 订阅主题
* > ③ 拉取消息进行消费
* > ④ 提交位移 offset
* > ⑤ 关闭消费者实例
* @param props 消费者参数
*/
public static void consumerToPoll(Properties props) {
//如果client未接收到消息,直接退出
if (client == null) {
System.out.println("client is null");
return;
}
consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList(topic));
System.out.println("消费者开始消费数据");
try {
while (isRunning.get()) {
ConsumerRecords<String, String> records =
consumer.poll(Duration.ofMillis(100));
if (records.isEmpty()){
System.out.println(props.get("client.id")+" it is null");
}
for (ConsumerRecord<String, String> record : records) {
// 创建请求
IndexRequest request = new IndexRequest("test-1");
// 设置文档ID,此处可以不设置,如果需要设置需要将其转换成一个基于分布式可以进行数据递增的参数
request.id("1");
// request.timeout("1s")
request.timeout(TimeValue.timeValueMillis(1000));
String s = record.value();
System.out.println(s);
// 将我们的数据放入请求中JSON.toJSONString(user)
request.source(s , XContentType.JSON);
// 客户端发送请求,获取响应的结果
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
// 获取建立索引的状态信息 CREATED
System.out.println(response.status());
// 查看返回内容 IndexResponse[index=liuyou_index,type=_doc,id=1,version=1,result=created,seqNo=0,primaryTerm=1,shards={"total":2,"successful":1,"failed":0}]
System.out.println(response);
return;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
consumer.close();
}
}
}
总结
如果需要测试的话,可以将生产者设置成多线程的生产者,生产出大量的数据,再有消费者进行消费测试,这样更能看出存在Kafka的日志存储系统的优异特点。