背景
从指定的kafka消费数据,落地成指定的数据格式的文件
生产者线程
任务:从kafka中拉取数据并存入线程安全的集合中
- 从kafka中读取消息,需要用到KafkaConsumer,通过和Producer使用相同的topic约定来消费指定数据。配置消费端consumer需要配置文件properties以及订阅的主题topic,这些在构造线程类时就进行配置。
- 从kafka中拉取的数据都被存储在consumerRecords中,对于这个类,我认为可以将它看作是一个容器,用于保存在特定主题下的每个分区里的consumerRecord列表。
- 因为采用多线程的方式进行数据的存储与转存,所以采用阻塞队列这个结构来作为数据临时存储的容器。
主要代码如下:
private static final Properties props = getProperties();
private static String topic = "test01"; //消费者主题
private static LinkedBlockingQueue<String> messageQueue = null;
/**
* 线程1:从kafka中拉取数据并存入线程安全的集合
*/
static class KafkaConsumerThread extends Thread{
private KafkaConsumer<String, String> kafkaConsumer;
public KafkaConsumerThread(Properties properties, String topic){
this.kafkaConsumer = new KafkaConsumer<String, String>(properties);
this.kafkaConsumer.subscribe(Arrays.asList(topic));
}
/**
* 循环的往阻塞队列里推入数据,当达到队列容量上限的时候,线程停止。
* 等到另一线程将数据取出并存入文件时,才继续向队列推入数据
*/
@Override
public void run() {
try {
while(true){
//拉取消息, 在这个时间段里拉取,时间到了就立刻返回数据
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(100);
//处理消息->存储数据, 得到元素是json对象的queue,需要对每个元素进行处理
messageQueue = convertRecordsToQueue(consumerRecords);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static LinkedBlockingQueue<String> convertRecordsToQueue(ConsumerRecords records) throws InterruptedException {
Iterator<ConsumerRecord<String, String>> iterator = records.iterator();
while(iterator.hasNext()){
if(messageQueue == null){
//1000表示阻塞队列数量的上限,后期可以写在配置文件中
messageQueue = new LinkedBlockingQueue<>(1000);
}
//每个consumerRecord.value都是一组数据,依次将数据存入队列中
ConsumerRecord<String, String> consumerRecord = iterator.next();
String value = consumerRecord.value();
messageQueue.put(value);
}
return messageQueue;
}
/**
* 消费者配置信息导入
* @return
*/
private static Properties getProperties() {
Properties props = new Properties();
try{
props = PropertiesLoaderUtils.loadAllProperties("consumer.properties");
}catch(IOException e){
e.printStackTrace();
}
return props;
}
其中consumer.properties如下所示,因为项目要求不高,所以没有重写的配置均为默认值。
bootstrap.servers=ip_host:port
group.id=test
key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
value.deserializer=org.apache.kafka.common.serialization.StringDeserializer
消费者线程
任务:从线程安全集合中取出数据并存入文件
将数据存入文件时需要满足:每5s生成一份数据文件且每份文件中存入的记录数量不能大于200,因此在每一次写入前都进行如下判定:
- 当文件存在时间小于5s并且存入文件的记录数量小于200时,继续向文件中存储。
- 当文件存在时间大于5s或者存入文件的记录数量达到200时,跳出内部循环,新建文件。
/**
* 线程2:从线程安全集合中取出数据并存入文件
*/
static class FileInThread extends Thread{
public FileInThread(){}
@Override
public void run() {
int counter;
while(true){
counter = 0;
File file = new File(filePath);
try(BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(file, true)))){
//while循环判定条件,每次写入文件的时候进行判定
//因为我将创建文件的时间写在了文件名中,所以下面获取创建时间的方法是对文件名进行处理得到的
while (counter < 200 && (new Date().getTime() - Long.parseLong(file.getAbsolutePath().substring(19, 32)) < 5000)) {
if(messageQueue != null) {
//messageQueue.take() 获得一个完整的json对象,以String表示
JSONObject jsonobj = JSONObject.parseObject(messageQueue.take());
//获取相应属性key所对应的值,再将相应的值存入file
String fileIn = jsonobj.getString("xxxx") + "\r\n";
bw.write(fileIn);
counter++;
}
}
bw.flush();
}catch(Exception e){
e.printStackTrace();
}
}
}
}