分佈式全文搜索引擎ElasticSearch—超詳細

1 ElasticSearch

1.1 ES的概念和特色

ES:全文檢索的框架,專門作搜索,支持分佈式、集羣。封裝的Lucene。java

特色:node

  1. 原生的Lucene使用的不足,優化了Lucene的調用方式
  2. 高可用的分佈式集羣,處理PB級別的數據
  3. 目的是經過簡單的restful API來隱藏Lucene的複雜性,從而使全文檢索變得簡單,達到「開瓶即飲」的效果

Lucene:全文檢索,api比較麻煩,操做全文檢索的最底層技術。apache

核心:建立索引,搜索索引npm

1.2 ES的對手

Solr和ES的區別:json

(1) Solr重量級,支持不少種類型操做,支持分佈式,它裏面有不少功能,可是在實時領域上沒有ES好。windows

(2) ES輕量級,支持json的操做格式,在實時搜索領域裏面作得不錯,若是想使用其餘的功能,須要額外安裝插件。api

2 ElasticSearch安裝及使用說明

2.1 安裝ES

ES服務只依賴於JDK,推薦使用JDK1.7+。
(1)下載ES安裝包:ES官方下載地址
(2)運行ES (雙擊bin目錄下的elasticsearch.bat)
(3)驗證是否運行成功,訪問:http://localhost:9200/
若是看到以下信息,則說明ES集羣已經啓動而且正常運行。跨域

 

 

 2.2 ES交互方式客戶端

(1)基於RESTful API
ES和全部客戶端的交互都是使用JSON格式的數據.
其餘全部程序語言均可以使用RESTful API,經過9200端口的與ES進行通訊,在開發測試階段,你可使用你喜歡的WEB客戶端, curl命令以及火狐的POSTER插件方式和ES通訊。
Curl命令方式:
默認windows下不支持curl命令,在資料中有curl的工具及簡單使用說明。
數組

 

 

 火狐的POSTER插件界面:
相似於Firebug,在火狐的「擴展」中搜索「POSTER」,並安裝改擴展工具。緩存

 

 

 使用POSTER模擬請求的效果

(2)Java API
ES爲Java用戶提供了兩種內置客戶端:
節點客戶端(node client):
節點客戶端以無數據節點(none data node)身份加入集羣,換言之,它本身不存儲任何數據,可是它知道數據在集羣中的具體位置,而且可以直接轉發請求到對應的節點上。
傳輸客戶端(Transport client):
這個更輕量的傳輸客戶端可以發送請求到遠程集羣。它本身不加入集羣,只是簡單轉發請求給集羣中的節點。
兩個Java客戶端都經過9300端口與集羣交互,使用ES傳輸協議(ES Transport Protocol)。集羣中的節點
之間也經過9300端口進行通訊。若是此端口未開放,你的節點將不能組成集羣。
注意:
Java客戶端所在的ES版本必須與集羣中其餘節點一致,不然,它們可能互相沒法識別。

2.3 輔助管理工具Kibana5

(1)Kibana5.2.2下載地址:Kibana官方下載地址
(2)解壓並編輯config/kibana.yml,設置elasticsearch.url的值爲已啓動的ES
(3)啓動Kibana5 (在bin目錄下雙擊kibana.bat)
(4)驗證是否成功,默認訪問地址:http://localhost:5601

2.4 head工具入門 + postman

(1)進入head文件中,輸入cmd,打開控制檯,輸入npm install進行安裝。
(2)安裝完成,輸入命令npm run start啓動服務
(3)配置容許跨域訪問,在elasticsearch/config/elasticsearch.yml文件末尾加上
http.cors.enabled: true
http.cors.allow-origin: 「*」
(4)重啓elasticsearch服務,訪問http://localhost:9100

3 ES的基本操做

3.1 ES的CRUD

#新增
PUT crm/employee/1
{
  "name":"xxxx",
  "age":18
}
#查詢
GET crm/employee/1

#修改
POST crm/employee/1
{
  "name":"yyyy",
  "age":28
}

#刪除
DELETE crm/employee/1

#沒有指定id 字段生成id
POST crm/employee
{
  "name":"yyyy",
  "age":28
}

# AW8iLW-mRN4d1HhhqMMJ
GET crm/employee/AW8iLW-mRN4d1HhhqMMJ

GET _search

3.2 ES的特殊寫法

# 查詢全部
GET _search
#漂亮格式
GET crm/employee/AW8iLW-mRN4d1HhhqMMJ?pretty

#指定返回的列
GET crm/employee/AW8iLW-mRN4d1HhhqMMJ?_source=name,age

#不要元數據 只返回具體數據
GET crm/employee/AW8iLW-mRN4d1HhhqMMJ/_source

3.3 局部修改

#修改 --覆蓋之前json
POST crm/employee/AW8iLW-mRN4d1HhhqMMJ
{
  "name":"yyyy888"
}

#局部更新
POST crm/employee/AW8iLW-mRN4d1HhhqMMJ/_update
{
  "doc":{
    "name":"baocheng"
    }
}

