1. 程式人生 > 其它 >Docker部署ElasticSearch以及使用

Docker部署ElasticSearch以及使用

1.1 聊聊ElasticSearch的簡介
​ Elaticsearch,簡稱為es, es是一個開源的高擴充套件的分散式全文檢索引擎,它可以近乎實時的儲存、檢索資料;本身擴充套件性很好,可以擴充套件到上百臺伺服器,處理PB級別(大資料時代)的資料。es也使用Java開發並使用Lucene作為其核心來實現所有索引和搜尋的功能,但是它的目的是通過簡單的RESTful API來隱藏Lucene的複雜性,從而讓全文搜尋變得簡單。

​ 據國際權威的資料庫產品評測機構DB Engines的統計,在2016年1月,ElasticSearch已超過Solr等,成為排名第一的搜尋引擎類應用。

ElasticSearch的小故事

​ 多年前,一個叫做Shay Banon的剛結婚不久的失業開發者,由於妻子要去倫敦學習廚師,他便跟著也去了。在他找工作的過程中,為了給妻子構建一個食譜的搜尋引擎,他開始構建一個早期版本的Lucene。直接基於Lucene工作會比較困難,所以Shay開始抽象Lucene程式碼以便Java程式設計師可以在應用中新增搜 索功能。他釋出了他的第一個開源專案,叫做“Compass”。

​ 後來Shay找到一份工作,這份工作處在高效能和記憶體資料網格的分散式環境中,因此高效能的、實時 的、分散式的搜尋引擎也是理所當然需要的。然後他決定重寫Compass庫使其成為一個獨立的服務叫做Elasticsearch。第一個公開版本出現在2010年2月,在那之後Elasticsearch已經成為Github上最受歡迎的專案之一,代 碼貢獻者超過300人。一家主營Elasticsearch的公司就此成立,他們一邊提供商業支援一邊開發新功能,不過Elasticsearch將永遠開源且對所有人可用。

​ Shay的妻子依舊等待著她的食譜搜尋……

1.2 使用場景
1、維基百科,類似百度百科,全文檢索,高亮,搜尋推薦/2 (權重,百度!)

2、The Guardian(國外新聞網站),類似搜狐新聞,使用者行為日誌(點選,瀏覽,收藏,評論)+社交網路資料(對某某新聞的相關看法),資料分析,給到每篇新聞文章的作者,讓他知道他的文章的公眾 反饋(好,壞,熱門,垃圾,鄙視,崇拜)

3、Stack Overflow(國外的程式異常討論論壇),IT問題,程式的報錯,提交上去,有人會跟你討論和回答,全文檢索,搜尋相關問題和答案,程式報錯了,就會將報錯資訊貼上到裡面去,搜尋有沒有對應的答案

4、GitHub(開原始碼管理),搜尋上千億行程式碼

5、電商網站,檢索商品

6、日誌資料分析,logstash採集日誌,ES進行復雜的資料分析,ELK技術, elasticsearch+logstash+kibana

7、商品價格監控網站,使用者設定某商品的價格閾值,當低於該閾值的時候,傳送通知訊息給使用者,比如 說訂閱牙膏的監控,如果高露潔牙膏的家庭套裝低於50塊錢,就通知我,我就去買。

8、BI系統,商業智慧,Business Intelligence。比如說有個大型商場集團,BI,分析一下某某區域最近3年的使用者消費金額的趨勢以及使用者群體的組成構成,產出相關的數張報表,**區,最近3年,每年消費 金額呈現100%的增長,而且使用者群體85%是高階白領,開一個新商場。ES執行資料分析和挖掘, Kibana進行資料視覺化

9、國內:站內搜尋(電商,招聘,門戶,等等),IT系統搜尋(OA,CRM,ERP,等等),資料分析(ES熱門的一個使用場景)

