从零到日志采集索引可视化、监控报警、rpc trace跟踪-日志索引
来源:互联网 发布:37传奇霸业网络异常 编辑:程序博客网 时间:2024/05/20 11:20
github开源 欢迎一起维护~~
之前说到日志事件的设计、如何埋点以及基于jvm的程序如何对接我们的系统,接下去我们说下日志如何进行索引。通过前三篇博客可以知道数据通过LOGGER.info等打印日志的函数就可以存入kafka,所以我们对日志建立索引只需要实时读kafka写入es,为了提高实时索引的速率,我们会部署3个实例实时消费kafka的9个partition,并且使用es的bulk load api,这样测试下来大概3台pc上能够实时每秒索引2w+的数据,实时处理kafka数据写文件大概每秒50w+的处理速度,完全能够满足我们公司现有的日志实时采集索引需求。
代码比较简单,核心代码如下:
BulkRequestBuilder bulkRequest = transportClient.prepareBulk();
int count = 0;
try {
while (true) {
ConsumerRecords<byte[], String> records = this.kafkaConsumerApp.poll(this.kafkaProperties.getPollTimeout());
if (!records.isEmpty()) {
for (ConsumerRecord<byte[], String> record : records) {
String value = record.value();
XContentBuilder source = this.buildXContentBuilder(value);
if (source != null) {
bulkRequest.add(transportClient.prepareIndex(this.esProperties.getIndex(), this.esProperties.getDoc())
.setSource(source));
} else {
LOGGER.info("record transform error, {}", value);
}
currentOffsets.put(new TopicPartition(record.topic(), record.partition()), new OffsetAndMetadata(record.offset() + 1));
count++;
if (count >= 1000) {
// 当达到了1000触发向kafka提交offset
kafkaConsumerApp.commitAsync(currentOffsets, new KafkaOffsetCommitCallback());
count = 0;
}
}
int size = bulkRequest.numberOfActions();
if (size != 0) {
bulkRequest.execute().actionGet();
}
LOGGER.info("total record: {}, indexed {} records to es", records.count(), size);
bulkRequest = transportClient.prepareBulk();
kafkaConsumerApp.commitAsync(currentOffsets, new KafkaOffsetCommitCallback());
}
}
} catch (WakeupException e) {
// do not process, this is shutdown
LOGGER.error("wakeup, start to shutdown, {}", e);
} catch (Exception e) {
LOGGER.error("process records error, {}", e);
} finally {
kafkaConsumerApp.commitSync(currentOffsets);
LOGGER.info("finally commit the offset");
// 不需要主动调kafkaConsumer.close(), spring bean容器会调用
}
int count = 0;
try {
while (true) {
ConsumerRecords<byte[], String> records = this.kafkaConsumerApp.poll(this.kafkaProperties.getPollTimeout());
if (!records.isEmpty()) {
for (ConsumerRecord<byte[], String> record : records) {
String value = record.value();
XContentBuilder source = this.buildXContentBuilder(value);
if (source != null) {
bulkRequest.add(transportClient.prepareIndex(this.esProperties.getIndex(), this.esProperties.getDoc())
.setSource(source));
} else {
LOGGER.info("record transform error, {}", value);
}
currentOffsets.put(new TopicPartition(record.topic(), record.partition()), new OffsetAndMetadata(record.offset() + 1));
count++;
if (count >= 1000) {
// 当达到了1000触发向kafka提交offset
kafkaConsumerApp.commitAsync(currentOffsets, new KafkaOffsetCommitCallback());
count = 0;
}
}
int size = bulkRequest.numberOfActions();
if (size != 0) {
bulkRequest.execute().actionGet();
}
LOGGER.info("total record: {}, indexed {} records to es", records.count(), size);
bulkRequest = transportClient.prepareBulk();
kafkaConsumerApp.commitAsync(currentOffsets, new KafkaOffsetCommitCallback());
}
}
} catch (WakeupException e) {
// do not process, this is shutdown
LOGGER.error("wakeup, start to shutdown, {}", e);
} catch (Exception e) {
LOGGER.error("process records error, {}", e);
} finally {
kafkaConsumerApp.commitSync(currentOffsets);
LOGGER.info("finally commit the offset");
// 不需要主动调kafkaConsumer.close(), spring bean容器会调用
}
该kafka group为es-indexer-consume-group
/**
* 根据log字符串构造XContentBuilder
* @param line
* @return
*/
private XContentBuilder buildXContentBuilder(String line) {
try {
LogDto logDto = new LogDto(line);
return jsonBuilder()
.startObject()
.field(Constants.DAY, logDto.getDay())
.field(Constants.TIME, logDto.getTime())
.field(Constants.NANOTIME, logDto.getNanoTime())
.field(Constants.CREATED, logDto.getCreated())
.field(Constants.APP, logDto.getApp())
.field(Constants.HOST, logDto.getHost())
.field(Constants.THREAD, logDto.getThread())
.field(Constants.LEVEL, logDto.getLevel())
.field(Constants.EVENT_TYPE, logDto.getEventType())
.field(Constants.PACK, logDto.getPack())
.field(Constants.CLAZZ, logDto.getClazz())
.field(Constants.LINE, logDto.getLine())
.field(Constants.MESSAGE_SMART, logDto.getMessageSmart())
.field(Constants.MESSAGE_MAX, logDto.getMessageMax())
.endObject();
} catch (Exception e) {
return null;
}
}
* 根据log字符串构造XContentBuilder
* @param line
* @return
*/
private XContentBuilder buildXContentBuilder(String line) {
try {
LogDto logDto = new LogDto(line);
return jsonBuilder()
.startObject()
.field(Constants.DAY, logDto.getDay())
.field(Constants.TIME, logDto.getTime())
.field(Constants.NANOTIME, logDto.getNanoTime())
.field(Constants.CREATED, logDto.getCreated())
.field(Constants.APP, logDto.getApp())
.field(Constants.HOST, logDto.getHost())
.field(Constants.THREAD, logDto.getThread())
.field(Constants.LEVEL, logDto.getLevel())
.field(Constants.EVENT_TYPE, logDto.getEventType())
.field(Constants.PACK, logDto.getPack())
.field(Constants.CLAZZ, logDto.getClazz())
.field(Constants.LINE, logDto.getLine())
.field(Constants.MESSAGE_SMART, logDto.getMessageSmart())
.field(Constants.MESSAGE_MAX, logDto.getMessageMax())
.endObject();
} catch (Exception e) {
return null;
}
}
由于是进行日志消费,可以允许有一定的丢失和重复消费,但是应该尽量避免。
代码其实很简单,主要说明下:
- kafka消费的时候尽量自己控制offset,以防kafka出现异常的时候导致大量的重复消费和丢失当kafka consumer进行rebalance的时候需要将当前的消费者的offset进行提交同步提交offset commitSync(xxx)会等待提交完成异步提交offset commitAsync(xxx, callback)进行异步提交,无需等待
- 针对以上情况,同步提交我们可以放在rebalance的时候,异步提交应该放在正常消费的时候,并且提交出错需要打印异常进行排查错误
以上的代码是每1000条进行一个commit,如果以此poll的数据不足1000条也会进行commit,这就既保证了向es提交bulk的效率,同时也能保证正常的offset提交,该方法有一定的重复消费和丢失的情况,因为会出现向es进行了bulk 提交,但是向kafka提交offset的时候程序挂掉,也可能提交了offset之后程序挂掉,但是还没有向es进行bulk提交,但是这种情况比较少见。回头再介绍一篇如果完全确保日志有且仅消费以此的代码,需要用到rollback机制,将offset存入第三方缓存数据。
加入hook的目的是程序被kill的时候可以确保consumer的线程运行完成再退出。
3 0
- 从零到日志采集索引可视化、监控报警、rpc trace跟踪-日志索引
- 从零到日志采集索引可视化、监控报警、rpc trace跟踪-自定义日志框架
- 从零到日志采集索引可视化、监控报警、rpc trace跟踪-系统上下线监控
- 从零到日志采集索引可视化、监控报警、rpc trace跟踪-架构介绍
- 从零到日志采集索引可视化、监控报警、rpc trace跟踪-分布式唯一ID生成
- 从零到日志采集索引可视化、监控报警、rpc trace跟踪-日志事件、埋点设计及对接
- 索引数据块损坏日志不停trace ero 8102
- Perl 监控日志异常报警
- 采集日志到 Hive
- 代码分享,日志文件监控报警
- docker 监控报警以及日志分析
- 从Hive处理日志到可视化到实时
- CDH5.1从采集(Flume)到索引(Solr)
- CDH5.1从Flume采集到索引(Solr)
- 报警日志
- Flume 从入门到实时日志采集实例
- trace 日志
- Nginx日志监控(包括可视化)工具
- 让Struts2与Spring进行整合原因及原理
- android新特性:TextInputLayout使用方法
- Kubernetes and Container Operations
- 小程序的坑
- ShapeFile工具类
- 从零到日志采集索引可视化、监控报警、rpc trace跟踪-日志索引
- c++双向链表构成的队列
- 关于Hadoop中是否还有hadoop-core-xxx.jar包吗
- Android新特性之CardView的简单使用
- ffmpeg 和 x264的参数对照
- ButterKnife8.5.1最新版本使用详细步骤
- 11. Container With Most Water
- 自定义注解
- java-基础 如何查询java的版本