3.4 批量操做

POST _bulk
{ "delete": { "_index": "xlj", "_type": "department", "_id": "123" }}
{ "create": { "_index": "xlj", "_type": "book", "_id": "123" }}
{ "title": "我發行的第一本書" }
{ "index": { "_index": "itsource", "_type": "book" }}
{ "title": "我發行的第二本書" }

# 普通查詢:
GET  crm/department/id
# 批量查詢:
GET xlj/book/_mget
{
  "ids" : [ "123", "AH8ht-oSqTn8hjKcHo2i" ]
}

3.5 查詢條件

# 從第0條開始查詢3條student信息
GET crm/student/_search?size=3

# 從第2條開始查詢2條student信息
GET crm/student/_search?from=2&size=2

#  表示查詢age=15的人
GET crm/student/_search?q=age:15

# 查詢3條student的信息,他們的age範圍到10到20
GET crm/student/_search?size=3&q=age[10 TO 20]

若是上面的查詢涉及條件比較多,就不適合使用

4 DSL查詢與過濾

4.1 什麼是DSL

由ES提供豐富且靈活的查詢語言叫作DSL查詢(Query DSL),它容許你構建更加複雜、強大的查詢。

DSL(Domain Specific Language特定領域語言)以JSON請求體的形式出現。 

DSL分紅兩部分:

  DSL查詢

  DSL過濾

4.2 DSL過濾與DSL查詢在性能上的區別

(1)過濾結果能夠緩存並應用到後續請求。
​(2)查詢語句同時匹配文檔,計算相關性,因此更耗時,且不緩存。
(3)過濾語句可有效地配合查詢語句完成文檔過濾。

總之在原則上,使用DSL查詢作全文本搜索或其餘須要進行相關性評分的場景,其它全用DSL過濾。

4.2.1 DSL查詢

GET crm/student/_search
{
"query": {
   "match_all": {}
},
"from": 0, 
"size": 3,
"_source": ["name", "age"],
"sort": [{"age": "asc"}]
}

4.2.2 DSL過濾

#DSL過濾 -->  name = 'tangtang'  --支持緩存
#select * from student where name=tangtang and age = 500
GET crm/student/_search
{
"query": {
   "bool": {
     "must": [
       {"match": {
         "name": "tangtang"
       }}
     ],
     "filter": {
        "term":{"age":500}
     }
   }
},
"from": 0, 
"size": 3,
"_source": ["name", "age"],
"sort": [{"age": "asc"}]
}
#select * from student where age = 500 and name != 'tangtang'

GET crm/student/_search
{
"query": {
   "bool": {
     "must_not": [
       {"match": {
         "name": "tangtang"
       }}
     ],
     "filter": {
        "term":{"age":500}
     }
   }
},
"from": 0, 
"size": 3,
"_source": ["name", "age"],
"sort": [{"age": "asc"}]
}

5 分詞器

什麼叫分詞:把一段話按照必定規則拆分開

爲何要分詞:便於檢索

分詞器放入ES:

  解壓ik分詞器 -->在es 在plugins目錄 -->建立一個IK文件夾 -->把ik插件拷貝到ik文件下面

測試ES怎麼使用分詞:

POST _analyze
{
  "analyzer":"ik_smart",
  "text":"中國駐洛杉磯領事館遭亞裔男子槍擊 嫌犯已自首"
}

7 ES集羣

7.1 爲何須要集羣

  • 解決單點故障問題
  • 解決高併發問題
  • 解決海量數據問題

7.2 ES集羣的相關概念

分片::存儲內容,主分片和從分片

node:節點,有不少類型的節點

節點屬性的配置:

四種組合配置方式:

(1)node.master: true node.data: true

這種組合表示這個節點即有成爲主節點的資格,又存儲數據。

若是某個節點被選舉成爲了真正的主節點,那麼他還要存儲數據,這樣對於這個節點的壓力就比較大了。ElasticSearch默認每一個節點都是這樣的配置,在測試環境下這樣作沒問題。實際工做中建議不要這樣設置,由於這樣至關於主節點和數據節點的角色混合到一塊了。

(2)node.master: false node.data: true

這種組合表示這個節點沒有成爲主節點的資格,也就不參與選舉,只會存儲數據。

這個節點咱們稱爲data(數據)節點。在集羣中須要單獨設置幾個這樣的節點負責存儲數據,後期提供存儲和查詢服務。

(3)node.master: true node.data: false

這種組合表示這個節點不會存儲數據,有成爲主節點的資格,能夠參與選舉,有可能成爲真正的主節點,這個節點咱們稱爲master節點。

(4)node.master: false node.data: false

這種組合表示這個節點即不會成爲主節點,也不會存儲數據,這個節點的意義是做爲一個client(客戶端)節點,主要是針對海量請求的時候能夠進行負載均衡。

7.3 ES集羣理解

7.3.1 單node環境

  • 單node環境下,建立一個index,有3個primary shard,3個replica shard
  • 集羣status是yellow
  • 這個時候,只會將3個primary shard分配到僅有的一個node上去,另外3個replica shard是沒法分配的
  • 集羣能夠正常工做,可是一旦出現節點宕機,數據所有丟失,並且集羣不可用,沒法承接任何請求

