[TOC]前端
搜索,就是在任何場景下,找尋想要的信息。經過關鍵字檢索出與此關鍵字有關的信息。這和查詢還不太同樣,查詢一般是在表格類型的數據中查找,字段的內容的長度每每不大。java
傳統數據庫的狀況下,若是要查詢某個字段是否包含某些關鍵字的話,須要使用到like關鍵字來進行字段匹配,很大機率致使全表掃描,自己來講性能就不算好。若是再加上查詢的字段很是長,那麼使用like匹配的工做量是很大的,另外若是表的行數也不少,那麼性能就更差了。node
全文檢索是指計算機索引程序經過掃描文章中的每個詞,對每個詞創建一個索引,指明該詞在文章中出現的次數和位置,當用戶查詢時,檢索程序就根據事先創建的索引進行查找,並將查找的結果反饋給用戶的檢索方式。這個過程相似於經過字典中的檢索字表查字的過程。全文搜索引擎數據庫中的數據。而全文檢索用到的關鍵技術就是倒排索引。什麼是倒排索引?看看例子就知道了git
數據庫中有以下數據 id 員工描述 1 優秀論文 2 優秀員工稱號 3 優秀項目 4 優秀團隊 創建倒排索引的步驟: 一、每行切詞, 怎麼切均可以,看實際須要 1 優秀 論文 2 優秀 員工 稱號 3 優秀 項目 4 優秀 團隊 二、創建倒排索引 優秀 1,2,3,4 論文 1 員工 2 稱號 2 項目 3 團隊 4 三、檢索 倒排索引意思簡單就是指定的詞出如今哪些行中,這些行都用惟一id進行標識。 因此這就是爲何倒排索引用到全文檢索中,由於能夠直接查詢到包含相關關鍵字的內容有哪些。 好比搜索優秀,能夠看到優秀這個詞在1234中都有出現,而後根據id查詢原始數據。
有了倒排索引,當咱們須要從不少端很長的內容中檢索包含指定關鍵字的內容時,直接根據倒排索引就知道有沒有指定關鍵字了。而若是使用傳統數據庫,那麼必須掃描所有內容,若是數據有1000行,那工做量就很恐怖了。而倒排索引只是查詢個關鍵字而已,無需掃描所有內容。es6
Lucene就是一個jar包,裏面包含了封裝好的各類創建倒排索引,以及進行搜索的代碼,包括各類算法。咱們就用java開發的時候,引入lucene jar,而後基於lucene的api進行去進行開發就能夠了。可是它只是根據文本作出索引,而後保存下來,可是自己並不提供搜索功能。
因爲Lucene使用比較複雜,繁瑣,因此基於Lucene開發了一個新的項目,也就是Elasticsearch(簡稱ES)。github
特色:算法
1)能夠做爲一個大型分佈式集羣(數百臺服務器)技術,處理PB級數據,服務大公司;也能夠運行在單機上,服務小公司; 2)Elasticsearch不是什麼新技術,主要是將全文檢索、數據分析以及分佈式技術,合併在了一塊兒,才造成了獨一無二的ES;lucene(全文檢索),商用的數據分析軟件(也是有的),分佈式數據庫(mycat); 3)對用戶而言,是開箱即用的,很是簡單,做爲中小型的應用,直接3分鐘部署一下ES,就能夠做爲生產環境的系統來使用了,數據量不大,操做不是太複雜; 4)數據庫的功能面對不少領域是不夠用的(事務,還有各類聯機事務型的操做);特殊的功能,好比全文檢索,同義詞處理,相關度排名,複雜數據分析,海量數據的近實時處理;Elasticsearch做爲傳統數據庫的一個補充,提供了數據庫所不能提供的不少功能。
適用場景:chrome
1)維基百科,相似百度百科,牙膏,牙膏的維基百科,全文檢索,高亮,搜索推薦。 2)The Guardian(國外新聞網站),相似搜狐新聞,用戶行爲日誌(點擊,瀏覽,收藏,評論)+ 社交網絡數據(對某某新聞的相關見解),數據分析,給到每篇新聞文章的做者,讓他知道他的文章的公衆反饋(好,壞,熱門,垃圾,鄙視,崇拜)。 3)Stack Overflow(國外的程序異常討論論壇),IT問題,程序的報錯,提交上去,有人會跟你討論和回答,全文檢索,搜索相關問題和答案,程序報錯了,就會將報錯信息粘貼到裏面去,搜索有沒有對應的答案。 4)GitHub(開源代碼管理),搜索上千億行代碼。 5)國內:站內搜索(電商,招聘,門戶,等等),IT系統搜索(OA,CRM,ERP,等等),數據分析(ES熱門的一個使用場景)。
近實時數據庫
兩個意思,從寫入數據到數據能夠被搜索到有一個小延遲(大概1秒);基於es執行搜索和分析能夠達到秒級。
集羣clusterapache
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', } } } } });
寫流程:
一、客戶端根據提供的es節點,選擇一個node做爲協調節點,併發送寫請求 二、協調節點對寫入的document進行路由,將document進行分片。每一個分片單獨進行寫,每一個分片默認都是雙備份,寫在不一樣的節點上。 三、分片寫入時,主備份由協調節點寫入,副備份則是從主備份所在節點同步數據過去。 四、當分片都寫完後,由協調節點返回寫入完成給客戶端
讀流程:
讀流程就很簡單了,若是經過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
一、默認狀況下,一個節點是數據節點,也是master節點。對於3-5個節點的小集羣來說,一般讓全部節點存儲數據和具備得到主節點的資格。你能夠將任何請求發送給任何節點,而且因爲全部節點都具備集羣狀態的副本,它們知道如何路由請求。多個master的元數據也會同步,不用擔憂不一致。要注意,master節點的數量最好最少爲3,且爲單數 二、當集羣節點數量比較大時,那麼一般就會將主節點、數據節點分開,專門部署在對應的節點上,而後主節點是多個均可用的,造成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 { //一、建立es配置對象 Settings settings = Settings.builder().put("cluster.name", "my-application").build(); //二、鏈接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() { //一、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 { //二、另一種方式添加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 { //三、經過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 { //四、一次請求內部添加多個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的數據時, 一、採用source方式時: 只需產生一次磁盤IO,由於_source存儲的時候,直接把整個doc當作一個字段來存儲。當咱們須要doc中的某個字段時,是先從source讀取數據,而後再解析成json,獲取到指定字段內容 二、採用store方式時, 由於每一個字段都單獨存儲了,當須要得到整個doc的數據時,就須要單獨每一個字段進行取值,有多少個字段就產生多少次磁盤IO。 三、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
一、 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 } ] } 二、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 性能好,最佳的狀況下,就是你的機器的內存,至少能夠容納你的總數據量的一半。固然若是內存能容納所有數據,天然是最好,然而基本生產中沒有那麼多錢的啦。走內存能夠知足秒級之內的查詢要求
一、去掉寫入ES的doc中沒必要要的字段
若是一個doc中有不少字段,可是有些字段壓根是沒用的(也就是說該字段不會用於搜索),可是讀取的時候仍舊會將這些字段都讀取,而後緩存到filesytem cache中,佔據了大量空間,致使後面的數據只能從新從硬盤中讀取。這個時候就要想着取消一些沒怎麼用的字段了。減少索引的體積。從而節省filesytem cache空間
二、採用 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 秒才能查出來一頁數據了。
解決方案:
一、不容許深度分頁(默認深度分頁性能不好)。跟產品經理說,你係統不容許翻那麼深的頁,默認翻的越深,性能就越差。 二、相似於 App 裏的推薦商品不斷下拉出來一頁一頁的;相似於微博中,下拉刷微博,刷出來一頁一頁的,你能夠用 Scroll API,關於如何使用,你們能夠自行上網搜索學習一下。 Scroll是如何作的呢?它會一次性給你生成全部數據的一個快照,而後每次滑動向後翻頁就是經過遊標 scroll_id 移動,獲取下一頁、下一頁這樣子,性能會比上面說的那種分頁性能要高不少不少,基本上都是毫秒級的。 可是,惟一的一點就是,這個適合於那種相似微博下拉翻頁的,不能隨意跳到任何一頁的場景。也就是說,你不能先進入第 10 頁,而後去第 120 頁,而後又回到第 58 頁,不能隨意亂跳頁。因此如今不少產品,都是不容許你隨意翻頁的,你只能往下拉,一頁一頁的翻。 使用時須要注意,初始化必須指定 Scroll 參數,告訴 ES 要保存這次搜索的上下文多長時間。你須要確保用戶不會持續不斷翻頁翻幾個小時,不然可能由於超時而失敗。 除了用 Scroll API,你也能夠用 search_after 來作。search_after 的思想是使用前一頁的結果來幫助檢索下一頁的數據。 顯然,這種方式也不容許你隨意翻頁,你只能一頁頁日後翻。初始化時,須要使用一個惟一值的字段做爲 Sort 字段。