一、引言
1.1 數(shù)據(jù)庫(kù)查詢?yōu)槭裁催€要ElasticSearch?
數(shù)據(jù)庫(kù)一般只適合保存搜索結(jié)構(gòu)化的數(shù)據(jù),對(duì)于非結(jié)構(gòu)化的數(shù)據(jù)( 比如文章內(nèi)容),只能通過(guò)like%%模糊查詢,但是在大量的數(shù)據(jù)面前,like%%有兩個(gè)弊端:
1)搜索效率會(huì)很差,因?yàn)槭亲鲆粋€(gè)全表掃描(like%%會(huì)讓索引失效)
2)搜索沒(méi)辦法通過(guò)相關(guān)度匹配排序(可能返回的是用戶不關(guān)心的結(jié)果)
ElasticSearch就可以解決這些問(wèn)題
1.2 什么是全文檢索?
全文檢索 將非結(jié)構(gòu)化數(shù)據(jù)中的一部分信息提取出來(lái),重新組織,使其變得具有一定結(jié)構(gòu),然后對(duì)此有一定結(jié)構(gòu)的數(shù)據(jù)進(jìn)行搜索,從而達(dá)到搜索相對(duì)較快的目的。這部分從非結(jié)構(gòu)化數(shù)據(jù)中提取出的然后重新組織的信息,我們稱之索引。
例如字典的拼音表和部首檢字表就相當(dāng)于字典的索引,通過(guò)查找拼音表或者部首檢字表就可以快速的查找到我們要查的字。
這種先建立索引,再對(duì)索引進(jìn)行搜索的過(guò)程就叫全文檢索(Full-text Search)。
1.3 全文檢索的流程

1.4 構(gòu)建索引的過(guò)程
索引過(guò)程,對(duì)要搜索的原始內(nèi)容進(jìn)行索引構(gòu)建一個(gè)索引庫(kù),索引過(guò)程包括:
獲得文檔→創(chuàng)建文檔→分析文檔→索引文檔。
1.4.1 獲得原始文檔
原始文檔是指要索引和搜索的內(nèi)容。原始內(nèi)容包括互聯(lián)網(wǎng)上的網(wǎng)頁(yè)、數(shù)據(jù)庫(kù)中的數(shù)據(jù)、磁盤(pán)上的文件等。
1.4.2 創(chuàng)建文檔對(duì)象(Document)
獲取原始文檔的目的是為了索引,在索引前需要將原始內(nèi)容創(chuàng)建成文檔(Document),文檔中包括一個(gè)一個(gè)的域(Field),域中存儲(chǔ)內(nèi)容。
1.4.3 分析文檔(分詞)
將原始內(nèi)容創(chuàng)建為包含域(Field)的文檔(document),需要再對(duì)域中的內(nèi)容進(jìn)行分析,分析的過(guò)程是經(jīng)過(guò)對(duì)原始文檔提取單詞、將字母轉(zhuǎn)為小寫(xiě)、去除標(biāo)點(diǎn)符號(hào)、去除停用詞等過(guò)程生成最終的語(yǔ)匯單元。
1.4.4 創(chuàng)建索引
創(chuàng)建索引是對(duì)語(yǔ)匯單元索引,通過(guò)詞語(yǔ)找文檔,這種索引的結(jié)構(gòu)叫倒排索引結(jié)構(gòu)。
1.5 倒排索引
1.5.1 正向索引
簡(jiǎn)單來(lái)說(shuō),正向索引就是根據(jù)文件ID找到該文件的內(nèi)容,在文件內(nèi)容中匹配搜索關(guān)鍵字,這種方法是順序掃描方法,數(shù)據(jù)量大、搜索慢。

1.5.2 反向(倒排)索引
倒排索引和正向索引剛好相反,是根據(jù)內(nèi)容(詞語(yǔ))找文檔

二、ElasticSearch
2.1 ElasticSearch簡(jiǎn)介
ElasticSearch基本概念
2.1.1 索引庫(kù)(index)
索引庫(kù)是ElasticSearch存放數(shù)據(jù)的地方,可以理解為關(guān)系型數(shù)據(jù)庫(kù)中的一個(gè)數(shù)據(jù)庫(kù)。事實(shí)上,我們的數(shù)據(jù)被存儲(chǔ)和索引在分片(shards)中,索引只是一個(gè)把一個(gè)或多個(gè)分片分組在一起的邏輯空間。
2.1.2 映射類型(type)
映射類型用于區(qū)分同一個(gè)索引下不同的數(shù)據(jù)類型,相當(dāng)于關(guān)系型數(shù)據(jù)庫(kù)中的表。
注意:在 6.0 的index下是無(wú)法創(chuàng)建多個(gè)type,并且會(huì)在 7.0 中完全移除。
2.1.3 文檔(documents)
文檔是ElasticSearch中存儲(chǔ)的實(shí)體,類比關(guān)系型數(shù)據(jù)庫(kù),每個(gè)文檔相當(dāng)于數(shù)據(jù)庫(kù)表中的一行數(shù)據(jù)。
2.1.4 字段(fields)
文檔由字段組成,相當(dāng)于關(guān)系數(shù)據(jù)庫(kù)中列的屬性。
2.1.5 分片與副本
如果一個(gè)索引具有很大的數(shù)據(jù)量,它的數(shù)據(jù)量可能會(huì)超出單個(gè)節(jié)點(diǎn)的容量限制(硬盤(pán)容量),而且單個(gè)節(jié)點(diǎn)數(shù)據(jù)量過(guò)大,執(zhí)行性能也會(huì)隨之下降,每個(gè)搜索請(qǐng)求的執(zhí)行效率都會(huì)降低。 為了解決上述問(wèn)題, Elasticsearch 提出了分片的概念,索引將劃分成多份,稱為分片。每個(gè)分片都可以創(chuàng)建對(duì)應(yīng)的副本,以便保證服務(wù)的高可用性。
2.2 ElasticSearch的安裝
1)準(zhǔn)備ElasticSearch的docker-compose.yml文件
version: '3.1'
services:
elasticsearch:
image: elasticsearch:6.8.5
restart: always
container_name: elasticsearch
ports:
- 9200:9200
- 9300:9300
environment:
discovery.type: single-node
volumes:
- ./es/data:/usr/share/elasticsearch/data:rw
- ./es/logs:/usr/share/elasticsearch/logs:rw
- ./es/plugins:/usr/share/elasticsearch/plugins
- config:/usr/share/elasticsearch/config
kibana:
image: kibana:6.8.5
container_name: kibana
restart: always
environment:
SERVER_NAME: kibana
ELASTICSEARCH_URL: http://193.168.195.135:9200
ports:
- 5601:5601
volumes:
config:
注意:第23行必須寫(xiě)elasticsearch所在機(jī)器的ip地址,不能寫(xiě)127.0.0.1
2)執(zhí)行docker-compose up -d 命令啟動(dòng)容器
docker-compose up -d
注意:第一次創(chuàng)建容器會(huì)失敗,需要給.es文件夾賦予權(quán)限,執(zhí)行chmod 777 -R ./es命令,然后重啟容器
3)安裝中文分詞器
進(jìn)入elasticsearch容器,執(zhí)行中文分詞器相關(guān)安裝命令
docker exec -it elasticsearch bash
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.8.5/elasticsearch-analysis-ik-6.8.5.zip
注意:可以訪問(wèn)
https://github.com/medcl/elasticsearch-analysis-ik/releases
查找和當(dāng)前es匹配的版本
4)手動(dòng)安裝中文分詞器
如果第3步安裝超時(shí)失敗,可以嘗試進(jìn)行手動(dòng)安裝,如果第三步成功則跳過(guò)該步驟
4.1)手動(dòng)下載對(duì)應(yīng)的IK分詞器版本https://github.com/medcl/elasticsearch-analysis-ik/releases
4.2)直接將分詞器壓縮包上傳到容器的plugins路徑(上傳到宿主機(jī)的容器卷路徑即可)
4.3)解壓分詞器
unzip elasticsearch-analysis-ik-6.8.5.zip -d ./ik-analyze
5)重啟容器
docker-compose restart
6)訪問(wèn)kibana服務(wù)

