文章目錄
- 一.elasticsearch簡介
- 二.docker部署es和kibana
- 三.IK分詞器
- 四.DSL及Dev Tools
- 五.索引庫操作
- 五.檔案操作
- 六.RestClient操作索引庫
- 七.RestClient操作檔案
- 八.DSL查詢語法
- 九.搜索結果處理
- 十.RestClient查詢檔案
- 十一.資料聚合
- 十二.RestClient資料聚合
- 十三.自動補全
- 十四.資料同步
- 十五.ES集群
- 十六.附錄
- 1.相關代碼
- 2.相關DSL
一.elasticsearch簡介
Elasticsearch是一個基于Lucene的搜索服務器,它提供了一個分布式多用戶能力的全文搜索引擎,基于RESTful web介面,Elasticsearch是用Java語言開發的,并作為Apache許可條款下的開放原始碼發布,是一種流行的企業級搜索引擎,Elasticsearch用于云計算中,能夠達到實時搜索,穩定,可靠,快速,安裝使用方便,
Lucene是Apache的開源搜索引擎類別庫,提供了搜索引擎的核心API
mysql采用正向索引(B樹,B+樹)
elasticsearch采用倒排索引

Mysql:擅長事務型別操作,可以確保資料的安全和一致性
Elasticsearch:擅長海量資料的搜索、分析、計算
概念對比

二.docker部署es和kibana
kibana可以給我們提供一個elasticsearch的可視化界面,便于學習
1.創建互聯網聯,讓es和kibana容器互聯
docker network create es-net
2.拉取鏡像
docker pull elasticsearch:7.12.1
docker pull kibana:7.12.1
3.部署單點es
docker run -d \
--name es \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "discovery.type=single-node" \
-v es-data:/usr/share/elasticsearch/data \
-v es-plugins:/usr/share/elasticsearch/plugins \
--privileged \
--network es-net \
-p 9200:9200 \
-p 9300:9300 \
elasticsearch:7.12.1
命令解釋:
-e "cluster.name=es-docker-cluster":設定集群名稱-e "http.host=0.0.0.0":監聽的地址,可以外網訪問-e "ES_JAVA_OPTS=-Xms512m -Xmx512m":記憶體大小-e "discovery.type=single-node":非集群模式-v es-data:/usr/share/elasticsearch/data:掛載邏輯卷,系結es的資料目錄-v es-logs:/usr/share/elasticsearch/logs:掛載邏輯卷,系結es的日志目錄-v es-plugins:/usr/share/elasticsearch/plugins:掛載邏輯卷,系結es的插件目錄--privileged:授予邏輯卷訪問權--network es-net:加入一個名為es-net的網路中-p 9200:9200:埠映射配置
訪問9200埠即可看到elasticsearch的回應結果

4.部署kibana
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=es-net \
-p 5601:5601 \
kibana:7.12.1
--network es-net:加入一個名為es-net的網路中,與elasticsearch在同一個網路中-e ELASTICSEARCH_HOSTS=http://es:9200":設定elasticsearch的地址,因為kibana已經與elasticsearch在一個網路,因此可以用容器名直接訪問elasticsearch-p 5601:5601:埠映射配置
訪問5601埠即可看到kibana的回應結果

三.IK分詞器
es在創建倒排索引時需要對檔案分詞;在搜索時,需要對用戶輸入內容分詞,但默認的分詞規則對中文處理并不友好
處理中文分詞,一般會使用IK分詞器,https://github.com/medcl/elasticsearch-analysis-ik
1.安裝IK分詞器
# 進入容器內部
docker exec -it elasticsearch /bin/bash
# 在線下載并安裝
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
#退出
exit
#重啟容器
docker restart elasticsearch
2.IK分詞器包含兩種模式:
-
ik_smart:智能切分 最少切分 粗粒度 分出的詞較少 -
ik_max_word:最細切分 細粒度 分出的詞較多 記憶體消耗高
3.拓展詞庫
要拓展ik分詞器的詞庫,只需要修改一個ik分詞器目錄中的config目錄中的IkAnalyzer.cfg.xml檔案:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 擴展配置</comment>
<!--用戶可以在這里配置自己的擴展字典 *** 添加擴展詞典-->
<entry key="ext_dict">ext.dic</entry>
</properties>
然后在名為ext.dic的檔案中,添加想要拓展的詞語即可
4.停用詞庫
要禁用某些敏感詞條,只需要修改一個ik分詞器目錄中的config目錄中的IkAnalyzer.cfg.xml檔案:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 擴展配置</comment>
<!--用戶可以在這里配置自己的擴展字典-->
<entry key="ext_dict">ext.dic</entry>
<!--用戶可以在這里配置自己的擴展停止詞字典 *** 添加停用詞詞典-->
<entry key="ext_stopwords">stopword.dic</entry>
</properties>
然后在名為stopword.dic的檔案中,添加想要拓展的詞語即可
四.DSL及Dev Tools
官網學習地址:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
DSL是elasticsearch提供的JSON風格的請求陳述句,用來操作elasticsearch,實作CRUD
GET / 相當于直接訪問9200埠
Dev Tools是kibana提供的一種可視化工具

