一、Elasticsearch原理與基本使用

[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)銷(xiāo)推廣。我們專(zhuān)注企業(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ù)。

一、Elasticsearch概述

1.1 什么是搜索

? 搜索,就是在任何場(chǎng)景下,找尋想要的信息。通過(guò)關(guān)鍵字檢索出與此關(guān)鍵字有關(guān)的信息。這和查詢還不太一樣,查詢通常是在表格類(lèi)型的數(shù)據(jù)中查找,字段的內(nèi)容的長(zhǎng)度往往不大。

1.2 使用傳統(tǒng)數(shù)據(jù)庫(kù)實(shí)現(xiàn)搜索

? 傳統(tǒng)數(shù)據(jù)庫(kù)的情況下,如果要查詢某個(gè)字段是否包含某些關(guān)鍵字的話,需要使用到like關(guān)鍵字來(lái)進(jìn)行字段匹配,很大概率導(dǎo)致全表掃描,本身來(lái)說(shuō)性能就不算好。如果再加上查詢的字段非常長(zhǎng),那么使用like匹配的工作量是很大的,另外如果表的行數(shù)也很多,那么性能就更差了。

1.3 全文檢索與倒排索引

? 全文檢索是指計(jì)算機(jī)索引程序通過(guò)掃描文章中的每一個(gè)詞,對(duì)每一個(gè)詞建立一個(gè)索引,指明該詞在文章中出現(xiàn)的次數(shù)和位置,當(dāng)用戶查詢時(shí),檢索程序就根據(jù)事先建立的索引進(jìn)行查找,并將查找的結(jié)果反饋給用戶的檢索方式。這個(gè)過(guò)程類(lèi)似于通過(guò)字典中的檢索字表查字的過(guò)程。全文搜索引擎數(shù)據(jù)庫(kù)中的數(shù)據(jù)。而全文檢索用到的關(guān)鍵技術(shù)就是倒排索引。什么是倒排索引?看看例子就知道了

數(shù)據(jù)庫(kù)中有如下數(shù)據(jù)
id  員工描述
1   優(yōu)秀論文
2   優(yōu)秀員工稱(chēng)號(hào)
3   優(yōu)秀項(xiàng)目
4   優(yōu)秀團(tuán)隊(duì)

建立倒排索引的步驟:
1、每行切詞, 怎么切都可以,看實(shí)際需要
1 優(yōu)秀    論文
2 優(yōu)秀    員工  稱(chēng)號(hào)
3 優(yōu)秀    項(xiàng)目
4 優(yōu)秀    團(tuán)隊(duì)

2、建立倒排索引
優(yōu)秀  1,2,3,4
論文  1
員工  2
稱(chēng)號(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)容。

1.4 Lucene和Elasticsearch

? 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)稱(chēng)ES)。

1.5 ES的特點(diǎn)與適用場(chǎng)景

特點(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)維基百科,類(lèi)似百度百科,牙膏,牙膏的維基百科,全文檢索,高亮,搜索推薦。
2)The Guardian(國(guó)外新聞網(wǎng)站),類(lèi)似搜狐新聞,用戶行為日志(點(diǎn)擊,瀏覽,收藏,評(píng)論)+ 社交網(wǎng)絡(luò)數(shù)據(jù)(對(duì)某某新聞的相關(guān)看法),數(shù)據(jù)分析,給到每篇新聞文章的作者,讓他知道他的文章的公眾反饋(好,壞,熱門(mén),垃圾,鄙視,崇拜)。
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)搜索(電商,招聘,門(mén)戶,等等),IT系統(tǒng)搜索(OA,CRM,ERP,等等),數(shù)據(jù)分析(ES熱門(mén)的一個(gè)使用場(chǎng)景)。

1.6 ES中的相關(guān)概念

近實(shí)時(shí)

兩個(gè)意思,從寫(xiě)入數(shù)據(jù)到數(shù)據(jù)可以被搜索到有一個(gè)小延遲(大概1秒);基于es執(zhí)行搜索和分析可以達(dá)到秒級(jí)。

集群cluster

ES集群可以有多個(gè)節(jié)點(diǎn),但是每個(gè)節(jié)點(diǎn)屬于哪個(gè)ES集群中是通過(guò)配置集群名稱(chēng)來(lái)指定的。當(dāng)然一個(gè)集群只有一個(gè)節(jié)點(diǎn)也是OK的

節(jié)點(diǎn)node