7)測(cè)試IK分詞器
POST _analyze
{
"analyzer":"ik_smart",
"text":"殲10系列戰(zhàn)斗機(jī)"
}
POST _analyze
{
"analyzer":"ik_max_word",
"text":"殲10系列戰(zhàn)斗機(jī)"
}

2.3 索引庫(kù)(Index)相關(guān)操作
2.3.1 概述
ElasticSearch采用Rest風(fēng)格的API,因此其API就是一次Http請(qǐng)求
請(qǐng)求分為: PUT POST GET DELETE
GET:查詢數(shù)據(jù)
PUT:插入數(shù)據(jù)
POST:更新數(shù)據(jù),實(shí)際上很多情況下 es 不是很清晰你到底要作什么,有些時(shí)候POST也可用于新增或者查詢
DELETE: 刪除數(shù)據(jù)
2.3.2 新增索引庫(kù)語(yǔ)法
PUT /索引庫(kù)名稱
{
"settings":{
"number_of_shards": 3, #分片的數(shù)量
"number_of_replicas": 2 #副本的數(shù)量
}
}
settings:表示索引庫(kù)的設(shè)置 number_of_shards:表示分片的數(shù)量 number_of_replicas:副本數(shù)量
2.3.3 查詢索引信息
GET /索引庫(kù)名稱
2.3.4 判斷索引庫(kù)是否存在
HEAD /索引庫(kù)名稱
2.3.5 刪除索引庫(kù)
DELETE /索引庫(kù)名稱
注意
1)索引庫(kù) 類似于 MySQL中數(shù)據(jù)庫(kù)的概念
2)如果創(chuàng)建索引不指定settings,默認(rèn)會(huì)有5個(gè)分片,1個(gè)副本
2.4 映射類型(type)相關(guān)操作
2.4.1 新增映射類型語(yǔ)法
PUT /索引庫(kù)名/_mapping/類型名稱
{
"properties": {
"字段名1": {
"type": "類型",
"index": true,
"store": true,
"analyzer": "分詞器"
},
"字段名2": {
"type": "類型",
"index": true,
"store": true,
"analyzer": "分詞器"
}
}
}
type:類型,可以是text、long、date、integer、object、keyword(表示關(guān)鍵字,不能被分詞)
index:是否參與索引,默認(rèn)為true
store:是否參與存儲(chǔ),默認(rèn)為false
analyzer:分詞器,可選 “ik_max_word”或者“ik_smart”,表示使用ik分詞器
2.4.2 查看映射類型信息
GET /索引庫(kù)名稱/_mapping/類型名稱
2.4.3 字段屬性詳解
type
String類型,又分兩種: text:可分詞,不可參與聚合 keyword:不可分詞,數(shù)據(jù)會(huì)作為完整字段進(jìn)行匹配,可以參與聚合
Numerical:數(shù)值類型,分兩類 基本數(shù)據(jù)類型:long、interger、short、byte、double、flfloat、half_flfloat 浮點(diǎn)數(shù)的高精度類型:scaled_float,需要指定一個(gè)精度因子,比如10或100,elasticsearch會(huì)把真實(shí)值乘以這個(gè)因子后存儲(chǔ),取出時(shí)再還原
Date:日期類型 elasticsearch可以對(duì)日期格式化為字符串存儲(chǔ),但是建議我們存儲(chǔ)為毫秒值,存儲(chǔ)為long,節(jié)省空間
boolean: 設(shè)置字段類型為boolean后,可以填入的值為:true、false、"true"、"false"
binary: binary類型接受base64編碼的字符串
geo_point: 地理點(diǎn)類型用于存儲(chǔ)地理位置的經(jīng)緯度對(duì)
更多類型參考:https://www.elastic.co/guide/en/elasticsearch/reference/6.5/mapping-types.html
index
index影響字段的索引情況。
true:字段會(huì)被索引,則可以用來(lái)進(jìn)行搜索。默認(rèn)值就是true
false:字段不會(huì)被索引,不能用來(lái)搜索
index的默認(rèn)值就是true,也就是說(shuō)你不進(jìn)行任何配置,所有字段都會(huì)被索引。但是有些字段是我們不希望被索引的,比如商品的圖片信息,就需要手動(dòng)設(shè)置index為false。
store
是否將數(shù)據(jù)進(jìn)行額外存儲(chǔ)。在學(xué)習(xí)lucene和solr時(shí),我們知道如果一個(gè)字段的store設(shè)置為false,那么在文檔列表中就不會(huì)有這個(gè)字段的值,用戶的搜索結(jié)果中不會(huì)顯示出來(lái)。
但是在Elasticsearch中,即便store設(shè)置為false,也可以搜索到結(jié)果。
原因是Elasticsearch在創(chuàng)建文檔索引時(shí),會(huì)將文檔中的原始數(shù)據(jù)備份,保存到一個(gè)叫做 _source 的屬性中。而且我們可以通過(guò)過(guò)濾 _source 來(lái)選擇哪些要顯示,哪些不顯示。而如果設(shè)置store為true,就會(huì)在 _source 以外額外存儲(chǔ)一份數(shù)據(jù),多余,因此一般我們都會(huì)將store設(shè)置為false,事實(shí)上,store的默認(rèn)值就是false。
analyzer
定義的是該字段的分析器,默認(rèn)的分析器是 standard 標(biāo)準(zhǔn)分析器,這個(gè)地方可定義為自定義的分析器。
比如IK分詞器為: ik_max_word 或者 ik_smart
boost
激勵(lì)因子。這個(gè)與lucene中一樣,我們可以通過(guò)指定一個(gè)boost值來(lái)控制每個(gè)查詢子句的相對(duì)權(quán)重。
該值默認(rèn)為1。一個(gè)大于1的boost會(huì)增加該查詢子句的相對(duì)權(quán)重
比如:
GET /_search {
"query": {
"bool": {
"must": {
"match": {
"content": {
"query": "full text search",
"operator": "and"
}
}
},
"should": [{
"match": {
"content": {
"query": "Elasticsearch",
"boost": 3
}
}
},
{
"match": {
"content": {
"query": "Lucene",
"boost": 2
}
}
}
]
}
}
}
注意
1)映射類型(type) 類似于 MySQL數(shù)據(jù)庫(kù)中表的概念
2)從ElasticSearch 6.x之后,一個(gè)Index下只能有一個(gè)type
2.5 文檔相關(guān)(document)操作
2.5.1 添加文檔
#指定id的添加方式
PUT /索引庫(kù)名/類型名稱/id #id需要自己指定
{
"field1":"value1",
"field2":"value2",
...
}
#自動(dòng)生成id的添加方式
POST /索引庫(kù)名/類型名稱 #使用POST無(wú)需指定id
{
"field1":"value1",
"field2":"value2",
...
}
#批量添加文檔
PUT /索引庫(kù)名稱/類型名稱/_bulk
{"index":{"_id":id值1}}
{"field1":"value1", "field2":"value2"...}
{"index":{"_id":id值2}}
{"field1":"value1", "field2":"value2"...}
....
2.5.2 更新文檔
#全局更新,會(huì)將所有字段更新,沒(méi)有指定的字段會(huì)自動(dòng)刪除
PUT /索引庫(kù)名/類型名稱/id #需要更新的id,id必須存在,如果不存在就變成了添加
{
"field1":"value1",
"field2":"value2",
...
}
#局部更新,只更新需要更新的字段
POST /索引庫(kù)名/類型名稱/id/_update
{
"doc":{
"field1": "新的value"
}
}
2.5.3 刪除文檔
DELETE /索引庫(kù)名/類型名稱/id
2.5.4 查詢文檔
#查詢索引庫(kù)全部數(shù)據(jù)
GET /索引庫(kù)名稱/_search
#根據(jù)id查詢
GET /索引庫(kù)名稱/類型名稱/id
#批量查詢
GET /_mget
{
"docs": [
{
"_index": "索引庫(kù)名稱1",
"_type": "映射類型1",
"_id":"查詢文檔id1"
},
{
"_index": "索引庫(kù)名稱2",
"_type": "映射類型2",
"_id":"查詢文檔id2"
}
]
}
注意
1)文檔(document)類似于 數(shù)據(jù)庫(kù)中表的一條記錄
2)當(dāng)添加的文檔中,設(shè)置的field,而type中沒(méi)有時(shí),type會(huì)自動(dòng)的添加該field的映射記錄,
這是elasticsearch的自動(dòng)映射功能
三、SpringBoot操作ElasticSearch(elasticsearch-rest-high-level-client)
3.1 配置ElasticSearch
1)添加依賴
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>6.8.5</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>6.8.5</version>
</dependency>
2)配置application.yml
spring:
elasticsearch:
rest:
uris: http://192.168.195.135:9200
3)在需要的地方注入RestHighLevelClient對(duì)象
@Autowired
private RestHighLevelClient restHighLevelClient;
3.2 使用SpringBoot操作索引庫(kù)(Index)
@Autowired
private RestHighLevelClient client;
/**
* 創(chuàng)建索引
* @param indexName
* @return
*/
@Override
public boolean createIndex(String indexName) {
CreateIndexRequest indexRequest = new CreateIndexRequest(indexName);
//設(shè)置索引庫(kù)的相關(guān)屬性
Settings settings = Settings.builder()
.put("number_of_shards", 1)//設(shè)置分片數(shù)量
.put("number_of_replicas", 0)//設(shè)置副本數(shù)量
.build();
indexRequest.settings(settings);
try {
CreateIndexResponse response = client.indices().create(indexRequest, RequestOptions.DEFAULT);
//返回結(jié)果
return response.isAcknowledged();
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* 判斷索引是否存在
* @param indexName
* @return
*/
@Override
public boolean isExistsIndex(String indexName) {
GetIndexRequest getIndexRequest = new GetIndexRequest(indexName);
try {
boolean exists = client.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
return exists;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* 刪除索引
* @param indexName
* @return
*/
@Override
public boolean deleteIndex(String indexName) {
DeleteIndexRequest request = new DeleteIndexRequest(indexName);
try {
AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
return response.isAcknowledged();
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
3.3 使用SpringBoot操作映射類型(type)
/**
* 添加映射
* PUT /partform_hotal/_mapping/hotal
* {
* "properties": {
* "hotalName":{
* "type": "text",
* "analyzer": "ik_max_word"
* },
* "hotalImage":{
* "type": "keyword",
* "index": false
* },
* "type":{
* "type": "integer"
* },
* "hotalInfo":{
* "type":"text",
* "analyzer": "ik_max_word"
* },
* "keyword":{
* "type":"text",
* "analyzer": "ik_max_word"
* },
* "location":{
* "type": "geo_point"
* },
* "star":{
* "type": "integer"
* },
* "brand":{
* "type": "text",
* "analyzer": "ik_max_word"
* },
* "address":{
* "type": "keyword"
* },
* "openTime":{
* "type": "date",
* "format": "yyyy-MM-dd"
* },
* "cityname":{
* "type": "keyword"
* },
* "regid":{
* "type": "text",
* "analyzer": "ik_max_word"
* }
* }
* }
*
*
* @return
*/
@Override
public boolean createMapping(String index) {
PutMappingRequest putMappingRequest = new PutMappingRequest(index);
try {
XContentBuilder builder = JsonXContent.contentBuilder();
builder
.startObject()
.startObject("properties")
.startObject("hotalName")
.field("type", "text")
.field("analyzer", "ik_max_word")
.endObject()
.startObject("hotalImage")
.field("type", "keyword")
.field("index", "false")
.endObject()
.startObject("type")
.field("type", "integer")
.endObject()
.startObject("hotalInfo")
.field("type", "text")
.field("analyzer", "ik_max_word")
.endObject()
.startObject("keyword")
.field("type", "text")
.field("analyzer", "ik_max_word")
.endObject()
.startObject("location")
.field("type", "geo_point")
.endObject()
.startObject("star")
.field("type", "integer")
.endObject()
.startObject("brand")
.field("type", "text")
.field("analyzer", "ik_max_word")
.endObject()
.startObject("address")
.field("type", "text")
.field("analyzer", "ik_max_word")
.endObject()
.startObject("openTime")
.field("type", "date")
.field("format", "yyyy-MM-dd")
.endObject()
.startObject("cityname")
.field("type", "keyword")
.endObject()
.startObject("regid")
.field("type", "text")
.field("analyzer", "ik_max_word")
.endObject()
.endObject().endObject();
//設(shè)置到Request對(duì)象中
putMappingRequest.source(builder);
client.indices().putMapping(putMappingRequest, RequestOptions.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
3.4 使用SpringBoot操作文檔(document)
新增文檔
/**
* 給索引庫(kù)添加文檔
* @param indexName
* @param hotal
* @return
*/
@Override
public boolean insertDco(String indexName, Hotal hotal) {
String json = JSON.toJSONString(hotal);
System.out.println(json);
IndexRequest indexRequest = new IndexRequest(indexName, "_doc")
.id(hotal.getId() + "")
.source(json, XContentType.JSON);
try {
IndexResponse index = client.index(indexRequest, RequestOptions.DEFAULT);
long seqNo = index.getSeqNo();
String lowercase = index.getResult().getLowercase();
int status = index.status().getStatus();
System.out.println("狀態(tài):" + status);
System.out.println("返回:" + lowercase);
System.out.println("序號(hào):" + seqNo);
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
刪除文檔
/**
* 根據(jù)ID刪除
* @param indexName
* @param id
* @return
*/
@Override
public boolean deleteDco(String indexName, Integer id) {
DeleteRequest deleteRequest = new DeleteRequest(indexName, "_doc", id + "");
try {
DeleteResponse resp = client.delete(deleteRequest, RequestOptions.DEFAULT);
int status = resp.status().getStatus();
System.out.println("結(jié)果:" + status);
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
更新文檔
/**
* 根據(jù)id修改信息
* @param indexName
* @param hotal
* @return
*/
@Override
public boolean updateDco(String indexName, Hotal hotal) {
String json = JSON.toJSONString(hotal);
System.out.println(json);
// Map map = new HashMap();
// map.put("hotalInfo", "xxxx");
UpdateRequest updateRequest = new UpdateRequest(indexName, "_doc", hotal.getId() + "");
updateRequest.doc(json, XContentType.JSON);
try {
UpdateResponse response = client.update(updateRequest, RequestOptions.DEFAULT);
int status = response.status().getStatus();
System.out.println("狀態(tài):" + status);
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
四、基本查詢
4.1 term、terms查詢
什么是term查詢?
term是代表完全匹配,也就是精確查詢,搜索前不會(huì)再對(duì)搜索詞進(jìn)行分詞,所以我們的搜索詞必須是文檔分詞集合中的一個(gè)。 比如文檔內(nèi)容為:"美的微波爐",被分詞為"美的"和"微波爐",term搜索的關(guān)鍵字必須為"美的"或者"微波爐"才能搜索出這個(gè)文檔,搜索"美的微波爐"搜索不出來(lái)
語(yǔ)法
#term查詢
GET /索引庫(kù)/映射類型/_search
{
"query": {
"term": {
"字段名稱": {
"value": "搜索關(guān)鍵字"
}
}
}
}
#terms查詢 - 可以同時(shí)查詢多個(gè)關(guān)鍵詞
GET /索引庫(kù)/映射類型/_search
{
"query":{
"terms": {
"字段名稱": [
"關(guān)鍵字1","關(guān)鍵字2"...
]
}
}
}
注意:terms查詢 多個(gè)關(guān)鍵字之間是或者的關(guān)系,也就是說(shuō)只要符合一個(gè)關(guān)鍵字的文檔就會(huì)被查詢出來(lái)
4.2 match查詢
什么是match查詢?
match 查詢是高層查詢,它們了解字段映射的信息:
1.如果查詢 日期(date) 或 整數(shù)(integer) 字段,它們會(huì)將查詢字符串分別作為日期或整數(shù)對(duì)待。
2.如果查詢一個(gè)( not_analyzed )未分詞的精確值字符串字段, 它們會(huì)將整個(gè)查詢字符串作為單個(gè)詞項(xiàng)對(duì)待。
3.但如果要查詢一個(gè)( analyzed )已分析的全文字段, 它們會(huì)先將查詢字符串傳遞到一個(gè)合適的分析器,然后生成一個(gè)供查詢的詞項(xiàng)列表。 一旦組成了詞項(xiàng)列表,這個(gè)查詢會(huì)對(duì)每個(gè)詞項(xiàng)逐一執(zhí)行底層的查詢,再將結(jié)果合并,然后為每個(gè)文檔生成一個(gè)最終的相關(guān)度評(píng)分。 match查詢其實(shí)底層是多個(gè)term查詢,最后將term的結(jié)果合并。
語(yǔ)法
#match_all查詢 - 查詢指定庫(kù)的指定類型的所有文檔
GET /索引庫(kù)/映射類型/_search
{
"query": {
"match_all": {}
}
}
#match查詢 - 根據(jù)關(guān)鍵字查詢
GET /索引庫(kù)/映射類型/_search
{
"query": {
"match": {
"字段名稱": "搜索關(guān)鍵字"
}
}
}
#布爾match查詢
GET /索引庫(kù)/映射類型/_search
{
"query": {
"match": {
"字段名稱": {
"query": "搜索關(guān)鍵字",
"operator": "OR或者AND"
}
}
}
}
注意:operator值為
and表示關(guān)鍵詞分詞后的結(jié)果,必須全部匹配上
or表示需要一個(gè)分詞匹配上即可,默認(rèn)為or
#mulit_match查詢 - 可以查詢多個(gè)字段
GET /索引庫(kù)/映射類型/_search
{
"query": {
"multi_match": {
"query": "搜索關(guān)鍵字",
"fields": ["字段名稱1^2.0", "字段名稱2^0.5"],
"operator": "or"
}
}
}
注意:
^2.0表示這個(gè)字段在搜索中的權(quán)重,值越高權(quán)重越大,可以不設(shè)置。

#match_phrase查詢 - 短語(yǔ)查詢
GET /索引庫(kù)/映射類型/_search
{
"query": {
"match_phrase": {
"字段名稱": "關(guān)鍵詞1 關(guān)鍵詞2"
}
}
}
注意:
match_phrase查詢,只會(huì)匹配關(guān)鍵詞1 和關(guān)鍵詞2 挨在一起的文檔,如果兩個(gè)關(guān)鍵詞分開(kāi)太遠(yuǎn)的文檔是不會(huì)匹配上的
4.3 ids查詢
什么ids查詢?
ids查詢是一類簡(jiǎn)單的查詢,它過(guò)濾返回的文檔只包含其中指定標(biāo)識(shí)符的文檔,
該查詢默認(rèn)指定作用在“_id”上面。
語(yǔ)法
GET /索引庫(kù)/映射類型/_search
{
"query": {
"ids": {
"values": ["1","3","6"...]
}
}
}

4.4 prefix前綴查詢
什么是prefix查詢?
前綴查詢,可以使我們找到某個(gè)字段以給定前綴開(kāi)頭的文檔。最典型的使用場(chǎng)景,一般是在文本框錄入的時(shí)候的聯(lián)想功能
語(yǔ)法
GET /索引庫(kù)/映射類型/_search
{
"query": {
"prefix": {
"字段名稱": {
"value": "前綴"
}
}
}
}
注意:前綴查詢并不是和搜索字段的內(nèi)容前綴匹配,而是和搜索字段的所有分詞的前綴匹配,匹配上一個(gè)分詞后,就會(huì)查詢出該文檔,建議和keyword類型的字段結(jié)合使用

4.5 fuzzy查詢
什么是fuzzy查詢?
fuzzy(模糊)查詢是一種模糊查詢,term 查詢的模糊等價(jià)。
語(yǔ)法
GET /索引庫(kù)/映射類型/_search
{
"query": {
"fuzzy": {
"字段名稱": {
"value": "關(guān)鍵詞",
"fuzziness": "2"
}
}
}
}
注意:
1、fuzzy搜索以后,會(huì)自動(dòng)嘗試將你的搜索文本進(jìn)行糾錯(cuò),然后去跟文本進(jìn)行匹配
2、fuzziness屬性表示關(guān)鍵詞最多糾正的次數(shù), 比如空條 -> 空調(diào),需要糾正一次,fuzziness需要設(shè)置為1
3、prefix_length屬性表示不能被 “模糊化” 的初始字符數(shù)。 大部分的拼寫(xiě)錯(cuò)誤發(fā)生在詞的結(jié)尾,而不是詞的開(kāi)始。 例如通過(guò)將prefix_length 設(shè)置為 3 ,你可能夠顯著降低匹配的詞項(xiàng)數(shù)量。(前面3個(gè)字不能出錯(cuò),否則查不到)

4.6 wildcard查詢
什么是wildcard查詢?
wildcard(通配符)查詢意為通配符查詢
GET /索引庫(kù)/映射類型/_search
{
"query": {
"wildcard": {
"字段名稱": {
"value": "關(guān)鍵詞? *"
}
}
}
}
注意:
*表示匹配0或者多個(gè)字符
?表示匹配一個(gè)字符
wildcard查詢不注意查詢性能,應(yīng)盡可能避免使用。
4.7 range查詢
什么range查詢?
range查詢既范圍查詢,可以對(duì)某個(gè)字段進(jìn)行范圍匹配
GET /索引庫(kù)/映射類型/_search
{
"query": {
"range": {
"字段名稱": {
"gte": 0,
"lte": 2000,
}
}
}
}

4.8 regexp查詢
什么是regexp查詢?
正則表達(dá)式查詢,wildcard和regexp查詢的工作方式和prefix查詢完全一樣。它們也需要遍歷倒排索引中的詞條列表來(lái)找到所有的匹配詞條,然后逐個(gè)詞條地收集對(duì)應(yīng)的文檔ID。它們和prefix查詢的唯一區(qū)別在于它們能夠支持更加復(fù)雜的模式。
語(yǔ)法
GET /索引庫(kù)/映射類型/_search
{
"query": {
"regexp": {
"字段名稱": "正則表達(dá)式"
}
}
}
注意:
1、prefix(前綴),wildcard(通配符)以及regexp(正則)查詢基于詞條進(jìn)行操作。如果你在一個(gè)analyzed字段上使用了它們,它們會(huì)檢查字段中的每個(gè)詞條,而不是整個(gè)字段。
2、對(duì)一個(gè)含有很多不同詞條的字段運(yùn)行這類查詢是非常消耗資源的。應(yīng)該避免使用一個(gè)以通配符開(kāi)頭的模式(比如,*foo)

4.9 使用JavaAPI實(shí)現(xiàn)以上查詢
查詢的基礎(chǔ)結(jié)構(gòu),通過(guò)不同的QueryBuilder對(duì)象,可以實(shí)現(xiàn)不同的查詢
@Override
public List<Hotal> queryHotals(String indexName, QueryBuilder queryBuilder) {
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(queryBuilder);
SearchRequest searchRequest = new SearchRequest(indexName);
searchRequest.source(searchSourceBuilder);
try {
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = search.getHits();
//循環(huán)結(jié)果
for (SearchHit hit : hits) {
System.out.println("------------------------------------------");
Map<String, DocumentField> fields = hit.getFields();
for (Map.Entry<String, DocumentField> entry : fields.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue().getValue());
}
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
//term
TermQueryBuilder termQueryBuilder =
QueryBuilders.termQuery("hotalName", "連鎖");
//terms
TermsQueryBuilder termsQueryBuilder =
QueryBuilders.termsQuery("hotalName", "7天", "連鎖");
//match
MatchQueryBuilder matchQueryBuilder =
QueryBuilders.matchQuery("hotalName", "愛(ài)麗絲");
//match查詢
MultiMatchQueryBuilder matchBuilder = QueryBuilders.multiMatchQuery("大中華")
.field("hotalName", 1.0f)
.field("hotalInfo", 2.0f);
//matchall
MatchAllQueryBuilder matchAllQueryBuilder =
QueryBuilders.matchAllQuery();
//Ids
IdsQueryBuilder idsQueryBuilder =
QueryBuilders.idsQuery().addIds("2","3");
//prefix
PrefixQueryBuilder prefixQueryBuilder =
QueryBuilders.prefixQuery("hotalName", "連");
//fuzzy
FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("regid", "平三區(qū)")
.fuzziness(Fuzziness.TWO)
.prefixLength(0);
//wildcard
WildcardQueryBuilder wildcardQueryBuilder =
QueryBuilders.wildcardQuery("hotalName", "愛(ài)麗*");
//range
RangeQueryBuilder rangeQuery =
QueryBuilders.rangeQuery("star").gte(0).lt(3);
//regexp
RegexpQueryBuilder regexQuery =
QueryBuilders.regexpQuery("hotalName", "\\S{0,}[0-9]{1}.*");
五、復(fù)合查詢
5.1 bool查詢
bool 過(guò)濾器。 這是個(gè) 復(fù)合過(guò)濾器(compound fifilter) ,它可以接受多個(gè)其他過(guò)濾器作為參數(shù),并將這些過(guò)濾器結(jié)合成各式各樣的布爾(邏輯)組合。
語(yǔ)法
GET /索引庫(kù)/映射類型/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"content": {
"value": "性價(jià)比"
}
}
},{
"match": {
"title": "微波爐"
}
}
],
"must": [
{
"match": {
"content": "格力造"
}
}
],
"must_not": [
{
"range": {
"price": {
"gte": 300,
"lte": 3000
}
}
}
],
"filter": {
"match": {
"title": "美的"
}
}
}
}
}
例子
#bool查詢 - 將多個(gè)基本查詢組合在一起
GET /hotal_index/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"brand": {
"value": "7天"
}
}
}
],
"must": [
{
"match": {
"hotalName": "連鎖"
}
},{
"range": {
"price": {
"gte": 500,
"lte": 3000
}
}
}
],
##minimum_should_match:1
"filter": {
"multi_match": {
"query": "大中華",
"fields": ["hotalName^2.0", "hotalInfo"]
}
}
}
}
}
屬性含義
must: 返回的文檔必須滿足must子句的條件,并且參與計(jì)算分值,與 AND 等價(jià)
must_not:所有的語(yǔ)句都 不能(must not) 匹配,與 NOT 等價(jià)
should: 返回的文檔可能滿足should子句的條件。在一個(gè)Bool查詢中,如果沒(méi)有must或者filter,有一個(gè)或 者多個(gè)should子句,那么只要滿足一個(gè)就可以返回,與 OR 等價(jià)
minimum_should_match:用來(lái)指定should至少需要匹配幾個(gè)語(yǔ)句
filter:返回的文檔必須滿足filter子句的條件。但是不會(huì)像Must一樣,參與計(jì)算分值
注意
如果查詢中沒(méi)有must語(yǔ)句,那么至少要匹配一個(gè)should語(yǔ)句
5.1.1 什么是filter?
filter vs query
filter ,僅僅只是按照搜索條件過(guò)濾出需要的數(shù)據(jù)而已,不計(jì)算任何相關(guān)度分?jǐn)?shù),對(duì)相關(guān)度沒(méi)有任何影響;
query ,會(huì)去計(jì)算每個(gè)document相對(duì)于搜索條件的相關(guān)度,并按照相關(guān)度進(jìn)行排序;
一般來(lái)說(shuō),如果你是在進(jìn)行搜索,需要將最匹配搜索條件的數(shù)據(jù)先返回,那么用query;如果你只是要根據(jù)一些條件篩選出一部分?jǐn)?shù)據(jù),不關(guān)注其排序,那么用filter; 除非是你的這些搜索條件,你希望越符合這些搜索條件的document越排在前面返回,那么這些搜索條件要放在query中;如果你不希望一些搜索條件來(lái)影響你的document排序,那么就放在filter中即可;
filter和query的性能對(duì)比
filter ,不需要計(jì)算相關(guān)度分?jǐn)?shù),不需要按照相關(guān)度分?jǐn)?shù)進(jìn)行排序,同時(shí)還有內(nèi)置的自動(dòng)cache最常使用filter的數(shù)據(jù)
query ,相反,要計(jì)算相關(guān)度分?jǐn)?shù),按照分?jǐn)?shù)進(jìn)行排序,而且無(wú)法cache結(jié)果
所以filter查詢性能會(huì)高于query
5.3 boosting查詢
什么是boosting查詢?
該查詢用于將兩個(gè)查詢封裝在一起,并降低其中一個(gè)查詢所返回文檔的分值。它接受一個(gè)positive查詢和一個(gè)negative查詢。只有匹配了positive查詢的文檔才會(huì)被包含到結(jié)果集中,但是同時(shí)匹配了negative查詢的文檔會(huì)被降低其相關(guān)度,通過(guò)將文檔原本的score和negative_boost參數(shù)進(jìn)行相乘來(lái)得到新的score。因此,negative_boost參數(shù)必須小于1.0
(positive作為查詢條件,如果查詢條件同時(shí)符合negative條件,則會(huì)降低分值)
"negative_boost": 會(huì)把原來(lái)的分值乘以negative_boost的值作為最后的分值
如果negative_boost>1:提高分值
如果negative_boost<1:降低分值
運(yùn)用場(chǎng)景
例如,在互聯(lián)網(wǎng)上搜索"蘋(píng)果"也許會(huì)返回,水果或者各種食譜的結(jié)果。但是用戶可能只想搜索到蘋(píng)果手機(jī)等電子產(chǎn)品,當(dāng)然我們可以通過(guò)排除“水果 喬木 維生素”和這類單詞,結(jié)合bool查詢中的must_not子句,將結(jié)果范圍縮小到只剩蘋(píng)果手機(jī),但是這種做法難免會(huì)排除掉那些真的想搜索水果的用戶,這時(shí)可以通過(guò)boosting查詢,通過(guò)降低“水果 喬木 維生素”等關(guān)鍵詞的評(píng)分,讓蘋(píng)果等電子產(chǎn)品的排名靠前
語(yǔ)法
GET /索引庫(kù)/映射類型/_search
{
"query": {
"boosting": {
"positive": {
"match": {
"title": "性價(jià)比"
}
},
"negative": {
"match": {
"content": "性價(jià)比"
}
},
"negative_boost": 0.1
}
}
}
5.4 使用JavaAPI實(shí)現(xiàn)以上查詢
//bool
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
.must(....)
.mustNot(...)
.should(...)
.filter(...)
.minimumShouldMatch(1);
//boosting
BoostingQueryBuilder boostingQueryBuilder = QueryBuilders
.boostingQuery(..., ...)
.negativeBoost(0.2f);
例子:

六、排序
ElasticSearch默認(rèn)會(huì)有一套相關(guān)性分?jǐn)?shù)計(jì)算,分?jǐn)?shù)越高,說(shuō)明文檔相關(guān)性越大,也就越會(huì)排在前面。除了相關(guān)性排序之外,開(kāi)發(fā)者也可以通過(guò)自己的需要,通過(guò)某些規(guī)則設(shè)置查詢文檔的排序
如果進(jìn)行手動(dòng)排序,則評(píng)分為null
語(yǔ)法
GET /索引庫(kù)/映射類型/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"排序字段1": {
"order": "asc"
}
},
{
"排序字段2":{
"order": "desc"
}
}
]
}
例子:
#手動(dòng)排序
GET /hotal_index/_search
{
"query": {
"match": {
"hotalName": "品牌連鎖酒店"
}
},
"sort": [
{
"price": {
"order": "asc"
}
}
]
}
//創(chuàng)建查詢構(gòu)建器
QueryBuilder queryBuilder = .........
..................
//執(zhí)行查詢
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder
.query(queryBuilder)
//設(shè)置排序
.sort("star", SortOrder.DESC)
.sort("type", SortOrder.ASC);
七、高亮
什么是高亮?
許多應(yīng)用都傾向于在每個(gè)搜索結(jié)果中 高亮 顯示搜索的關(guān)鍵詞,比如字體的加粗,改變字體的顏色等.以便讓用戶知道為何該文檔符合查詢條件。在 Elasticsearch 中檢索出高亮片段也很容易。高亮顯示需要一個(gè)字段的實(shí)際內(nèi)容。 如果該字段沒(méi)有被存儲(chǔ)(映射mapping沒(méi)有將存儲(chǔ)設(shè)置為 true),則加載實(shí)際的source,并從source中提取相關(guān)的字段。
語(yǔ)法
GET /索引庫(kù)/映射類型/_search
{
"query": {
....
},
"highlight": {
"fields": {
"待高亮字段1": {},
"待高亮字段2": {}
},
"post_tags": ["</font>"],
"pre_tags": ["<font color='red'>"],
//功能:搜索的摘要顯示
"number_of_fragments": 5,
"fragment_size": 3
}
}
例子:

摘要顯示

參數(shù)含義
number_of_fragments: fragment 是指一段連續(xù)的文字。返回結(jié)果最多可以包含幾段不連續(xù)的文字。
默認(rèn)是5。
fragment_size: 某字段的值,長(zhǎng)度是1萬(wàn),但是我們一般不會(huì)在頁(yè)面展示這么長(zhǎng),可能只是展示一部分。設(shè)置要顯示出來(lái)的fragment文本判斷的長(zhǎng)度,默認(rèn)是100
noMatchSize: 搜索出來(lái)的這個(gè)文檔這個(gè)字段已經(jīng)顯示出高亮的情況,可是其它字段并沒(méi)有任何顯示,設(shè)置這個(gè)屬性可以顯示出來(lái)。
pre_tags: 標(biāo)記 highlight 的開(kāi)始標(biāo)簽。
post_tags: 標(biāo)記 highlight 的結(jié)束標(biāo)簽。
JavaAPI
//設(shè)置高亮信息
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder
.field("title", 100)
.field("content", 100)
.preTags("<font color='red'>")
.postTags("</font>");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder
.query(queryBuilder)
.highlighter(highlightBuilder)
.sort("star", SortOrder.DESC)
.sort("type", SortOrder.ASC);
........
//獲得高亮結(jié)果
Map<String, HighlightField> highlightFields
= hit.getHighlightFields();
for (Map.Entry<String, HighlightField> entry : highlightFields.entrySet()) {
System.out.println(entry.getKey() + "--"
+ entry.getValue().getFragments()[0].string());
}
八、地理位置搜索
地理位置在ElasticSearch中的字段類型geo-point
8.1 地理位置類型的相關(guān)操作
#創(chuàng)建映射類型:
PUT /soufang/_mapping/house
{
"properties": {
"name": {
"type": "text"
},
"location": {
"type": "geo_point"
}
}
}
#添加坐標(biāo)點(diǎn)數(shù)據(jù):
PUT /soufang/house/1
{
"name": "市民中心",
"location": {
"lat": 22.54737, #lat代表緯度
"lon": 114.067531 #lon代表經(jīng)度
}
}
8.2 通過(guò)geo_distance過(guò)濾器搜索坐標(biāo)
geo_distance:地理距離過(guò)濾器( geo_distance )以給定位置為圓心畫(huà)一個(gè)圓,來(lái)找出那些地理坐標(biāo)落在其中的文檔
GET /soufang/house/_search
{
"query": {
"geo_distance":{
"location": {
"lat": 22.551013,
"lon": 114.065432
},
"distance": "1km",
"distance_type": "arc"
}
}
}
distance:中心點(diǎn)的半徑距離
distance_type:兩點(diǎn)間的距離計(jì)算的精度算法
arc - 最慢但是最精確是弧形(arc)計(jì)算方式,這種方式把世界當(dāng)作是球體來(lái)處理
plane - 平面(plane)計(jì)算方式,把地球當(dāng)成是平坦的。 這種方式快一些但是精度略遜
8.3 通過(guò)geo_bounding_box過(guò)濾器搜索坐標(biāo)
geo_bounding_box: 查找某個(gè)長(zhǎng)方形區(qū)域內(nèi)的位置
GET /soufang/house/_search
{
"query": {
"geo_bounding_box":{
"location":{
"top_left": {
"lat": 22.628427,
"lon": 114.009234
},
"bottom_right": {
"lat": 22.521103,
"lon": 114.148939
}
}
}
}
}
top_left:代表矩形左上角
bottom_right:代表矩形右下角
8.4 通過(guò)geo_polygon過(guò)濾器搜索坐標(biāo)
geo_polygon:查找位于多邊形內(nèi)的地點(diǎn)
GET /soufang/house/_search
{
"query": {
"geo_polygon": {
"location":{
"points": [
[113.908911, 22.613748],
[114.056952,22.634298],
[114.031368,22.575843],
[114.097196,22.500803],
[113.9,22.493591]
]
}
}
}
}
8.5 過(guò)濾結(jié)果通過(guò)距離排序
query:是查詢這個(gè)區(qū)域的house
sort:這個(gè)區(qū)域的house為location里的坐標(biāo)為零點(diǎn),按距離排序返回?cái)?shù)據(jù)
GET /soufang/house/_search
{
"query": {
"geo_distance":{
"location": {
"lat": 22.551013,
"lon": 114.065432
},
"distance": "1km",
"distance_type": "arc"
}
},
"sort": [
{
"_geo_distance": {
"order": "asc",
"location": {
"lat": 22.551013,
"lon": 114.065432
},
"unit": "km",
"distance_type": "arc"
}
}
]
}
unit:以 公里(km)為單位,將距離設(shè)置到每個(gè)返回結(jié)果的 sort 鍵中
8.6 JavaApi的執(zhí)行方式
//geo_distance查找方式
QueryBuilders.geoDistanceQuery("location")
.point(22.55243, 114.044335)
.distance(2.8, DistanceUnit.KILOMETERS)
//geo_bounding_box查找方式
QueryBuilders.geoBoundingBoxQuery("location")
.setCorners(
new GeoPoint(22.628427, 114.009234),
new GeoPoint(22.521103, 114.148939))
//geo_polygon查找方式
List<GeoPoint> points = new ArrayList<>();
points.add(new GeoPoint(22.613748, 113.908911));
points.add(new GeoPoint(22.634298, 114.056952));
points.add(new GeoPoint(22.575843,114.031368));
points.add(new GeoPoint(22.500803,114.097196));
points.add(new GeoPoint(22.493591,113.9));
QueryBuilders.geoPolygonQuery("location", points)
//根據(jù)距離排序
SortBuilders
.geoDistanceSort("location", 22.586737, 113.960829)
.order(SortOrder.DESC)
.unit(DistanceUnit.KILOMETERS)
九、function_score自定義文檔相關(guān)性
9.1 什么是function_score?
在使用ES進(jìn)行全文搜索時(shí),搜索結(jié)果默認(rèn)會(huì)以文檔的相關(guān)度進(jìn)行排序,而這個(gè) "文檔的相關(guān)度",是可以通過(guò) function_score 自己定義的,也就是說(shuō)我們可以透過(guò)使用function_score,來(lái)控制 "怎么樣的文檔相關(guān)度更高" 這件事
9.2 文檔相關(guān)度評(píng)分默認(rèn)大概規(guī)則
1、關(guān)鍵詞詞頻越高,評(píng)分越高
2、關(guān)鍵詞在所有文檔中出現(xiàn)的頻率越高,評(píng)分越低
3、搜索的關(guān)鍵詞與目標(biāo)文檔中分詞匹配個(gè)數(shù)越多,評(píng)分越高
4、匹配的字段權(quán)重越高,評(píng)分越高
9.3 function_score的基本用法
9.3.1 function_score提供的加強(qiáng)_score的函數(shù)
1、weight:設(shè)置權(quán)重提升值,可以用于任何查詢
2、field_value_factor: 將某個(gè)字段的值乘上old_score
3、random_score : 為每個(gè)用戶都使用一個(gè)不同的隨機(jī)評(píng)分對(duì)結(jié)果排序,但對(duì)某一具體用戶來(lái)說(shuō),看到的順序始終是一致的
4、衰減函數(shù) (linear、exp、guass) : 以某個(gè)字段的值為基準(zhǔn),距離某個(gè)值越近得分越高
5、script_score : 當(dāng)需求超出以上范圍時(shí),可以用自定義腳本完全控制評(píng)分計(jì)算,不過(guò)因?yàn)檫€要額外維護(hù)腳本不好維護(hù),因此盡量使用ES提供的評(píng)分函數(shù),需求真的無(wú)法滿足再使用script_score