五.索引庫操作
1.mapping屬性
映射是定義檔案及其包含的欄位如何存盤和索引的程序,
每個檔案都是欄位的集合,每個欄位都有自己的資料型別, 在映射資料時,創建一個映射定義,該定義包含與檔案相關的欄位串列, 映射定義還包括元資料欄位,比如_source欄位,它自定義如何處理檔案的相關元資料,
mapping是對索引庫中檔案的約束,常見的mapping屬性包括:
- type:欄位資料型別,常見的簡單型別有:
- 字串:text(可分詞的文本)、keyword(精確值,例如:品牌、國家、ip地址)
- 數值:long、integer、short、byte、double、float、
- 布爾:boolean
- 日期:date
- 物件:object
- index:是否創建索引,默認為true
- analyzer:使用哪種分詞器
- properties:該欄位的子欄位
2.創建索引庫
ES中通過Restful請求操作索引庫、檔案,請求內容用DSL陳述句來表示,創建索引庫和mapping的DSL語法如下:

實體

3.查詢索引庫
GET /索引庫名
實體

4.洗掉索引庫
DELETE /索引庫名

5.修改索引庫
索引庫和mapping一旦創建無法修改,但是可以添加新的欄位,語法如下:

實體

五.檔案操作
1.添加檔案

2.查詢檔案
GET /索引庫名/_doc/檔案id

3.洗掉檔案
DELETE /索引庫名/_doc/檔案id

4.修改檔案
-
方式一:全量修改,會洗掉舊檔案,添加新檔案


-
方式二:增量修改,修改指定欄位值


六.RestClient操作索引庫
ES官方提供了各種不同語言的客戶端,用來操作ES,這些客戶端的本質就是組裝DSL陳述句,通過http請求發送給ES
官方檔案地址:https://www.elastic.co/guide/en/elasticsearch/client/index.html
1.初始化RestClient
指定版本,需要與es版本一致
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>
匯入包
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
初始化RestClient
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://101.43.16.42:9200")
));;
2.創建索引庫
@Test
void createHotelIndex() throws IOException {
// 1.創建Request物件
CreateIndexRequest request = new CreateIndexRequest("hotel");
// 2.準備請求引數;DSL陳述句
//MAPPING_TEMPLATE是靜態常量字串,內容是創建索引庫的DSL陳述句
request.source(MAPPING_TEMPLATE, XContentType.JSON);
// 3.發送請求
client.indices().create(request, RequestOptions.DEFAULT);
}
3.洗掉索引庫
@Test
void testDeleteHotelIndex() throws IOException {
// 1.創建Request物件
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
// 2.發送請求
client.indices().delete(request, RequestOptions.DEFAULT);
}
4.判斷索引庫是否存在
@Test
void testExitHotelIndex() throws IOException {
// 1.創建Request物件
GetIndexRequest request = new GetIndexRequest("hotel");
// 2.發送請求
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
//3.輸出結果
System.out.println(exists);
}
七.RestClient操作檔案
1.新增檔案
@Test
void testAddDocument() throws IOException {
//根據id查詢酒店資料
Hotel hotel = hotelService.getById(61083L);
// 1.創建Request物件
IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
// 2.準備請求引數;DSL陳述句
request.source(JSON.toJSONString(hotel),XContentType.JSON);
// 3.發送請求
client.index(request, RequestOptions.DEFAULT);
}
2.查詢檔案
@Test
void testGetDocumentById() throws IOException {
// 1.創建Request物件
GetRequest request = new GetRequest("hotel", "61083");
// 2.發送請求
GetResponse response = client.get(request, RequestOptions.DEFAULT);
// 3.決議回應結果
String json = response.getSourceAsString();
//反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println(hotelDoc);
}
3.更新檔案
@Test
void testUpdateDocument() throws IOException {
// 1.準備request
UpdateRequest request = new UpdateRequest("hotel", "61083");
// 2.準備請求引數
request.doc(
"price","952",
"starName","四鉆"
);
// 3.發送請求
client.update(request, RequestOptions.DEFAULT);
}
4.洗掉檔案
@Test
void testDeleteDocument() throws IOException {
// 1.準備request
DeleteRequest request = new DeleteRequest("hotel", "61083");
// 2.發送請求
client.delete(request, RequestOptions.DEFAULT);
}
5.批量新增檔案
@Test
void testBulkDocument() throws IOException {
//批量查詢酒店資料
List<Hotel> hotels = hotelService.list();
// 1.創建Request物件
BulkRequest request = new BulkRequest();
// 2.準備請求引數,添加多個新增的Request
for(Hotel hotel:hotels){
// 轉換為HotelDoc
HotelDoc hotelDoc = new HotelDoc(hotel);
request.add(new IndexRequest("hotel")
.id(hotel.getId().toString())
.source(JSON.toJSONString(hotelDoc),XContentType.JSON));
}
// 3.發送請求
client.bulk(request, RequestOptions.DEFAULT);
}
八.DSL查詢語法
Elasticsearch提供了基于JSON的DSL(Domain Specific Language)來定義查詢,
官方檔案:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html
常見的查詢型別包括:
- 查詢所有:查詢出所有資料,一般測驗用,例如:
- match_all
- 全文檢索(full text)查詢:利用分詞器對用戶輸入內容分詞,然后去倒排索引庫中匹配,例如:
- match_query
- multi_match_query
- 精確查詢:根據精確詞條值查找資料,一般是查找keyword、數值、日期、boolean等型別欄位,例如:
- ids
- range
- term
- 地理(geo)查詢:根據經緯度查詢,例如:
- geo_distance
- geo_bounding_box
- 復合(compound)查詢:復合查詢可以將上述各種查詢條件組合起來,合并查詢條件,例如:
- bool
- function_score
查詢的語法基本一致:

- 查詢型別為match_all
- 沒有查詢條件
1.全文檢索
全文檢索查詢,會對輸入框輸入內容分詞,常用于搜索框搜索
①match查詢:單欄位查詢
②multi_match查詢:多欄位查詢,任意一個欄位符合條件就算符合查詢條件

ps:multi_match根據多個欄位查詢,參與查詢欄位越多,查詢性能越差,使用copy_to將多欄位拷貝到一個欄位中可以提升性能
2.精確查詢
精確查詢一般是查找keyword、數值、日期、boolean等型別欄位,所以不會對搜索條件分詞
①term:根據詞條精確值查詢
因為精確查詢的欄位搜是不分詞的欄位,因此查詢的條件也必須是不分詞的詞條,查詢時,用戶輸入的內容跟自動值完全匹配時才認為符合條件,如果用戶輸入的內容過多,反而搜索不到資料

②range:根據值的范圍查詢
gte代表大于等于,gt則代表大于
lte代表小于等于,lt則代表小于

3.地理查詢
①geo_distance
附近查詢,也叫做距離查詢(geo_distance):查詢到指定中心點小于某個距離值的所有檔案

②geo_bounding_box
矩形范圍查詢,也就是geo_bounding_box查詢,查詢坐標落在某個矩形范圍的所有檔案

4.復合查詢
復合(compound)查詢:復合查詢可以將其它簡單查詢組合起來,實作更復雜的搜索邏輯,常見的有兩種:
- fuction score:算分函式查詢,可以控制檔案相關性算分,控制檔案排名
- bool query:布爾查詢,利用邏輯關系組合多個其它的查詢,實作復雜搜索
相關性演算法
TF對比BM25
①fuction score
function score query定義的三要素
- 過濾條件:哪些檔案要加分
- 算分函式:如何計算function score
- 加權方式:function score 與 query score如何運算


②bool query
布爾查詢是一個或多個查詢子句的組合,每一個子句就是一個子查詢,子查詢的組合方式有:
- must:必須匹配每個子查詢,類似“與”
- should:選擇性匹配子查詢,類似“或”
- must_not:必須不匹配,不參與算分,類似“非”
- filter:必須匹配,不參與算分

需要注意的是,搜索時,參與打分的欄位越多,查詢的性能也越差,因此這種多條件查詢時,一遍這樣做:
- 搜索框的關鍵字搜索,是全文檢索查詢,使用must查詢,參與算分
- 其它過濾條件,采用filter查詢,不參與算分
九.搜索結果處理
elasticsearch默認是根據相關度算分(_score)來排序,但是也支持自定義方式對搜索結果排序,可以排序欄位型別有:keyword型別、數值型別、地理坐標型別、日期型別等,
官方檔案:https://www.elastic.co/guide/en/elasticsearch/reference/current/sort-search-results.html
1.排序
①常規欄位排序


②地理位置排序


2.分頁

深度分頁問題


解決深度分頁問題
官方檔案:https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html
- search after:分頁時需要排序,原理是從上一次的排序值開始,查詢下一頁資料,官方推薦使用的方式,
- scroll:原理將排序后的檔案id形成快照,保存在記憶體,官方已經不推薦使用,
分頁查詢的常見實作方案以及優缺點
-
from + size:- 優點:支持隨機翻頁
- 缺點:深度分頁問題,默認查詢上限(from + size)是10000
- 場景:百度、京東、谷歌、淘寶這樣的隨機翻頁搜索
-
after search:- 優點:沒有查詢上限(單次查詢的size不超過10000)
- 缺點:只能向后逐頁查詢,不支持隨機翻頁
- 場景:沒有隨機翻頁需求的搜索,例如手機向下滾動翻頁
-
scroll:- 優點:沒有查詢上限(單次查詢的size不超過10000)
- 缺點:會有額外記憶體消耗,并且搜索結果是非實時的
- 場景:海量資料的獲取和遷移,從ES7.1開始不推薦,建議用 after search方案,
3.高亮
高亮顯示的實作分為兩步:
給檔案中的所有關鍵字都添加一個標簽,例如標簽
頁面給標簽撰寫CSS樣式


