[TOC]
創(chuàng)新互聯(lián)公司是一家網(wǎng)站設(shè)計(jì)公司,集創(chuàng)意、互聯(lián)網(wǎng)應(yīng)用、軟件技術(shù)為一體的創(chuàng)意網(wǎng)站建設(shè)服務(wù)商,主營(yíng)產(chǎn)品:成都響應(yīng)式網(wǎng)站建設(shè)公司、成都品牌網(wǎng)站建設(shè)、全網(wǎng)整合營(yíng)銷推廣。我們專注企業(yè)品牌在網(wǎng)站中的整體樹(shù)立,網(wǎng)絡(luò)互動(dòng)的體驗(yàn),以及在手機(jī)等移動(dòng)端的優(yōu)質(zhì)呈現(xiàn)。成都網(wǎng)站建設(shè)、成都做網(wǎng)站、移動(dòng)互聯(lián)產(chǎn)品、網(wǎng)絡(luò)運(yùn)營(yíng)、VI設(shè)計(jì)、云產(chǎn)品.運(yùn)維為核心業(yè)務(wù)。為用戶提供一站式解決方案,我們深知市場(chǎng)的競(jìng)爭(zhēng)激烈,認(rèn)真對(duì)待每位客戶,為客戶提供賞析悅目的作品,網(wǎng)站的價(jià)值服務(wù)。
? 搜索,就是在任何場(chǎng)景下,找尋想要的信息。通過(guò)關(guān)鍵字檢索出與此關(guān)鍵字有關(guān)的信息。這和查詢還不太一樣,查詢通常是在表格類型的數(shù)據(jù)中查找,字段的內(nèi)容的長(zhǎng)度往往不大。
? 傳統(tǒng)數(shù)據(jù)庫(kù)的情況下,如果要查詢某個(gè)字段是否包含某些關(guān)鍵字的話,需要使用到like關(guān)鍵字來(lái)進(jìn)行字段匹配,很大概率導(dǎo)致全表掃描,本身來(lái)說(shuō)性能就不算好。如果再加上查詢的字段非常長(zhǎng),那么使用like匹配的工作量是很大的,另外如果表的行數(shù)也很多,那么性能就更差了。
? 全文檢索是指計(jì)算機(jī)索引程序通過(guò)掃描文章中的每一個(gè)詞,對(duì)每一個(gè)詞建立一個(gè)索引,指明該詞在文章中出現(xiàn)的次數(shù)和位置,當(dāng)用戶查詢時(shí),檢索程序就根據(jù)事先建立的索引進(jìn)行查找,并將查找的結(jié)果反饋給用戶的檢索方式。這個(gè)過(guò)程類似于通過(guò)字典中的檢索字表查字的過(guò)程。全文搜索引擎數(shù)據(jù)庫(kù)中的數(shù)據(jù)。而全文檢索用到的關(guān)鍵技術(shù)就是倒排索引。什么是倒排索引?看看例子就知道了
數(shù)據(jù)庫(kù)中有如下數(shù)據(jù)
id 員工描述
1 優(yōu)秀論文
2 優(yōu)秀員工稱號(hào)
3 優(yōu)秀項(xiàng)目
4 優(yōu)秀團(tuán)隊(duì)
建立倒排索引的步驟:
1、每行切詞, 怎么切都可以,看實(shí)際需要
1 優(yōu)秀 論文
2 優(yōu)秀 員工 稱號(hào)
3 優(yōu)秀 項(xiàng)目
4 優(yōu)秀 團(tuán)隊(duì)
2、建立倒排索引
優(yōu)秀 1,2,3,4
論文 1
員工 2
稱號(hào) 2
項(xiàng)目 3
團(tuán)隊(duì) 4
3、檢索
倒排索引意思簡(jiǎn)單就是指定的詞出現(xiàn)在哪些行中,這些行都用唯一id進(jìn)行標(biāo)識(shí)。
所以這就是為什么倒排索引用到全文檢索中,因?yàn)榭梢灾苯硬樵兊桨嚓P(guān)關(guān)鍵字的內(nèi)容有哪些。
比如搜索優(yōu)秀,可以看到優(yōu)秀這個(gè)詞在1234中都有出現(xiàn),然后根據(jù)id查詢?cè)紨?shù)據(jù)。
? 有了倒排索引,當(dāng)我們需要從很多端很長(zhǎng)的內(nèi)容中檢索包含指定關(guān)鍵字的內(nèi)容時(shí),直接根據(jù)倒排索引就知道有沒(méi)有指定關(guān)鍵字了。而如果使用傳統(tǒng)數(shù)據(jù)庫(kù),那么必須掃描全部?jī)?nèi)容,如果數(shù)據(jù)有1000行,那工作量就很恐怖了。而倒排索引只是查詢個(gè)關(guān)鍵字而已,無(wú)需掃描全部?jī)?nèi)容。
? Lucene就是一個(gè)jar包,里面包含了封裝好的各種建立倒排索引,以及進(jìn)行搜索的代碼,包括各種算法。我們就用java開(kāi)發(fā)的時(shí)候,引入lucene jar,然后基于lucene的api進(jìn)行去進(jìn)行開(kāi)發(fā)就可以了。但是它只是根據(jù)文本做出索引,然后保存下來(lái),但是本身并不提供搜索功能。
? 由于Lucene使用比較復(fù)雜,繁瑣,所以基于Lucene開(kāi)發(fā)了一個(gè)新的項(xiàng)目,也就是Elasticsearch(簡(jiǎn)稱ES)。
特點(diǎn):
1)可以作為一個(gè)大型分布式集群(數(shù)百臺(tái)服務(wù)器)技術(shù),處理PB級(jí)數(shù)據(jù),服務(wù)大公司;也可以運(yùn)行在單機(jī)上,服務(wù)小公司;
2)Elasticsearch不是什么新技術(shù),主要是將全文檢索、數(shù)據(jù)分析以及分布式技術(shù),合并在了一起,才形成了獨(dú)一無(wú)二的ES;lucene(全文檢索),商用的數(shù)據(jù)分析軟件(也是有的),分布式數(shù)據(jù)庫(kù)(mycat);
3)對(duì)用戶而言,是開(kāi)箱即用的,非常簡(jiǎn)單,作為中小型的應(yīng)用,直接3分鐘部署一下ES,就可以作為生產(chǎn)環(huán)境的系統(tǒng)來(lái)使用了,數(shù)據(jù)量不大,操作不是太復(fù)雜;
4)數(shù)據(jù)庫(kù)的功能面對(duì)很多領(lǐng)域是不夠用的(事務(wù),還有各種聯(lián)機(jī)事務(wù)型的操作);特殊的功能,比如全文檢索,同義詞處理,相關(guān)度排名,復(fù)雜數(shù)據(jù)分析,海量數(shù)據(jù)的近實(shí)時(shí)處理;Elasticsearch作為傳統(tǒng)數(shù)據(jù)庫(kù)的一個(gè)補(bǔ)充,提供了數(shù)據(jù)庫(kù)所不能提供的很多功能。
適用場(chǎng)景:
1)維基百科,類似百度百科,牙膏,牙膏的維基百科,全文檢索,高亮,搜索推薦。
2)The Guardian(國(guó)外新聞網(wǎng)站),類似搜狐新聞,用戶行為日志(點(diǎn)擊,瀏覽,收藏,評(píng)論)+ 社交網(wǎng)絡(luò)數(shù)據(jù)(對(duì)某某新聞的相關(guān)看法),數(shù)據(jù)分析,給到每篇新聞文章的作者,讓他知道他的文章的公眾反饋(好,壞,熱門,垃圾,鄙視,崇拜)。
3)Stack Overflow(國(guó)外的程序異常討論論壇),IT問(wèn)題,程序的報(bào)錯(cuò),提交上去,有人會(huì)跟你討論和回答,全文檢索,搜索相關(guān)問(wèn)題和答案,程序報(bào)錯(cuò)了,就會(huì)將報(bào)錯(cuò)信息粘貼到里面去,搜索有沒(méi)有對(duì)應(yīng)的答案。
4)GitHub(開(kāi)源代碼管理),搜索上千億行代碼。
5)國(guó)內(nèi):站內(nèi)搜索(電商,招聘,門戶,等等),IT系統(tǒng)搜索(OA,CRM,ERP,等等),數(shù)據(jù)分析(ES熱門的一個(gè)使用場(chǎng)景)。
近實(shí)時(shí)
兩個(gè)意思,從寫入數(shù)據(jù)到數(shù)據(jù)可以被搜索到有一個(gè)小延遲(大概1秒);基于es執(zhí)行搜索和分析可以達(dá)到秒級(jí)。
集群cluster
ES集群可以有多個(gè)節(jié)點(diǎn),但是每個(gè)節(jié)點(diǎn)屬于哪個(gè)ES集群中是通過(guò)配置集群名稱來(lái)指定的。當(dāng)然一個(gè)集群只有一個(gè)節(jié)點(diǎn)也是OK的
節(jié)點(diǎn)node
集群中的一個(gè)節(jié)點(diǎn),節(jié)點(diǎn)也有一個(gè)名稱(默認(rèn)是隨機(jī)分配的),節(jié)點(diǎn)名稱很重要(在執(zhí)行運(yùn)維管理操作的時(shí)候),默認(rèn)節(jié)點(diǎn)會(huì)去加入一個(gè)名稱為“elasticsearch”的集群,如果直接啟動(dòng)一堆節(jié)點(diǎn),那么它們會(huì)自動(dòng)組成一個(gè)elasticsearch集群,當(dāng)然一個(gè)節(jié)點(diǎn)也可以組成一個(gè)elasticsearch集群。
index--database
索引包含一堆有相似結(jié)構(gòu)的文檔數(shù)據(jù),比如可以有一個(gè)客戶索引,商品分類索引,訂單索引,索引有一個(gè)名稱。一個(gè)index包含很多document,一個(gè)index就代表了一類類似的或者相同的document。比如說(shuō)建立一個(gè)product index,商品索引,里面可能就存放了所有的商品數(shù)據(jù),所有的商品document。類似于傳統(tǒng)數(shù)據(jù)庫(kù)中的庫(kù)的概念
type--table
每個(gè)索引里都可以有一個(gè)或多個(gè)type,type是index中的一個(gè)邏輯數(shù)據(jù)分類,一個(gè)type下的document,都有相同的field,比如博客系統(tǒng),有一個(gè)索引,可以定義用戶數(shù)據(jù)type,博客數(shù)據(jù)type,評(píng)論數(shù)據(jù)type。類似于傳統(tǒng)數(shù)據(jù)庫(kù)中的表的概念。
要注意:es逐漸拋棄掉這個(gè)概念了,到6.x版本中,已經(jīng)只允許一個(gè)index只有一個(gè)type了。
document--行
文檔是es中的最小數(shù)據(jù)單元,一個(gè)document可以是一條客戶數(shù)據(jù),一條商品分類數(shù)據(jù),一條訂單數(shù)據(jù),通常用JSON數(shù)據(jù)結(jié)構(gòu)表示,每個(gè)index下的type中,都可以去存儲(chǔ)多個(gè)document。相當(dāng)于行
field--字段
Field是Elasticsearch的最小單位。一個(gè)document里面有多個(gè)field,每個(gè)field就是一個(gè)數(shù)據(jù)字段。
如:
product document
{
"product_id": "1",
"product_name": "高露潔牙膏",
"product_desc": "高效美白",
"category_id": "2",
"category_name": "日化用品" 這些就是字段
}
mapping--映射約束
數(shù)據(jù)如何存放到索引對(duì)象上,需要有一個(gè)映射配置,包括:數(shù)據(jù)類型、是否存儲(chǔ)、是否分詞等。所謂映射是對(duì)type的存儲(chǔ)的一些限制。
例子:
這樣就創(chuàng)建了一個(gè)名為blog的Index。Type不用單獨(dú)創(chuàng)建,在創(chuàng)建Mapping 時(shí)指定就可以。Mapping用來(lái)定義Document中每個(gè)字段的類型,即所使用的 analyzer、是否索引等屬性。創(chuàng)建Mapping 的代碼示例如下:
client.indices.putMapping({
index : 'blog',
type : 'article',
這里還可以設(shè)置type的一些工作屬性,比如_source等,后面會(huì)講
body : {
article: {
properties: {
id: {
type: 'string',
analyzer: 'ik',
store: 'yes',
},
title: {
type: 'string',
analyzer: 'ik',
store: 'no',
},
content: {
type: 'string',
analyzer: 'ik',
store: 'yes',
}
}
}
}
});
寫流程:
1、客戶端根據(jù)提供的es節(jié)點(diǎn),選擇一個(gè)node作為協(xié)調(diào)節(jié)點(diǎn),并發(fā)送寫請(qǐng)求
2、協(xié)調(diào)節(jié)點(diǎn)對(duì)寫入的document進(jìn)行路由,將document進(jìn)行分片。每個(gè)分片單獨(dú)進(jìn)行寫,每個(gè)分片默認(rèn)都是雙備份,寫在不同的節(jié)點(diǎn)上。
3、分片寫入時(shí),主備份由協(xié)調(diào)節(jié)點(diǎn)寫入,副備份則是從主備份所在節(jié)點(diǎn)同步數(shù)據(jù)過(guò)去。
4、當(dāng)分片都寫完后,由協(xié)調(diào)節(jié)點(diǎn)返回寫入完成給客戶端
讀流程:
讀流程就很簡(jiǎn)單了,如果通過(guò)docid來(lái)讀取,直接根據(jù)docid進(jìn)行hash。判斷出該doc存儲(chǔ)在哪個(gè)節(jié)點(diǎn)上,然后到相應(yīng)節(jié)點(diǎn)上讀取數(shù)據(jù)即可。
圖1.1 ES存儲(chǔ)結(jié)構(gòu)
首先分為兩個(gè)區(qū)域,一個(gè)是索引區(qū)域,一個(gè)是數(shù)據(jù)區(qū)域。前者用來(lái)存儲(chǔ)生成的倒排索引,后者用來(lái)存儲(chǔ)原始的document(可以選擇不存,后面有說(shuō))。
1)索引對(duì)象(index):存儲(chǔ)數(shù)據(jù)的表結(jié)構(gòu) ,任何搜索數(shù)據(jù),存放在索引對(duì)象上 。
2)映射(mapping):數(shù)據(jù)如何存放到索引對(duì)象上,需要有一個(gè)映射配置, 包括:數(shù)據(jù)類型、是否存儲(chǔ)、是否分詞等。
3)文檔(document):一條數(shù)據(jù)記錄,存在索引對(duì)象上 。es會(huì)給每個(gè)document生成一個(gè)唯一的documentID,用于標(biāo)識(shí)該document。當(dāng)然也可以手動(dòng)指定docid
4)文檔類型(type):一個(gè)索引對(duì)象,存放多種類型數(shù)據(jù),數(shù)據(jù)用文檔類型進(jìn)行標(biāo)識(shí)。
使用的es版本為:6.6.2
下載地址:https://www.elastic.co/products/elasticsearch
解壓程序到指定目錄:
tar zxf elasticsearch-6.6.2.tar.gz -C /opt/modules/
修改配置文件:
cd /opt/modules/elasticsearch-6.6.2/
vim config/elasticsearch.yml
修改如下內(nèi)容:
# ---------------------------------- Cluster -------------------------------------
# 集群名稱
cluster.name: my-application
# ------------------------------------ Node --------------------------------------
# 節(jié)點(diǎn)名稱,需要保證全局唯一
node.name: bigdata121
# ----------------------------------- Paths ---------------------------------------
# 配置es數(shù)據(jù)目錄,以及日志目錄
path.data: /opt/modules/elasticsearch-6.6.2/data
path.logs: /opt/modules/elasticsearch-6.6.2/logs
# ----------------------------------- Memory -----------------------------------
# 配置es不檢查內(nèi)存限制,內(nèi)存不夠時(shí)啟動(dòng)會(huì)檢查報(bào)錯(cuò)
bootstrap.memory_lock: false
bootstrap.system_call_filter: false
# ---------------------------------- Network ------------------------------------
# 綁定ip
network.host: 192.168.50.121
# --------------------------------- Discovery ------------------------------------
# 初始發(fā)現(xiàn)節(jié)點(diǎn),用來(lái)給新添加的節(jié)點(diǎn)進(jìn)行詢問(wèn)加入集群
discovery.zen.ping.unicast.hosts: ["bigdata121"]
修改Linux一些內(nèi)核參數(shù)
vim /etc/security/limits.conf
添加如下內(nèi)容:
Es硬性要求打開(kāi)最小數(shù)目最小為65536,進(jìn)程數(shù)最小為4096,否則無(wú)法啟動(dòng)
* soft nofile 65536
* hard nofile 131072
* soft nproc 4096
* hard nproc 4096
vim /etc/security/limits.d/20-nproc.conf
* soft nproc 1024
#修改為
* soft nproc 4096
這些內(nèi)核參數(shù)需要重啟才生效
vim /etc/sysctl.conf
添加下面配置:
vm.max_map_count=655360
并執(zhí)行命令:
sysctl -p
創(chuàng)建es的數(shù)據(jù)目錄以及日志目錄
mkdir /opt/modules/elasticsearch-6.6.2/{logs,data}
啟動(dòng)es服務(wù)
bin/elasticsearch -d
-d 表示以后臺(tái)進(jìn)程服務(wù)的方式啟動(dòng),不加此選項(xiàng)就以前臺(tái)進(jìn)程方式啟動(dòng)
測(cè)試es
es會(huì)啟動(dòng)兩個(gè)對(duì)外端口:
9200:restful api的端口
9300:java api端口
可以直接使用curl訪問(wèn)9200端口
curl http://bigdata121:9200
{
"name" : "bigdata121",
"cluster_name" : "my-application",
"cluster_uuid" : "DM6wmLzsQv2xVDkLMBJzOQ",
"version" : {
"number" : "6.6.2",
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "3bd3e59",
"build_date" : "2019-03-06T15:16:26.864148Z",
"build_snapshot" : false,
"lucene_version" : "7.6.0",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}
這樣就正常了
master node:master 節(jié)點(diǎn)主要用于元數(shù)據(jù)(metadata)的處理,比如索引的新增、刪除、分片分配等。
data node:data 節(jié)點(diǎn)上保存了數(shù)據(jù)分片。它負(fù)責(zé)數(shù)據(jù)相關(guān)操作,比如分片的 CRUD,以及搜索和整合操作。這些操作都比較消耗 CPU、內(nèi)存和 I/O 資源;
client node:client 節(jié)點(diǎn)起到路由請(qǐng)求的作用,實(shí)際上可以看做負(fù)載均衡器。
那么這三種節(jié)點(diǎn)該如何配置,例子:
# 配置文件中給出了三種配置高性能集群拓?fù)浣Y(jié)構(gòu)的模式,如下:
# 1. 如果你想讓節(jié)點(diǎn)從不選舉為主節(jié)點(diǎn),只用來(lái)存儲(chǔ)數(shù)據(jù),可作為負(fù)載器
# node.master: false
# node.data: true
# 2. 如果想讓節(jié)點(diǎn)成為主節(jié)點(diǎn),且不存儲(chǔ)任何數(shù)據(jù),并保有空閑資源,可作為協(xié)調(diào)器
# node.master: true
# node.data: false
# 3. 如果想讓節(jié)點(diǎn)既不成為主節(jié)點(diǎn),又不成為數(shù)據(jù)節(jié)點(diǎn),那么可將他作為搜索器,從節(jié)點(diǎn)中獲取數(shù)據(jù),生成搜索結(jié)果等
# node.master: false
# node.data: false
# 4. 節(jié)點(diǎn)是數(shù)據(jù)節(jié)點(diǎn),也是master節(jié)點(diǎn),這是默認(rèn)配置
# node.master: true
# node.data: true
1、默認(rèn)情況下,一個(gè)節(jié)點(diǎn)是數(shù)據(jù)節(jié)點(diǎn),也是master節(jié)點(diǎn)。對(duì)于3-5個(gè)節(jié)點(diǎn)的小集群來(lái)講,通常讓所有節(jié)點(diǎn)存儲(chǔ)數(shù)據(jù)和具有獲得主節(jié)點(diǎn)的資格。你可以將任何請(qǐng)求發(fā)送給任何節(jié)點(diǎn),并且由于所有節(jié)點(diǎn)都具有集群狀態(tài)的副本,它們知道如何路由請(qǐng)求。多個(gè)master的元數(shù)據(jù)也會(huì)同步,不用擔(dān)心不一致。要注意,master節(jié)點(diǎn)的數(shù)量最好最少為3,且為單數(shù)
2、當(dāng)集群節(jié)點(diǎn)數(shù)量比較大時(shí),那么通常就會(huì)將主節(jié)點(diǎn)、數(shù)據(jù)節(jié)點(diǎn)分開(kāi),專門部署在對(duì)應(yīng)的節(jié)點(diǎn)上,然后主節(jié)點(diǎn)是多個(gè)都可用的,形成HA的結(jié)構(gòu)。要注意,master節(jié)點(diǎn)的數(shù)量最好最少為3,且為單數(shù)
實(shí)際部署其實(shí)和單節(jié)點(diǎn)差不多,主要看部署的方案選哪個(gè),master有幾個(gè),數(shù)據(jù)節(jié)點(diǎn)有幾個(gè),設(shè)置下角色即可,這里不多說(shuō)
用qq瀏覽器或者chrome,直接到應(yīng)用商店搜索elasticsearch-head,直接安裝插件即可
?
org.elasticsearch
elasticsearch
6.6.2
org.elasticsearch.client
transport
6.6.2
org.apache.logging.log4j
log4j-core
2.9.0
junit
junit
4.12
另外需要自己添加一個(gè)log4j2的日志格式配置文件,添加到resource目錄下
log4j2.xml
下面代碼中使用 junit進(jìn)行運(yùn)行測(cè)試,不會(huì)用的自己百度
public class ESDemo1 {
private TransportClient client;
@Before
public void getClient() throws UnknownHostException {
//1、創(chuàng)建es配置對(duì)象
Settings settings = Settings.builder().put("cluster.name", "my-application").build();
//2、連接es集群
client = new PreBuiltTransportClient(settings);
//配置es集群地址
client.addTransportAddress(new TransportAddress(
InetAddress.getByName("192.168.50.121"),
9300
));
System.out.println(client.toString());
}
}
// .get() 表示觸發(fā)操作
@Test
public void createBlog() {
//創(chuàng)建索引blog
//創(chuàng)建index需要admin用戶
client.admin().indices().prepareCreate("blog").get();
client.close();
}
//刪除索引
@Test
public void deleteIndex() {
client.admin().indices().prepareDelete("blog").get();
client.close();
}
@Test
public void addDocument() {
//1、json方式添加document
String d = "{\"id\":1, \"name\":\"山海經(jīng)\"}";
//導(dǎo)入document,并指定源的格式為 json.
IndexResponse indexResponse = client.prepareIndex("blog", "article").setSource(d, XContentType.JSON).execute().actionGet();
System.out.println(indexResponse.getId());
client.close();
}
@Test
public void addDocument2() throws IOException {
//2、另外一種方式添加document
IndexResponse indexResponse = client.prepareIndex("blog3", "article")
.setSource(XContentFactory.jsonBuilder()
.startObject()
.field("name","靜夜思")
.field("id",4)
.endObject()
).execute().actionGet();
System.out.println(indexResponse.getResult());
client.close();
}
@Test
public void addDocument3() throws IOException {
//3、通過(guò)hashmap組織數(shù)據(jù)
HashMap json = new HashMap<>();
json.put("name","spark從入門到放棄");
json.put("id","6");
IndexResponse indexResponse = client.prepareIndex("blog", "article")
.setSource(json).execute().actionGet();
System.out.println(indexResponse.getResult());
client.close();
}
@Test
public void addMoreDocument() throws IOException {
//4、一次請(qǐng)求內(nèi)部添加多個(gè)document
BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
bulkRequestBuilder.add(
client.prepareIndex("blog2", "comment").setSource(
XContentFactory.jsonBuilder()
.startObject()
.field("name", "山海經(jīng)")
.field("id",1)
.field("commentValue","這是一部很好的作品")
.endObject())
);
bulkRequestBuilder.add(
client.prepareIndex("blog2", "comment").setSource(
XContentFactory.jsonBuilder()
.startObject()
.field("name", "駱駝祥子")
.field("id",2)
.field("commentValue","這是講一個(gè)人的故事")
.endObject())
);
BulkResponse bulkItemResponses = bulkRequestBuilder.get();
System.out.println(bulkItemResponses);
client.close();
}
要注意的是,從6.x版本開(kāi)始,一個(gè)index中只能有一個(gè)type了,如果創(chuàng)建多個(gè)type會(huì)有以下報(bào)錯(cuò)
Rejecting mapping update to [blog] as the final mapping would have more than 1
根據(jù)docid搜索document
//搜索單個(gè)document
@Test
public void getType() {
GetResponse documentFields = client.prepareGet().setIndex("blog3").setType("article").setId("2OlH9WwBaToKuF8JhwB5").get();
System.out.println(documentFields.getSourceAsString());
client.close();
}
//查詢多個(gè)doc
@Test
public void getDocFromMoreIndex() {
MultiGetResponse multiGetResponse = client.prepareMultiGet()
.add("blog", "article", "1")
.add("blog", "article", "2")
.get();
//結(jié)果打印
for (MultiGetItemResponse itemResponse : multiGetResponse) {
System.out.println( itemResponse.getResponse().getSourceAsString());
}
client.close();
}
@Test
public void updateData() throws IOException {
//更新數(shù)據(jù)方式1:通過(guò) prepareupdate方法
UpdateResponse updateResponse = client.prepareUpdate("blog", "article", "4")
.setDoc(XContentFactory.jsonBuilder()
.startObject()
.field("name", "天黑")
.field("id", "5")
.endObject()
).get();
System.out.println(updateResponse.getResult());
}
@Test
public void updateData2() throws IOException, ExecutionException, InterruptedException {
//更新數(shù)據(jù)方式2:通過(guò)update方法
UpdateRequest updateRequest = new UpdateRequest().index("blog").type("article").id("4");
updateRequest.doc(XContentFactory.jsonBuilder()
.startObject()
.field("name", "亞瑟")
.field("id", "7")
.endObject());
UpdateResponse updateResponse = client.update(updateRequest).get();
System.out.println(updateResponse.getResult());
}
@Test
public void upsertData() throws IOException, ExecutionException, InterruptedException {
//指定doc不存在時(shí)就插入,存在就修改
//不存在就插入這個(gè)
IndexRequest indexRequest = new IndexRequest("blog","article","6").source(
XContentFactory.jsonBuilder().startObject()
.field("name","wang")
.field("id","10")
.endObject()
);
//存在就更新這個(gè),注意最后的那個(gè) upsert操作,意思就是不存在就插入上面的 indexrequest
UpdateRequest updateRequest = new UpdateRequest().index("blog").type("article").id("6");
updateRequest.doc(XContentFactory.jsonBuilder()
.startObject()
.field("name", "king")
.field("id", "7")
.endObject()).upsert(indexRequest);
UpdateResponse updateResponse = client.update(updateRequest).get();
System.out.println(updateResponse.getResult());
client.close();
}
@Test
public void deleteDocument() {
//刪除document
DeleteResponse deleteResponse = client.prepareDelete("blog", "article", "6").get();
System.out.println(deleteResponse.getResult());
client.close();
}
關(guān)鍵性一個(gè)類是 org.elasticsearch.index.query.QueryBuilders;
@Test
public void matchAll() {
//構(gòu)建全部查詢
SearchResponse searchResponse = client.prepareSearch("blog").setQuery(QueryBuilders.matchAllQuery()).get();
//從返回結(jié)構(gòu)中解析doc
SearchHits hits = searchResponse.getHits();
for (SearchHit hit:hits){
System.out.println(hit.getSourceAsString());
}
client.close();
}
搜索全部字段中包含指定字符的document
@Test
public void matchSome() {
//直接全文檢索指定字符
SearchResponse searchResponse = client.prepareSearch("blog3").setQuery(QueryBuilders.queryStringQuery("思")).get();
SearchHits hits = searchResponse.getHits();
for(SearchHit hit:hits) {
System.out.println(hit.getId());
System.out.println();
}
}
@Test
public void wildMatch() {
//通配符查詢,*表示0或者多個(gè)字符,?表示單個(gè)字符
SearchResponse searchResponse = client.prepareSearch("blog").setTypes("article").setQuery(QueryBuilders.wildcardQuery("name", "wa*")).get();
SearchHits hits = searchResponse.getHits();
for(SearchHit h:hits) {
System.out.println(h.getSourceAsString());
}
}
這個(gè)方法用于匹配某個(gè)字段的整個(gè)內(nèi)容,類似like操作
@Test
public void matchField() {
//這是對(duì)分詞結(jié)果進(jìn)行等值操作的方法,不是對(duì)整個(gè)字段,而是對(duì)字段的分詞結(jié)果
SearchResponse searchResponse = client.prepareSearch("blog").setQuery(QueryBuilders.termQuery("name", "山")).get();
SearchHits hits = searchResponse.getHits();
for(SearchHit hit:hits) {
System.out.println(hit.getSourceAsString());
}
client.close();
}
這個(gè)方法一定要注意:
比如有一個(gè)字段內(nèi)容如下: 我愛(ài)中國(guó)
假設(shè)分詞如下: 我 愛(ài) 中國(guó)
如果使用 QueryBuilders.termQuery("name", "中") 也就是搜索“中”這個(gè)字時(shí),實(shí)際上沒(méi)有結(jié)果返回的。因?yàn)榉衷~中并沒(méi)有含有單獨(dú)的“中”。
所以這個(gè)方法是用于完整匹配分詞結(jié)果中的某個(gè)分詞的。
由此,可以得出,即便是用整個(gè)字段的內(nèi)容來(lái)搜索,這個(gè)方法也不會(huì)返回任何結(jié)果的,因?yàn)榉衷~結(jié)果不包含。
@Test
public void fuzzy() {
// 1 模糊查詢
SearchResponse searchResponse = client.prepareSearch("blog").setTypes("article")
.setQuery(QueryBuilders.fuzzyQuery("title", "lucene")).get();
// 2 打印查詢結(jié)果
SearchHits hits = searchResponse.getHits(); // 獲取命中次數(shù),查詢結(jié)果有多少對(duì)象
for(SearchHit hit:hits) {
System.out.println(hit.getSourceAsString());
}
// 3 關(guān)閉連接
client.close();
}
這個(gè)方法和 termQuery很類似,但是有區(qū)別。感興趣的話可以自己查找資料。這個(gè)方法比較少用
? 映射是規(guī)定index中的一些屬性,以及各自type下的字段的屬性(再?gòu)?qiáng)調(diào)一遍,現(xiàn)在6.x版本一個(gè)index下只能有一個(gè)type,其實(shí)就是變相地去除掉了type)。Elasticsearch映射雖然有idnex和type兩層關(guān)系,但是實(shí)際索引時(shí)是以index為基礎(chǔ)的。如果同一個(gè)index下不同type的字段出現(xiàn)mapping不一致的情況,雖然數(shù)據(jù)依然可以成功寫入并生成各自的mapping,但實(shí)際上fielddata中的索引結(jié)果卻依然是以index內(nèi)第一個(gè)mapping類型來(lái)生成的
定義mapping時(shí),依舊是使用json格式定義定義。一般格式如下:
{
元數(shù)據(jù)屬性字段,如:
_type:是哪個(gè)type的mapping,還是那句話,type基本不怎么提了
_index:屬于哪個(gè)index
。。。。。。。
properties:{
"field1":{
字段屬性字段,如:
type:字段數(shù)據(jù)類型
}
"field2":{
字段屬性字段,如:
type:字段數(shù)據(jù)類型
}
。。。。。。。。。
}
}
基本格式就是這樣,分為兩大部分,一個(gè)是整個(gè)index 的元數(shù)據(jù)信息,一個(gè)是針對(duì)具體type中的字段信息。
核心數(shù)據(jù)類型
字符串:text,keyword
數(shù)字:long, integer, short, byte, double, float, half_float, scaled_float
布爾值:boolean
時(shí)間:date
二進(jìn)制:binary
范圍:integer_range, float_range, long_range, double_range, date_range
復(fù)雜數(shù)據(jù)類型
數(shù)組:array
對(duì)象:object
堆疊/嵌套對(duì)象: nested
地理:geo_point,geo_point
IP: ip
字符個(gè)數(shù):token_count(輸入一個(gè)字符串,保存的是它的長(zhǎng)度)
元數(shù)據(jù)字段:
_all : 它是文檔中所有字段的值整合成的一個(gè)大字符串,用空格分割。它進(jìn)行了索引但沒(méi)有存儲(chǔ),所以我們只能對(duì)他進(jìn)行搜索不能獲取。如果我們沒(méi)有指定搜索的字段,就默認(rèn)是在_all字段上進(jìn)行搜索。
_source :文檔信息
包含在文檔在創(chuàng)建時(shí)的實(shí)際主體,它會(huì)被存儲(chǔ)但不會(huì)被索引,用于get或search是返回主體。如果你并不關(guān)系數(shù)據(jù)的主體,只注重?cái)?shù)量,那可以將此字段禁用
_routing :路由字段
es會(huì)使用下面的計(jì)算公式計(jì)算數(shù)據(jù)應(yīng)保存在哪個(gè)分片,索引指定一個(gè)路由字段可以自己來(lái)控制哪些值放在一起。
shard_num = hash(_routing) % num_primary_shards
_meta 自定義的元數(shù)據(jù) ,因?yàn)樵獢?shù)據(jù)是每個(gè)文檔都會(huì)帶的,索引如果你想要在每個(gè)文檔上標(biāo)注一些信息,就可以使用此屬性,自定義一些元數(shù)據(jù)。
_field_names :保存著非空值得屬性名集合,可以通過(guò)它查詢包含某個(gè)字段非空值的文檔
_id :主鍵
_index :索引
_type :類型
_uid :類型和id的組合 uid字段的值可以在查詢、聚合、腳本和排序中訪問(wèn):
_parent :父類,可用于關(guān)聯(lián)兩個(gè)索引
字段屬性:
type 數(shù)據(jù)類型
改屬性用來(lái)指定字段的數(shù)據(jù)類型,一但指點(diǎn)后就不能再修改,如果數(shù)據(jù)不是以設(shè)置的數(shù)據(jù)類型傳入,es會(huì)去轉(zhuǎn)換數(shù)據(jù),裝換不成功則報(bào)錯(cuò)。具體可配置的參數(shù),可看前面的數(shù)據(jù)類型說(shuō)明。
analyzer 分析器
用于指定索引創(chuàng)建時(shí)使用的分析器是什么,即對(duì)同一段內(nèi)容,不同的分析器會(huì)用不同的方式分詞,最后在倒排索引上的值是不同的。
index 是否索引
索引選項(xiàng)控制字段值是否被索引。它接受true或false,默認(rèn)為true。沒(méi)有索引的字段不是可查詢的。
store
屬性值是否被存儲(chǔ),默認(rèn)情況下字段是可以被搜索但是內(nèi)容不存儲(chǔ)的,值一般都是保存在_source中。但比如一篇文章你有它的內(nèi)容和原網(wǎng)址,現(xiàn)在需要對(duì)內(nèi)容進(jìn)行檢索,但查看是跳轉(zhuǎn)到它原網(wǎng)址的,那這時(shí)就不需要存儲(chǔ)內(nèi)容了。
fielddata 現(xiàn)場(chǎng)數(shù)據(jù)
如果你要對(duì)一個(gè)text類型進(jìn)行聚合操作,你必須設(shè)置這個(gè)參數(shù)為true。
doc_values 文檔數(shù)據(jù)
建立一個(gè)文檔對(duì)應(yīng)字段的“正排索引”,其實(shí)就是把文檔的字段按列存儲(chǔ)了,它不會(huì)保存分析的字段。方便聚合排序時(shí)訪問(wèn)。
format 默認(rèn)格式
一般用于時(shí)間格式的數(shù)據(jù),指定默認(rèn)的數(shù)據(jù)格式, “yyyy-MM-dd HH:mm:ss”
search_analyzer 搜索分析器
指定搜索時(shí)使用的分析器,一般不設(shè)置在搜索時(shí)就會(huì)使用創(chuàng)建索引時(shí)使用的分析器,如果要自己指定不同的也只要配置即可。
boost 分值
指定字段的相關(guān)性評(píng)分默認(rèn)是1.0,數(shù)值越大,搜索時(shí)排序時(shí)使用。也可以直接通過(guò)查詢時(shí)指定分值的方式
coerce 是否轉(zhuǎn)換
在插入數(shù)據(jù)時(shí),在插入數(shù)據(jù)類型和映射類型不一致的情況下是否強(qiáng)制轉(zhuǎn)換數(shù)據(jù)類型。默認(rèn)是開(kāi)啟的
normalizer 轉(zhuǎn)換器
因?yàn)閗eyword類型的字段是不進(jìn)行分析的,但是我們又想要將其統(tǒng)一成一個(gè)規(guī)則,比如都是小寫,比如用ASCILL進(jìn)行編碼,其實(shí)就是個(gè)給keyword用的分析器??梢栽趕etting下的analysis下定義自己的normalizer使用
copy_to 同步復(fù)制
在插入值是,會(huì)把值一同放到另一個(gè)字段中。主要用于自己定義一個(gè)類似于_all字段的字端。
dynamic 動(dòng)態(tài)映射控制
該字段是用來(lái)控制動(dòng)態(tài)映射的,它有三個(gè)值
-true-自動(dòng)添加映射
-false-新值不索引,不能被搜索,但返回的命中源字段中會(huì)存在這個(gè)值
-strict-遇到新值拋出異常
enabled 是否啟動(dòng)
這個(gè)值是否要用于搜索
ignore_above 忽視上限
一個(gè)字符串超過(guò)指定長(zhǎng)度后就不會(huì)索引了
ignore_malformed 忽視錯(cuò)誤數(shù)據(jù)
比如一個(gè)文檔數(shù)據(jù)傳過(guò)來(lái),只用一個(gè)字段的數(shù)據(jù)時(shí)不能被存儲(chǔ),ES會(huì)拋出異常并且不會(huì)存儲(chǔ)此數(shù)據(jù)。我們就可以配置此屬性保證數(shù)據(jù)被存儲(chǔ)
include_in_all 是否保存在_all字段中
fields 多字段配置
比如出現(xiàn)標(biāo)題既要索引,又有不用索引的情景。我們不能對(duì)一個(gè)字段設(shè)置兩個(gè)類型,又不想再建一個(gè)不同類型的相同字段。我們可以使用多字段的方式,在保存數(shù)據(jù)時(shí),我們只需保存一個(gè)字段,ES會(huì)默認(rèn)將數(shù)據(jù)保存到這個(gè)字段下的多字段上。
null_value 空值
假如你插入的數(shù)據(jù)為空,或者數(shù)據(jù)中沒(méi)有這個(gè)字段的值。那這個(gè)文檔的這個(gè)字段就不參與搜索了。我們可以通過(guò)指定一個(gè)顯示的空值來(lái)讓他能夠參與搜索
norms 規(guī)范
如果一個(gè)字段只用于聚合,可以設(shè)置為false
背景:
首先,我們要知道一點(diǎn),當(dāng)doc傳入es時(shí),es會(huì)根據(jù)配置給doc的每個(gè)字段生成索引,并且會(huì)將生成的索引保存到es中。但是至于doc的原始數(shù)據(jù)是否保存到es中,是可以選擇的。這點(diǎn)要先搞清楚,并一定非得把doc的原始數(shù)據(jù)保存在es中的,es非保存不可的是生成的索引,而不是原始數(shù)據(jù)
========================
_all:
這是一個(gè)特殊字段,是把所有其它字段中的值,以空格為分隔符組成一個(gè)大字符串,然后被分析和索引,但是不存儲(chǔ)原始數(shù)據(jù),也就是說(shuō)它能被查詢,但不能被取回顯示。注意這個(gè)字段是可以被索引的。默認(rèn)情況下,如果要進(jìn)行全文檢索,需要指定在哪個(gè)字段上檢索,如果不知道在哪個(gè)字段上,那么_all就起到作用了。_all能讓你在不知道要查找的內(nèi)容是屬于哪個(gè)具體字段的情況下進(jìn)行搜索
======================
_source: true/false,默認(rèn)為true
保存的是doc的本來(lái)的原數(shù)數(shù)據(jù),也就是是json格式的doc。他和_all不同,他是json格式的字符串。而且這個(gè)字段不會(huì)被索引。
當(dāng)我們執(zhí)行檢索操作時(shí),是到倒排索引中查詢,然后獲得含有指定關(guān)鍵字的doc的id,
當(dāng) _source 設(shè)置為 true時(shí)
可以根據(jù)上面查詢到的docid,返回對(duì)應(yīng)id的document的原始數(shù)據(jù)。
當(dāng) _source 設(shè)置為 false時(shí)
就只能返回對(duì)應(yīng)的document的id,無(wú)法回顯對(duì)應(yīng)document的原始數(shù)據(jù)
這種情況下,一般是使用額外的方式來(lái)保存document的原始數(shù)據(jù)的,比如hbase。而es就單純保存索引而已
=======================
store:true/false,默認(rèn)為false
這個(gè)屬性用于指定是否保存document中對(duì)應(yīng)字段的value,這個(gè)的概念和上面的source有點(diǎn)類似了,只不過(guò)這里store是針對(duì)某個(gè)field的原始數(shù)據(jù),source是針對(duì)整個(gè)document的原始數(shù)據(jù)。
當(dāng)執(zhí)行想獲取一個(gè)document的數(shù)據(jù)時(shí),
1、采用source方式時(shí):
只需產(chǎn)生一次磁盤IO,因?yàn)開(kāi)source存儲(chǔ)的時(shí)候,直接把整個(gè)doc當(dāng)做一個(gè)字段來(lái)存儲(chǔ)。當(dāng)我們需要doc中的某個(gè)字段時(shí),是先從source讀取數(shù)據(jù),然后再解析成json,獲取到指定字段內(nèi)容
2、采用store方式時(shí),
因?yàn)槊總€(gè)字段都單獨(dú)存儲(chǔ)了,當(dāng)需要獲得整個(gè)doc的數(shù)據(jù)時(shí),就需要單獨(dú)每個(gè)字段進(jìn)行取值,有多少個(gè)字段就產(chǎn)生多少次磁盤IO。
3、store和source混合使用時(shí)
如果操作是獲取整個(gè)doc的數(shù)據(jù),那么es會(huì)優(yōu)先從source讀取數(shù)據(jù)。
如果操作是獲取某些字段的數(shù)據(jù),那么es會(huì)優(yōu)先從store存儲(chǔ)中讀取數(shù)據(jù)。因?yàn)檫@樣讀取的數(shù)據(jù)量相對(duì)較少,無(wú)需讀取整個(gè)doc的數(shù)據(jù)再解析。
但是注意的是,這兩個(gè)屬性都是單獨(dú)自己保存數(shù)據(jù)的,所以如果兩個(gè)啟用的話,相當(dāng)于數(shù)據(jù)存儲(chǔ)了兩次,挺浪費(fèi)存儲(chǔ)空間的,增大了索引的體積
創(chuàng)建mapping,要注意,mapping創(chuàng)建之后不能更改
@Test
public void createMapping() throws Exception {
// 1設(shè)置mapping,使用jsonbuilder構(gòu)建mapping
XContentBuilder builder = XContentFactory.jsonBuilder()
.startObject()
.startObject("article")
.startObject("properties")
.startObject("id1")
.field("type", "string")
.field("store", "yes")
.endObject()
.startObject("title2")
.field("type", "string")
.field("store", "no")
.endObject()
.startObject("content")
.field("type", "string")
.field("store", "yes")
.endObject()
.endObject()
.endObject()
.endObject();
// 2 添加mapping
PutMappingRequest mapping = Requests.putMappingRequest("blog4").type("article").source(builder);
client.admin().indices().putMapping(mapping).get();
// 3 關(guān)閉資源
client.close();
}
查看map
@Test
public void getIndexMapping() throws ExecutionException, InterruptedException {
//構(gòu)建查看mapping的請(qǐng)求,查看blog3這個(gè)index的mapping
GetMappingsResponse mappingsResponse = client.admin().indices().getMappings(new GetMappingsRequest().indices("blog3")).get();
//獲取mapping
ImmutableOpenMap> mappings = mappingsResponse.getMappings();
//迭代打印mapping數(shù)據(jù)
for (ObjectObjectCursor> mapping : mappings) {
if (mapping.value.isEmpty()) {
continue;
}
//最外層的key是index的名稱
System.out.println("index key:" + mapping.key);
//value包裹的是每個(gè)type的mapping,里面以type為key,mapping為value
for (ObjectObjectCursor mapValue : mapping.value) {
System.out.println("type key:" + mapValue.key);
System.out.println("type value:" + mapValue.value.sourceAsMap());
}
}
client.close();
}
/*
結(jié)果如下:
index key:blog3
type key:article
type value:{_source={enabled=false}, properties={id={type=long}, name={type=text, fields={keyword={type=keyword, ignore_above=256}}}}}
*/
在spark.2.1和es6.6項(xiàng)目中混合使用,報(bào)錯(cuò):
java.lang.NoSuchMethodError: io.netty.buffer.ByteBuf.retainedSlice(II)Lio/netty/buffer/ByteBuf;
這種問(wèn)題,一般都是使用的某個(gè)依賴包的版本問(wèn)題。使用mvn dependency:tree 看了下,原來(lái)spark和es各自依賴的版本不一致,spark使用的是3.x版本,es使用的是4.1.32.Final版本。但是因?yàn)閟park的依賴在pom.xml中寫在前面,迫使es使用的是3.x版本的依賴,導(dǎo)致有些方法不存在,就報(bào)錯(cuò)。解決方式很簡(jiǎn)答,直接指定使用新版本的就好,如下:
io.netty
netty-all
4.1.32.Final
我們知道,建立索引過(guò)程中,最重要的一個(gè)步驟就是分詞,分詞的策略有很多,我們看看es默認(rèn)的中文分詞器的效果
[root@bigdata121 elasticsearch-6.6.2]# curl -H "Content-Type:application/json" -XGET 'http://bigdata121:9200/_analyze?pretty' -d '{"analyzer":"standard","text":"中華人民共和國(guó)"}'
{
"tokens" : [
{
"token" : "中",
"start_offset" : 0,
"end_offset" : 1,
"type" : "",
"position" : 0
},
{
"token" : "華",
"start_offset" : 1,
"end_offset" : 2,
"type" : "",
"position" : 1
},
{
"token" : "人",
"start_offset" : 2,
"end_offset" : 3,
"type" : "",
"position" : 2
},
{
"token" : "民",
"start_offset" : 3,
"end_offset" : 4,
"type" : "",
"position" : 3
},
{
"token" : "共",
"start_offset" : 4,
"end_offset" : 5,
"type" : "",
"position" : 4
},
{
"token" : "和",
"start_offset" : 5,
"end_offset" : 6,
"type" : "",
"position" : 5
},
{
"token" : "國(guó)",
"start_offset" : 6,
"end_offset" : 7,
"type" : "",
"position" : 6
}
]
}
可以看到,標(biāo)準(zhǔn)的中文分詞器只是單純將字分開(kāi),其實(shí)并不智能,沒(méi)有詞語(yǔ)考慮進(jìn)去。所以需要更加強(qiáng)大的分詞器。常用的有ik分詞器
cd /opt/modules/elasticsearch-6.6.2
執(zhí)行下面的命令安裝,需要聯(lián)網(wǎng)
bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.6.2/elasticsearch-analysis-ik-6.6.2.zip
注意要根據(jù)ES的版本安裝對(duì)應(yīng)版本的ik
分兩種模式:ik_smart 和 ik_max_word
1、 ik_smart 模式,智能解析詞語(yǔ)結(jié)構(gòu)
curl -H "Content-Type:application/json" -XGET 'http://bigdata121:9200/_analyze?pretty' -d '{"analyzer":"ik_smart","text":"中華人民共和國(guó)"}'
{
"tokens" : [
{
"token" : "中華人民共和國(guó)",
"start_offset" : 0,
"end_offset" : 7,
"type" : "CN_WORD",
"position" : 0
}
]
}
2、ik_max_word 模式,智能解析字和詞語(yǔ)
curl -H "Content-Type:application/json" -XGET 'http://192.168.109.133:9200/_analyze?pretty' -d '{"analyzer":"ik_max_word","text":"中華人民共和國(guó)"}'
{
"tokens" : [
{
"token" : "中華人民共和國(guó)",
"start_offset" : 0,
"end_offset" : 7,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "中華人民",
"start_offset" : 0,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "中華",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 2
},
{
"token" : "華人",
"start_offset" : 1,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 3
},
{
"token" : "人民共和國(guó)",
"start_offset" : 2,
"end_offset" : 7,
"type" : "CN_WORD",
"position" : 4
},
{
"token" : "人民",
"start_offset" : 2,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 5
},
{
"token" : "共和國(guó)",
"start_offset" : 4,
"end_offset" : 7,
"type" : "CN_WORD",
"position" : 6
},
{
"token" : "共和",
"start_offset" : 4,
"end_offset" : 6,
"type" : "CN_WORD",
"position" : 7
},
{
"token" : "國(guó)",
"start_offset" : 6,
"end_offset" : 7,
"type" : "CN_CHAR",
"position" : 8
}
]
}
這里其實(shí)和mapping的使用差不多,只是在mapping的字段屬性中添加一個(gè) “analyzer” 屬性,指定使用的分詞器而已。其他都沒(méi)有區(qū)別,這里不重復(fù)
ES在數(shù)十億級(jí)別的數(shù)據(jù)如何提高檢索效率?
? 這個(gè)問(wèn)題說(shuō)白了,就是看你有沒(méi)有實(shí)際用過(guò) ES,因?yàn)樯??其?shí) ES 性能并沒(méi)有你想象中那么好的。很多時(shí)候數(shù)據(jù)量大了,特別是有幾億條數(shù)據(jù)的時(shí)候,可能你會(huì)懵逼的發(fā)現(xiàn),跑個(gè)搜索怎么一下 5~10s,坑爹了。第一次搜索的時(shí)候,是 5~10s,后面反而就快了,可能就幾百毫秒。
? 然后你就很懵,每個(gè)用戶第一次訪問(wèn)都會(huì)比較慢,比較卡么?所以你要是沒(méi)玩兒過(guò) ES,或者就是自己玩玩兒 Demo,被問(wèn)到這個(gè)問(wèn)題容易懵逼,顯示出你對(duì) ES 確實(shí)玩的不怎么樣?說(shuō)實(shí)話,ES 性能優(yōu)化是沒(méi)有銀彈的。啥意思呢?就是不要期待著隨手調(diào)一個(gè)參數(shù),就可以萬(wàn)能的應(yīng)對(duì)所有的性能慢的場(chǎng)景。也許有的場(chǎng)景是你換個(gè)參數(shù),或者調(diào)整一下語(yǔ)法,就可以搞定,但是絕對(duì)不是所有場(chǎng)景都可以這樣。
? 下面看看幾個(gè)優(yōu)化的手段
圖5.1 ES filesytem cache
? 你往 ES 里寫的數(shù)據(jù),實(shí)際上都寫到磁盤文件里去了,查詢的時(shí)候,操作系統(tǒng)會(huì)將磁盤文件里的數(shù)據(jù)自動(dòng)緩存到 Filesystem Cache 里面去。ES 的搜索引擎嚴(yán)重依賴于底層的 Filesystem Cache,你如果給 Filesystem Cache 更多的內(nèi)存,盡量讓內(nèi)存可以容納所有的 IDX Segment File 索引數(shù)據(jù)文件,那么你搜索的時(shí)候就基本都是走內(nèi)存的,性能會(huì)非常高。
問(wèn)題:直接讀取硬盤數(shù)據(jù)和從緩存讀取數(shù)據(jù),性能差距究竟可以有多大?
回答:
我們之前很多的測(cè)試和壓測(cè),如果走磁盤一般肯定上秒,搜索性能絕對(duì)是秒級(jí)別的,1 秒、5 秒、10 秒。但如果是走 Filesystem Cache,是走純內(nèi)存的,那么一般來(lái)說(shuō)性能比走磁盤要高一個(gè)數(shù)量級(jí),基本上就是毫秒級(jí)的,從幾毫秒到幾百毫秒不等。
案例:
? 來(lái)看一個(gè)真實(shí)的案例:某個(gè)公司 ES 節(jié)點(diǎn)有 3 臺(tái)機(jī)器,每臺(tái)機(jī)器看起來(lái)內(nèi)存很多 64G,總內(nèi)存就是 64 3 = 192G。每臺(tái)機(jī)器給 ES JVM Heap 是 32G,那么剩下來(lái)留給 Filesystem Cache 的就是每臺(tái)機(jī)器才 32G,總共集群里給 Filesystem Cache 的就是 32 3 = 96G 內(nèi)存。
? 而此時(shí),整個(gè)磁盤上索引數(shù)據(jù)文件,在 3 臺(tái)機(jī)器上一共占用了 1T 的磁盤容量,ES 數(shù)據(jù)量是 1T,那么每臺(tái)機(jī)器的數(shù)據(jù)量是 300G。這樣性能會(huì)好嗎?
? Filesystem Cache 的內(nèi)存才 100G,十分之一的數(shù)據(jù)可以放內(nèi)存,其他的都在磁盤,然后你執(zhí)行搜索操作,大部分操作都是走磁盤,性能肯定差。
? 首先要知道一點(diǎn):歸根結(jié)底,你要讓 ES 性能好,最佳的情況下,就是你的機(jī)器的內(nèi)存,至少可以容納你的總數(shù)據(jù)量的一半。當(dāng)然如果內(nèi)存能容納全部數(shù)據(jù),自然是最好,然而基本生產(chǎn)中沒(méi)有那么多錢的啦。走內(nèi)存可以滿足秒級(jí)以內(nèi)的查詢要求
1、去掉寫入ES的doc中不必要的字段
如果一個(gè)doc中有很多字段,但是有些字段壓根是沒(méi)用的(也就是說(shuō)該字段不會(huì)用于搜索),但是讀取的時(shí)候仍舊會(huì)將這些字段都讀取,然后緩存到filesytem cache中,占據(jù)了大量空間,導(dǎo)致后面的數(shù)據(jù)只能重新從硬盤中讀取。這個(gè)時(shí)候就要想著取消一些沒(méi)怎么用的字段了。減小索引的體積。從而節(jié)省filesytem cache空間
2、采用 ES+HBase架構(gòu)
? 之前也說(shuō)到,es可以只存儲(chǔ)索引,不存儲(chǔ)原始doc數(shù)據(jù);或者只存儲(chǔ)某些字段的原始數(shù)據(jù)。通常完整的原始數(shù)據(jù)都保存在hbase中,然后通過(guò)rowkey作為docid導(dǎo)入到es中,最終通過(guò)這個(gè)rowkey進(jìn)行唯一性關(guān)聯(lián)。為什么要采用這種架構(gòu)呢?
? 比如說(shuō)你現(xiàn)在有一行數(shù)據(jù):id,name,age .... 30 個(gè)字段。但是你現(xiàn)在搜索,只需要根據(jù) id,name,age 三個(gè)字段來(lái)搜索。如果你傻乎乎往 ES 里寫入一行數(shù)據(jù)所有的字段,就會(huì)導(dǎo)致 90% 的數(shù)據(jù)是不用來(lái)搜索的。但是呢,這些數(shù)據(jù)硬是占據(jù)了 ES 機(jī)器上的 Filesystem Cache 的空間,單條數(shù)據(jù)的數(shù)據(jù)量越大,就會(huì)導(dǎo)致 Filesystem Cahce 能緩存的數(shù)據(jù)就越少。其實(shí),僅僅寫入 ES 中要用來(lái)檢索的少數(shù)幾個(gè)字段就可以了,比如說(shuō)就寫入 es id,name,age 三個(gè)字段。然后你可以把其他的字段數(shù)據(jù)存在 MySQL/HBase 里,我們一般是建議用 ES + HBase 這么一個(gè)架構(gòu)(官方建議的方案)。
? HBase是列式數(shù)據(jù)庫(kù),其特點(diǎn)是適用于海量數(shù)據(jù)的在線存儲(chǔ),就是對(duì) HBase 可以寫入海量數(shù)據(jù),但是不要做復(fù)雜的搜索,做很簡(jiǎn)單的一些根據(jù) id 或者范圍進(jìn)行查詢的這么一個(gè)操作就可以了。hbase非常適合這種簡(jiǎn)單通過(guò)key直接獲取數(shù)據(jù)的應(yīng)用場(chǎng)景。
? 例如:從 ES 中根據(jù) name 和 age 去搜索,拿到的結(jié)果可能就 20 個(gè) doc id,然后根據(jù) doc id 到 HBase 里去查詢每個(gè) doc id 對(duì)應(yīng)的完整的數(shù)據(jù),給查出來(lái),再返回給前端。而寫入 ES 的數(shù)據(jù)最好小于等于,或者是略微大于 ES 的 Filesystem Cache 的內(nèi)存容量。然后你從 ES 檢索可能就花費(fèi) 20ms,然后再根據(jù) ES 返回的 id 去 HBase 里查詢,查 20 條數(shù)據(jù),可能也就耗費(fèi)個(gè) 30ms。如果你像原來(lái)那么玩兒,1T 數(shù)據(jù)都放 ES,可能會(huì)每次查詢都是 5~10s,而現(xiàn)在性能就會(huì)很高,每次查詢就是 50ms。
? 從概率上來(lái)說(shuō),大部分的訪問(wèn)量往往集中小部分的數(shù)據(jù)上,也就是我們所說(shuō)的數(shù)據(jù)熱點(diǎn)的情況。數(shù)據(jù)預(yù)熱通常就是事先將一些可能有大量訪問(wèn)的數(shù)據(jù)先通過(guò)手動(dòng)訪問(wèn)讓它們提前緩存到cache中,然而后面的用戶訪問(wèn)這些數(shù)據(jù)時(shí),就直接走cache查詢了,非???。而且這些數(shù)據(jù)因?yàn)樵L問(wèn)量多,所以還需要保證這些熱點(diǎn)數(shù)據(jù)不要被其他非熱點(diǎn)數(shù)據(jù)加載到cache時(shí),被覆蓋掉了。這就需要時(shí)常手動(dòng)訪問(wèn),加載數(shù)據(jù)到cache中。
? 例子:
? 比如電商,你可以將平時(shí)查看最多的一些商品,比如說(shuō) iPhone 8,熱數(shù)據(jù)提前后臺(tái)搞個(gè)程序,每隔 1 分鐘自己主動(dòng)訪問(wèn)一次,刷到 Filesystem Cache 里去。
? 總之,就是對(duì)于那些你覺(jué)得比較熱的、經(jīng)常會(huì)有人訪問(wèn)的數(shù)據(jù),最好做一個(gè)專門的緩存預(yù)熱子系統(tǒng)。然后對(duì)熱數(shù)據(jù)每隔一段時(shí)間,就提前訪問(wèn)一下,讓數(shù)據(jù)進(jìn)入 Filesystem Cache 里面去。這樣下次別人訪問(wèn)的時(shí)候,性能一定會(huì)好很多。
? 這個(gè)也是數(shù)據(jù)熱點(diǎn)的問(wèn)題。ES 可以做類似于 MySQL 的水平拆分,就是說(shuō)將大量的訪問(wèn)很少、頻率很低的數(shù)據(jù),單獨(dú)寫一個(gè)索引,然后將訪問(wèn)很頻繁的熱數(shù)據(jù)單獨(dú)寫一個(gè)索引。最好是將冷數(shù)據(jù)寫入一個(gè)索引中,然后熱數(shù)據(jù)寫入另外一個(gè)索引中,這樣可以確保熱數(shù)據(jù)在被預(yù)熱之后,盡量都讓他們留在 Filesystem OS Cache 里,別讓冷數(shù)據(jù)給沖刷掉。
? 還是來(lái)一個(gè)例子,假設(shè)你有 6 臺(tái)機(jī)器,2 個(gè)索引,一個(gè)放冷數(shù)據(jù),一個(gè)放熱數(shù)據(jù),每個(gè)索引 3 個(gè) Shard。3 臺(tái)機(jī)器放熱數(shù)據(jù) Index,另外 3 臺(tái)機(jī)器放冷數(shù)據(jù) Index。這樣的話,你大量的時(shí)間是在訪問(wèn)熱數(shù)據(jù) Index,熱數(shù)據(jù)可能就占總數(shù)據(jù)量的 10%,此時(shí)數(shù)據(jù)量很少,幾乎全都保留在 Filesystem Cache 里面了,就可以確保熱數(shù)據(jù)的訪問(wèn)性能是很高的。
? 但是對(duì)于冷數(shù)據(jù)而言,是在別的 Index 里的,跟熱數(shù)據(jù) Index 不在相同的機(jī)器上,大家互相之間都沒(méi)什么聯(lián)系了。如果有人訪問(wèn)冷數(shù)據(jù),可能大量數(shù)據(jù)是在磁盤上的,此時(shí)性能差點(diǎn),就 10% 的人去訪問(wèn)冷數(shù)據(jù),90% 的人在訪問(wèn)熱數(shù)據(jù),也無(wú)所謂了。
? 對(duì)于 MySQL,我們經(jīng)常有一些復(fù)雜的關(guān)聯(lián)查詢,在 ES 里該怎么玩兒?ES 里面的復(fù)雜的關(guān)聯(lián)查詢盡量別用,一旦用了性能一般都不太好。最好是先在 Java 系統(tǒng)里就完成關(guān)聯(lián),將關(guān)聯(lián)好的數(shù)據(jù)直接寫入 ES 中。搜索的時(shí)候,就不需要利用 ES 的搜索語(yǔ)法來(lái)完成 Join 之類的關(guān)聯(lián)搜索了。
? Document 模型設(shè)計(jì)是非常重要的,很多操作,不要在搜索的時(shí)候才想去執(zhí)行各種復(fù)雜的亂七八糟的操作。
? ES 能支持的操作就那么多,不要考慮用 ES 做一些它不好操作的事情。如果真的有那種操作,盡量在 Document 模型設(shè)計(jì)的時(shí)候,寫入的時(shí)候就完成。另外對(duì)于一些太復(fù)雜的操作,比如 join/nested/parent-child 搜索都要盡量避免,性能都很差的。
? 總結(jié)一句就是說(shuō),ES不適合執(zhí)行復(fù)雜查詢操作
背景:
ES 的分頁(yè)是較坑的,為啥呢?舉個(gè)例子吧,假如你每頁(yè)是 10 條數(shù)據(jù),你現(xiàn)在要查詢第 100 頁(yè),實(shí)際上是會(huì)把每個(gè) Shard 上存儲(chǔ)的前 1000 條數(shù)據(jù)都查到一個(gè)協(xié)調(diào)節(jié)點(diǎn)上。如果你有 5 個(gè) Shard,那么就有 5000 條數(shù)據(jù),接著協(xié)調(diào)節(jié)點(diǎn)對(duì)這 5000 條數(shù)據(jù)進(jìn)行一些合并、處理,再獲取到最終第 100 頁(yè)的 10 條數(shù)據(jù)。
由于是分布式的,你要查第 100 頁(yè)的 10 條數(shù)據(jù),不可能說(shuō)從 5 個(gè) Shard,每個(gè) Shard 就查 2 條數(shù)據(jù),最后到協(xié)調(diào)節(jié)點(diǎn)合并成 10 條數(shù)據(jù)吧?你必須得從每個(gè) Shard 都查 1000 條數(shù)據(jù)過(guò)來(lái),然后根據(jù)你的需求進(jìn)行排序、篩選等等操作,最后再次分頁(yè),拿到里面第 100 頁(yè)的數(shù)據(jù)。
也就是說(shuō),你翻頁(yè)的時(shí)候,翻的越深,每個(gè) Shard 返回的數(shù)據(jù)就越多,而且協(xié)調(diào)節(jié)點(diǎn)處理的時(shí)間越長(zhǎng),非??拥K杂?ES 做分頁(yè)的時(shí)候,你會(huì)發(fā)現(xiàn)越翻到后面,就越是慢。
我們之前也是遇到過(guò)這個(gè)問(wèn)題,用 ES 作分頁(yè),前幾頁(yè)就幾十毫秒,翻到 10 頁(yè)或者幾十頁(yè)的時(shí)候,基本上就要 5~10 秒才能查出來(lái)一頁(yè)數(shù)據(jù)了。
解決方案:
1、不允許深度分頁(yè)(默認(rèn)深度分頁(yè)性能很差)。跟產(chǎn)品經(jīng)理說(shuō),你系統(tǒng)不允許翻那么深的頁(yè),默認(rèn)翻的越深,性能就越差。
2、類似于 App 里的推薦商品不斷下拉出來(lái)一頁(yè)一頁(yè)的;類似于微博中,下拉刷微博,刷出來(lái)一頁(yè)一頁(yè)的,你可以用 Scroll API,關(guān)于如何使用,大家可以自行上網(wǎng)搜索學(xué)習(xí)一下。
Scroll是如何做的呢?它會(huì)一次性給你生成所有數(shù)據(jù)的一個(gè)快照,然后每次滑動(dòng)向后翻頁(yè)就是通過(guò)游標(biāo) scroll_id 移動(dòng),獲取下一頁(yè)、下一頁(yè)這樣子,性能會(huì)比上面說(shuō)的那種分頁(yè)性能要高很多很多,基本上都是毫秒級(jí)的。
但是,唯一的一點(diǎn)就是,這個(gè)適合于那種類似微博下拉翻頁(yè)的,不能隨意跳到任何一頁(yè)的場(chǎng)景。也就是說(shuō),你不能先進(jìn)入第 10 頁(yè),然后去第 120 頁(yè),然后又回到第 58 頁(yè),不能隨意亂跳頁(yè)。所以現(xiàn)在很多產(chǎn)品,都是不允許你隨意翻頁(yè)的,你只能往下拉,一頁(yè)一頁(yè)的翻。
使用時(shí)需要注意,初始化必須指定 Scroll 參數(shù),告訴 ES 要保存此次搜索的上下文多長(zhǎng)時(shí)間。你需要確保用戶不會(huì)持續(xù)不斷翻頁(yè)翻幾個(gè)小時(shí),否則可能因?yàn)槌瑫r(shí)而失敗。
除了用 Scroll API,你也可以用 search_after 來(lái)做。search_after 的思想是使用前一頁(yè)的結(jié)果來(lái)幫助檢索下一頁(yè)的數(shù)據(jù)。
顯然,這種方式也不允許你隨意翻頁(yè),你只能一頁(yè)頁(yè)往后翻。初始化時(shí),需要使用一個(gè)唯一值的字段作為 Sort 字段。