9.3.2 function_score其他輔助的參數(shù)
boost_mode
決定 old_score 和 加強(qiáng)score 如何合并
可選值:
multiply(默認(rèn)) : new_score = old_score * 加強(qiáng)score
sum : new_score = old_score + 加強(qiáng)score
min : old_score 和 加強(qiáng)score 取較小值,new_score = min(old_score, 加強(qiáng)score)
max : old_score 和 加強(qiáng)score 取較大值,new_score = max(old_score, 加強(qiáng)score)
replace : 加強(qiáng)score直接替換掉old_score,new_score = 加強(qiáng)score
score_mode
決定functions里面的加強(qiáng)score們?cè)趺春喜?,?huì)先合并加強(qiáng)score們成一個(gè)總加強(qiáng)score,再使用總加強(qiáng)score去和old_score做合并,換言之就是會(huì)先執(zhí)行score_mode,再執(zhí)行boost_mode
可選值:
multiply (默認(rèn)):將所有加強(qiáng)score相乘
sum:求和
avg:取平均值
first : 使加強(qiáng)首個(gè)函數(shù)(可以有filter,也可以沒(méi)有)的結(jié)果作為最終結(jié)果
max:取最大值
min:取最小值
max_boost
限制加強(qiáng)函數(shù)的最大效果,就是限制加強(qiáng)score最大能多少,但要注意不會(huì)限制old_score
9.3.3 function_score語(yǔ)法
單加強(qiáng)函數(shù)語(yǔ)法
GET /索引庫(kù)/映射類型/_search
{
"query": {
"function_score": {
//主查詢,查詢完后這裡自己會(huì)有一個(gè)評(píng)分,就是old_score
"query": {.....},
//在old_score的基礎(chǔ)上,給他加強(qiáng)其他字段的評(píng)分,這裡會(huì)產(chǎn)生一個(gè)加強(qiáng)score,如果只有一個(gè)加強(qiáng)function時(shí),直接將加強(qiáng)函數(shù)名寫(xiě)在query下面就可以了
"field_value_factor": {...},
//指定用哪種方式結(jié)合old_score和加強(qiáng)score成為new_score
"boost_mode": "multiply",
//限制加強(qiáng)score的最高分,但是不會(huì)限制old_score
"max_boost": 1.5
}
}
}
多加強(qiáng)函數(shù)語(yǔ)法
GET /索引庫(kù)/映射類型/_search
{
"query": {
"function_score": {
"query": {.....},
"functions": [
//可以有多個(gè)加強(qiáng)函數(shù)(或是filter+加強(qiáng)函數(shù)),每一個(gè)加強(qiáng)函數(shù)會(huì)產(chǎn)生一個(gè)加強(qiáng)score,因此functions會(huì)有多個(gè)加強(qiáng)score
{ "field_value_factor": ... },
{ "gauss": ... },
{ "filter": {...}, "weight": ... }
],
//決定加強(qiáng)score們?cè)趺春喜? "score_mode": "sum",
//決定總加強(qiáng)score怎么和old_score合并
"boost_mode": "multiply"
}
}
}
weight加強(qiáng)函數(shù)用法
GET /shop/goods/_search
{
"query": {
"function_score": {
"query": {
"match_all": {}
},
"functions": [
{"filter": {
"range": {
"price": {
"gte": 1000,
"lte": 3000
}
}
}, "weight": 3}
],
"boost_mode": "sum"
}
}
}
解析:查詢所有文檔,如果某個(gè)文檔的價(jià)格在1000~3000范圍內(nèi),文檔評(píng)分就會(huì)*3,并且new_score會(huì)和old_score相加得到最終評(píng)分