十.RestClient查詢檔案
查詢的基本步驟是:
創建SearchRequest物件
準備Request.source(),也就是DSL,
① QueryBuilders來構建查詢條件
② 傳入Request.source() 的 query() 方法
發送請求,得到結果
決議結果(參考JSON結果,從外到內,逐層決議)


elasticsearch回傳的結果是一個JSON字串,結構包含:
hits:命中的結果total:總條數,其中的value是具體的總條數值max_score:所有結果中得分最高的檔案的相關性算分hits:搜索結果的檔案陣列,其中的每個檔案都是一個json物件_source:檔案中的原始資料,也是json物件
因此,決議回應結果,就是逐層決議JSON字串,流程如下:
SearchHits:通過response.getHits()獲取,就是JSON中的最外層的hits,代表命中的結果SearchHits#getTotalHits().value:獲取總條數資訊SearchHits#getHits():獲取SearchHit陣列,也就是檔案陣列SearchHit#getSourceAsString():獲取檔案結果中的_source,也就是原始的json檔案資料
完整代碼
@Test
void testMatchAll() throws IOException {
// 1.創建Request物件
SearchRequest request = new SearchRequest("hotel");
// 2.準備請求引數;DSL陳述句
request.source().query(QueryBuilders.matchAllQuery());
// 3.發送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.決議回應
SearchHits searchHits = response.getHits();
// 4.1.獲得總條數
long total = searchHits.getTotalHits().value;
System.out.println(total);
// 4.1.檔案陣列
SearchHit[] hits = searchHits.getHits();
// 4.3.遍歷
for(SearchHit hit : hits){
// 獲取檔案source
String json = hit.getSourceAsString();
// 反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println(hotelDoc);
}
}
1.全文檢索查詢

@Test
void testMatch() throws IOException {
// 1.創建Request物件
SearchRequest request = new SearchRequest("hotel");
// 2.準備請求引數;DSL陳述句
request.source()
.query(QueryBuilders.matchQuery("all", "如家"));
// 3.發送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.決議回應
SearchHits searchHits = response.getHits();
// 4.1.獲得總條數
long total = searchHits.getTotalHits().value;
System.out.println(total);
// 4.1.檔案陣列
SearchHit[] hits = searchHits.getHits();
// 4.3.遍歷
for(SearchHit hit : hits){
// 獲取檔案source
String json = hit.getSourceAsString();
// 反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println(hotelDoc);
}
}
Ctrl+Alt+M可以抽取重復代碼
private void handleResponse(SearchResponse response) {
// 4.決議回應
SearchHits searchHits = response.getHits();
// 4.1.獲得總條數
long total = searchHits.getTotalHits().value;
System.out.println(total);
// 4.1.檔案陣列
SearchHit[] hits = searchHits.getHits();
// 4.3.遍歷
for (SearchHit hit : hits) {
// 獲取檔案source
String json = hit.getSourceAsString();
// 反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println(hotelDoc);
}
}
2.精確查詢

3.復合查詢


@Test
void testBool() throws IOException {
// 1.創建Request物件
SearchRequest request = new SearchRequest("hotel");
// 2.準備請求引數;DSL陳述句
// 2.1.準備BooleanQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 2.2.添加term
boolQuery.must(QueryBuilders.termQuery("city","上海"));
// 2.3.添加range
boolQuery.filter(QueryBuilders.rangeQuery("price").gte(100));
request.source().query(boolQuery);
// 3.發送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
handleResponse(response);
}
4.排序和分頁

@Test
void testPageAndSort() throws IOException {
// 頁碼,每頁大小
int page = 2, size = 5;
// 1.創建Request物件
SearchRequest request = new SearchRequest("hotel");
// 2.準備請求引數;DSL陳述句
// 2.1.query
request.source().query(QueryBuilders.matchAllQuery());
// 2.2.sort
request.source().sort("price", SortOrder.ASC);
// 2.3.分頁
request.source().from((page-1)*size).size(size);
// 3.發送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
handleResponse(response);
}
距離排序

5.高亮

@Test
void testHighlight() throws IOException {
// 1.創建Request物件
SearchRequest request = new SearchRequest("hotel");
// 2.準備請求引數;DSL陳述句
// 2.1.query
request.source().query(QueryBuilders.matchQuery("all", "如家"));
// 2.2.高亮
request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
// 3.發送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
handleResponse(response);
}
高亮結果決議

