Elastic Stack 進階

  • 全文搜索

    • 倒排索引

      • 倒排索引源於實際應用中需要根據屬性的值來查找記錄。這種索引表中的每一項都包括一個屬性值和具有該屬性值的各記錄的地址。由於不是由記錄來確定屬性值,而是由屬性值來確定記錄的位置,因而稱爲倒排索引(invertedindex)。帶有倒排索引的文件我們稱爲倒排索引文件,簡稱倒排文件(inverted fifile)

        • 轉化成倒排索引:

        • 說明:

          • “單詞ID”一欄記錄了每個單詞的單詞編號;
            第二欄是對應的單詞;
            第三欄即每個單詞對應的倒排列表;
            比如單詞“谷歌”,其單詞編號爲1,倒排列表爲{1,2,3,4,5},說明文檔集合中每個文檔都包含了這個單詞。
            
            而事實上,索引系統還可以記錄除此之外的更多信息,在單詞對應的倒排列表中不僅記錄了文檔編號,還記載了單
            詞頻率信息(TF),即這個單詞在某個文檔中的出現次數,之所以要記錄這個信息,是因爲詞頻信息在搜索結果排
            序時,計算查詢和文檔相似度是很重要的一個計算因子,所以將其記錄在倒排列表中,以方便後續排序時進行分值
            計算。

             

        • 倒排索引還可以記載更多的信息,除了記錄文檔編號和單詞頻率信息外,額外記載了兩類信息,即每個單詞對應的文檔頻率信息,以及在倒排列表中記錄單詞在某個文檔出現的位置信息。

  • 全文搜索

    • 全文搜索兩個最重要的方面是:

      • 相關性(Relevance) 它是評價查詢與其結果間的相關程度,並根據這種相關程度對結果排名的一種能力,這種計算方式可以是 TF/IDF 方法、地理位置鄰近、模糊相似,或其他的某些算法。

      • 分析(Analysis) 它是將文本塊轉換爲有區別的、規範化的 token 的一個過程,目的是爲了創建倒排索引以及查詢倒排索引。

    • 構造數據:

      • PUT http://172.16.55.185:9200/test
        
        {
        	"settings": {
        		"index": {
        			"number_of_shards": "1",
        			"number_of_replicas": "0"
        		}
        	},
        	"mappings": {
        		"person": {
        			"properties": {
        				"name": {
        					"type": "text"
        				},
        				"age": {
        					"type": "integer"
        				},
        				"mail": {
        					"type": "keyword"
        				},
        				"hobby": {
        					"type": "text",
        					"analyzer": "ik_max_word"
        				}
        			}
        		}
        	}
        }

         

    • 批量插入數據:

      • POST http://172.16.55.185:9200/test/_bulk
        
        {
        	"index": {
        		"_index": "test",
        		"_type": "person"
        	}
        } {
        	"name": "張三",
        	"age": 20,
        	"mail": "[email protected]",
        	"hobby": "羽毛球、乒乓球、足球"
        } {
        	"index": {
        		"_index": "test",
        		"_type": "person"
        	}
        } {
        	"name": "李四",
        	"age": 21,
        	"mail": "[email protected]",
        	"hobby": "羽毛球、乒乓球、足球、籃球"
        } {
        	"index": {
        		"_index": "test",
        		"_type": "person"
        	}
        } {
        	"name": "王五",
        	"age": 22,
        	"mail": "[email protected]",
        	"hobby": "羽毛球、籃球、游泳、聽音樂"
        } {
        	"index": {
        		"_index": "test",
        		"_type": "person"
        	}
        } {
        	"name": "趙六",
        	"age": 23,
        	"mail": "[email protected]",
        	"hobby": "跑步、游泳、籃球"
        } {
        	"index": {
        		"_index": "test",
        		"_type": "person"
        	}
        } {
        	"name": "孫七",
        	"age": 24,
        	"mail": "[email protected]",
        	"hobby": "聽音樂、看電影、羽毛球"
        }

         

    • 單詞搜索

      • POST http://172.16.55.185:9200/test/person/_search
        
        { "query":{ "match":{ "hobby":"音樂" } },"highlight": { "fields": { "hobby": {} } } }
        
        結果:
        {
        	"took": 9,
        	"timed_out": false,
        	"_shards": {
        		"total": 1,
        		"successful": 1,
        		"skipped": 0,
        		"failed": 0
        	},
        	"hits": {
        		"total": 2,
        		"max_score": 0.6841192,
        		"hits": [{
        			"_index": "test",
        			"_type": "person",
        			"_id": "Uv0cDWgBR-bSw8-LpdkZ",
        			"_score": 0.6841192,
        			"_source": {
        				"name": "王五",
        				"age": 22,
        				"mail": "[email protected]",
        				"hobby": "羽毛球、籃球、游泳、聽音樂"
        			},
        			"highlight": {
        				"hobby": ["羽毛球、籃球、游泳、聽<em>音樂</em>"]
        			}
        		}, {
        			"_index": "test",
        			"_type": "person",
        			"_id": "VP0cDWgBR-bSw8-LpdkZ",
        			"_score": 0.6841192,
        			"_source": {
        				"name": "孫七",
        				"age": 24,
        				"mail": "[email protected]",
        				"hobby": "聽音樂、看電影、羽毛球"
        			},
        			"highlight": {
        				"hobby": ["聽<em>音樂</em>、看電影、羽毛球"]
        			}
        		}]
        	}
        }

         

      • 過程說明:

        • 1. 檢查字段類型愛好 hobby 字段是一個 text 類型( 指定了IK分詞器),這意味着查詢字符串本身也應該被分詞。
          2. 分析查詢字符串 。將查詢的字符串 “音樂” 傳入IK分詞器中,輸出的結果是單個項 音樂。因爲只有一個單詞項,所以 match 查詢執行的是單個底層 term 查詢。
          3. 查找匹配文檔 。 用 term 查詢在倒排索引中查找 “音樂” 然後獲取一組包含該項的文檔,本例的結果是文檔:3 、5 。 
          4. 爲每個文檔評分 。 用 term 查詢計算每個文檔相關度評分 _score ,這是種將 詞頻(termfrequency,即詞 “音樂” 在相關文檔的hobby 字段中出現的頻率)和 反向文檔頻率(inverse document frequency,即詞 “音樂” 在所有文檔的hobby 字段中出現的頻率),以及字段的長度(即字段越短相關度越高)相結合的計算方式。

           

    • 多詞搜索

      • POST http://172.16.55.185:9200/test/person/_search 
        
        { "query":{ "match":{ "hobby":"音樂 籃球" } },"highlight": { "fields": { "hobby": {} }}} 
        
        結果:
        {
        	"took": 3,
        	"timed_out": false,
        	"_shards": {
        		"total": 1,
        		"successful": 1,
        		"skipped": 0,
        		"failed": 0
        	},
        	"hits": {
        		"total": 4,
        		"max_score": 1.3192271,
        		"hits": [{
        			"_index": "test",
        			"_type": "person",
        			"_id": "Uv0cDWgBR-bSw8-LpdkZ",
        			"_score": 1.3192271,
        			"_source": {
        				"name": "王五",
        				"age": 22,
        				"mail": "[email protected]",
        				"hobby": "羽毛球、籃球、游泳、聽音樂"
        			},
        			"highlight": {
        				"hobby": ["羽毛球、<em>籃球</em>、游泳、聽<em>音樂</em>"]
        			}
        		}, {
        			"_index": "test",
        			"_type": "person",
        			"_id": "VP0cDWgBR-bSw8-LpdkZ",
        			"_score": 0.81652206,
        			"_source": {
        				"name": "孫七",
        				"age": 24,
        				"mail": "[email protected]",
        				"hobby": "聽音樂、看電影、羽毛球"
        			},
        			"highlight": {
        				"hobby": ["聽<em>音樂</em>、看電影、羽毛球"]
        			}
        		}, {
        			"_index": "test",
        			"_type": "person",
        			"_id": "Vf0gDWgBR-bSw8-LOdm_",
        			"_score": 0.6987338,
        			"_source": {
        				"name": "趙六",
        				"age": 23,
        				"mail": "[email protected]",
        				"hobby": "跑步、游泳、籃球"
        			},
        			"highlight": {
        				"hobby": ["跑步、游泳、<em>籃球</em>"]
        			}
        		}, {
        			"_index": "test",
        			"_type": "person",
        			"_id": "Uf0cDWgBR-bSw8-LpdkZ",
        			"_score": 0.50270504,
        			"_source": {
        				"name": "李四",
        				"age": 21,
        				"mail": "[email protected]",
        				"hobby": "羽毛球、乒乓球、足球、籃球"
        			},
        			"highlight": {
        				"hobby": ["羽毛球、乒乓球、足球、<em>籃球</em>"]
        			}
        		}]
        	}
        }

         

      • 可以看到,包含了音樂籃球的數據都已經被搜索到了。可是,搜索的結果並不符合我們的預期,因爲我們想搜索的是既包含音樂又包含籃球的用戶,顯然結果返回的關係。

      • Elasticsearch中,可以指定詞之間的邏輯關係,如下:

        • POST http://172.16.55.185:9200/test/person/_search
          
          
          { "query":{ "match":{ "hobby":{ "query":"音樂 籃球", "operator":"and" } } },"highlight": { "fields": { "hobby": {} } } }
          
          
          可以看到結果符合預期。前面我們測試了“OR” 和 “AND”搜索,這是兩個極端,其實在實際場景中,並不會選取這2個極端,更有可能是選取這種,或者說,只需要符合一定的相似度就可以查詢到數據,在Elasticsearch中也支持這樣的查詢,通過minimum_should_match來指定匹配度,如:70%;
          
          示例:
          {
          	"query": {
          		"match": {
          			"hobby": {
          				"query": "游泳 羽毛球",
          				"minimum_should_match": "80%"
          			}
          		}
          	},
          	"highlight": {
          		"fields": {
          			"hobby": {}
          		}
          	}
          }
          
          #相似度應該多少合適,需要在實際的需求中進行反覆測試,纔可得到合理的值。

           

    • 組合搜索

      • 在搜索時,也可以使用過濾器中講過的bool組合查詢,示例:

        • POST http://172.16.55.185:9200/test/person/_search
          
          {
          	"query": {
          		"bool": {
          			"must": {
          				"match": {
          					"hobby": "籃球"
          				}
          			},
          			"must_not": {
          				"match": {
          					"hobby": "音樂"
          				}
          			},
          			"should": [{
          				"match": {
          					"hobby": "游泳"
          				}
          			}]
          		}
          	},
          	"highlight": {
          		"fields": {
          			"hobby": {}
          		}
          	}
          }
          
          上面搜索的意思是:
          搜索結果中必須包含籃球,不能包含音樂,如果包含了游泳,那麼它的相似度更高。
          
          評分的計算規則:
          bool 查詢會爲每個文檔計算相關度評分 _score , 再將所有匹配的 must 和 should 語句的分數 _score 求和,最後除以 must 和 should 語句的總數。must_not 語句不會影響評分; 它的作用只是將不相關的文檔排除。
          
          默認情況下,should中的內容不是必須匹配的,如果查詢語句中沒有must,那麼就會至少匹配其中一個。當然
          了,也可以通過minimum_should_match參數進行控制,該值可以是數字也可以的百分比。
          
          
          示例:
          POST http://172.16.55.185:9200/test/person/_search
          
          {
          	"query": {
          		"bool": {
          			"should": [{
          				"match": {
          					"hobby": "游泳"
          				}
          			}, {
          				"match": {
          					"hobby": "籃球"
          				}
          			}, {
          				"match": {
          					"hobby": "音樂"
          				}
          			}],
          			"minimum_should_match": 2
          		}
          	},
          	"highlight": {
          		"fields": {
          			"hobby": {}
          		}
          	}
          }
          
          minimum_should_match爲2,意思是should中的三個詞,至少要滿足2個。

           

    • 權重

      • 有些時候,我們可能需要對某些詞增加權重來影響該條數據的得分。如下:

        • 搜索關鍵字爲游泳籃球,如果結果中包含了音樂權重爲10,包含了跑步權重爲2

        • POST http://172.16.55.185:9200/test/person/_search
          
          {
          	"query": {
          		"bool": {
          			"must": {
          				"match": {
          					"hobby": {
          						"query": "游泳籃球",
          						"operator": "and"
          					}
          				}
          			},
          			"should": [{
          				"match": {
          					"hobby": {
          						"query": "音樂",
          						"boost": 10
          					}
          				}
          			}, {
          				"match": {
          					"hobby": {
          						"query": "跑步",
          						"boost": 2
          					}
          				}
          			}]
          		}
          	},
          	"highlight": {
          		"fields": {
          			"hobby": {}
          		}
          	}
          }
          
          

           

  • 短語匹配

    • Elasticsearch中,短語匹配意味着不僅僅是詞要匹配,並且詞的順序也要一致,如下:

      • POST http://172.16.55.185:9200/test/person/_search 
        {
        	"query": {
        		"match_phrase": {
        			"hobby": {
        				"query": "羽毛球籃球"
        			}
        		}
        	},
        	"highlight": {
        		"fields": {
        			"hobby": {}
        		}
        	}
        }
        
        結果符合預期。
        如果覺得這樣太過於苛刻,可以增加slop參數,允許跳過N個詞進行匹配。
        POST http://172.16.55.185:9200/test/person/_search 
        
        {
        	"query": {
        		"match_phrase": {
        			"hobby": {
        				"query": "羽毛球足球"
        			}
        		}
        	},
        	"highlight": {
        		"fields": {
        			"hobby": {}
        		}
        	}
        }

         

  • Elasticsearch集羣

    • 集羣節點

      • ELasticsearch的集羣是由多個節點組成的,通過cluster.name設置集羣名稱,並且用於區分其它的集羣,每個節點通過node.name指定節點的名稱。

      • 在Elasticsearch中,節點的類型主要有4種:
        master節點
            配置文件中node.master屬性爲true(默認爲true),就有資格被選爲master節點。
            master節點用於控制整個集羣的操作。比如創建或刪除索引,管理其它非master節點等。
        data節點
            配置文件中node.data屬性爲true(默認爲true),就有資格被設置成data節點。
            data節點主要用於執行數據相關的操作。比如文檔的CRUD。
        客戶端節點
            配置文件中node.master屬性和node.data屬性均爲false。
            該節點不能作爲master節點,也不能作爲data節點。
            可以作爲客戶端節點,用於響應用戶的請求,把請求轉發到其他節點
        部落節點
            當一個節點配置tribe.*的時候,它是一個特殊的客戶端,它可以連接多個集羣,在所有連接的集羣上執
                行搜索和其他操作。

         

    • 使用docker搭建集羣

      • 查詢集羣狀態:http://172.16.55.185:9200/_cluster/health

      • 集羣狀態的三種顏色:

        • green 所有主要分片和複製分片都可用

          yellow 所有主要分片可用,但不是所有複製分片都可用

          red 不是所有的主要分片都可用

    • 故障轉移

      • 腦裂問題:

        • 思路:不能讓節點很容易的變成master,必須有多個節點認可後纔可以。

          • 設置minimum_master_nodes的大小爲2

          • 官方推薦:(N/2)+1,N爲集羣中節點數

  • 分片和副本

    • 爲了將數據添加到Elasticsearch,我們需要索引(index)——一個存儲關聯數據的地方。實際上,索引只是一個用來指向一個或多個分片(shards)邏輯命名空間(logical namespace)”.

      • 一個分片(shard)是一個最小級別工作單元(worker unit)”,它只是保存了索引中所有數據的一部分。

      • 我們需要知道是分片就是一個Lucene實例,並且它本身就是一個完整的搜索引擎。應用程序不會和它直接通信。

      • 分片可以是主分片(primary shard)或者是複製分片(replica shard)

      • 索引中的每個文檔屬於一個單獨的主分片,所以主分片的數量決定了索引最多能存儲多少數據。

      • 複製分片只是主分片的一個副本,它可以防止硬件故障導致的數據丟失,同時可以提供讀請求,比如搜索或者從別的shard取回文檔。

      • 當索引創建完成的時候,主分片的數量就固定了,但是複製分片的數量可以隨時調整。

  •  

  • Java客戶端

    • Elasticsearch中,爲java提供了2種客戶端,一種是REST風格的客戶端,另一種是Java API的客戶端。

      • https://www.elastic.co/guide/en/elasticsearch/client/index.html

    • REST客戶端

      • Elasticsearch提供了2REST客戶端,一種是低級客戶端,一種是高級客戶端。

        • Java Low Level REST Client:官方提供的低級客戶端。該客戶端通過http來連接Elasticsearch集羣。用戶在使用該客戶端時需要將請求數據手動拼接成Elasticsearch所需JSON格式進行發送,收到響應時同樣也需要將 返回的JSON數據手動封裝成對象。雖然麻煩,不過該客戶端兼容所有的Elasticsearch版本。

        • Java High Level REST Client:官方提供的高級客戶端。該客戶端基於低級客戶端實現,它提供了很多便捷的API來解決低級客戶端需要手動轉換數據格式的問題。

      • 低級客戶端:

        •        <dependency>
                      <groupId>org.elasticsearch.client</groupId>
                      <artifactId>elasticsearch-rest-client</artifactId>
                      <version>6.5.4</version>
                  </dependency>
                  <dependency>
                      <groupId>junit</groupId>
                      <artifactId>junit</artifactId>
                      <version>4.12</version>
                  </dependency>
                  <dependency>
                      <groupId>com.fasterxml.jackson.core</groupId>
                      <artifactId>jackson-databind</artifactId>
                      <version>2.9.4</version>
                  </dependency>

           

        • 編寫測試用例

          • import com.fasterxml.jackson.databind.ObjectMapper;
            import org.apache.http.HttpHost;
            import org.elasticsearch.client.*;
            import org.junit.Before;
            
            import java.io.IOException;
            
            public class TestESREST {
                private static final ObjectMapper MAPPER = new ObjectMapper();
                private RestClient restClient;
            
                @Before
                public void init() {
                    RestClientBuilder restClientBuilder = RestClient.builder(new HttpHost("172.16.55.185", 9200, "http"), new HttpHost("172.16.55.185", 9201, "http"), new HttpHost("172.16.55.185", 9202, "http"));
                    restClientBuilder.setFailureListener(new RestClient.FailureListener() {
                        @Override
                        public void onFailure(Node node) {
                            System.out.println("出錯了 -> " + node);
                        }
                    });
                    this.restClient = restClientBuilder.build();
                }
            
                @Afterpublic
                void after() throws IOException {
                    restClient.close();
                }
                // 查詢集羣狀態 
                @Test
                public void testGetInfo() throws IOException {
                    Request request = new Request("GET", "/_cluster/state");
                    request.addParameter("pretty", "true");
                    Response response = this.restClient.performRequest(request);
                    System.out.println(response.getStatusLine());
                    System.out.println(EntityUtils.toString(response.getEntity()));
                }
            
                // 新增數據 
                @Test
                public void testCreateData() throws IOException {
                    Request request = new Request("POST", "/test/house");
                    Map<String, Object> data = new HashMap<>();
                    data.put("id", "2001");
                    data.put("title", "張江高科");
                    data.put("price", "3500");
                    request.setJsonEntity(MAPPER.writeValueAsString(data));
                    Response response = this.restClient.performRequest(request);
                    System.out.println(response.getStatusLine());
                    System.out.println(EntityUtils.toString(response.getEntity()));
                }
            
                // 根據id查詢數據 
                @Test
                public void testQueryData() throws IOException {
                    Request request = new Request("GET", "/test/house/G0pfE2gBCKv8opxuRz1y");
                    Response response = this.restClient.performRequest(request);
                    System.out.println(response.getStatusLine());
                    System.out.println(EntityUtils.toString(response.getEntity()));
                }
                //搜索數據
                @Test
                public void testSearchData() throws IOException {
                    Request request = new Request("POST", "/test/house/_search");
                    String searchJson = "{\"query\": {\"match\": {\"title\": \"拎包入住\"}}}";
                    request.setJsonEntity(searchJson);
                    request.addParameter("pretty", "true");
                    Response response = this.restClient.performRequest(request);
                    System.out.println(response.getStatusLine());
                    System.out.println(EntityUtils.toString(response.getEntity()));
                }
            }

             

        •  

      • REST高級客戶端

        •         <dependency>
                      <groupId>org.elasticsearch.client</groupId>
                      <artifactId>elasticsearch-rest-high-level-client</artifactId>
                      <version>6.5.4</version>
                  </dependency>

           按照demo的案例改一改就可以了 

    • Spring Data Elasticsearch

      • <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-test</artifactId>
                    <scope>test</scope>
                </dependency>
                <dependency>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok</artifactId>
                    <version>1.18.4</version>
                </dependency>
        
                <dependency>
                    <groupId>org.elasticsearch.client</groupId>
                    <artifactId>elasticsearch-rest-client</artifactId>
                    <version>6.5.4</version>
                </dependency>
                <dependency>
                    <groupId>org.elasticsearch.client</groupId>
                    <artifactId>elasticsearch-rest-high-level-client</artifactId>
                    <version>6.5.4</version>
                </dependency>
                <dependency>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                    <version>4.12</version>
                </dependency>
                <dependency>
                    <groupId>com.fasterxml.jackson.core</groupId>
                    <artifactId>jackson-databind</artifactId>
                    <version>2.9.4</version>
                </dependency>
        
            </dependencies>
        
            <build>
                <plugins>
                    <!-- java編譯插件 -->
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-compiler-plugin</artifactId>
                        <version>3.2</version>
                        <configuration>
                            <source>1.8</source>
                            <target>1.8</target>
                            <encoding>UTF-8</encoding>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        

         

    •  編寫application.properties

      • spring.application.name = 服務名稱 
        spring.data.elasticsearch.cluster-name= 名稱
        spring.data.elasticsearch.cluster-nodes= ip
    •  編寫啓動類

      • 
        import org.springframework.boot.SpringApplication;
        import org.springframework.boot.autoconfigure.SpringBootApplication;
        
        @SpringBootApplication
        public class MyApplication {
        
            public static void main(String[] args) {
                SpringApplication.run(MyApplication.class, args);
            }
        
        }

         

    • user對象

      • import lombok.AllArgsConstructor;
        import lombok.Data;
        import lombok.NoArgsConstructor;
        import org.springframework.data.annotation.Id;
        import org.springframework.data.elasticsearch.annotations.Document;
        import org.springframework.data.elasticsearch.annotations.Field;
        
        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        @Document(indexName = "test", type = "user", createIndex = false)
        public class User {
        
            @Id
            private Long id;
            @Field(store = true)
            private String name;
            @Field
            private Integer age;
            @Field(store = true)
            private String hobby;
        
        }
        
    • 新增數據
      • 
        import cn.test.es.pojo.User;
        import org.elasticsearch.action.index.IndexRequest;
        import org.elasticsearch.index.query.QueryBuilders;
        import org.junit.Test;
        import org.junit.runner.RunWith;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.boot.test.context.SpringBootTest;
        import org.springframework.data.domain.PageRequest;
        import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
        import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
        import org.springframework.data.elasticsearch.core.query.*;
        import org.springframework.test.context.junit4.SpringRunner;
        
        import java.util.ArrayList;
        import java.util.List;
        
        @RunWith(SpringRunner.class)
        @SpringBootTest
        public class TestSpringBootES {
        
            @Autowired
            private ElasticsearchTemplate elasticsearchTemplate;
        
            @Test
            public void save() {
                User user = new User();
                user.setId(1001L);
                user.setName("張三");
                user.setAge(20);
                user.setHobby("足球、籃球、聽音樂");
        
                IndexQuery indexQuery = new IndexQueryBuilder()
                        .withObject(user).build();
        
                String index = this.elasticsearchTemplate.index(indexQuery);
        
                System.out.println(index);
            }
        
            @Test
            public void testBulk() {
                List list = new ArrayList();
                for (int i = 0; i < 5000; i++) {
                    User user = new User();
                    user.setId(1001L + i);
                    user.setAge(i % 50 + 10);
                    user.setName("張三" + i);
                    user.setHobby("足球、籃球、聽音樂");
                    IndexQuery indexQuery = new
                            IndexQueryBuilder().withObject(user).build();
                    list.add(indexQuery);
                }
                Long start = System.currentTimeMillis();
                this.elasticsearchTemplate.bulkIndex(list);
                System.out.println("用時:" + (System.currentTimeMillis() - start)); //用時:7836
        
            }
        
            /**
             * 局部更新,全部更新使用index覆蓋即可
             */
            @Test
            public void testUpdate() {
                IndexRequest indexRequest = new IndexRequest();
                indexRequest.source("age", "30");
        
                UpdateQuery updateQuery = new UpdateQueryBuilder()
                        .withId("1001")
                        .withClass(User.class)
                        .withIndexRequest(indexRequest).build();
        
                this.elasticsearchTemplate.update(updateQuery);
            }
        
            @Test
            public void testDelete() {
                this.elasticsearchTemplate.delete(User.class, "1001");
            }
        
            @Test
            public void testSearch() {
                PageRequest pageRequest = PageRequest.of(1, 10); //設置分頁參數
        
                SearchQuery searchQuery = new NativeSearchQueryBuilder()
                        .withQuery(QueryBuilders.matchQuery("name", "張三")) // match查詢
                        .withPageable(pageRequest)
                        .build();
        
                AggregatedPage<User> users =
                        this.elasticsearchTemplate.queryForPage(searchQuery, User.class);
        
                System.out.println("總頁數:" + users.getTotalPages()); //獲取總頁數
        
                for (User user : users.getContent()) { // 獲取搜索到的數據
                    System.out.println(user);
                }
            }
        
        
        }

         

    • 測試:

      • 
        import org.apache.http.HttpHost;
        import org.elasticsearch.action.ActionListener;
        import org.elasticsearch.action.delete.DeleteRequest;
        import org.elasticsearch.action.delete.DeleteResponse;
        import org.elasticsearch.action.get.GetRequest;
        import org.elasticsearch.action.get.GetResponse;
        import org.elasticsearch.action.index.IndexRequest;
        import org.elasticsearch.action.index.IndexResponse;
        import org.elasticsearch.action.search.SearchRequest;
        import org.elasticsearch.action.search.SearchResponse;
        import org.elasticsearch.action.update.UpdateRequest;
        import org.elasticsearch.action.update.UpdateResponse;
        import org.elasticsearch.client.RequestOptions;
        import org.elasticsearch.client.RestClient;
        import org.elasticsearch.client.RestClientBuilder;
        import org.elasticsearch.client.RestHighLevelClient;
        import org.elasticsearch.common.Strings;
        import org.elasticsearch.common.unit.TimeValue;
        import org.elasticsearch.index.query.QueryBuilders;
        import org.elasticsearch.search.SearchHit;
        import org.elasticsearch.search.SearchHits;
        import org.elasticsearch.search.builder.SearchSourceBuilder;
        import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
        import org.junit.After;
        import org.junit.Before;
        import org.junit.Test;
        
        import java.util.HashMap;
        import java.util.Map;
        import java.util.concurrent.TimeUnit;
        
        public class TestRestHighLevel {
        
            private RestHighLevelClient restHighLevelClient;
        
            @Before
            public void init() {
                RestClientBuilder restClientBuilder = RestClient.builder(
                        new HttpHost("172.16.55.185", 9200),
                        new HttpHost("172.16.55.185", 9201),
                        new HttpHost("172.16.55.185", 9202)
                );
        
                this.restHighLevelClient = new RestHighLevelClient(restClientBuilder);
            }
        
            @After
            public void close() throws Exception {
                this.restHighLevelClient.close();
            }
        
            @Test
            public void testSave() throws Exception {
        
                Map<String, Object> data = new HashMap<>();
                data.put("id", 2002);
                data.put("title", "南京東路 二室一廳");
                data.put("price", 4000);
        
                IndexRequest indexRequest = new IndexRequest("test", "house").source(data);
        
                IndexResponse response = this.restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        
                System.out.println("id -> " + response.getId());
                System.out.println("version -> " + response.getVersion());
                System.out.println("result -> " + response.getResult());
        
        
            }
        
            @Test
            public void testCreateAsync() throws Exception {
                Map<String, Object> data = new HashMap<>();
                data.put("id", "2004");
                data.put("title", "南京東路2 最新房源 二室一廳");
                data.put("price", "5600");
        
                IndexRequest indexRequest = new IndexRequest("test", "house")
                        .source(data);
        
                this.restHighLevelClient.indexAsync(indexRequest, RequestOptions.DEFAULT, new
                        ActionListener<IndexResponse>() {
                            @Override
                            public void onResponse(IndexResponse indexResponse) {
                                System.out.println("id->" + indexResponse.getId());
                                System.out.println("index->" + indexResponse.getIndex());
                                System.out.println("type->" + indexResponse.getType());
                                System.out.println("version->" + indexResponse.getVersion());
                                System.out.println("result->" + indexResponse.getResult());
                                System.out.println("shardInfo->" + indexResponse.getShardInfo());
                            }
        
                            @Override
                            public void onFailure(Exception e) {
                                System.out.println(e);
                            }
                        });
        
                System.out.println("ok");
        
                Thread.sleep(20000);
            }
        
            @Test
            public void testQuery() throws Exception {
                GetRequest getRequest = new GetRequest("test", "house",
                        "jpugHGgBqiMwr19HZE4A");
        
                // 指定返回的字段
                String[] includes = new String[]{"title", "id"};
                String[] excludes = Strings.EMPTY_ARRAY;
                FetchSourceContext fetchSourceContext =
                        new FetchSourceContext(true, includes, excludes);
        
                getRequest.fetchSourceContext(fetchSourceContext);
        
                GetResponse response = this.restHighLevelClient.get(getRequest,
                        RequestOptions.DEFAULT);
        
                System.out.println("數據 -> " + response.getSource());
            }
        
            /**
             * 判斷是否存在
             *
             * @throws Exception
             */
            @Test
            public void testExists() throws Exception {
                GetRequest getRequest = new GetRequest("test", "house",
                        "jpugHGgBqiMwr19HZE4b");
        
                // 不返回的字段
                getRequest.fetchSourceContext(new FetchSourceContext(false));
                boolean exists = this.restHighLevelClient.exists(getRequest, RequestOptions.DEFAULT);
                System.out.println("exists -> " + exists);
            }
        
            /**
             * 刪除數據
             *
             * @throws Exception
             */
            @Test
            public void testDelete() throws Exception {
                DeleteRequest deleteRequest = new DeleteRequest("test", "house",
                        "kJv6HGgBqiMwr19H005b");
        
                DeleteResponse response = this.restHighLevelClient.delete(deleteRequest,
                        RequestOptions.DEFAULT);
        
                System.out.println(response.status());// OK or NOT_FOUND
            }
        
            /**
             * 更新數據
             *
             * @throws Exception
             */
            @Test
            public void testUpdate() throws Exception {
                UpdateRequest updateRequest = new UpdateRequest("test", "house",
                        "j5vaHGgBqiMwr19H-k63");
        
                Map<String, Object> data = new HashMap<>();
                data.put("title", "南京西路2 一室一廳2");
                data.put("price", "4000");
                updateRequest.doc(data);
        
                UpdateResponse response = this.restHighLevelClient.update(updateRequest,
                        RequestOptions.DEFAULT);
                System.out.println("version -> " + response.getVersion());
            }
        
            /**
             * 測試搜索
             *
             * @throws Exception
             */
            @Test
            public void testSearch() throws Exception {
                SearchRequest searchRequest = new SearchRequest("test");
                searchRequest.types("house");
        
                SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
                sourceBuilder.query(QueryBuilders.matchQuery("title", "拎包入住"));
                sourceBuilder.from(0);
                sourceBuilder.size(5);
                sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
                searchRequest.source(sourceBuilder);
        
                SearchResponse search = this.restHighLevelClient.search(searchRequest,
                        RequestOptions.DEFAULT);
        
                System.out.println("搜索到 " + search.getHits().totalHits + " 條數據.");
        
                SearchHits hits = search.getHits();
                for (SearchHit hit : hits) {
                    System.out.println(hit.getSourceAsString());
                }
            }
        
        }
        

         

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章