集群中的一個(gè)節(jié)點(diǎn),節(jié)點(diǎn)也有一個(gè)名稱(chēng)(默認(rèn)是隨機(jī)分配的),節(jié)點(diǎn)名稱(chēng)很重要(在執(zhí)行運(yùn)維管理操作的時(shí)候),默認(rèn)節(jié)點(diǎn)會(huì)去加入一個(gè)名稱(chēng)為“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è)客戶索引,商品分類(lèi)索引,訂單索引,索引有一個(gè)名稱(chēng)。一個(gè)index包含很多document,一個(gè)index就代表了一類(lèi)類(lèi)似的或者相同的document。比如說(shuō)建立一個(gè)product index,商品索引,里面可能就存放了所有的商品數(shù)據(jù),所有的商品document。類(lèi)似于傳統(tǒng)數(shù)據(jù)庫(kù)中的庫(kù)的概念

type--table

    每個(gè)索引里都可以有一個(gè)或多個(gè)type,type是index中的一個(gè)邏輯數(shù)據(jù)分類(lèi),一個(gè)type下的document,都有相同的field,比如博客系統(tǒng),有一個(gè)索引,可以定義用戶數(shù)據(jù)type,博客數(shù)據(jù)type,評(píng)論數(shù)據(jù)type。類(lèi)似于傳統(tǒng)數(shù)據(jù)庫(kù)中的表的概念。
    要注意:es逐漸拋棄掉這個(gè)概念了,到6.x版本中,已經(jīng)只允許一個(gè)index只有一個(gè)type了。

document--行

    文檔是es中的最小數(shù)據(jù)單元,一個(gè)document可以是一條客戶數(shù)據(jù),一條商品分類(lèi)數(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ù)類(lèi)型、是否存儲(chǔ)、是否分詞等。所謂映射是對(duì)type的存儲(chǔ)的一些限制。
例子:
    這樣就創(chuàng)建了一個(gè)名為blog的Index。Type不用單獨(dú)創(chuàng)建,在創(chuàng)建Mapping 時(shí)指定就可以。Mapping用來(lái)定義Document中每個(gè)字段的類(lèi)型,即所使用的 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.7 ES讀寫(xiě)數(shù)據(jù)的機(jī)制

寫(xiě)流程:

1、客戶端根據(jù)提供的es節(jié)點(diǎn),選擇一個(gè)node作為協(xié)調(diào)節(jié)點(diǎn),并發(fā)送寫(xiě)請(qǐng)求
2、協(xié)調(diào)節(jié)點(diǎn)對(duì)寫(xiě)入的document進(jìn)行路由,將document進(jìn)行分片。每個(gè)分片單獨(dú)進(jìn)行寫(xiě),每個(gè)分片默認(rèn)都是雙備份,寫(xiě)在不同的節(jié)點(diǎn)上。
3、分片寫(xiě)入時(shí),主備份由協(xié)調(diào)節(jié)點(diǎn)寫(xiě)入,副備份則是從主備份所在節(jié)點(diǎn)同步數(shù)據(jù)過(guò)去。
4、當(dāng)分片都寫(xiě)完后,由協(xié)調(diào)節(jié)點(diǎn)返回寫(xiě)入完成給客戶端

讀流程:

讀流程就很簡(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.8 ES數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)

一、Elasticsearch原理與基本使用

圖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ù)類(lèi)型、是否存儲(chǔ)、是否分詞等。
3)文檔(document):一條數(shù)據(jù)記錄,存在索引對(duì)象上 。es會(huì)給每個(gè)document生成一個(gè)唯一的documentID,用于標(biāo)識(shí)該document。當(dāng)然也可以手動(dòng)指定docid
4)文檔類(lèi)型(type):一個(gè)索引對(duì)象,存放多種類(lèi)型數(shù)據(jù),數(shù)據(jù)用文檔類(lèi)型進(jìn)行標(biāo)識(shí)。

二、ES部署

使用的es版本為:6.6.2
下載地址:https://www.elastic.co/products/elasticsearch

2.1 單節(jié)點(diǎn)部署

解壓程序到指定目錄:

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 -------------------------------------
# 集群名稱(chēng)
cluster.name: my-application
# ------------------------------------ Node --------------------------------------
# 節(jié)點(diǎn)名稱(chēng),需要保證全局唯一
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"
}
這樣就正常了

2.2 多節(jié)點(diǎn)部署