重寫決議方法
private void handleResponse(SearchResponse response) {
// 4.決議回應
SearchHits searchHits = response.getHits();
// 4.1.獲得總條數
long total = searchHits.getTotalHits().value;
System.out.println(total);
// 4.1.檔案陣列
SearchHit[] hits = searchHits.getHits();
// 4.3.遍歷
for (SearchHit hit : hits) {
// 獲取檔案source
String json = hit.getSourceAsString();
// 反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
// 獲取高亮結果
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if(!CollectionUtils.isEmpty(highlightFields)){
// 根據欄位名獲取高亮結果
HighlightField highlightField = highlightFields.get("name");
// 獲取高亮值
String name = highlightField.getFragments()[0].string();
// 覆寫非高亮結果
hotelDoc.setName(name);
}
// 覆寫非高亮值
System.out.println(hotelDoc);
}
}
十一.資料聚合
聚合是對檔案資料的統計、分析、計算
參與聚合的欄位型別必須是:keyword,數值,日期,布爾
聚合常見的有三類:
-
桶(Bucket)聚合:用來對檔案做分組
- TermAggregation:按照檔案欄位值分組,例如按照品牌值分組、按照國家分組
- Date Histogram:按照日期階梯分組,例如一周為一組,或者一月為一組
-
度量(Metric)聚合:用以計算一些值,比如:最大值、最小值、平均值等
- Avg:求平均值
- Max:求最大值
- Min:求最小值
- Stats:同時求max、min、avg、sum等
-
管道(pipeline)聚合:其它聚合的結果為基礎做聚合
1.桶(Bucket)聚合


默認情況下,Bucket聚合會統計Bucket內的檔案數量,記為_count,并且按照_count降序排序
可以指定order屬性,自定義聚合的排序方式


限定聚合范圍


2. 度量(Metric)聚合


十二.RestClient資料聚合


@Test
void testAggregation() throws IOException {
// 1.準備Request
SearchRequest request = new SearchRequest("hotel");
// 2.準備DSL
request.source().size(0);
request.source().aggregation(AggregationBuilders
.terms("brandAgg")
.field("brand")
.size(10)
);
// 3.發出請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.決議結果
Aggregations aggregations = response.getAggregations();
// 4.1.根據聚合名稱獲取聚合結果
Terms brandTerms = aggregations.get("brandAgg");
// 4.2.獲取buckets
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
// 4.3.遍歷
for (Terms.Bucket bucket : buckets){
// 4.4.獲取key
String key = bucket.getKeyAsString();
System.out.println(key);
}
}
十三.自動補全
1.拼音分詞器
要實作根據字母做補全,就必須對檔案按照拼音分詞,
在GitHub上有elasticsearch的拼音分詞插件,
地址:https://github.com/medcl/elasticsearch-analysis-pinyin
2.自定義分詞器
elasticsearch中分詞器(analyzer)的組成包含三部分:
character filters:在tokenizer之前對文本進行處理,例如洗掉字符、替換字符
tokenizer:將文本按照一定的規則切割成詞條(term),例如keyword,就是不分詞;還有ik_smart
tokenizer filter:將tokenizer輸出的詞條做進一步處理,例如大小寫轉換、同義詞處理、拼音處理等
參考官網配置

自定義分詞器語法如下
PUT /test
{
"settings": {
"analysis": {
"analyzer": { // 自定義分詞器
"my_analyzer": { // 分詞器名稱
"tokenizer": "ik_max_word",
"filter": "py"
}
},
"filter": { // 自定義tokenizer filter
"py": { // 過濾器名稱
"type": "pinyin", // 過濾器型別,這里是pinyin
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_original": true,
"limit_first_letter_length": 16,
"remove_duplicated_term": true,
"none_chinese_pinyin_tokenize": false
}
}
}
},
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "my_analyzer",
"search_analyzer": "ik_smart"
}
}
}
}
拼音分詞器注意事項:
為了避免搜索到同音字,搜索時不要使用拼音分詞器
3.自動補全
elasticsearch提供了Completion Suggester查詢來實作自動補全功能,這個查詢會匹配以用戶輸入內容開頭的詞條并回傳
官方檔案:https://www.elastic.co/guide/en/elasticsearch/reference/7.6/search-suggesters.html
為了提高補全查詢的效率,對于檔案中欄位的型別有一些約束:
-
參與補全查詢的欄位必須是completion型別,
-
欄位的內容一般是用來補全的多個詞條形成的陣列,




@Test
void testSuggestion() throws IOException {
// 1.準備Request
SearchRequest request = new SearchRequest("hotel");
// 2.準備DSL
request.source().suggest(new SuggestBuilder().addSuggestion(
"suggestion",
SuggestBuilders.completionSuggestion("suggestion")
.prefix("h")
.skipDuplicates(true)
.size(10)
));
// 3.發起請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.決議結果
Suggest suggest = response.getSuggest();
// 4.1.根據補全查詢名稱,獲取補全結果
CompletionSuggestion suggestion = suggest.getSuggestion("suggestion");
// 4.2.獲取options
List<CompletionSuggestion.Entry.Option> options = suggestion.getOptions();
for(CompletionSuggestion.Entry.Option option : options){
String text = option.getText().toString();
System.out.println(text);
}
}
十四.資料同步
elasticsearch中的酒店資料來自于mysql資料庫,因此mysql資料發生改變時,elasticsearch也必須跟著改變,這個就是elasticsearch與mysql之間的資料同步
常見的資料同步方案有三種:
- 同步呼叫
- 異步通知
- 監聽binlog
1.方式一:同步呼叫
- 優點:實作簡單,粗暴
- 缺點:業務耦合度高

