MySQL換成ES+filebeat的簡要說明。ES的簡要使用入門

背景

    最近手頭有個項目ESB改造,原先的ESB在系統信息統計時,例如交易異常統計,交易流水統計,交易用時統計等等統計計算時,壓測時會有瓶頸,我的老師希望將該部分查詢功能由MySQL改造爲ES。因爲也是第一次接觸ES(以前只是用過日誌收集系統ELK,但是環境搭起來就可以用,所以沒有細究。),現將一小段時間的摸索過程記錄,希望能幫到剛接觸ES和有類似需求的朋友。

改造要求

      原ESB將每次進件消息進行拆解和計算,將所需要統計的字段進行字段拆解存放到mysql中,配合很多視圖來實現查詢統計功能。

      現在改造,就是要將這一塊功能給換成ES的查詢和filebeat的收集。

可行性分析

     本來擔心ES底層是lucene搜索引擎,因此其本質也是搜索引擎,或者說是NoSql。類似redis 可能讀取速度快,但是沒有mysql統計功能全面。經官網學習,發現ES支持聚合查詢,但是沒有找到加減乘除運算,也沒有關聯查詢。最後將官網大部分英文文檔翻了一遍發現:有加減乘除運算,也可以關聯查詢。但是關聯查詢(ES的版本是6.2.4。需要存儲的時候就要將關聯的父記錄存儲進去,而不能做關聯查詢。所以我並不想把這個功能叫做類似mysql的join查詢。)

至此總結一下可行性:

      1 記錄增刪改查沒問題 

      2 關聯查詢,按照官網所說,使用關聯查詢,效率降低幾百倍。不過可以通過filebeat收集時,全量收集避                                       免join查詢

      3 統計可以使用聚合查詢和script腳本計算。

總的來說可以滿足需求,聚合查詢有三種實現方式,因爲項目緊張,並沒有仔細研究,所以到底join可不可以實現,並未詳細論證,從很多人的blog來看,是的確無法做到mysql 的join  on的效果。

補充說明:(本文講解從零基礎開始,爲自己的學習過程體會,膚淺之處,望高手勿噴。)

         ES搭建起來支持restful訪問,網上有客戶端  elasticsearch-head 但是可以當作navicat使用。但是發送restful請求時,                   我比較喜歡postman的簡潔。

ES+filebeat的使用

          實戰開始

1 首先是配置索引的mapping 

  這個mapping相當於mysql的數據類(每一個字段的類型,如 int,char ,vchar等等類型相似。)型定義。目的是將log日誌中的字段進行拆分後,可以將制定字段存儲爲int,long等數字類型,否則默認就是String類型(ES中叫做keyword類型),將需要計算的字段名稱設置爲int 或者long,否則後邊計算是會報錯類型轉換異常。下圖爲postman截圖

(粘圖片可以強制自己敲json代碼?)