2.2.1 es集群節(jié)點(diǎn)類(lèi)型

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

2.2.2 es集群常用部署方案

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),專(zhuān)門(mén)部署在對(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ō)

2.3 安裝head插件

用qq瀏覽器或者chrome,直接到應(yīng)用商店搜索elasticsearch-head,直接安裝插件即可

?

三、java api操作ES

3.1 基本操作

3.1.1 maven依賴準(zhǔn)備

<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>

另外需要自己添加一個(gè)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進(jìn)行運(yùn)行測(cè)試,不會(huì)用的自己百度

3.1.2 創(chuàng)建ES連接操作對(duì)象

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());

    }
}

3.1.3 索引操作

// .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();
    }

3.1.4 添加doc

@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<String, Object> json = new HashMap<>();
        json.put("name","spark從入門(mén)到放棄");
        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

3.1.5 搜索doc

根據(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();
    }

3.1.6 更新doc

@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();
    }

3.1.7 刪除doc

@Test
    public void deleteDocument() {
        //刪除document
        DeleteResponse deleteResponse = client.prepareDelete("blog", "article", "6").get();
        System.out.println(deleteResponse.getResult());
        client.close();
    }

3.2 條件查詢doc

關(guān)鍵性一個(gè)類(lèi)是 org.elasticsearch.index.query.QueryBuilders;

3.2.1 查詢指定index所有doc

@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();
    }

3.2.2 全部字段進(jìn)行全文檢索

搜索全部字段中包含指定字符的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();
        }
    }

3.2.3 通配符字段全文檢索

@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)容,類(lèi)似like操作

3.2.4 對(duì)指定字段進(jìn)行分詞搜索

@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é)果不包含。

3.2.5對(duì)指定字段進(jìn)行模糊檢索

@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很類(lèi)似,但是有區(qū)別。感興趣的話可以自己查找資料。這個(gè)方法比較少用

3.3 映射mapping

3.3.1 mapping的定義

? 映射是規(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ù)依然可以成功寫(xiě)入并生成各自的mapping,但實(shí)際上fielddata中的索引結(jié)果卻依然是以index內(nèi)第一個(gè)mapping類(lèi)型來(lái)生成的

3.3.2 mapping的寫(xiě)法

定義mapping時(shí),依舊是使用json格式定義定義。一般格式如下:

{
    元數(shù)據(jù)屬性字段,如:
    _type:是哪個(gè)type的mapping,還是那句話,type基本不怎么提了
    _index:屬于哪個(gè)index
    。。。。。。。
    properties:{
        "field1":{
            字段屬性字段,如:
            type:字段數(shù)據(jù)類(lèi)型
        }
         "field2":{
            字段屬性字段,如:
            type:字段數(shù)據(jù)類(lèi)型
        }
         。。。。。。。。。
    }

}

基本格式就是這樣,分為兩大部分,一個(gè)是整個(gè)index 的元數(shù)據(jù)信息,一個(gè)是針對(duì)具體type中的字段信息。

3.3.3 數(shù)據(jù)類(lèi)型

核心數(shù)據(jù)類(lèi)型
字符串: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ù)類(lèi)型
數(shù)組:array
對(duì)象:object
堆疊/嵌套對(duì)象: nested
地理:geo_point,geo_point
IP: ip
字符個(gè)數(shù):token_count(輸入一個(gè)字符串,保存的是它的長(zhǎng)度)

3.3.4mapping屬性字段