random_score加強(qiáng)函數(shù)使用案例
GET /shop/goods/_search
{
"query": {
"function_score": {
"query": {
"match_all": {}
},
"functions": [
{"random_score": {
"seed": 2
}}
]
}
}
}
解析:不同的用戶,可以設(shè)置不同的seed值(比如用戶的id號(hào)),實(shí)現(xiàn)隨機(jī)排序的效果,但是對(duì)同一個(gè)用戶排序結(jié)果又是恒定的
GET /shop/goods/_search
{
"query": {
"function_score": {
"query": {
"match_all": {}
},
"functions": [
{"field_value_factor": {
"field": "price"
}}
]
}
}
}
解析:查詢所有文檔,并且將所有文檔的old_score,乘以本身的價(jià)格,得到new_score,默認(rèn)將new_score * old_score,得到最終評(píng)分
9.3.4 衰減函數(shù)評(píng)分
什么是衰減函數(shù)?
以某一個(gè)范圍為基準(zhǔn),距離這個(gè)范圍越遠(yuǎn),評(píng)分越低。 比如以100為基準(zhǔn),那么大于100,或者小于100評(píng)分都將變得越來(lái)越低。
為什么需要衰減函數(shù)?
在一次搜索中,某個(gè)條件并不一定是線性增長(zhǎng)或者遞減來(lái)影響最終結(jié)果評(píng)分的。比如搜索商品,并不是價(jià)格越低就意味著越好,用戶就會(huì)越感興趣,往往可能維系在一個(gè)價(jià)格區(qū)間的用戶會(huì)更感興趣一些。比如原來(lái)做過(guò)一個(gè)調(diào)查,如果買(mǎi)車大概會(huì)買(mǎi)什么價(jià)位的,最后有40%的人選擇的是10~15w之間的車型。所以我們會(huì)發(fā)現(xiàn),價(jià)格這個(gè)因素,對(duì)用戶來(lái)說(shuō)并不是越高越好,同時(shí)也不意味著越低越好,而衰減函數(shù)就是為了對(duì)這一類的數(shù)據(jù)進(jìn)行評(píng)分的
衰減函數(shù)的分類
linear、exp 和 gauss,三種衰減函數(shù)的差別只在于衰減曲線的形狀,在DSL的語(yǔ)法上的用法完全一樣
linear : 線性函數(shù)是條直線,一旦直線與橫軸相交,所有其他值的評(píng)分都是0
exp : 指數(shù)函數(shù)是先劇烈衰減然后變緩
gauss(最常用) : 高斯函數(shù)則是鐘形的,他的衰減速率是先緩慢,然后變快,最后又放緩
field_value_factor加強(qiáng)函數(shù)使用案例