1.1特殊情況,如果mapping已經存在,就要用重命名的方式,將其替換。附件下載中有txt文件說明(https://download.csdn.net/download/liuhua121/11540325

2 設置filebeat的解析表達式

   filebeat需要配置解析表達式,目的是爲了將日誌打印的字段進行拆分。

同時還需要設置日誌是否支持換行,換行的標誌是什麼等。

在這裏配置了一個pipeline 裏面存放了解析表達式,這樣filebeat.yml文件配置如下。

上圖是filebeat的部署配置文件。

上圖是我配置好pipeline後查出來的。存放的json代碼如下

3 JavaApi的統計寫法如下。

    這個也是很費勁的,ES版本更新速度極快,每個版本對應的JavaApi可能會有不同,而且聚合查詢是比較複雜的。

package spc.esb.console.elasticsearch;

import com.alibaba.fastjson.JSONObject;

import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.Scroll;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.sum.ParsedSum;
import org.elasticsearch.search.aggregations.metrics.sum.SumAggregationBuilder;
import org.elasticsearch.search.aggregations.pipeline.ParsedSimpleValue;
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregatorBuilders;
import org.elasticsearch.search.aggregations.pipeline.bucketscript.BucketScriptPipelineAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ESSearch
{

	// @Autowired
	private RestHighLevelClient restClient;

	//private final Logger log = LoggerFactory.getLogger(ESSearch.class);
	private static final RequestOptions COMMON_OPTIONS;

	static
	{
		RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
		COMMON_OPTIONS = builder.build();
	}

	public List<String> search(Map<String, String> matchQueryMaps) throws Exception
	{
		List<String> resList = null;
		final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L));
		SearchRequest searchRequest = new SearchRequest("liuhua");
		searchRequest.scroll(scroll);
		SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
		searchSourceBuilder.profile(true);

		/* 1用來統計流水號的聚合器 */
		TermsAggregationBuilder serviceIdAgg = AggregationBuilders.terms("service")
				.field("serviceId");
		SumAggregationBuilder successNmAgg = AggregationBuilders.sum("successNum")
				.field("successnm");
		SumAggregationBuilder errorNumAgg = AggregationBuilders.sum("errrorNum").field("errornum");
		SumAggregationBuilder timeoutNumAgg = AggregationBuilders.sum("timeoutNum")
				.field("timeoutnum");
		// 用來計算每個bucket的和
		Map<String, String> bucketsPaths = new HashMap<>();
		bucketsPaths.put("sucnm", "successNum");
		bucketsPaths.put("errnm", "errrorNum");
		bucketsPaths.put("tionm", "timeoutNum");
		Script successScript = new Script("params.sucnm/(params.sucnm+params.errnm+params.tionm)");
		Script errorScript = new Script("params.errnm/(params.sucnm+params.errnm+params.tionm)");
		Script tionmScript = new Script("params.tionm/(params.sucnm+params.errnm+params.tionm)");

		BucketScriptPipelineAggregationBuilder sucessBucketScript = PipelineAggregatorBuilders
				.bucketScript("successrate", bucketsPaths, successScript);
		BucketScriptPipelineAggregationBuilder errorBucketScript = PipelineAggregatorBuilders
				.bucketScript("errorrate", bucketsPaths, errorScript);
		BucketScriptPipelineAggregationBuilder timeoutBucketScript = PipelineAggregatorBuilders
				.bucketScript("timeoutrate", bucketsPaths, tionmScript);
		serviceIdAgg.subAggregation(successNmAgg).subAggregation(errorNumAgg)
				.subAggregation(timeoutNumAgg).subAggregation(sucessBucketScript)
				.subAggregation(errorBucketScript).subAggregation(timeoutBucketScript);

		searchSourceBuilder.aggregation(serviceIdAgg);
		searchRequest.source(searchSourceBuilder);

		System.out.println("日誌搜索查詢請求:{}"+searchRequest);
		SearchResponse searchResponse = restClient.search(searchRequest, COMMON_OPTIONS);
		System.out.println("日誌搜索查詢結果,條數:{}"+searchResponse.getHits().totalHits);
		if (searchResponse.getHits().totalHits == 0)
		{
			return null;
		}
		else
		{
			if ("OK".equals(searchResponse.status().toString()))
			{
				/* 第一中嘗試獲取groupby的方式 */
				Aggregations terms = searchResponse.getAggregations();
				for (Aggregation a : terms)
				{
					ParsedStringTerms teamSum = (ParsedStringTerms) a;
					for (Terms.Bucket bucket : teamSum.getBuckets())
					{

						Map subaggmap = bucket.getAggregations().asMap();
						double sNum = ((ParsedSum) subaggmap.get("successNum")).getValue();
						double fNum = ((ParsedSum) subaggmap.get("errrorNum")).getValue();
						double tNum = ((ParsedSum) subaggmap.get("timeoutNum")).getValue();
						double successrate = ((ParsedSimpleValue) subaggmap.get("successrate"))
								.value();
						double errorrate = ((ParsedSimpleValue) subaggmap.get("errorrate")).value();
						double timeoutrate = ((ParsedSimpleValue) subaggmap.get("timeoutrate"))
								.value();

						System.out.println(bucket.getKeyAsString() + "   " + bucket.getDocCount() + "    "
								+ "     成功數 :   " + sNum + "      " + "     失敗數 :   " + fNum
								+ "     超時數 :  " + tNum + "      成功率 :  " + successrate
								+ "      錯誤率 :  " + errorrate + "      超時率 :   " + timeoutrate);

					}
				}

				resList = new ArrayList<>();
				SearchHit[] searchHits = searchResponse.getHits().getHits();
				for (SearchHit hit : searchHits)
				{
					String res = hit.getSourceAsString();
					if (hit.getHighlightFields() != null && hit.getHighlightFields().size() > 0)
					{
						JSONObject resJson = JSONObject.parseObject(res);
						HighlightField highlightField = hit.getHighlightFields().get("message");
						String highlighMessage = highlightField.getFragments()[0].string();
						String repMessage = highlighMessage.replace("\\tat",
								"&nbsp;&nbsp;&nbsp;&nbsp;");
						resJson.put("message", repMessage);
						resList.add(resJson.toJSONString());
					}
					else
					{
						String repMessage = res.replace("\\tat", "&nbsp;&nbsp;&nbsp;&nbsp;");
						resList.add(repMessage);
					}
				}

			}
			else
			{
				System.out.println("查詢失敗!");
			}
		}
		return resList;
	}

//	public static void main(String[] args)
//	{
//		try
//		{
//			ESConfig esConfig = new ESConfig();
//			RestHighLevelClient client = esConfig.highLevelClient();
//			ESSearch esSearch = new ESSearch();
//			esSearch.restClient = client;
//			Map<String, String> matchQueryMaps = null;
//			esSearch.search(matchQueryMaps);
//			Thread.sleep(10000);
//		}
//		catch (Exception e)
//		{
//			System.out.println(e.getMessage());
//		}
//
//	}
}

以上代碼實現的SQL是

SELECT
	sum( successNum ),
	sum( errrorNum ),
	sum( timeoutNum ),
	sum( successNum ) / ( sum( successNum ) + sum( errrorNum ) + sum( timeoutNum ) ) AS 正確率,
	sum( errrorNum ) / ( sum( successNum ) + sum( errrorNum ) + sum( timeoutNum ) ) AS 錯誤率,
	sum( timeoutNum ) / ( sum( successNum ) + sum( errrorNum ) + sum( timeoutNum ) ) AS 超時率 
FROM
	索引中的 type 
GROUP BY
	serviceId

說明:      

TermsAggregationBuilder serviceIdAgg = AggregationBuilders.terms("service").field("serviceId");

JavaAPI中這個builder就是group by 的對應API,因爲sql中group by是將所有的其他查詢字段包在裏面的,所以,可以看到Java代碼中的其他的所有聚合函數都是被這個builder作爲子查詢的。

總結

到此爲止,實戰已經差不多了,剩下的就是業務代碼的實現。

代碼敲一遍,json敲一遍,感覺就會學到很多東西。官網整整看了一週,感謝我的老師也是領導的指導與寬容 ,讓我啥也不幹,看官網英文文檔看了一週。得到的經驗就是,靜下心來打開有道邊翻譯邊看官網,比看很多網上的其他資料有用千百倍。

最後再貼一點pipeline的配置的Java代碼

算了,不貼了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章