元數(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 :類(lèi)型
_uid :類(lèi)型和id的組合 uid字段的值可以在查詢、聚合、腳本和排序中訪問(wèn):
_parent :父類(lèi),可用于關(guān)聯(lián)兩個(gè)索引

字段屬性:

type 數(shù)據(jù)類(lèi)型 
改屬性用來(lái)指定字段的數(shù)據(jù)類(lèi)型,一但指點(diǎn)后就不能再修改,如果數(shù)據(jù)不是以設(shè)置的數(shù)據(jù)類(lèi)型傳入,es會(huì)去轉(zhuǎn)換數(shù)據(jù),裝換不成功則報(bào)錯(cuò)。具體可配置的參數(shù),可看前面的數(shù)據(jù)類(lèi)型說(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類(lèi)型進(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ù)類(lèi)型和映射類(lèi)型不一致的情況下是否強(qiáng)制轉(zhuǎn)換數(shù)據(jù)類(lèi)型。默認(rèn)是開(kāi)啟的

normalizer 轉(zhuǎn)換器 
因?yàn)閗eyword類(lèi)型的字段是不進(jìn)行分析的,但是我們又想要將其統(tǒng)一成一個(gè)規(guī)則,比如都是小寫(xiě),比如用ASCILL進(jìn)行編碼,其實(shí)就是個(gè)給keyword用的分析器??梢栽趕etting下的analysis下定義自己的normalizer使用

copy_to 同步復(fù)制 
在插入值是,會(huì)把值一同放到另一個(gè)字段中。主要用于自己定義一個(gè)類(lèi)似于_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è)類(lèi)型,又不想再建一個(gè)不同類(lèi)型的相同字段。我們可以使用多字段的方式,在保存數(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

3.3.5 _all,_sources,store的區(qū)別

背景:
    首先,我們要知道一點(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)類(lèi)似了,只不過(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)生一次磁盤(pá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)生多少次磁盤(pá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ǔ)空間的,增大了索引的體積

3.3.6 api操作mapping

創(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<String, ImmutableOpenMap<String, MappingMetaData>> mappings = mappingsResponse.getMappings();
        //迭代打印mapping數(shù)據(jù)
        for (ObjectObjectCursor<String, ImmutableOpenMap<String, MappingMetaData>> mapping : mappings) {
            if (mapping.value.isEmpty()) {
                continue;
            }
            //最外層的key是index的名稱(chēng)
            System.out.println("index key:" + mapping.key);
            //value包裹的是每個(gè)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();
    }

/*
結(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}}}}}
*/

3.4 spark操作es時(shí)的報(bào)錯(cuò)

在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中寫(xiě)在前面,迫使es使用的是3.x版本的依賴,導(dǎo)致有些方法不存在,就報(bào)錯(cuò)。解決方式很簡(jiǎn)答,直接指定使用新版本的就好,如下:

<dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.32.Final</version>
        </dependency>

四、分詞器

4.1 默認(rèn)分詞器

我們知道,建立索引過(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" : "<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" : "國(guó)",
      "start_offset" : 6,
      "end_offset" : 7,
      "type" : "<IDEOGRAPHIC>",
      "position" : 6
    }
  ]
}

可以看到,標(biāo)準(zhǔn)的中文分詞器只是單純將字分開(kāi),其實(shí)并不智能,沒(méi)有詞語(yǔ)考慮進(jìn)去。所以需要更加強(qiáng)大的分詞器。常用的有ik分詞器

4.2 安裝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

4.3 命令行下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
    }
  ]
}

4.4 java api使用ik分詞器

這里其實(shí)和mapping的使用差不多,只是在mapping的字段屬性中添加一個(gè) “analyzer” 屬性,指定使用的分詞器而已。其他都沒(méi)有區(qū)別,這里不重復(fù)

五、優(yōu)化

5.1 背景

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.2 優(yōu)化1--filesystemCache

5.2.1 基本原理

一、Elasticsearch原理與基本使用

圖5.1 ES filesytem cache

? 你往 ES 里寫(xiě)的數(shù)據(jù),實(shí)際上都寫(xiě)到磁盤(pán)文件里去了,查詢的時(shí)候,操作系統(tǒng)會(huì)將磁盤(pán)文件里的數(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)題:直接讀取硬盤(pán)數(shù)據(jù)和從緩存讀取數(shù)據(jù),性能差距究竟可以有多大?
回答:
我們之前很多的測(cè)試和壓測(cè),如果走磁盤(pán)一般肯定上秒,搜索性能絕對(duì)是秒級(jí)別的,1 秒、5 秒、10 秒。但如果是走 Filesystem Cache,是走純內(nèi)存的,那么一般來(lái)說(shuō)性能比走磁盤(pán)要高一個(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è)磁盤(pán)上索引數(shù)據(jù)文件,在 3 臺(tái)機(jī)器上一共占用了 1T 的磁盤(pán)容量,ES 數(shù)據(jù)量是 1T,那么每臺(tái)機(jī)器的數(shù)據(jù)量是 300G。這樣性能會(huì)好嗎?
? Filesystem Cache 的內(nèi)存才 100G,十分之一的數(shù)據(jù)可以放內(nèi)存,其他的都在磁盤(pán),然后你執(zhí)行搜索操作,大部分操作都是走磁盤(pán),性能肯定差。

5.2.2 優(yōu)化具體方式