衰減函數(shù)的支持參數(shù)
origin : 中心點(diǎn),或是字段可能的最佳值,落在原點(diǎn)(origin)上的文檔評(píng)分_score為滿分1.0,支持?jǐn)?shù)值、時(shí)間 以及 "經(jīng)緯度地理座標(biāo)點(diǎn)"等類型字段 _
offset : 從 origin 為中心,為他設(shè)置一個(gè)偏移量offset覆蓋一個(gè)范圍,在此范圍內(nèi)所有的評(píng)分_score也都是和origin一樣滿分1.0
scale : 衰減率,即是一個(gè)文檔從origin下落時(shí),_score改變的速度
衰減函數(shù)案例
GET /mytest/doc/_search
{
"query": {
"function_score": {
"functions": [
//第一個(gè)gauss加強(qiáng)函數(shù),決定距離的衰減率
{
"gauss": {
"location": {
"origin": { //origin點(diǎn)設(shè)成酒店的經(jīng)緯度座標(biāo)
"lat": 51.5,
"lon": 0.12
},
"offset": "2km", //距離中心點(diǎn)2km以內(nèi)都是滿分1.0,2km外開(kāi)始衰減
"scale": "3km" //衰減率
}
}
},
//第二個(gè)gauss加強(qiáng)函數(shù),決定價(jià)格的衰減率,因?yàn)橛脩魧?duì)價(jià)格更敏感,所以給了這個(gè)gauss 加強(qiáng)函數(shù)2倍的權(quán)重
{
"gauss": {
"price": {
"origin": "50",
"offset": "50",
"scale": "20"
}
},
"weight": 2
}
]
}
}
}
JavaAPI設(shè)置評(píng)分
/**
* 自定義評(píng)分
*/
@Test
public void functionScore() throws IOException {
List<FunctionScoreQueryBuilder.FilterFunctionBuilder> list
= new ArrayList<>();
list.add(new FunctionScoreQueryBuilder.
FilterFunctionBuilder(ScoreFunctionBuilders.
gaussDecayFunction("location", new GeoPoint(22.586203, 114.031687), "6km", "5km")));
SearchRequest searchRequest = new SearchRequest("soufang").types("house");
searchRequest.source().query(
QueryBuilders.functionScoreQuery(QueryBuilders.matchAllQuery(), list.toArray(new FunctionScoreQueryBuilder.FilterFunctionBuilder[0]))
.boostMode(CombineFunction.REPLACE));
SearchResponse response =
restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
for (SearchHit hit : hits) {
System.out.println("查詢結(jié)果:" + hit.getSourceAsString() + " 評(píng)分:" + hit.getScore());
}
}
例子:
注意:如果price用“scaled_float”會(huì)報(bào)錯(cuò)。需要改成double

