博客 / 詳情

返回

全文檢索elasticsearch入門,看這篇就夠了

圖片

一、elasticsearch介紹

1、背景

在訂單管理系統中,訂單查詢的調用量都非常大,如果直接查詢數據庫,那數據庫的壓力可想而知,而且有時需要執行一些複雜的查詢,sql 並不能夠友好的支持,需要查詢很多張表。再比如用户手誤輸入的關鍵詞錯了或存在錯別字,那使用 sql 是無法搜索到。所以打算使用 Elasticsearch 來承載訂單查詢的主要壓力。

總的來説,使用 elasticsearch,以下簡稱es 的幾個原因如下

  • 關係型數據庫在進行模糊(%關鍵字%)搜索的時候,會全表掃描,查詢非常慢
  • 關係型數據庫在關鍵字搜索時,並不支持全文分詞搜索,比如用户本打算搜索:公眾號-臻大蝦,卻手誤:公號-臻大蝦,es 可以根據分詞的結果搜索出想要的結果。
  • 在數據分析、日誌分析上用到 es

2、es 基本概念

Elasticsearch 是一個分佈式、RESTful 風格的搜索和數據分析引擎,適用於包括文本、數字、地理空間、結構化和非結構化數據等在內的所有類型的數據。Elasticsearch 在 Apache Lucene 的基礎上開發而成,由 Elasticsearch N.V.(即現在的 Elastic)於 2010 年首次發佈。

Elasticsearch 是文件存儲,Elasticsearch 是面向文檔型數據庫,一條數據在這裏就是一個文檔,用 JSON 作為文檔序列化的格式,比如下面這條用户數據:

{
    "name":"臻大蝦",
    "sex":0,
    "age":24
}

3、es 優勢

  • 分佈式:橫向可擴展性,增加服務器可直接配置在集羣中
  • 高可用:提供了複製功能,具有容錯機制,能自動發現新的或失敗的節點,重組和重新平衡節點數據
  • 實時性:數據進入 es,可達到近實時搜索
  • Restful api:json 格式的 RESTful 風格
  • 全文檢索:基於 lucene 的強大的全文檢索能力

4、使用場景

全文檢索

當我們使用百度搜索、谷歌搜索時,輸入關鍵字,就能搜索到最相關的文章,這就是利用了 es 強大的全文檢索的能力。

用户行為

平時淘寶買東西時,你是否發現推薦的商品跟你最近搜索的關鍵詞很享受,這就是通過收集用户的行為日誌,分析並建立用户模型,保存在 es 中,並利用 es 強大的深入搜索和聚合的能力,可以更好的分析和展示用户的行為數據。例如推薦系統,就是利用用户模型的用户數據,對用户數據交叉查詢,分析出用户細粒度的喜好。

監控系統

利用 es 高性能查詢的特性,收集系統的監控數據,近實時展現監控數據,同時也方便用户對監控數據進行關鍵字排查。

日誌系統

常用的方案是 ELK(elasticsearch+logstash+kibana),利用 logstash 去收集 logback 的日誌信息,再通過 es 做存儲,最後可以再 kibana 去利用 es api 查看和分析日誌的相關信息。

5、es 的核心概念

索引(index)

索引是 es 最大的數據單元,類似於關係型數據庫中的庫,是多個相似文檔的集合。每個索引有一個或多個分片,每個分片有多個副片。

文檔(document)

一條數據就是一個文檔,類似於數據庫表中的一條記錄,比如:

{
    "name":"臻大蝦",
    "sex":0,
    "age":24
}

字段(field)

文檔的屬性,類似表中的字段,比如如下 json 的健:name

{
    "name":"臻大蝦"
}

映射(mapping)

映射是對文檔中每個字段類型進行定義,類似表結構,包含數據類型,長度之類的,比如:

"mappings": {
        "properties": {
            "name": {
                "type": "text"
            },
            "age": {
                "type": "long"
            }
        }
    }