1.3 ES的核心概念
​ ES是如何去儲存資料,資料結構是什麼,又是如何實現搜尋的呢?我們先來聊聊ElasticSearch的相關概念吧!叢集,節點,索引,型別,文件,分片,對映是什麼?

1.31 與關係型資料庫對比
Relational DB Elasticsearch
資料庫(database) 索引 index
表(tables) 型別 types
行(rows) 文件 documents
欄位(columns) fields
​ 綜上所示,elasticsearch(叢集)中可以包含多個索引(資料庫),每個索引中可以包含多個型別(表),每個型別下又包 含多個文件(行),每個文件中又包含多個欄位(列)。

物理設計:

elasticsearch 在後臺把每個索引劃分成多個分片,每分分片可以在叢集中的不同伺服器間遷移一個人就是一個叢集!預設的叢集名稱就是 elaticsearh

邏輯設計

一個索引型別中,包含多個文件,比如說文件1,文件2。 當我們索引一篇文件時,可以通過這樣的一各順序找到 它: **索引 ▷ 型別 ▷ 文件ID **,通過這個組合我們就能索引到某個具體的文件。 注意:ID不必是整數,實際上它是個字串。就是我們的一條條資料。

1.32 文件
之前說elasticsearch是面向文件的,那麼就意味著索引和搜尋資料的最小單位是文件,elasticsearch中,文件有幾個重要屬性 :

自我包含,一篇文件同時包含欄位和對應的值,也就是同時包含 key:value!
可以是層次型的,一個文件中包含自文件,複雜的邏輯實體就是這麼來的! {就是一個json物件! fastjson進行自動轉換!}
靈活的結構,文件不依賴預先定義的模式,我們知道關係型資料庫中,要提前定義欄位才能使用,在elasticsearch中,對於欄位是非常靈活的,有時候,我們可以忽略該欄位,或者動態的新增一個 新的欄位。
儘管我們可以隨意的新增或者忽略某個欄位,但是,每個欄位的型別非常重要,比如一個年齡欄位型別,可以是字元 串也可以是整形。因為elasticsearch會儲存欄位和型別之間的對映及其他的設定。這種對映具體到每個對映的每種型別,這也是為什麼在elasticsearch中,型別有時候也稱為對映型別。

1.33 型別
​ 型別是文件的邏輯容器,就像關係型資料庫一樣,表格是行的容器。 型別中對於欄位的定義稱為對映, 比如 name 映 射為字串型別。 我們說文件是無模式的,它們不需要擁有對映中所定義的所有欄位, 比如新增一個欄位,那麼elasticsearch是怎麼做的呢?elasticsearch會自動的將新欄位加入對映,但是這 個欄位的不確定它是什麼型別,elasticsearch就開始猜,如果這個值是18,那麼elasticsearch會認為它 是整形。 但是elasticsearch也可能猜不對, 所以最安全的方式就是提前定義好所需要的對映,這點跟關係型資料庫殊途同歸了,先定義好欄位,然後再使用,別 整什麼么蛾子。

1.34 索引
​ 索引是對映型別的容器,elasticsearch中的索引是一個非常大的文件集合。索引儲存了對映型別的欄位 和其他設定。 然後它們被儲存到了各個分片上了。 我們來研究下分片是如何工作的。

2.1 安裝ElasticSearch
2.11 Windows安裝
宣告:JDK1.8 ,最低要求! ElasticSearch 客戶端,介面工具!官網:https://www.elastic.co/

下載地址:https://www.elastic.co/cn/downloads/elasticsearch 官網下載巨慢,FQ,網盤中下載即可!

1.下載完解壓就可以使用了!

2.目錄結構

3.啟動,訪問9200;