十、分頁(yè)
任何搜索都可以加兩個(gè)參數(shù)“from”/"size",相當(dāng)于數(shù)據(jù)庫(kù)的limit ?,?
總條數(shù)在結(jié)果中的“total”參數(shù)中,可以自己運(yùn)算出分頁(yè)數(shù)

十一、ElasticSearch集群搭建
1、創(chuàng)建基本目錄/usr/local/es-cluster

2、在master/conf/elasticsearch.yml添加如下內(nèi)容
bootstrap.memory_lock: false
cluster.name: "es-cluster"
node.name: es-master
node.master: true
node.data: false
network.host: 0.0.0.0
http.port: 9200
transport.tcp.port: 9300
discovery.zen.ping.unicast.hosts: ["es-master:9300"]
discovery.zen.minimum_master_nodes: 1
path.logs: /usr/share/elasticsearch/logs
http.cors.enabled: true
http.cors.allow-origin: "*"
xpack.security.audit.enabled: true
3、在node1&node2/conf/elasticsearch.yml添加如下內(nèi)容
cluster.name: "es-cluster"
node.name: node1 #這里注意替換
node.master: false
node.data: true
network.host: 0.0.0.0
http.port: 9202
transport.tcp.port: 9302
discovery.zen.ping.unicast.hosts: ["es-master:9300"]
path.logs: /usr/share/elasticsearch/logs
4、編寫(xiě)docker-compose.yml
version: '3.1'
services:
es-master:
image: elasticsearch:6.8.5
container_name: es-master
restart: always
volumes:
- ./master/data:/usr/share/elasticsearch/data:rw
- ./master/conf/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
- ./master/logs:/user/share/elasticsearch/logs:rw
ports:
- 9200:9200
- 9300:9300
networks:
- es-network
es-node1:
image: elasticsearch:6.8.5
container_name: es-node1
restart: always
networks:
- es-network
volumes:
- ./node1/data:/usr/share/elasticsearch/data:rw
- ./node1/conf/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
- ./node1/logs:/user/share/elasticsearch/logs:rw
es-node2:
image: elasticsearch:6.8.5
container_name: es-node2
restart: always
networks:
- es-network
volumes:
- ./node2/data:/usr/share/elasticsearch/data:rw
- ./node2/conf/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
- ./node2/logs:/user/share/elasticsearch/logs:rw
es-head:
image: mobz/elasticsearch-head:5
container_name: es-head
restart: always
ports:
- 9100:9100
networks:
- es-network
kibana:
image: kibana:6.8.5
restart: always
container_name: kibana
environment:
SERVER_NAME: kibana
ELASTICSEARCH_URL: http://192.168.195.135:9200
ports:
- 5601:5601
networks:
- es-network
networks:
es-network:
5、啟動(dòng)docker-compose.yml
#授權(quán)
chmod 777 -R master node1 node2
#啟動(dòng)
docker-compose up -d
安裝中遇到問(wèn)題
問(wèn)題:max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
解決:在宿主機(jī)執(zhí)行sysctl -w vm.max_map_count=262144,重啟docker容器
十二、綜合使用
12.1、案例1、


代碼實(shí)現(xiàn)
/**
* 通過(guò)查詢條件,執(zhí)行相應(yīng)的查詢
* @param searchParams
* @return
* @throws IOException
*/
@Override
public List<Hotal> query(SearchParams searchParams) throws IOException {
//構(gòu)建查詢構(gòu)建器
//城市查詢
TermQueryBuilder cityQuery = QueryBuilders.termQuery("cityname", searchParams.getCityName());
//通過(guò)關(guān)鍵詞匹配多個(gè)字段
QueryBuilder keywordQuery = null;
if (StringUtils.isNotEmpty(searchParams.getKeyword())) {
//用戶關(guān)鍵字不為空
keywordQuery = QueryBuilders
.multiMatchQuery(searchParams.getKeyword())
.field("hotalName").boost(2)
.field("brand").boost(2)
.field("regid")
.field("keyword")
.field("hotalInfo");
} else {
keywordQuery = cityQuery;
}
//通過(guò)價(jià)格限制條件查詢
RangeQueryBuilder priceQuery = QueryBuilders.rangeQuery("price")
.gte(searchParams.getMinPirce() != null ? searchParams.getMinPirce().doubleValue() : 0)
.lte(searchParams.getMaxPirce() != null ? searchParams.getMaxPirce().doubleValue() : Integer.MAX_VALUE);
//bool查詢將以上兩個(gè)查詢整合
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.must(keywordQuery)
.must(priceQuery);
//降級(jí)查詢 - 符合這個(gè)條件的文檔,評(píng)分會(huì)降低
//bool對(duì)城市查詢?nèi)》? BoolQueryBuilder boolQuery2 = QueryBuilders.boolQuery().mustNot(cityQuery);
//使用boosting查詢將以上兩個(gè)bool查詢組合起來(lái)
BoostingQueryBuilder execQuery = QueryBuilders.boostingQuery(
boolQuery,
boolQuery2
).negativeBoost(0.2f);
return execQuery;
}