分片(Shards)

在創建索引時,可以設置主分片個數和副本個數,類似數據庫的分表,將單個索引文件分成多份存儲,當請求過來時,通過路由計算找到主分片(hash(字段,比如 id)%分主片數量)。

好處:

  • 如果一個索引數據量很大,會造成硬盤和搜索速度的瓶頸,分片能分擔壓力
  • 分片允許我們進行水平切分和擴展容量
  • 可以在多個分片上進行分佈式的、並行的操作,提高系統吞吐量
注意:主分片在創建之後是無法修改的,而副本可以隨時修改。那想修改主分片的數量怎麼辦呢,刪除重新建。
"settings": {
    "number_of_shards": 2,//主分片
    "number_of_replicas": 1//副本
}

副本(Replicas)

由主分片複製來的,提供高可用

好處:

  • 高可用,當一個主分片掛了,副本可以代替工作
  • 副本也可以執行搜索操作,分攤了主分片的壓力

集羣(Cluster)

一個集羣就是由一個或多個節點組織在一起,具有相同集羣名的節點才能組成一個集羣。它們共同持有整個的數據,並一起提供索引和搜索功能。

注意:主分片和副本處於不同節點,這樣當主分片的機器掛了,副本由於在不同機器上,不會受到影響,副本變為主分片繼續工作。所以 es 最小的高可用配置為兩台服務器

節點(node)

單個 es 實例稱為一個節點(node),一個節點是集羣中的一個服務器,作為集羣的一部分,存儲數據。

類型(type)

7.x 移除了 type,8.x 將徹底移出

圖片

image-20210920183804825

二、索引原理

es 使用的是倒排索引也叫反向索引,既然有倒排索引,那是不是有正排索引,有的,我們先介紹下正排索引。

1、正排索引

正排索引是以文檔的 ID 為關鍵字,文檔中每個字段的值為 value,主要場景是通過 id 獲取文檔信息,平時用的 msyql 關係型數據庫就是以這種方式查詢的。

舉個例子

id 內容
1 my name is zhendaxia
2 my name is jack

通過 id 可以很快查詢到內容,但是當查詢比如 name 的時候,需要使用 like,再加上數據量大的時候,查詢的時間是很久的,無法滿足查詢快速的要求。

2、倒排索引

倒排索引是以字或詞為關鍵字進行索引,記錄出現這個關鍵詞的文檔的 ID

比如上面的例子使用倒排索引如下:

content docid
my 1,2
name 1,2
is 1,2
zhendaxia 1
jack 2

倒排索引,通過字或詞快速的找到所有文檔的 id,在根據文檔 id 能快速找到內容。由於人類的詞彙數量是相對有限且固定的,所以效率並不會由於日後關鍵詞的增長而受到很大的影響。

三、集羣擴容

1、集羣健康

圖片

image-20210920221154778

集羣的健康狀態有三種:綠色 green、黃色 yellow、紅色 red

綠色(健康):所有的主分片和副分片都正常運行

黃色(亞健康):所有主分片正常運行,但有副分片沒正常運行

紅色(不健康):有主分片沒正常運行

2、擴容

擴容一般分為兩種,垂直和水平

1)、垂直擴容

升級服務器,買性能更好的服務器替換原有的服務器,不過這種擴容不推薦,畢竟單台機器的性能總是有瓶頸的

2)、水平擴容

水平擴容也叫橫向擴容,就是增加服務器數量,多台普通的服務器組織在一起形成強大的計算能力。俗話説:團結就是力量。

四、瀏覽器插件

head 插件是 ES 的一個可視化插件,類似於 navicat 和 mysql 的關係。head 插件是一個用來瀏覽、與 ES 數據進行交互的 web 前端展示插件,是一個用來監視 ES 狀態的客户端插件。

以下是插件的一些簡單介紹

圖片

image-20210920225224119

五、常用 api

1、創建索引