? 首先要知道一點(diǎn):歸根結(jié)底,你要讓 ES 性能好,最佳的情況下,就是你的機(jī)器的內(nèi)存,至少可以容納你的總數(shù)據(jù)量的一半。當(dāng)然如果內(nèi)存能容納全部數(shù)據(jù),自然是最好,然而基本生產(chǎn)中沒(méi)有那么多錢(qián)的啦。走內(nèi)存可以滿足秒級(jí)以內(nèi)的查詢要求

1、去掉寫(xiě)入ES的doc中不必要的字段
如果一個(gè)doc中有很多字段,但是有些字段壓根是沒(méi)用的(也就是說(shuō)該字段不會(huì)用于搜索),但是讀取的時(shí)候仍舊會(huì)將這些字段都讀取,然后緩存到filesytem cache中,占據(jù)了大量空間,導(dǎo)致后面的數(shù)據(jù)只能重新從硬盤(pán)中讀取。這個(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 里寫(xiě)入一行數(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í),僅僅寫(xiě)入 ES 中要用來(lái)檢索的少數(shù)幾個(gè)字段就可以了,比如說(shuō)就寫(xiě)入 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 可以寫(xiě)入海量數(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),再返回給前端。而寫(xiě)入 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。

5.3 優(yōu)化2--數(shù)據(jù)預(yù)熱

? 從概率上來(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查詢了,非??臁6疫@些數(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è)專(zhuān)門(mén)的緩存預(yù)熱子系統(tǒng)。然后對(duì)熱數(shù)據(jù)每隔一段時(shí)間,就提前訪問(wèn)一下,讓數(shù)據(jù)進(jìn)入 Filesystem Cache 里面去。這樣下次別人訪問(wèn)的時(shí)候,性能一定會(huì)好很多。

5.4 優(yōu)化3--冷熱分離

? 這個(gè)也是數(shù)據(jù)熱點(diǎn)的問(wèn)題。ES 可以做類(lèi)似于 MySQL 的水平拆分,就是說(shuō)將大量的訪問(wèn)很少、頻率很低的數(shù)據(jù),單獨(dú)寫(xiě)一個(gè)索引,然后將訪問(wèn)很頻繁的熱數(shù)據(jù)單獨(dú)寫(xiě)一個(gè)索引。最好是將冷數(shù)據(jù)寫(xiě)入一個(gè)索引中,然后熱數(shù)據(jù)寫(xiě)入另外一個(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ù)是在磁盤(pán)上的,此時(shí)性能差點(diǎn),就 10% 的人去訪問(wèn)冷數(shù)據(jù),90% 的人在訪問(wèn)熱數(shù)據(jù),也無(wú)所謂了。

5.5 優(yōu)化4--避免關(guān)聯(lián)查詢

? 對(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ù)直接寫(xiě)入 ES 中。搜索的時(shí)候,就不需要利用 ES 的搜索語(yǔ)法來(lái)完成 Join 之類(lèi)的關(guān)聯(lián)搜索了。

5.6 優(yōu)化5--document模型設(shè)計(jì)

? Document 模型設(shè)計(jì)是非常重要的,很多操作,不要在搜索的時(shí)候才想去執(zhí)行各種復(fù)雜的亂七八糟的操作。
? ES 能支持的操作就那么多,不要考慮用 ES 做一些它不好操作的事情。如果真的有那種操作,盡量在 Document 模型設(shè)計(jì)的時(shí)候,寫(xiě)入的時(shí)候就完成。另外對(duì)于一些太復(fù)雜的操作,比如 join/nested/parent-child 搜索都要盡量避免,性能都很差的。
? 總結(jié)一句就是說(shuō),ES不適合執(zhí)行復(fù)雜查詢操作

5.7 優(yōu)化6--分頁(yè)性能優(yōu)化

背景:

    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),非??拥?。所以用 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、類(lèi)似于 App 里的推薦商品不斷下拉出來(lái)一頁(yè)一頁(yè)的;類(lèi)似于微博中,下拉刷微博,刷出來(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è)適合于那種類(lèi)似微博下拉翻頁(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 字段。

名稱(chēng)欄目:一、Elasticsearch原理與基本使用
文章路徑:http://bm7419.com/article32/pcgepc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信公眾號(hào)、網(wǎng)站設(shè)計(jì)公司、網(wǎng)頁(yè)設(shè)計(jì)公司、網(wǎng)站設(shè)計(jì)云服務(wù)器、企業(yè)網(wǎng)站制作

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站制作