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