日誌處理
其實做這個Demo的目的是如何基於Elasticsearch構建網站日誌處理系統,通過資料同步工具等一些列開源元件來快速構建一個日誌處理系統,專案雛形初步成型中。
日誌演示網址:http://es.52itstyle.com
區域演示網址:http://es.52itstyle.com/area/index
當然,專案功能會逐步增加,實現一個365°全方位的Demo案例。
開發環境
JDK1.7、Maven、Eclipse、SpringBoot1.5.9、elasticsearch2.4.6、Dubbox2.8.4、zookeeper3.4.6、Redis、kafka、Vue、Iview
版本介紹
spring-boot-starter-parent-1.5.9.RELEASE、spring-data-elasticsearch-2.1.9.RELEAS、elasticsearch-2.4.6(5.0+以上需要依賴JDK8)
截止2018年1月22日,ElasticSearch目前最新的已到6.1.2,但是spring-boot的更新速度遠遠跟不上ElasticSearch更新的速度,目前spring-boot支援的最新版本是elasticsearch-2.4.6。
參考:https://github.com/spring-projects/spring-data-elasticsearch/wiki/Spring-Data-Elasticsearch---Spring-Boot---version-matrix
接入方式
使用spring-boot中的spring-data-elasticsearch,可以使用兩種內建客戶端接入
1、節點客戶端(node client): 配置檔案中設定為local:false,節點客戶端以無資料節點(node-master或node-client)身份加入叢集,換言之,它自己不儲存任何資料,但是它知道資料在叢集中的具體位置,並且能夠直接轉發請求到對應的節點上。
2、傳輸客戶端(Transport client): 配置檔案中設定為local:true,這個更輕量的傳輸客戶端能夠傳送請求到遠端叢集。它自己不加入叢集,只是簡單轉發請求給叢集中的節點。 兩個Java客戶端都通過9300埠與叢集互動,使用Elasticsearch傳輸協議(Elasticsearch Transport Protocol)。叢集中的節點之間也通過9300埠進行通訊。如果此埠未開放,你的節點將不能組成叢集。
服務說明
使用本地ElasticSearch服務(application-dev.properties)
spring.data.elasticsearch.cluster-name=elasticsearch
#預設就是本機,如果要使用遠端伺服器,或者區域網伺服器,那就需要在這裡配置ip:prot;可以配置多個,以逗號分隔,相當於叢集。
#Java客戶端:通過9300埠與叢集進行互動
#其他所有程式語言:都可以使用RESTful API,通過9200埠的與Elasticsearch進行通訊。
#spring.data.elasticsearch.cluster-nodes=192.168.1.180:9300
複製程式碼
使用遠端ElasticSearch服務(application-dev.properties)
-
需要自行安裝ElasticSearch,注意ElasticSearch版本儘量要與JAR包一致。
-
下載地址:https://www.elastic.co/downloads/past-releases/elasticsearch-2-4-6
-
安裝說明:http://www.52itstyle.com/thread-20114-1-1.html
-
新版本不建議使用root使用者啟動,需要自建ElasticSearch使用者,也可以使用以下命令啟動 elasticsearch -Des.insecure.allow.root=true -d 或者在elasticsearch中加入ES_JAVA_OPTS="-Des.insecure.allow.root=true"。
專案結構
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─itstyle
│ │ │ └─es
│ │ │ │ Application.java
│ │ │ │
│ │ │ ├─common
│ │ │ │ ├─constant
│ │ │ │ │ PageConstant.java
│ │ │ │ │
│ │ │ │ └─interceptor
│ │ │ │ MyAdapter.java
│ │ │ │
│ │ │ └─log
│ │ │ ├─controller
│ │ │ │ LogController.java
│ │ │ │
│ │ │ ├─entity
│ │ │ │ Pages.java
│ │ │ │ SysLogs.java
│ │ │ │
│ │ │ ├─repository
│ │ │ │ ElasticLogRepository.java
│ │ │ │
│ │ │ └─service
│ │ │ │ LogService.java
│ │ │ │
│ │ │ └─impl
│ │ │ LogServiceImpl.java
│ │ │
│ │ ├─resources
│ │ │ │ application-dev.properties
│ │ │ │ application-prod.properties
│ │ │ │ application-test.properties
│ │ │ │ application.yml
│ │ │ │ spring-context-dubbo.xml
│ │ │ │
│ │ │ ├─static
│ │ │ │ ├─iview
│ │ │ │ │ │ iview.css
│ │ │ │ │ │ iview.min.js
│ │ │ │ │ │
│ │ │ │ │ └─fonts
│ │ │ │ │ ionicons.eot
│ │ │ │ │ ionicons.svg
│ │ │ │ │ ionicons.ttf
│ │ │ │ │ ionicons.woff
│ │ │ │ │
│ │ │ │ ├─jquery
│ │ │ │ │ jquery-3.2.1.min.js
│ │ │ │ │
│ │ │ │ └─vue
│ │ │ │ vue.min.js
│ │ │ │
│ │ │ └─templates
│ │ │ └─log
│ │ │ index.html
│ │ │
│ │ └─webapp
│ │ │ index.jsp
│ │ │
│ │ └─WEB-INF
│ │ web.xml
│ │
│ └─test
│ └─java
│ └─com
│ └─itstyle
│ └─es
│ └─test
│ Logs.java
│
複製程式碼
專案演示
演示網址:http://es.52itstyle.com
專案截圖
分頁查詢
使用ElasticsearchTemplate模板插入了20萬條資料,本地向外網伺服器(1核1G),用時60s+,一分鐘左右的時間。雖然索引庫容量有增加,但是等了大約 10分鐘左右的時間才能搜尋出來。
分頁查詢到10000+的時候系統報錯,Result window is too large,修改config下的elasticsearch.yml 追加以下程式碼即可:
# 自行定義數量
index.max_result_window : '10000000'
複製程式碼
參考:https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html
Java API
Elasticsearch為Java使用者提供了兩種內建客戶端:
- 節點客戶端(node client):
節點客戶端,顧名思義,其本身也是Elasticsearch叢集的一個組成部分。以無資料節點(none data node)身份加入叢集,換言之,它自己不儲存任何資料,但是它知道資料在叢集中的具體位置,並且能夠直接轉發請求到對應的節點上。
- 傳輸客戶端(Transport client):
這個更輕量的傳輸客戶端能夠傳送請求到遠端叢集。它自己不加入叢集,只是簡單轉發請求給叢集中的節點。兩個Java客戶端都通過9300埠與叢集互動,使用Elasticsearch傳輸協議(Elasticsearch Transport Protocol)。叢集中的節點之間也通過9300埠進行通訊。如果此埠未開放,你的節點將不能組成叢集。
安裝Elasticsearch-Head
elasticsearch-head是一個介面化的叢集操作和管理工具,可以對叢集進行傻瓜式操作。你可以通過外掛把它整合到es(首選方式),也可以安裝成一個獨立webapp。
es-head主要有三個方面的操作:
- 顯示叢集的拓撲,並且能夠執行索引和節點級別操作
- 搜尋介面能夠查詢叢集中原始json或表格格式的檢索資料
- 能夠快速訪問並顯示叢集的狀態
外掛安裝方式、參考:https://github.com/mobz/elasticsearch-head
- for Elasticsearch 5.x: site plugins are not supported. Run as a standalone server
- for Elasticsearch 2.x: sudo elasticsearch/bin/plugin install mobz/elasticsearch-head
- for Elasticsearch 1.x: sudo elasticsearch/bin/plugin -install mobz/elasticsearch-head/1.x
- for Elasticsearch 0.x: sudo elasticsearch/bin/plugin -install mobz/elasticsearch-head/0.9
安裝成功以後會在plugins目錄下出現一個head目錄,表明安裝已經成功。
瀏覽截圖:
x-pack監控
Elasticsearch、Logstash 隨著 Kibana 的命名升級直接從2.4跳躍到了5.0,5.x版本的 ELK 在版本對應上要求相對較高,不再支援5.x和2.x的混搭,同時 Elastic 做了一個 package ,對原本的 marvel、watch、alert 做了一個封裝,形成了 x-pack 。
安裝:https://www.elastic.co/guide/en/elasticsearch/reference/6.1/installing-xpack-es.html
使用者管理
x-pack安裝之後有一個超級使用者elastic ,其預設的密碼是changeme,擁有對所有索引和資料的控制權,可以使用該使用者建立和修改其他使用者,當然這裡可以通過kibana的web介面進行使用者和使用者組的管理。
修改elastic使用者的密碼:
curl -XPUT -u elastic 'localhost:9200/_xpack/security/user/elastic/_password' -d '{
"password" : "123456"
}'
複製程式碼
IK Analysis for Elasticsearch
下載安裝:
-
方式一 - download pre-build package from here: https://github.com/medcl/elasticsearch-analysis-ik/releases unzip plugin to folder your-es-root/plugins/
-
方式一二 - use elasticsearch-plugin to install ( version > v5.5.1 ): ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.0.0/elasticsearch-analysis-ik-6.0.0.zip
由於Elasticsearch版本是2.4.6,這裡選擇IK版本為1.10.6
wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v1.10.6/elasticsearch-analysis-ik-1.10.6.zip
複製程式碼
下載解壓以後在 Elasticsearch 的config下的elasticsearch.yml檔案中,新增如下程式碼(2.0以上可以不設定)。
index:
analysis:
analyzer:
ik:
alias: [ik_analyzer]
type: org.elasticsearch.index.analysis.IkAnalyzerProvider
ik_max_word:
type: ik
use_smart: false
ik_smart:
type: ik
use_smart: true
複製程式碼
或者
index.analysis.analyzer.ik.type : “ik”
複製程式碼
安裝前:
http://192.168.1.180:9200/_analyze?analyzer=standard&pretty=true&text=我愛你中國
{
"tokens" : [ {
"token" : "我",
"start_offset" : 0,
"end_offset" : 1,
"type" : "<IDEOGRAPHIC>",
"position" : 0
}, {
"token" : "愛",
"start_offset" : 1,
"end_offset" : 2,
"type" : "<IDEOGRAPHIC>",
"position" : 1
}, {
"token" : "你",
"start_offset" : 2,
"end_offset" : 3,
"type" : "<IDEOGRAPHIC>",
"position" : 2
}, {
"token" : "中",
"start_offset" : 3,
"end_offset" : 4,
"type" : "<IDEOGRAPHIC>",
"position" : 3
}, {
"token" : "國",
"start_offset" : 4,
"end_offset" : 5,
"type" : "<IDEOGRAPHIC>",
"position" : 4
} ]
}
複製程式碼
安裝後:
http://121.42.155.213:9200/_analyze?analyzer=ik&pretty=true&text=我愛你中國
{
"tokens" : [ {
"token" : "我愛你",
"start_offset" : 0,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 0
}, {
"token" : "愛你",
"start_offset" : 1,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 1
}, {
"token" : "中國",
"start_offset" : 3,
"end_offset" : 5,
"type" : "CN_WORD",
"position" : 2
} ]
}
複製程式碼
資料同步
使用第三方工具類庫elasticsearch-jdbc實現MySql到elasticsearch的同步。
執行環境:
centos7.5、JDK8、elasticsearch-jdbc-2.3.2.0
安裝步驟:
- 這裡是列表文字第一步:下載(可能很卡、請耐心等待) wget http://xbib.org/repository/org/xbib/elasticsearch/importer/elasticsearch-jdbc/2.3.2.0/elasticsearch-jdbc-2.3.2.0-dist.zip
- 這裡是列表文字第二步:解壓 unzip elasticsearch-jdbc-2.3.2.0-dist.zip
- 這裡是列表文字第三步:配置指令碼mysql_import_es.sh
#!/bin/sh
# elasticsearch-jdbc 安裝路徑
bin=/home/elasticsearch-jdbc-2.3.2.0/bin
lib=/home/elasticsearch-jdbc-2.3.2.0/lib
echo '{
"type" : "jdbc",
"jdbc": {
# 如果資料庫中存在Json檔案 這裡設定成false,否則會同步出錯
"detect_json":false,
"url":"jdbc:mysql://127.0.0.1:3306/itstyle_log??useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true",
"user":"root",
"password":"root",
# 如果想自動生成_id,去掉第一個獲取欄位即可;如果想Id作為主鍵,把id設定為_id即可
"sql":"SELECT id AS _id,id,user_id AS userId ,username,operation,time,method,params,ip,device_type AS deviceType,log_type AS logType,exception_detail AS exceptionDetail,
gmt_create AS gmtCreate,plat_from AS platFrom FROM sys_log",
"elasticsearch" : {
"host" : "127.0.0.1",#elasticsearch服務地址
"port" : "9300" #遠端elasticsearch服務 此埠一定要開放
},
"index" : "elasticsearch",# 索引名相當於庫
"type" : "sysLog" # 型別名相當於表
}
}' | java \
-cp "${lib}/*" \
-Dlog4j.configurationFile=${bin}/log4j2.xml \
org.xbib.tools.Runner \
org.xbib.tools.JDBCImporter
複製程式碼
- 這裡是列表文字第四部:授權並執行
chmod +x mysql_import_es.sh
./mysql_import_es.sh
複製程式碼
ElasticSearchRepository和ElasticSearchTemplate
Spring-data-elasticsearch是Spring提供的操作ElasticSearch的資料層,封裝了大量的基礎操作,通過它可以很方便的操作ElasticSearch的資料。
ElasticSearchRepository的基本使用
/**
* @param <T>
* @param <ID>
* @author Rizwan Idrees
* @author Mohsin Husen
*/
@NoRepositoryBean
public interface ElasticsearchRepository<T, ID extends Serializable> extends ElasticsearchCrudRepository<T, ID> {
<S extends T> S index(S entity);
Iterable<T> search(QueryBuilder query);
Page<T> search(QueryBuilder query, Pageable pageable);
Page<T> search(SearchQuery searchQuery);
Page<T> searchSimilar(T entity, String[] fields, Pageable pageable);
void refresh();
Class<T> getEntityClass();
}
複製程式碼
ElasticsearchRepository裡面有幾個特殊的search方法,這些是ES特有的,和普通的JPA區別的地方,用來構建一些ES查詢的。 主要是看QueryBuilder和SearchQuery兩個引數,要完成一些特殊查詢就主要看構建這兩個引數。
一般情況下,我們不是直接是new NativeSearchQuery,而是使用NativeSearchQueryBuilder。 通過NativeSearchQueryBuilder.withQuery(QueryBuilder1).withFilter(QueryBuilder2).withSort(SortBuilder1).withXXXX().build();這樣的方式來完成NativeSearchQuery的構建。
ElasticSearchTemplate的使用
ElasticSearchTemplate更多是對ESRepository的補充,裡面提供了一些更底層的方法。
這裡我們主要實現快讀批量插入的功能,插入20萬條資料,本地向外網伺服器(1核1G),用時60s+,一分鐘左右的時間。雖然索引庫容量有增加,但是等了大約10分鐘左右的時間才能搜尋出來。
//批量同步或者插入資料
public void bulkIndex(List<SysLogs> logList) {
long start = System.currentTimeMillis();
int counter = 0;
try {
List<IndexQuery> queries = new ArrayList<>();
for (SysLogs log : logList) {
IndexQuery indexQuery = new IndexQuery();
indexQuery.setId(log.getId()+ "");
indexQuery.setObject(log);
indexQuery.setIndexName("elasticsearch");
indexQuery.setType("sysLog");
//也可以使用IndexQueryBuilder來構建
//IndexQuery index = new IndexQueryBuilder().withId(person.getId() + "").withObject(person).build();
queries.add(indexQuery);
if (counter % 1000 == 0) {
elasticSearchTemplate.bulkIndex(queries);
queries.clear();
System.out.println("bulkIndex counter : " + counter);
}
counter++;
}
if (queries.size() > 0) {
elasticSearchTemplate.bulkIndex(queries);
}
long end = System.currentTimeMillis();
System.out.println("bulkIndex completed use time:"+ (end-start));
} catch (Exception e) {
System.out.println("IndexerService.bulkIndex e;" + e.getMessage());
throw e;
}
}
複製程式碼
Redis日誌佇列
見包:com.itstyle.es.common.redis
監聽配置 RedisListener:
@Component
public class RedisListener {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisListener.class);
@Bean
RedisMessageListenerContainer container(
RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
LOGGER.info("啟動監聽");
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(listenerAdapter, new PatternTopic("itstyle_log"));
return container;
}
@Bean
MessageListenerAdapter listenerAdapter(Receiver receiver) {
return new MessageListenerAdapter(receiver, "receiveMessage");
}
@Bean
Receiver receiver(CountDownLatch latch) {
return new Receiver(latch);
}
@Bean
CountDownLatch latch() {
return new CountDownLatch(1);
}
@Bean
StringRedisTemplate template(RedisConnectionFactory connectionFactory) {
return new StringRedisTemplate(connectionFactory);
}
}
複製程式碼
日誌接收Receiver:
public class Receiver {
private static final Logger LOGGER = LoggerFactory.getLogger(Receiver.class);
@Autowired
private ElasticLogRepository elasticLogRepository;
private CountDownLatch latch;
@Autowired
public Receiver(CountDownLatch latch) {
this.latch = latch;
}
public void receiveMessage(String message) {
LOGGER.info("接收log訊息 <{}>",message);
if(message == null){
LOGGER.info("接收log訊息 <" + null + ">");
}else {
ObjectMapper mapper = new ObjectMapper();
try {
SysLogs log = mapper.readValue(message, SysLogs.class);
elasticLogRepository.save(log);
LOGGER.info("接收log訊息內容 <{}>",log.getOperation());
} catch (JsonParseException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
latch.countDown();
}
}
複製程式碼
測試 LogController:http://lip:port/redisLog
Kafka日誌佇列
見包: com.itstyle.es.common.kafka