kibana啟動同上述操作一樣,不在贅述。
2.12 Docker安裝
1.下載映象
docker pull elasticsearch:7.6.2
2.建立掛載的目錄
mkdir -p /mydata/elasticsearch/config mkdir -p /mydata/elasticsearch/data echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml
3.建立容器並啟動
`docker run --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms64m -Xmx128m" -v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml -v /mydata/elasticsearch/data:/usr/share/elasticsearch/data -v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins -d elasticsearch:7.6.2

其中elasticsearch.yml是掛載的配置檔案,data是掛載的資料,plugins是es的外掛,如ik,而資料掛載需要許可權,需要設定data檔案的許可權為可讀可寫,需要下邊的指令。
chmod -R 777 要修改的路徑

-e "discovery.type=single-node" 設定為單節點
特別注意:
-e ES_JAVA_OPTS="-Xms256m -Xmx256m" \ 測試環境下,設定ES的初始記憶體和最大記憶體,否則導致過大啟動不了ES

`
4.Kibana啟動

`docker pull kibana:7.6.2
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://自己的IP地址:9200 -p 5601:5601 -d kibana:7.6.2
//docker run --name kibana -e ELASTICSEARCH_URL=http://自己的IP地址:9200 -p 5601:5601 -d kibana:7.6.2

進入容器修改相應內容
server.port: 5601
server.host: 0.0.0.0
elasticsearch.hosts: [ "http://自己的IP地址:9200" ]
i18n.locale: "Zh-CN"

然後訪問頁面
http://自己的IP地址:5601/app/kibana

`
2.2 kibana操作ElasticSearch
2.21 文件操作

  1. _cat

GET /_cat/node 檢視所有節點 GET /_cat/health 檢視es健康狀況 GET /_cat/master 檢視主節點 GET /_cat/indices 檢視所有索引
2. 儲存文件
儲存一個數據,儲存在那個索引的那個型別下,指定用唯一的標識,customer為索引,external為型別,1為標識。其中PUT和POST都可以,POST新增。如果不指定ID,會自動生成ID,指定ID就會修改這個資料,並新增版本號。PUT可以新增可以修改,PUT必須指定ID,一般都用來修改操作,不指定ID會報錯

`PUT customer/external/1
{
"name":"張三"
}

返回結果
{
"_index" : "customer",
"_type" : "external",
"_id" : "1",
"_version" : 3,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1001,
"_primary_term" : 2
}

3. 查詢文件GET customer/external/1

結果:
{
"_index" : "customer", //在那個索引
"_type" : "external", //在那個型別
"_id" : "1", //記錄ID
"_version" : 1, //版本號
"_seq_no" : 0, //併發控制欄位,每次更新就+1,可用於樂觀鎖
"_primary_term" : 1, //主分片重新分配,如重啟,就會變化
"found" : true, //true就是找到資料了
"_source" : { //資料
"name" : "張三"
}
}

`
4. 更新文件

