背景:本人做大数据开发,从消息队列kafka消费数据做数据解析、聚合,并将最终结果写入到ES存储。写ES使用到了BulkProcessor做批量写入,实际使用过程中偶发性出现ES数据重复问题
分析过程:
1、通过给每条数据打上毫秒级写入时间戳,确认是同一条数据被写入多次(2~3次),因为他们的数据内容,写入时间戳完全一致。
2、通过查看ES集群监控,确认每次ES出现数据节点CPU飙高,相应慢时会出现数据重复
3、起初怀疑是BulkProcessor的补偿机制设置,导致数据重复写,去掉重试机制后还是有数据重复问题
4、每次数据重复的次数都在3次内,最多三次,后面联想到内网连接ES集群的代理IP也是三个,推断与此有关
5、根据第4点对BulkProcessor的源码进行分析,发现导致数据重复的原因
private void performRequestAsync(RestClient.NodeTuple<Iterator<Node>> nodeTuple, RestClient.InternalRequest request, RestClient.FailureTrackingResponseListener listener) {
request.cancellable.runIfNotCancelled(() -> {
final RestClient.RequestContext context = request.createContextForNextAttempt((Node)((Iterator)nodeTuple.nodes).next(), nodeTuple.authCache);
this.client.execute(context.requestProducer, context.asyncResponseConsumer, context.context, new FutureCallback<HttpResponse>() {
public void completed(HttpResponse httpResponse) {
try {
RestClient.ResponseOrResponseException responseOrResponseException = RestClient.this.convertResponse(request, context.node, httpResponse);
if (responseOrResponseException.responseException == null) {
listener.onSuccess(responseOrResponseException.response);
} else if (((Iterator)nodeTuple.nodes).hasNext()) {
listener.trackFailure(responseOrResponseException.responseException);
RestClient.this.performRequestAsync(nodeTuple, request, listener);
} else {
listener.onDefinitiveFailure(responseOrResponseException.responseException);
}
} catch (Exception var3) {
listener.onDefinitiveFailure(var3);
}
}
public void failed(Exception failure) {
try {
RequestLogger.logFailedRequest(RestClient.logger, request.httpRequest, context.node, failure);
RestClient.this.onFailure(context.node);
if (((Iterator)nodeTuple.nodes).hasNext()) {
listener.trackFailure(failure);
RestClient.this.performRequestAsync(nodeTuple, request, listener);
} else {
listener.onDefinitiveFailure(failure);
}
} catch (Exception var3) {
listener.onDefinitiveFailure(var3);
}
}
public void cancelled() {
listener.onDefinitiveFailure(Cancellable.newCancellationException());
}
});
});
}
completed和failed方法里面都会对exception的请求做node轮询请求,这里的node数量就是client里面设置的连接IP数量,当ES出现响应超时的时候,会抛出sockettimeout exception,bulk方法就会重新先下一个节点发送写请求。而ES本身是用写队列的方式落库数据,虽然响应慢,但是实际的写请求已经进入ES,这就导致数据重复。
解决方法:目前能想到的方法,1、只能调大sockettimeout的配置;2、对ES进行性能调优,保证ES不响应超时
实际处理后效果:
1、对ES进行性能调优,读写的并发度和batch做了调优,刚开始有效果,后面索引量变大,数据量变大已经出现响应超时问题
2、调大sockettimeout效应时间出现写入慢的情况,反过来阻塞上游数据消费,导致了消费堆积,出现生产事故
3、后面在VIP层面开启单ip负载均衡发送请求到client节点的操作,从根源解决突刺问题