7.3.2 2個node環境

  • replica shard分配:3個primary shard,3個replica shard,2 node
  • primary —> replica同步
  • 讀請求:primary/replica

7.4 搭建ES集羣

搭建三個節點的集羣

(1) 拷貝三個ES 分別取名爲node1 node2 node3

(2) 修改配置

  修改內存配置 xms xmx

  配置 elasticsearch.yml

# 統一的集羣名
cluster.name: my-ealsticsearch
# 當前節點名
node.name: node-1
# 對外暴露端口使外網訪問
network.host: 127.0.0.1
# 對外暴露端口
http.port: 9201
#集羣間通信端口號
transport.tcp.port: 9301
#集羣的ip集合,可指定端口,默認爲9300
discovery.zen.ping.unicast.hosts: [「127.0.0.1:9301」,」127.0.0.1:9302」,」127.0.0.1:9303」]

(3) 配置跨域

(4) 啓動 node1 node2 node3

(5) 啓動head

建立索引,指定分片(若是沒有分配從分片,磁盤佔用率過高,能夠設置:cluster.routing.allocation.disk.threshold_enabled: false)

8 Java API

8.1 什麼是JavaAPI

ES對Java提供一套操做索引庫的工具包,即Java API。全部的ES操做都使用Client對象執行。
ES的Maven引入:

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>transport</artifactId>
    <version>5.2.2</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.7</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.7</version>
</dependency>

8.2 測試代碼

import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.junit.Test;

import java.net.InetAddress;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class EsTest {

    /**
     * 鏈接es服務方法 嗅探方式
     */
    private TransportClient getClient() throws Exception {
        Settings settings = Settings.builder()
                .put("client.transport.sniff", true).build();
        TransportClient client = new PreBuiltTransportClient(settings).
                addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300));
        return client;
    }

    /**
     * 新增
     */
    @Test
    public void testAdd() throws Exception {
        TransportClient client = getClient();
        IndexRequestBuilder builder = client.prepareIndex("crm", "user", "1");
        Map map = new HashMap();
        map.put("name", "james");
        map.put("age", 35);
        System.out.println(builder.setSource(map).get());
        client.close();
    }

    /**
     * 查詢
     */
    @Test
    public void testGet() throws Exception {
        TransportClient client = getClient();
        System.out.println(client.prepareGet("crm", "user", "1").get().getSource());
        client.close();
    }

    /**
     * 修改
     */
    @Test
    public void testUpdate() throws Exception {
        TransportClient client = getClient();
        IndexRequest indexRequest = new IndexRequest("crm", "user", "1");
        Map map = new HashMap();
        map.put("name", "kobe");
        map.put("age", 18);
        //不存在就新增,存在就更新
        UpdateRequest upsert = new UpdateRequest("crm", "user", "1").doc(map).upsert(indexRequest);
        client.update(upsert).get();
        client.close();
    }

    /**
     * 刪除
     */
    @Test
    public void testDelete() throws Exception {
        TransportClient client = getClient();
        client.prepareDelete("crm","user","1").get();
        client.close();
    }

    /**
     * 批量操做
     */
    @Test
    public void testBulk() throws Exception {
        TransportClient client = getClient();
        BulkRequestBuilder bulk = client.prepareBulk();
        for (int i = 1; i < 51; i++) {
            Map map = new HashMap();
            map.put("name", "xx" + i);
            map.put("age", i);
            bulk.add(client.prepareIndex("crm", "user", "" + i).setSource(map));
        }
        BulkResponse response = bulk.get();
        if (response.hasFailures()) {
            System.out.println("插入失敗!");
        }
        client.close();
    }

    /**
     * DSL過濾(分頁,過濾,排序)
     */
    @Test
    public void testDsl() throws Exception {
        TransportClient client = getClient();
        //獲得builder
        SearchRequestBuilder builder = client.prepareSearch("crm").setTypes("user");
        //獲得boolQuery對象
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //獲得must
        List<QueryBuilder> must = boolQuery.must();
        must.add(QueryBuilders.matchAllQuery());
        //添加filter過濾器
        boolQuery.filter(QueryBuilders.rangeQuery("age").gte(18).lte(48));
        builder.setQuery(boolQuery);
        //添加分頁
        builder.setFrom(0);
        builder.setSize(10);
        //設置排序
        builder.addSort("age", SortOrder.ASC);
        //設置查詢字段
        builder.setFetchSource(new String[]{"name","age"}, null);
        //取值
        SearchResponse searchResponse = builder.get();
        //獲得查詢內容
        SearchHits hits = searchResponse.getHits();
        //獲得命中數據,返回數組
        SearchHit[] hitsHits = hits.getHits();
        //循環數組,打印獲取值
        for (SearchHit hitsHit : hitsHits) {
            System.out.println(hitsHit.getSource());
        }
        client.close();
    }

}
相關文章
相關標籤/搜索