2.方式二:異步通知
- 優點:低耦合,實作難度一般
- 缺點:依賴mq的可靠性

3.方式三:監聽binlog
- 優點:完全解除服務間耦合
- 缺點:開啟binlog增加資料庫負擔、實作復雜度高

十五.ES集群
單機的elasticsearch做資料存盤,必然面臨兩個問題:海量資料存盤問題、單點故障問題,
- 海量資料存盤問題:將索引庫從邏輯上拆分為N個分片(shard),存盤到多個節點
- 單點故障問題:將分片資料在不同節點備份(replica )
資料備份可以保證高可用,但是每個分片備份一份,所需要的節點數量就會翻一倍,成本實在是太高了
為了在高可用和成本間尋求平衡,我們可以這樣做:
- 首先對資料分片,存盤到不同節點
- 然后對每個分片進行備份,放到對方節點,完成互相備份
這樣可以大大減少所需要的服務節點數量

1.部署es集群
使用docker-compose
version: '2.2'
services:
es01:
image: elasticsearch:7.12.1
container_name: es01
environment:
- node.name=es01
- cluster.name=es-docker-cluster
- discovery.seed_hosts=es02,es03
- cluster.initial_master_nodes=es01,es02,es03
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
volumes:
- data01:/usr/share/elasticsearch/data
ports:
- 9200:9200
networks:
- elastic
es02:
image: elasticsearch:7.12.1
container_name: es02
environment:
- node.name=es02
- cluster.name=es-docker-cluster
- discovery.seed_hosts=es01,es03
- cluster.initial_master_nodes=es01,es02,es03
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
volumes:
- data02:/usr/share/elasticsearch/data
ports:
- 9201:9200
networks:
- elastic
es03:
image: elasticsearch:7.12.1
container_name: es03
environment:
- node.name=es03
- cluster.name=es-docker-cluster
- discovery.seed_hosts=es01,es02
- cluster.initial_master_nodes=es01,es02,es03
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
volumes:
- data03:/usr/share/elasticsearch/data
networks:
- elastic
ports:
- 9202:9200
volumes:
data01:
driver: local
data02:
driver: local
data03:
driver: local
networks:
elastic:
driver: bridge
es運行需要修改一些linux系統權限,修改/etc/sysctl.conf檔案
vi /etc/sysctl.conf
添加下面的內容:
vm.max_map_count=262144
然后執行命令,讓配置生效:
sysctl -p
通過docker-compose啟動集群:
docker-compose up -d
2.集群狀態監控
kibana可以監控es集群,不過新版本需要依賴es的x-pack 功能,配置比較復雜,
這里使用cerebro來監控es集群狀態
官方網址:https://github.com/lmenezes/cerebro

啟動后輸入es地址即可監控
3.創建索引庫
PUT /itcast
{
"settings": {
"number_of_shards": 3, // 分片數量
"number_of_replicas": 1 // 副本數量
},
"mappings": {
"properties": {
// mapping映射定義 ...
}
}
}

4.es集群節點角色

- master節點:對CPU要求高,但是記憶體要求低
- data節點:對CPU和記憶體要求都高
- coordinating節點:對網路帶寬、CPU要求高
職責分離可以讓我們根據不同節點的需求分配不同的硬體去部署,而且避免業務之間的互相干擾,
master eligible節點
- 參與集群選主
- 主節點可以管理集群狀態、管理分片資訊、處理創建和洗掉索引庫的請求
data節點
- 資料的CRUD
coordinator節點
路由請求到其它節點
合并查詢到的結果,回傳給用戶
一個典型的es集群職責劃分如圖:

5.腦裂
腦裂是因為集群中的節點失聯導致的,
例如一個集群中,主節點與其它節點失聯:
此時,node2和node3認為node1宕機,就會重新選主:
當node3當選后,集群繼續對外提供服務,node2和node3自成集群,node1自成集群,兩個集群資料不同步,出現資料差異,
當網路恢復后,因為集群中有兩個master節點,集群狀態的不一致,出現腦裂的情況:
解決腦裂的方案是,要求選票超過 ( eligible節點數量 + 1 )/ 2 才能當選為主,因此eligible節點數量最好是奇數,對應配置項是discovery.zen.minimum_master_nodes,在es7.0以后,已經成為默認配置,因此一般不會發生腦裂問題
例如:3個節點形成的集群,選票必須超過 (3 + 1) / 2 ,也就是2票,node3得到node2和node3的選票,當選為主,node1只有自己1票,沒有當選,集群中依然只有1個主節點,沒有出現腦裂,