`POST操作帶_update會對比原來的資料,如果是一樣的那就不會更新了
POST customer/external/1/_update
{
"doc":{
"name":"你好"
}
}
POST操作不帶_update會直接更新操作
POST customer/external/1
{
"name":"你好"
}

5. 刪除文件DELETE customer/external/16. bulk批量API需要加_bulk,然後請求體中的index是id,下邊的是要儲存的內容
POST customer/external/_bulk
{"index":{"_id":1}}
{"name":"榨乾"}
{"index":{"_id":2}}
{"name":"你瞅啥"}

7.查詢操作 先匯入批量的資料,在進行查詢操作。1.一種是通過REST request URI 傳送搜尋的引數,其中_search是固定寫法,q=是查詢所有,sort=balance排序是按照balance排序的,asc是升序排序
GET customer/_search?q=
&sort=balance:asc

結果集,took是花費時間,timed_out沒有超時,hits是命中的記錄

2.另一種是通過REST request body 來發送,query代表查詢條件,match_all是查詢所有,sort代表排序條件
GET customer/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"balance": "asc"
}
]
}

3.分頁操作,from是從第幾條資料開始,size是一頁多少個,預設是十條資料
4.按需返回引數為,_source
GET customer/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"balance": "asc"
}
],
"from": 11,
"size": 2,
"_source": ["account_number","balance"]
}

5.全文檢索,使用match操作,查詢的結果是按照評分從高到低排序的
GET customer/_search
{
"query": {
"match": {
"age": 20
}
}
}

6.match_phrase的精確匹配,
GET customer/_search
{
"query": {
"match_phrase": {
"age": 20
}
}
}

7.多欄位匹配,multi_match
GET customer/_search
{
"query": {
"multi_match": {
"query": "mill",
"fields": ["address","email"]
}
}
}

8.複合查詢bool,其中must是必須滿足,must_not是必須不滿足,should是應該滿足,不過不滿足的也能查出來,就是得分低,range是區間查詢
GET customer/_search
{
"query": {
"bool": {
"must": [
{"match": {
"gender": "F"
}},
{"match": {
"address": "Mill"
}}
],
"must_not": [
{"match": {
"age": "38"
}}
],
"should": [
{"match": {
"lastname": "Long"
}}
]
}
}
}

9.filter過濾,區間查詢操作,而且filter不會計算相關性得分
GET customer/_search
{
"query": {
"bool": {
"filter": [
{"range": {
"age": {
"gte": 10,
"lte": 30
}
}}
]
}
}
}

10.team查詢,一些精確欄位的推薦使用team,而一些全文檢索的推薦使用match
GET customer/_search
{
"query": {
"term": {
"age": "28"
}
}
}

11.keyword的作用:當有keyword的時候,就會精確查詢,而沒有keyword的時候,這個值會當成一個關鍵字
GET customer/_search
{
"query": {"match": {
"address.keyword": "789 Madison"
}}
}

GET customer/_search
{
"query": {"match_phrase": {
"address": "789 Madison"
}}
}

2.22 es分析功能(聚合函式)搜尋address中包含mill的所有人的年齡分佈以及平均年齡,但不顯示這些人的詳情
其中,aggs代表使用聚合函式,terms為結果種類求和,avg為平均值,size為0則不顯示詳細資訊
GET customer/_search
{
"query": {
"match": {
"address": "mill"
}
},
"aggs": {
"ageagg": {
"terms": {
"field": "age",
"size": 10
}
},
"ageavg":{
"avg": {
"field": "age"
}
}
},
"size": 0
}

聚合中還可以有子聚合
GET customer/_search
{
"query": {
"match_all": {}
},
"aggs": {
"ageagg": {
"terms": {
"field": "age",
"size": 10
},
"aggs": {
"ageAvg": {
"avg": {
"field": "balance"
}
}
}
}
},
"size": 0
}

`

3.1 rest-high-level-client整合ElasticSearch
1.匯入依賴
`

<java.version>1.8</java.version>
<elasticsearch.version>7.6.2</elasticsearch.version>

    <!-- elasticsearch-rest-high-level-client -->
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
        <version>7.6.2</version>
    </dependency>
    
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.68</version>
    </dependency>

2.編寫配置類@Configuration
public class ElasticSearchClientConfig {
@Bean
public RestHighLevelClient restHighLevelClient(){
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("自己的IP地址", 9200, "http")
)
);
return client;
}
}

3進行es的索引操作@Autowired
@Qualifier("restHighLevelClient")
private RestHighLevelClient client;
//index名字,靜態一般都是放在另一個類中的
public static final String ES_INDEX="han_index";

//建立索引
@Test
public void createIndex() throws IOException {
    //1. 建立索引
    CreateIndexRequest index = new CreateIndexRequest(ES_INDEX);
    //2. 客戶端執行請求,請求後獲得相應
    CreateIndexResponse response = client.indices().create(index, RequestOptions.DEFAULT);
    //3.列印結果
    System.out.println(response.toString());
}
//測試索引是否存在
@Test
public void exitIndex() throws IOException{
    //1.
    GetIndexRequest request = new GetIndexRequest(ES_INDEX);
    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    System.out.println("是否存在"+exists);
}
//刪除索引
@Test
public void deleteIndex() throws IOException{
    DeleteIndexRequest request = new DeleteIndexRequest(ES_INDEX);
    AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
    System.out.println("是否刪除"+response);
}

`