PUT /index
{
    "settings": {
        "number_of_shards": 2,
        "number_of_replicas": 1
    },
    "mappings": {
        "properties": {
            "text_name": {
                "type": "text"
            },
            "keyword_name": {
                "type": "keyword"
            },
            "english_name": {
                "type": "text",
                "fields": {
                    "keyword": {
                        "type": "keyword"
                    }
                }
            },
            "age": {
                "type": "long"
            },
            "classId": {
                "type": "long"
            },
            "score": {
                "type": "long"
            },
            "createTime": {
                "type": "long"
            }
        }
    }
}

當看到請求體時,細心的你可能會發現 text\_name、keyword\_name、english\_name 這三個字段都是字符串,但類型好像有些不同,區別是什麼呢?是的,這幾個類型往往是剛接觸 es 的新手經常弄錯的地方。

首先,看下 text 和 keyword 的區別

text:可以分詞,用户全文搜索,可以模糊匹配搜索

keyword:不能分詞,關鍵詞搜索,只能對某個值進行整體搜索

type 是 text,但有 fields-keyword:這種類型,一種是自己加入的,另一種是在往 es 插入數據的時候,字段 english\_name 還沒有創建。

這時 es 會根據數據類型,自動幫你創建一個字段,如果是字符串類型,由於無法判斷你的這個字符串你是用來精確查詢還是模糊查詢,所以 es 會創建類型是 text,支持模糊查詢,同時會創建 fields,type 是 keyword,支持精確查詢,所以當你要精確查詢的時候,字段名就不是原來的 english\_name,而是要使用 english\_name.keyword

舉個例子來説明下,首先插入了以下數據,關鍵字 zhen

{
    "text_name": "zhen daxia",
    "keyword_name": "zhen daxia",
    "english_name": "zhen daxia",
    "age": 18,
    "classId": 2,
    "score": 90,
    "createTime": 1629353892784
}
  • 查詢 text\_name,由於 text\_name 類型是 text,會講 zhen daxia 分詞為 zhen、daxia,所以當使用 zhen 查詢時,能匹配到 zhen,所以會有結果返回.
如何查看 zhen daxia 被分為哪些詞語,可以使用 GET 你的索引/\_doc/數據 id/\_termvectors?fields=字段名,比如我的索引是 test-user,那語句就是:GET test-user/\_doc/1/\_termvectors?fields=text\_name
GET test-user/_search
{
  "query": {
   "term": {
     "text_name": {
       "value": "zhen"
     }
   }
  }
}

圖片

image-20210920232241959

  • 查詢 keyword\_name,由於 keyword\_name 類型是 keyword,不會分詞,所以 zhen 無法搜索到數據

圖片

image-20210920232950219

  • 查詢 english\_name,同 text\_name,可以搜到

圖片

image-20210920233814208

  • 查詢 english\_name.keyword,同 keyword\_name,無法搜索到結果

圖片

image-20210920233927960

2、增加映射字段

PUT /index/_mapping
{
    "properties":{
        "keyword-name":{
            "type":"keyword"
        }
    }
}

3、查詢

GET test-user/_search

3.1 match(全文檢索)

全文檢索,會分詞,模糊查詢,比如關鍵字 zhen daxia,會被拆為 zhen、daxia

{
  "query": {
    "match": {
      "text_name": "zhen daxia"
    }
  }
}

spring boot 方法

boolQueryBuilder.filter(QueryBuilders.matchQuery("text_name", "zhen daxia"));

3.2 term(精確查詢)

精確查詢,不會拆詞,比如關鍵字 zhen daxia,會直接使用 zhen daxia 搜索

{
  "query": {
    "term": {
      "keyword_name": {
        "value": "zhen daxia"
      }
    }
  }
}

spring boot 方法

QueryBuilders.termQuery("keyword_name", "zhen daxia");

3.3 terms(多值匹配)

和 term 查詢一樣,但它允許你指定多值進行匹配,如果這個字段包含了指定值中的任何一個值,那麼這個文檔就算是滿足條件。類似 mysql 的 in