6.分片存盤原理
elasticsearch會通過hash演算法來計算檔案應該存盤到哪個分片:

說明:
- _routing默認是檔案的id
- 演算法與分片數量有關,因此索引庫一旦創建,分片數量不能修改!
新增檔案的流程如下:

解讀:
- 1)新增一個id=1的檔案
- 2)對id做hash運算,假如得到的是2,則應該存盤到shard-2
- 3)shard-2的主分片在node3節點,將資料路由到node3
- 4)保存檔案
- 5)同步給shard-2的副本replica-2,在node2節點
- 6)回傳結果給coordinating-node節點
7.集群分布式查詢
elasticsearch的查詢分成兩個階段:
-
scatter phase:分散階段,coordinating node會把請求分發到每一個分片
-
gather phase:聚集階段,coordinating node匯總data node的搜索結果,并處理為最終結果集回傳給用戶

8.集群故障轉移
集群的master節點會監控集群中的節點狀態,如果發現有節點宕機,會立即將宕機節點的分片資料遷移到其它節點,確保資料安全,這個叫做故障轉移

十六.附錄
1.相關代碼
github倉庫:https://github.com/Henrik-Yao/Hotel-ES
2.相關DSL
GET /
GET _search
{
"query": {
"match_all": {}
}
}
POST /_analyze
{
"text": "寧可卷死自己,不讓其他人休息",
"analyzer": "ik_smart"
}
POST /_analyze
{
"text": "寧可卷死自己,不讓其他人休息",
"analyzer": "ik_max_word"
}
POST /_analyze
{
"text": "不洗碗作業室寧可卷死自己,不讓其他人休息",
"analyzer": "ik_smart"
}
PUT /test
{
"mappings": {
"properties": {
"info":{
"type": "text",
"analyzer": "ik_smart"
},
"email":{
"type": "keyword",
"index": false
},
"name":{
"type": "object",
"properties": {
"firstName":{
"type": "keyword",
"index": false
},
"lastName":{
"type": "keyword",
"index": false
}
}
}
}
}
}
GET /test
DELETE /test
PUT /test/_mapping
{
"properties":{
"age":{
"type":"integer"
}
}
}
POST /test/_doc/1
{
"info":"不洗碗作業室",
"email":"henrik@qq.com",
"name": {
"firstName":"云",
"lastName":"趙"
}
}
GET /test/_doc/1
DELETE /test/_doc/1
PUT /test/_doc/1
{
"info":"不洗碗作業室",
"email":"henrik@qq.com",
"name": {
"firstName":"云",
"lastName":"趙"
}
}
POST /test/_update/1
{
"doc":{
"email":"henrik-yao@qq.com"
}
}
GET /hotel
DELETE /hotel
GET /hotel/_doc/61083
GET /hotel/_search
GET /hotel/_search
{
"query": {
"match_all": {}
}
}
GET /indexName/_search
{
"query": {
"查詢型別": {
"查詢條件": "條件值"
}
}
}
GET /hotel/_search
{
"query": {
"match": {
"all": "外灘如家"
}
}
}
DELETE /hotel
PUT /hotel
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "text",
"analyzer": "ik_max_word",
"copy_to": "all"
},
"address": {
"type": "keyword",
"index": false
},
"price": {
"type": "integer"
},
"score": {
"type": "integer"
},
"brand": {
"type": "keyword",
"copy_to": "all"
},
"city": {
"type": "keyword"
},
"starName": {
"type": "keyword"
},
"business": {
"type": "keyword",
"copy_to": "all"
},
"pic": {
"type": "keyword",
"index": false
},
"location": {
"type": "geo_point"
},
"all": {
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
GET /hotel/_search
{
"query": {
"match": {
"all": "外灘如家"
}
}
}
GET /hotel/_search
{
"query": {
"multi_match": {
"query": "外灘如家",
"fields": ["brand","name","business"]
}
}
}
GET /hotel/_search
{
"query": {
"term": {
"city": {
"value": "上海"
}
}
}
}
GET /hotel/_search
{
"query": {
"range": {
"price": {
"gte": 100,
"lte": 200
}
}
}
}
GET /hotel/_search
{
"query": {
"geo_distance": {
"distance": "15km",
"location": "31.21,121.5"
}
}
}
GET /hotel/_search
{
"query": {
"function_score": {
"query": {
"match": {
"all": "外灘"
}
},
"functions": [
{
"filter": {
"term": {
"brand": "如家"
}
},
"weight": 10
}
],
"boost_mode": "sum"
}
}
}
GET /hotel/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "如家"
}
}
],
"must_not": [
{
"range": {
"price": {
"gt": 400
}
}
}
],
"filter": [
{
"geo_distance": {
"distance": "10km",
"location": {
"lat": 31.21,
"lon": 121.5
}
}
}
]
}
}
}
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"score": "desc"
},
{
"price": "asc"
}
]
}
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"_geo_distance": {
"location": {
"lat": 31,
"lon": 121
},
"order": "asc",
"unit": "km"
}
}
]
}
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"price": {
"order": "asc"
}
}
],
"from": 0,
"size": 20
}
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"price": {
"order": "asc"
}
}
],
"from": 9999,
"size": 20
}
GET /hotel/_search
{
"query": {
"match": {
"all": "如家"
}
},
"highlight": {
"fields": {
"name": {
"require_field_match": "false"
}
}
}
}
POST /hotel/_update/2056126831
{
"doc": {
"isAD": true
}
}
POST /hotel/_update/1989806195
{
"doc": {
"isAD": true
}
}
POST /hotel/_update/2056105938
{
"doc": {
"isAD": true
}
}
GET /hotel/_search
{
"query": {
"match": {
"isAD": "true"
}
}
}
GET /hotel/_search
{
"size": 0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand",
"size": 10
}
}
}
}
GET /hotel/_search
{
"size": 0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand",
"order": {
"_count": "asc"
},
"size": 10
}
}
}
}
GET /hotel/_search
{
"query": {
"range": {
"price": {
"lte": 200
}
}
},
"size": 0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand",
"size": 20
}
}
}
}
GET /hotel/_search
{
"size": 0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand",
"size": 20
},
"aggs": {
"scoreAgg": {
"stats": {
"field": "score"
}
}
}
}
}
}
GET /hotel/_search
{
"size": 0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand",
"size": 20,
"order": {
"scoreAgg.avg": "desc"
}
},
"aggs": {
"scoreAgg": {
"stats": {
"field": "score"
}
}
}
}
}
}
GET /
POST /_analyze
{
"text": ["不洗碗作業室"],
"analyzer": "pinyin"
}
DELETE /test
PUT /test
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "ik_max_word",
"filter": "py"
}
},
"filter": {
"py": {
"type": "pinyin",
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_original": true,
"limit_first_letter_length": 16,
"remove_duplicated_term": true,
"none_chinese_pinyin_tokenize": false
}
}
}
},
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "my_analyzer",
"search_analyzer": "ik_smart"
}
}
}
}
POST /test/_doc/1
{
"id": 1,
"name": "獅子"
}
POST /test/_doc/2
{
"id": 2,
"name": "虱子"
}
GET /test/_search
{
"query": {
"match": {
"name": "掉入獅子籠咋辦"
}
}
}
// 自動補全的索引庫
PUT test2
{
"mappings": {
"properties": {
"title":{
"type": "completion"
}
}
}
}
// 示例資料
POST test2/_doc
{
"title": ["Sony", "WH-1000XM3"]
}
POST test2/_doc
{
"title": ["SK-II", "PITERA"]
}
POST test2/_doc
{
"title": ["Nintendo", "switch"]
}
POST test2/_search
{
"query": {
"match_all": {}
}
}
# 自動補全查詢
POST /test2/_search
{
"suggest": {
"title_suggest": {
"text": "s",
"completion": {
"field": "title",
"skip_duplicates": true,
"size": 10
}
}
}
}
GET /hotel/_mapping
DELETE /hotel
# 酒店資料索引庫
PUT /hotel
{
"settings": {
"analysis": {
"analyzer": {
"text_anlyzer": {
"tokenizer": "ik_max_word",
"filter": "py"
},
"completion_analyzer": {
"tokenizer": "keyword",
"filter": "py"
}
},
"filter": {
"py": {
"type": "pinyin",
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_original": true,
"limit_first_letter_length": 16,
"remove_duplicated_term": true,
"none_chinese_pinyin_tokenize": false
}
}
}
},
"mappings": {
"properties": {
"id":{
"type": "keyword"
},
"name":{
"type": "text",
"analyzer": "text_anlyzer",
"search_analyzer": "ik_smart",
"copy_to": "all"
},
"address":{
"type": "keyword",
"index": false
},
"price":{
"type": "integer"
},
"score":{
"type": "integer"
},
"brand":{
"type": "keyword",
"copy_to": "all"
},
"city":{
"type": "keyword"
},
"starName":{
"type": "keyword"
},
"business":{
"type": "keyword",
"copy_to": "all"
},
"location":{
"type": "geo_point"
},
"pic":{
"type": "keyword",
"index": false
},
"all":{
"type": "text",
"analyzer": "text_anlyzer",
"search_analyzer": "ik_smart"
},
"suggestion":{
"type": "completion",
"analyzer": "completion_analyzer"
}
}
}
}
GET /hotel/_search
{
"query": {
"match_all": {}
}
}
GET /hotel/_search
{
"suggest": {
"suggestions": {
"text":"sd",
"completion":{
"field":"suggestion"
}
}
}
}
GET /hotel/_doc/60223
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/423509.html
標籤:其他