4.es的文件操作

` @Autowired
@Qualifier("restHighLevelClient")
private RestHighLevelClient client;

public static final String ES_INDEX="han_index";

//建立文件
@Test
public void createDocument() throws IOException {
    //建立物件
    UserInfo userInfo = new UserInfo("張三",12);
    //建立請求
    IndexRequest request = new IndexRequest(ES_INDEX);
    //規則
    request.id("1").timeout(TimeValue.timeValueSeconds(1));
    //將資料放到請求中
    request.source(JSON.toJSONString(userInfo), XContentType.JSON);
    //客戶端傳送請求,獲取相應的結果
    IndexResponse response = client.index(request, RequestOptions.DEFAULT);
    //列印一下
    System.out.println(response.toString());
    System.out.println(response.status());
}

//判斷是否存在
@Test
public void exitDocument() throws IOException {
    GetRequest request = new GetRequest(ES_INDEX, "1");
    //不獲取返回的_source 的上下文
    request.fetchSourceContext(new FetchSourceContext(false));
    request.storedFields("_none");

    boolean exists = client.exists(request, RequestOptions.DEFAULT);
    System.out.println(exists);
}

//獲取文件資訊
@Test
public void getDocument() throws IOException {
    GetRequest request = new GetRequest(ES_INDEX, "1");
    GetResponse response = client.get(request, RequestOptions.DEFAULT);
    System.out.println("獲取到的結果"+response.getSourceAsString());
}

//更新文件
@Test
public void updateDocument() throws IOException {
    //建立物件
    UserInfo userInfo = new UserInfo("李四",12);

    UpdateRequest request = new UpdateRequest(ES_INDEX, "1");
    request.timeout("1s");

    request.doc(JSON.toJSONString(userInfo),XContentType.JSON);
    UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
    System.out.println(response.status());
}

//刪除文件
@Test
public void deleteDocument() throws IOException{
    DeleteRequest request = new DeleteRequest(ES_INDEX, "1");
    request.timeout("1s");

    DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
    System.out.println(response.status());
}

//批量新增
@Test
public void bulkDocument() throws IOException{
    BulkRequest request = new BulkRequest();
    request.timeout("10s");

    ArrayList<UserInfo> userInfos = new ArrayList<>();
    userInfos.add(new UserInfo("李四",1));
    userInfos.add(new UserInfo("李四",2));
    userInfos.add(new UserInfo("李四",3));
    userInfos.add(new UserInfo("李四",4));
    userInfos.add(new UserInfo("李四",5));
    userInfos.add(new UserInfo("李四",6));
    userInfos.add(new UserInfo("李四",7));

    //進行批處理請求
    for (int i = 0; i <userInfos.size() ; i++) {
        request.add(
                new IndexRequest(ES_INDEX)
                .id(""+(i+1))
                .source(JSON.toJSONString(userInfos.get(i)),XContentType.JSON));
    }

    BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
    System.out.println(response.hasFailures());
}

//查詢
@Test
public void SearchDocument() throws IOException{
    SearchRequest request = new SearchRequest(ES_INDEX);
    //構建搜尋條件
    SearchSourceBuilder builder = new SearchSourceBuilder();

    //查詢條件使用QueryBuilders工具來實現
    //QueryBuilders.termQuery 精準查詢
    //QueryBuilders.matchAllQuery() 匹配全部
    MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("name", "李四");
    builder.query(matchQuery);
    builder.timeout(new TimeValue(60, TimeUnit.SECONDS));

    request.source(builder);

    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    System.out.println("查詢出的結果"+JSON.toJSONString(response.getHits()));
}

`