{
  "query": {
   "terms": {
     "keyword_name": [
       "zhen",
       "daxia"
     ]
   }
  }
}

spring boot 方法

QueryBuilders.termsQuery("keyword_name", Lists.newArrayList("zhen","daxia"));

3.4 range(範圍查詢)

範圍查詢,比如搜索大於等於 20 且小於等於 30 的數據

{
  "query": {
    "range": {
      "age": {
        "gte": 20,   # 大於等於  大於用 gt
        "lte": 30    # 小於等於  小於用 lt
      }
    }
  }
}

spring boot 方法

RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("age");
rangeQueryBuilder.gte(20);
rangeQueryBuilder.lte(30);

3.5 prefix(前綴查詢)

前綴查詢,比如搜索 zhen,則前綴是 zhen 的都會被搜索出來

{
  "query": {
    "prefix": {
      "keyword_name": {
        "value": "zhen"
      }
    }
  }
}

spring boot 方法

QueryBuilders.prefixQuery("keyword_name","zhen");

3.6 wildcard(通配符模糊查詢)

通配符模糊查詢,類似 mysql 的 like,?匹配一個字符,*匹配 0~n 個字符

{
  "query": {
    "wildcard": {
      "keyword_name": {
        "value": "*大蝦"
      }
    }
  }
}

spring boot 方法

QueryBuilders.wildcardQuery("keyword_name","*大蝦")

3.7 fuzzy(模糊查詢,不精確查詢)

不同於 mysql 的 like,它可以錯誤一些字,比如搜索 mock,可以搜索出 mick

{
  "query": {
    "fuzzy": {
      "keyword_name": "mock"
    }
  }
}

spring boot 方法

QueryBuilders.fuzzyQuery("keyword_name","mock");

3.8 must、must not、should

//must:必須
boolQueryBuilder.must(QueryBuilders.termQuery("keyword_name","mick"));

//must not:非
boolQueryBuilder.mustNot(QueryBuilders.termQuery("keyword_name","mick"));

//should:類似mysql的或
boolQueryBuilder.should(QueryBuilders.termQuery("keyword_name","jack"));
boolQueryBuilder.should(QueryBuilders.termQuery("keyword_name","mick"));

3.9 match all(查詢全部)

查詢全部,默認 10 條

SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
sourceBuilder.query(matchAllQueryBuilder);
sourceBuilder.size(10);

3.10 match\_phrase

  • 分詞後,待查詢的字段同時匹配分詞後的所有關鍵詞
  • 順序也是一樣

比如有以下數據:

1. keyword_name:zhen daxia
2. keyword_name:daxia zhen
3. keyword_name:I am zhen daxia
4. keyword_name:daxia haha

查詢 zhen daxia,則返回 1 和 3,2:順序不對,4:沒有匹配到全部分詞

可通過 slp 調節因子,比如 1,少匹配一個也滿足

{
  "query": {
    "match_phrase": {
     "keyword_name": {
       "query": "zhen daxia",
       "slop": 1
     }
    }
  }
}

3.11 multi\_match(多字段匹配)

多字段匹配,有一個字段匹配,就滿足,keyword\_name=jack,或 english\_name=jack,就算滿足

{
  "query": {
   "multi_match": {
     "query": "jack",
     "fields": ["keyword_name","english_name"]
   }
  }
}j

3.12 filter 和 must(過濾)

filter 與 must 是屬於同一個級別的查詢方式,都可以作為 query->bool 的屬性 filter:不計算評分, 查詢效率高;有緩存(推薦) must:要計算評分,查詢效率低;無緩存

3.13 聚合查詢(聚合)

根據名字分組

builder.aggregation(AggregationBuilders.terms("agg").field("keyword_name").size(10));

關注公眾號:臻大蝦,分享更多java後端乾貨

你的支持是對我不斷創作的極大鼓勵,咱們下期見。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.