package com.yirendai.infra.cicada.repository;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.jcabi.aspects.Loggable;
import com.yirendai.infra.cicada.configure.CicadaWebProps;
import com.yirendai.infra.cicada.constants.DateTimeFormats;
import com.yirendai.infra.cicada.entity.JobSlice;
import com.yirendai.infra.cicada.entity.model.SpanModel;
import com.yirendai.infra.cicada.request.EntityPageRequest;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@Slf4j
@Loggable
@Component
public class SpanRepository {
private static final int TIMEOUT_SECOND_LIMITS = 5;
private static final String INDEX_CAT_CHARS = "_";
private static final int ES_SCROLL_KEEP_ALIVE_MILLIS = 60000;
private static final int ES_SCROLL_SIZE = 9000;
@Autowired
private CicadaWebProps props;
@Autowired
private TransportClient client;
/**
* 查询带异常信息的span分页.
*/
public List<SpanModel> fetchErrorSpanModelPage(final EntityPageRequest request) {
// 构造SearchRequestBuilder实例
final SearchRequestBuilder builder = getSearchBuilder(request);
final List<SpanModel> pageResult;
if (builder == null) {
pageResult = null;
} else {
// 设置查询条件
final BoolQueryBuilder query = new BoolQueryBuilder();
query.must(QueryBuilders.termQuery("methodId", request.getMethodId()))
.must(QueryBuilders.termQuery("hasException", true))
.filter(QueryBuilders.rangeQuery("startTime").from(request.getBeginTime().getMillis())
.to(request.getEndTime().getMillis()))
.filter(QueryBuilders.rangeQuery("durationServer").from(request.getFloorDuration())
.to(request.getCeilDuration()));
final SearchResponse response = builder.setQuery(query).execute().actionGet();
pageResult = parseResponse(response);
}
return pageResult;
}
/**
* 查询spanModel分页.
*/
public List<SpanModel> fetchSpanModelPage(final EntityPageRequest request) {
// 构造SearchRequestBuilder实例
final List<SpanModel> pageResult;
final SearchRequestBuilder builder = getSearchBuilder(request);
if (builder == null) {
pageResult = null;
} else {
// 设置查询条件
final BoolQueryBuilder query = new BoolQueryBuilder();
query.must(QueryBuilders.termQuery("methodId", request.getMethodId()))
.filter(QueryBuilders.rangeQuery("startTime").from(request.getBeginTime().getMillis())
.to(request.getEndTime().getMillis()))
.filter(QueryBuilders.rangeQuery("durationServer").from(request.getFloorDuration())
.to(request.getCeilDuration()));
final SearchResponse response = builder.setQuery(query).execute().actionGet();
pageResult = parseResponse(response);
}
return pageResult;
}
/**
* 根据TraceId获取所有的SpanModel信息.
*/
public List<SpanModel> getTraceSpanModels(final String traceId) {
// 创建SearchRequestBuilder实例,设置builder属性
final String indice = getDefaultIndice();
final SearchRequestBuilder builder = client.prepareSearch(indice);
builder.setTypes(props.getEsTypeName());
// 设置查询条件
final BoolQueryBuilder query = new BoolQueryBuilder();
query.must(QueryBuilders.termQuery("traceId", traceId));
// 执行查询
final SearchResponse response = builder.setQuery(query).execute().actionGet();
return parseResponse(response);
}
/**
* 根据Span收集条件,获取用于统计的数据.
*/
public List<SpanModel> collectSpan(final JobSlice jobSlice) {
// 获取需要进行查询的索引名
final DateTime beginTime = new DateTime(jobSlice.getStartTimestamp());
final DateTime endTime = new DateTime(jobSlice.getEndTimestamp());
final String indice = getIndice(beginTime, endTime);
List<SpanModel> allSpans; //
if (indice == null) {
allSpans = null;
} else {
// 设置查询条件
final BoolQueryBuilder query = new BoolQueryBuilder();
query.must(QueryBuilders.rangeQuery("startTime") //
.from(jobSlice.getStartTimestamp()).to(jobSlice.getEndTimestamp())) //
.filter(QueryBuilders.rangeQuery("sliceNo") //
.from(jobSlice.getStart()).to(jobSlice.getEnd()));
// 进行查询
SearchResponse scrollResp = client.prepareSearch(indice) //
.setTypes(props.getEsTypeName()) //
.setScroll(new TimeValue(ES_SCROLL_KEEP_ALIVE_MILLIS)) //
.setQuery(query).setSize(ES_SCROLL_SIZE) //
.execute().actionGet();
// 处理查询结果
allSpans = new LinkedList<SpanModel>();
final TimeValue tv = new TimeValue(60000);
while (true) {
final List<SpanModel> spans = parseResponse(scrollResp);
allSpans.addAll(spans);
scrollResp = client.prepareSearchScroll(scrollResp.getScrollId()) //
.setScroll(tv).execute().actionGet();
// Break condition: No hits are returned
if (scrollResp.getHits().getHits().length == 0) {
break;
}
}
}
return allSpans;
}
private SearchRequestBuilder getSearchBuilder(final EntityPageRequest request) {
// 创建SearchRequestBuilder实例
final String indice = getIndice(request.getBeginTime(), request.getEndTime());
final SearchRequestBuilder builder;
if (indice == null) {
builder = null;
} else {
builder = client.prepareSearch(indice);
// 设置builder属性
builder.setTypes(props.getEsTypeName()) //
.setFrom(request.getPage() * request.getSize()).setSize(request.getSize());
}
return builder;
}
private List<SpanModel> parseResponse(final SearchResponse response) {
// 处理返回结果
final List<SpanModel> spans = new LinkedList<SpanModel>();
for (final SearchHit hit : response.getHits().hits()) {
final String doc = hit.getSourceAsString();
try {
spans.add(JSON.parseObject(doc, SpanModel.class));
} catch (JSONException ex) {
log.error("failed load data {}, error {}", doc, ex);
continue;
}
}
return spans;
}
/**
* <p>
* 根据起始时间和结束时间,获取包含这个时间段的目标index 如果起始时间和结束时间在同一天,返回这一天的索引名称 span_$date.
* 如果起始时间和结束时间不在同一天,返回全部索引的通配符表达式 span_*.
* </p>
*/
private String getIndice(final DateTime beginTime, final DateTime endTime) {
final int beginDay = beginTime.getDayOfYear();
final int endDay = endTime.getDayOfYear();
String indexName;
if (beginDay == endDay) {
final String dateStr = endTime.toString(DateTimeFormats.FULL_DATE_ENGLISH);
indexName = props.getEsSpanIndexPrefix() + INDEX_CAT_CHARS + dateStr;
if (!exists(indexName)) {
indexName = null;
}
} else {
indexName = getDefaultIndice();
}
return indexName;
}
private String getDefaultIndice() {
return props.getEsSpanIndexPrefix() + INDEX_CAT_CHARS + "*";
}
private boolean exists(final String indexName) {
final IndicesExistsRequest req = new IndicesExistsRequest(indexName);
boolean isExists = false;
try {
final IndicesExistsResponse resp = client.admin() //
.indices().exists(req) //
.get(TIMEOUT_SECOND_LIMITS, TimeUnit.SECONDS);
isExists = resp.isExists();
} catch (InterruptedException ex) {
log.error("failed get index's exists status, indexName {}, error {}", indexName, ex);
} catch (TimeoutException ex) {
log.error("timeout when get index's exists status, indexName {}, error {}", indexName, ex);
} catch (ExecutionException ex) {
log.error("execution exception occured, indexName {}, error {}", indexName, ex);
}
return isExists;
}
}