一、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後端乾貨
你的支持是對我不斷創作的極大鼓勵,咱們下期見。