AngularJS原始碼分析之依賴注入$injector
一、引言
1.1 海量資料
在海量資料中執行搜尋功能時,如果使用MySQL,效率太低。
1.2 全文檢索
在海量資料中執行搜尋功能時,如果使用MySQL,效率太低。
1.3 高亮顯示
將搜尋關鍵字,以紅色的字型展示。
二、ES概述
2.1 ES的介紹
ES是一個使用Java語言並且基於Lucene編寫的搜尋引擎框架,他提供了分散式的全文搜尋功能,提供了一個統一的基於RESTful風格的WEB介面,官方客戶端也對多種語言都提供了相應的API。
Lucene:Lucene本身就是一個搜尋引擎的底層。
分散式:ES主要是為了突出他的橫向擴充套件能力。
全文檢索:將一段詞語進行分詞,並且將分出的單個詞語統一的放到一個分詞庫中,在搜尋時,根據關鍵字去分詞庫中檢索,找到匹配的內容。(倒排索引)
RESTful風格的WEB介面:操作ES很簡單,只需要傳送一個HTTP請求,並且根據請求方式的不同,攜帶引數的同,執行相應的功能。
應用廣泛:Github.com,WIKI,Gold Man用ES每天維護將近10TB的資料。
2.3 ES和Solr
- Solr在查詢死資料時,速度相對ES更快一些。但是資料如果是實時改變的,Solr的查詢速度會降低很多,ES的查詢的效率基本沒有變化。
- Solr搭建基於需要依賴Zookeeper來幫助管理。ES本身就支援叢集的搭建,不需要第三方的介入。
- 最開始Solr的社群可以說是非常火爆,針對國內的文件並不是很多。在ES出現之後,ES的社群火爆程度直線上升,ES的文件非常健全。
- ES對現在雲端計算和大資料支援的特別好。
2.4 倒排索引
將存放的資料,以一定的方式進行分詞,並且將分詞的內容存放到一個單獨的分詞庫中。
當用戶去查詢資料時,會將使用者的查詢關鍵字進行分詞。
然後去分詞庫中匹配內容,最終得到資料的id標識。
根據id標識去存放資料的位置拉取到指定的資料。
三、 ElasticSearch安裝
3.1 安裝ES&Kibana
yml檔案
version: "3.1" services: elasticsearch: image: daocloud.io/library/elasticsearch:6.5.4 restart: always container_name: elasticsearch ports: - 9200:9200 kibana: image: daocloud.io/library/kibana:6.5.4 restart: always container_name: kibana ports: - 5601:5601 environment: - elasticsearch_url=http://192.168.178.131:9200 depends_on: - elasticsearch
vi /etc/sysctl.conf
新增
vm.max_map_count=655360
儲存
執行如下命令讓他生效
sysctl -p
3.2 安裝IK分詞器
由於網路問題,採用國內的路徑去下載:http://tomcat01.qfjava.cn:81/elasticsearch-analysis-ik-6.5.4.zip
進去到ES容器內部,跳轉到bin目錄下,執行bin目錄下的指令碼檔案:
./elasticsearch-plugin install http://tomcat01.qfjava.cn:81/elasticsearch-analysis-ik-6.5.4.zip
重啟ES的容器,讓IK分詞器生效。
四、 ElasticSearch基本操作
4.1 ES的結構
4.1.1 索引Index,分片和備份
ES的服務中,可以建立多個索引。
每一個索引預設被分成5片儲存。
每一個分片都會存在至少一個備份分片。
備份分片預設不會幫助檢索資料,當ES檢索壓力特別大的時候,備份分片才會幫助檢索資料。
備份的分片必須放在不同的伺服器中。
4.1.2 型別 Type (相當於資料庫中的表)
一個索引下,可以建立多個型別。
4.1.3 文件 Doc (對應表格的行)
一個型別下,可以有多個文件。這個文件就類似於MySQL表中的多行資料。
4.1.4 屬性 Field (對應表格的欄位)
一個文件中,可以包含多個屬性。類似於MySQL表中的一行資料存在多個列。
4.2 操作ES的RESTful語法
- GET請求:
- POST請求:
- PUT請求:
- DELETE請求:
4.3 索引的操作
4.3.1 建立一個索引
語法如下
# 建立一個索引
PUT /person
{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1
}
}
4.3.2 檢視索引資訊
語法如下
# 檢視索引資訊
GET /person
4.3.3 刪除索引
語法如下
# 刪除索引
DELETE /person
4.4 ES中Field可以指定的型別
字串型別:
- text:一把被用於全文檢索。 將當前Field進行分詞。
- keyword:當前Field不會被分詞。
數值型別:
- long:取值範圍為-9223372036854774808~922337203685477480(-2的63次方到2的63次方-1),佔用8個位元組
- integer:取值範圍為-2147483648~2147483647(-2的31次方到2的31次方-1),佔用4個位元組
- short:取值範圍為-32768~32767(-2的15次方到2的15次方-1),佔用2個位元組
- byte:取值範圍為-128~127(-2的7次方到2的7次方-1),佔用1個位元組
- double:1.797693e+308~ 4.9000000e-324 (e+308表示是乘以10的308次方,e-324表示乘以10的負324次方)佔用8個位元組
- float:3.402823e+38 ~ 1.401298e-45(e+38表示是乘以10的38次方,e-45表示乘以10的負45次方),佔用4個位元組
- half_float:精度比float小一半。
- scaled_float:根據一個long和scaled來表達一個浮點型,long-345,scaled-100 -> 3.45
時間型別:
- date型別,針對時間型別指定具體的格式
布林型別:
- boolean型別,表達true和false
二進位制型別:
- binary型別暫時支援Base64 encode string
範圍型別:
- long_range:賦值時,無需指定具體的內容,只需要儲存一個範圍即可,指定gt,lt,gte,lte
- integer_range:同上
- double_range:同上
- float_range:同上
- date_range:同上
- ip_range:同上
經緯度型別:
- geo_point:用來儲存經緯度的
ip型別:
- ip:可以儲存IPV4或者IPV6
其他的資料型別參考官網:https://www.elastic.co/guide/en/elasticsearch/reference/6.5/mapping-types.html
4.5 建立索引並指定資料結構
語法如下
put /索引/型別名/文件id
# 建立索引,指定資料結構
PUT /book
{
"settings": {
# 分片數
"number_of_shards": 5,
# 備份數
"number_of_replicas": 1
},
# 指定資料結構
"mappings": {
# 型別 Type
"novel": {
# 文件儲存的Field
"properties": {
# Field屬性名
"name": {
# 型別
"type": "text",
# 指定分詞器
"analyzer": "ik_max_word",
# 指定當前Field可以被作為查詢的條件
"index": true ,
# 是否需要額外儲存
"store": false
},
"author": {
"type": "keyword"
},
"count": {
"type": "long"
},
"on-sale": {
"type": "date",
# 時間型別的格式化方式
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"descr": {
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
}
4.6 文件的操作
文件在ES服務中的唯一標識,
_index
,_type
,_id
三個內容為組合,鎖定一個文件,操作是新增還是修改。
4.6.1 新建文件
自動生成_id
# 新增文件,自動生成id
POST /book/novel
{
"name": "盤龍",
"author": "我吃西紅柿",
"count": 100000,
"on-sale": "2000-01-01",
"descr": "山重水複疑無路,柳暗花明又一村"
}
手動指定_id
# 新增文件,手動指定id
PUT /book/novel/1
{
"name": "紅樓夢",
"author": "曹雪芹",
"count": 10000000,
"on-sale": "1985-01-01",
"descr": "一個是閬苑仙葩,一個是美玉無瑕"
}
4.6.2 修改文件
覆蓋式修改
# 新增文件,手動指定id
PUT /book/novel/1
{
"name": "紅樓夢",
"author": "曹雪芹",
"count": 4353453,
"on-sale": "1985-01-01",
"descr": "一個是閬苑仙葩,一個是美玉無瑕"
}
doc修改方式
# 修改文件,基於doc方式
POST /book/novel/1/_update
{
"doc": {
# 指定上需要修改的field和對應的值
"count": "1234565"
}
}
4.6.3 刪除文件
根據id刪除
# 根據id刪除文件
DELETE /book/novel/_id
五、Java操作ElasticSearch【重點
】
5.1 Java連線ES
建立Maven工程
匯入依賴
<dependencies>
<!-- 1. elasticsearch-->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>6.5.4</version>
</dependency>
<!-- 2. elasticsearch的高階API-->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>6.5.4</version>
</dependency>
<!-- 3. junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- 4. lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
</dependency>
</dependencies>
建立測試類,連線ES
public class ESClient {
public static RestHighLevelClient getClient(){
// 建立HttpHost物件
HttpHost httpHost = new HttpHost("192.168.199.109",9200);
// 建立RestClientBuilder
RestClientBuilder clientBuilder = RestClient.builder(httpHost);
// 建立RestHighLevelClient
RestHighLevelClient client = new RestHighLevelClient(clientBuilder);
// 返回
return client;
}
}
5.2 Java操作索引
5.2.1 建立索引
程式碼如下
public class Demo2 {
RestHighLevelClient client = ESClient.getClient();
String index = "person";
String type = "man";
@Test
public void createIndex() throws IOException {
//1. 準備關於索引的settings
Settings.Builder settings = Settings.builder()
.put("number_of_shards", 3)
.put("number_of_replicas", 1);
//2. 準備關於索引的結構mappings
XContentBuilder mappings = JsonXContent.contentBuilder()
.startObject()
.startObject("properties")
.startObject("name")
.field("type","text")
.endObject()
.startObject("age")
.field("type","integer")
.endObject()
.startObject("birthday")
.field("type","date")
.field("format","yyyy-MM-dd")
.endObject()
.endObject()
.endObject();
//3. 將settings和mappings封裝到一個Request物件
CreateIndexRequest request = new CreateIndexRequest(index)
.settings(settings)
.mapping(type,mappings);
//4. 通過client物件去連線ES並執行建立索引
CreateIndexResponse resp = client.indices().create(request, RequestOptions.DEFAULT);
//5. 輸出
System.out.println("resp:" + resp.toString());
}
}
5.2.2 檢查索引是否存在
程式碼如下
@Test
public void exists() throws IOException {
//1. 準備request物件
GetIndexRequest request = new GetIndexRequest();
request.indices(index);
//2. 通過client去操作
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
//3. 輸出
System.out.println(exists);
}
5.2.3 刪除索引
程式碼如下
@Test
public void delete() throws IOException {
//1. 準備request物件
DeleteIndexRequest request = new DeleteIndexRequest();
request.indices(index);
//2. 通過client物件執行
AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
//3. 獲取返回結果
System.out.println(delete.isAcknowledged());
}
5.3 Java操作文件
5.3.1 新增文件操作
程式碼如下
public class Demo3 {
ObjectMapper mapper = new ObjectMapper();
RestHighLevelClient client = ESClient.getClient();
String index = "person";
String type = "man";
@Test
public void createDoc() throws IOException {
//1. 準備一個json資料
Person person = new Person(1,"張三",23,new Date());
String json = mapper.writeValueAsString(person);
//2. 準備一個request物件(手動指定id)
IndexRequest request = new IndexRequest(index,type,person.getId().toString());
request.source(json, XContentType.JSON);
//3. 通過client物件執行新增
IndexResponse resp = client.index(request, RequestOptions.DEFAULT);
//4. 輸出返回結果
System.out.println(resp.getResult().toString());
}
}
5.3.2 修改文件
程式碼如下
@Test
public void updateDoc() throws IOException {
//1. 建立一個Map,指定需要修改的內容
Map<String,Object> doc = new HashMap<>();
doc.put("name","張大三");
String docId = "1";
//2. 建立request物件,封裝資料
UpdateRequest request = new UpdateRequest(index,type,docId);
request.doc(doc);
//3. 通過client物件執行
UpdateResponse update = client.update(request, RequestOptions.DEFAULT);
//4. 輸出返回結果
System.out.println(update.getResult().toString());
}
5.3.3 刪除文件
程式碼如下
@Test
public void deleteDoc() throws IOException {
//1. 封裝Request物件
DeleteRequest request = new DeleteRequest(index,type,"1");
//2. client執行
DeleteResponse resp = client.delete(request, RequestOptions.DEFAULT);
//3. 輸出結果
System.out.println(resp.getResult().toString());
}
5.4 Java批量操作文件
5.4.1 批量新增
程式碼如下
@Test
public void bulkCreateDoc() throws IOException {
//1. 準備多個json資料
Person p1 = new Person(1,"張三",23,new Date());
Person p2 = new Person(2,"李四",24,new Date());
Person p3 = new Person(3,"王五",25,new Date());
String json1 = mapper.writeValueAsString(p1);
String json2 = mapper.writeValueAsString(p2);
String json3 = mapper.writeValueAsString(p3);
//2. 建立Request,將準備好的資料封裝進去
BulkRequest request = new BulkRequest();
request.add(new IndexRequest(index,type,p1.getId().toString()).source(json1,XContentType.JSON));
request.add(new IndexRequest(index,type,p2.getId().toString()).source(json2,XContentType.JSON));
request.add(new IndexRequest(index,type,p3.getId().toString()).source(json3,XContentType.JSON));
//3. 用client執行
BulkResponse resp = client.bulk(request, RequestOptions.DEFAULT);
//4. 輸出結果
System.out.println(resp.toString());
}
5.4.2 批量刪除
程式碼如下
@Test
public void bulkDeleteDoc() throws IOException {
//1. 封裝Request物件
BulkRequest request = new BulkRequest();
request.add(new DeleteRequest(index,type,"1"));
request.add(new DeleteRequest(index,type,"2"));
request.add(new DeleteRequest(index,type,"3"));
//2. client執行
BulkResponse resp = client.bulk(request, RequestOptions.DEFAULT);
//3. 輸出
System.out.println(resp);
}
5.5 ElasticSearch練習
建立索引,指定資料結構
索引名:sms-logs-index
型別名:sms-logs-type
結構如下:
六、 ElasticSearch的各種查詢
6.1 term&terms查詢【重點
】
6.1.1 term查詢
term的查詢是代表完全匹配,搜尋之前不會對你搜索的關鍵字進行分詞,對你的關鍵字去文件分詞庫中去匹配內容。
# term查詢
POST /sms-logs-index/sms-logs-type/_search
{
"from": 0, # limit ?
"size": 5, # limit x,?
"query": {
"term": {
"province": {
"value": "北京"
}
}
}
}
程式碼實現方式
// Java程式碼實現方式
@Test
public void termQuery() throws IOException {
//1. 建立Request物件
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查詢條件
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.from(0);
builder.size(5);
builder.query(QueryBuilders.termQuery("province","北京"));
request.source(builder);
//3. 執行查詢
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 獲取到_source中的資料,並展示
for (SearchHit hit : resp.getHits().getHits()) {
Map<String, Object> result = hit.getSourceAsMap();
System.out.println(result);
}
}
6.1.2 terms查詢
terms和term的查詢機制是一樣,都不會將指定的查詢關鍵字進行分詞,直接去分詞庫中匹配,找到相應文件內容。
terms是在針對一個欄位包含多個值的時候使用。
term:where province = 北京;
terms:where province = 北京 or province = ?or province = ?
# terms查詢
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"terms": {
"province": [
"北京",
"山西",
"武漢"
]
}
}
}
程式碼實現方式
// Java實現
@Test
public void termsQuery() throws IOException {
//1. 建立request
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 封裝查詢條件
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.termsQuery("province","北京","山西"));
request.source(builder);
//3. 執行查詢
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 輸出_source
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
6.2 match查詢【重點
】
match查詢屬於高層查詢,他會根據你查詢的欄位型別不一樣,採用不同的查詢方式。
- 查詢的是日期或者是數值的話,他會將你基於的字串查詢內容轉換為日期或者數值對待。
- 如果查詢的內容是一個不能被分詞的內容(keyword),match查詢不會對你指定的查詢關鍵字進行分詞。
- 如果查詢的內容時一個可以被分詞的內容(text),match會將你指定的查詢內容根據一定的方式去分詞,去分詞庫中匹配指定的內容。
match查詢,實際底層就是多個term查詢,將多個term查詢的結果給你封裝到了一起。
6.2.1 match_all查詢
查詢全部內容,不指定任何查詢條件。
# match_all查詢
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"match_all": {}
}
}
程式碼實現方式
// java程式碼實現
@Test
public void matchAllQuery() throws IOException {
//1. 建立Request
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查詢條件
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.matchAllQuery());
builder.size(20); // ES預設只查詢10條資料,如果想查詢更多,新增size
request.source(builder);
//3. 執行查詢
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 輸出結果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
System.out.println(resp.getHits().getHits().length);
}
6.2.2 match查詢
指定一個Field作為篩選的條件
# match查詢
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"match": {
"smsContent": "收貨安裝"
}
}
}
程式碼實現方式
@Test
public void matchQuery() throws IOException {
//1. 建立Request
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查詢條件
SearchSourceBuilder builder = new SearchSourceBuilder();
//-----------------------------------------------
builder.query(QueryBuilders.matchQuery("smsContent","收貨安裝"));
//-----------------------------------------------
request.source(builder);
//3. 執行查詢
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 輸出結果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
6.2.3 布林match查詢
基於一個Field匹配的內容,採用and或者or的方式連線
# 布林match查詢
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"match": {
"smsContent": {
"query": "中國 健康",
"operator": "and" # 內容既包含中國也包含健康
}
}
}
}
# 布林match查詢
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"match": {
"smsContent": {
"query": "中國 健康",
"operator": "or" # 內容包括健康或者包括中國
}
}
}
}
程式碼實現方式
// Java程式碼實現
@Test
public void booleanMatchQuery() throws IOException {
//1. 建立Request
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查詢條件
SearchSourceBuilder builder = new SearchSourceBuilder();
//----------------------------------------------- 選擇AND或者OR
builder.query(QueryBuilders.matchQuery("smsContent","中國 健康").operator(Operator.OR));
//-----------------------------------------------
request.source(builder);
//3. 執行查詢
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 輸出結果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
6.2.4 multi_match查詢
match針對一個field做檢索,multi_match針對多個field進行檢索,多個field對應一個text。
# multi_match 查詢
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"multi_match": {
"query": "北京", # 指定text
"fields": ["province","smsContent"] # 指定field們
}
}
}
程式碼實現方式
// java程式碼實現
@Test
public void multiMatchQuery() throws IOException {
//1. 建立Request
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查詢條件
SearchSourceBuilder builder = new SearchSourceBuilder();
//-----------------------------------------------
builder.query(QueryBuilders.multiMatchQuery("北京","province","smsContent"));
//-----------------------------------------------
request.source(builder);
//3. 執行查詢
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 輸出結果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
6.3 其他查詢
6.3.1 id查詢
根據id查詢 where id = ?
# id查詢
GET /sms-logs-index/sms-logs-type/1
程式碼實現方式
// Java程式碼實現
@Test
public void findById() throws IOException {
//1. 建立GetRequest
GetRequest request = new GetRequest(index,type,"1");
//2. 執行查詢
GetResponse resp = client.get(request, RequestOptions.DEFAULT);
//3. 輸出結果
System.out.println(resp.getSourceAsMap());
}
6.3.2 ids查詢
根據多個id查詢,類似MySQL中的where id in(id1,id2,id2...)
# ids查詢
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"ids": {
"values": ["1","2","3"]
}
}
}
程式碼實現方式
// Java程式碼實現
@Test
public void findByIds() throws IOException {
//1. 建立SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查詢條件
SearchSourceBuilder builder = new SearchSourceBuilder();
//----------------------------------------------------------
builder.query(QueryBuilders.idsQuery().addIds("1","2","3"));
//----------------------------------------------------------
request.source(builder);
//3. 執行
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 輸出結果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
6.3.3 prefix查詢
字首查詢,可以通過一個關鍵字去指定一個Field的字首,從而查詢到指定的文件。
#prefix 查詢
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"prefix": {
"corpName": {
"value": "途虎"
}
}
}
}
程式碼實現方式
// Java實現字首查詢
@Test
public void findByPrefix() throws IOException {
//1. 建立SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查詢條件
SearchSourceBuilder builder = new SearchSourceBuilder();
//----------------------------------------------------------
builder.query(QueryBuilders.prefixQuery("corpName","盒馬"));
//----------------------------------------------------------
request.source(builder);
//3. 執行
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 輸出結果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
6.3.4 fuzzy查詢
模糊查詢,我們輸入字元的大概,ES就可以去根據輸入的內容大概去匹配一下結果。
# fuzzy查詢
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"fuzzy": {
"corpName": {
"value": "盒馬先生",
"prefix_length": 2 # 指定前面幾個字元是不允許出現錯誤的
}
}
}
}
程式碼實現方式
// Java程式碼實現Fuzzy查詢
@Test
public void findByFuzzy() throws IOException {
//1. 建立SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查詢條件
SearchSourceBuilder builder = new SearchSourceBuilder();
//----------------------------------------------------------
builder.query(QueryBuilders.fuzzyQuery("corpName","盒馬先生").prefixLength(2));
//----------------------------------------------------------
request.source(builder);
//3. 執行
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 輸出結果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
6.3.5 wildcard查詢
通配查詢,和MySQL中的like是一個套路,可以在查詢時,在字串中指定萬用字元*和佔位符?
# wildcard 查詢
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"wildcard": {
"corpName": {
"value": "中國*" # 可以使用*和?指定萬用字元和佔位符
}
}
}
}
程式碼實現方式
// Java程式碼實現Wildcard查詢
@Test
public void findByWildCard() throws IOException {
//1. 建立SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查詢條件
SearchSourceBuilder builder = new SearchSourceBuilder();
//----------------------------------------------------------
builder.query(QueryBuilders.wildcardQuery("corpName","中國*"));
//----------------------------------------------------------
request.source(builder);
//3. 執行
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 輸出結果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
6.3.6 range查詢
範圍查詢,只針對數值型別,對某一個Field進行大於或者小於的範圍指定
# range 查詢
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"range": {
"fee": {
"gt": 5,
"lte": 10
# 可以使用 gt:> gte:>= lt:< lte:<=
}
}
}
}
程式碼實現方式
// Java實現range範圍查詢
@Test
public void findByRange() throws IOException {
//1. 建立SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查詢條件
SearchSourceBuilder builder = new SearchSourceBuilder();
//----------------------------------------------------------
builder.query(QueryBuilders.rangeQuery("fee").lte(10).gte(5));
//----------------------------------------------------------
request.source(builder);
//3. 執行
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 輸出結果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
6.3.7 regexp查詢
正則查詢,通過你編寫的正則表示式去匹配內容。
# regexp 查詢
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"regexp": {
"mobile": "180[0-9]{8}" # 編寫正則
}
}
}
程式碼實現方式
// Java程式碼實現正則查詢
@Test
public void findByRegexp() throws IOException {
//1. 建立SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查詢條件
SearchSourceBuilder builder = new SearchSourceBuilder();
//----------------------------------------------------------
builder.query(QueryBuilders.regexpQuery("mobile","139[0-9]{8}"));
//----------------------------------------------------------
request.source(builder);
//3. 執行
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 輸出結果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
6.4 深分頁Scroll
ES對from + size是有限制的,from和size二者之和不能超過1W
原理:
from+size在ES查詢資料的方式:
- 第一步現將使用者指定的關鍵進行分詞。
- 第二步將詞彙去分詞庫中進行檢索,得到多個文件的id。
- 第三步去各個分片中去拉取指定的資料。耗時較長。
- 第四步將資料根據score進行排序。耗時較長。
- 第五步根據from的值,將查詢到的資料捨棄一部分。
- 第六步返回結果。
scroll+size在ES查詢資料的方式:
- 第一步現將使用者指定的關鍵進行分詞。
- 第二步將詞彙去分詞庫中進行檢索,得到多個文件的id。
- 第三步將文件的id存放在一個ES的上下文中。
- 第四步根據你指定的size的個數去ES中檢索指定個數的資料,拿完資料的文件id,會從上下文中移除。
- 第五步如果需要下一頁資料,直接去ES的上下文中,找後續內容。
- 第六步迴圈第四步和第五步
# 執行scroll查詢,返回第一頁資料,並且將文件id資訊存放在ES上下文中,指定生存時間1m
POST /sms-logs-index/sms-logs-type/_search?scroll=1m
{
"query": {
"match_all": {}
},
"size": 2,
"sort": [ # 排序
{
"fee": {
"order": "desc"
}
}
]
}
# 根據scroll查詢下一頁資料
POST /_search/scroll
{
"scroll_id": "<根據第一步得到的scorll_id去指定>",
"scroll": "<scorll資訊的生存時間>"
}
# 刪除scroll在ES上下文中的資料
DELETE /_search/scroll/scroll_id
程式碼實現方式
// Java實現scroll分頁
@Test
public void scrollQuery() throws IOException {
//1. 建立SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定scroll資訊
request.scroll(TimeValue.timeValueMinutes(1L));
//3. 指定查詢條件
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.size(4);
builder.sort("fee", SortOrder.DESC);
builder.query(QueryBuilders.matchAllQuery());
request.source(builder);
//4. 獲取返回結果scrollId,source
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
String scrollId = resp.getScrollId();
System.out.println("----------首頁---------");
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
while(true) {
//5. 迴圈 - 建立SearchScrollRequest
SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
//6. 指定scrollId的生存時間
scrollRequest.scroll(TimeValue.timeValueMinutes(1L));
//7. 執行查詢獲取返回結果
SearchResponse scrollResp = client.scroll(scrollRequest, RequestOptions.DEFAULT);
//8. 判斷是否查詢到了資料,輸出
SearchHit[] hits = scrollResp.getHits().getHits();
if(hits != null && hits.length > 0) {
System.out.println("----------下一頁---------");
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsMap());
}
}else{
//9. 判斷沒有查詢到資料-退出迴圈
System.out.println("----------結束---------");
break;
}
}
//10. 建立CLearScrollRequest
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
//11. 指定ScrollId
clearScrollRequest.addScrollId(scrollId);
//12. 刪除ScrollId
ClearScrollResponse clearScrollResponse = client.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
//13. 輸出結果
System.out.println("刪除scroll:" + clearScrollResponse.isSucceeded());
}
6.5 delete-by-query
根據term,match等查詢方式去刪除大量的文件
Ps:如果你需要刪除的內容,是index下的大部分資料,推薦建立一個全新的index,將保留的文件內容,新增到全新的索引
# delete-by-query
POST /sms-logs-index/sms-logs-type/_delete_by_query
{
"query": {
"range": {
"fee": {
"lt": 4
}
}
}
}
程式碼實現方式
// Java程式碼實現
@Test
public void deleteByQuery() throws IOException {
//1. 建立DeleteByQueryRequest
DeleteByQueryRequest request = new DeleteByQueryRequest(index);
request.types(type);
//2. 指定檢索的條件 和SearchRequest指定Query的方式不一樣
request.setQuery(QueryBuilders.rangeQuery("fee").lt(4));
//3. 執行刪除
BulkByScrollResponse resp = client.deleteByQuery(request, RequestOptions.DEFAULT);
//4. 輸出返回結果
System.out.println(resp.toString());
}
6.6 複合查詢
6.6.1 bool查詢
複合過濾器,將你的多個查詢條件,以一定的邏輯組合在一起。
- must: 所有的條件,用must組合在一起,表示And的意思
- must_not:將must_not中的條件,全部都不能匹配,標識Not的意思
- should:所有的條件,用should組合在一起,表示Or的意思
# 查詢省份為武漢或者北京
# 運營商不是聯通
# smsContent中包含中國和平安
# bool查詢
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"province": {
"value": "北京"
}
}
},
{
"term": {
"province": {
"value": "武漢"
}
}
}
],
"must_not": [
{
"term": {
"operatorId": {
"value": "2"
}
}
}
],
"must": [
{
"match": {
"smsContent": "中國"
}
},
{
"match": {
"smsContent": "平安"
}
}
]
}
}
}
程式碼實現方式
// Java程式碼實現Bool查詢
@Test
public void BoolQuery() throws IOException {
//1. 建立SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查詢條件
SearchSourceBuilder builder = new SearchSourceBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// # 查詢省份為武漢或者北京
boolQuery.should(QueryBuilders.termQuery("province","武漢"));
boolQuery.should(QueryBuilders.termQuery("province","北京"));
// # 運營商不是聯通
boolQuery.mustNot(QueryBuilders.termQuery("operatorId",2));
// # smsContent中包含中國和平安
boolQuery.must(QueryBuilders.matchQuery("smsContent","中國"));
boolQuery.must(QueryBuilders.matchQuery("smsContent","平安"));
builder.query(boolQuery);
request.source(builder);
//3. 執行查詢
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 輸出結果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
6.6.2 boosting查詢
boosting查詢可以幫助我們去影響查詢後的score。
- positive:只有匹配上positive的查詢的內容,才會被放到返回的結果集中。
- negative:如果匹配上和positive並且也匹配上了negative,就可以降低這樣的文件score。
- negative_boost:指定係數,必須小於1.0
關於查詢時,分數是如何計算的:
- 搜尋的關鍵字在文件中出現的頻次越高,分數就越高
- 指定的文件內容越短,分數就越高
- 我們在搜尋時,指定的關鍵字也會被分詞,這個被分詞的內容,被分詞庫匹配的個數越多,分數越高
# boosting查詢 收貨安裝
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"boosting": {
"positive": {
"match": {
"smsContent": "收貨安裝"
}
},
"negative": {
"match": {
"smsContent": "王五"
}
},
"negative_boost": 0.5
}
}
}
程式碼實現方式
// Java實現Boosting查詢
@Test
public void BoostingQuery() throws IOException {
//1. 建立SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查詢條件
SearchSourceBuilder builder = new SearchSourceBuilder();
BoostingQueryBuilder boostingQuery = QueryBuilders.boostingQuery(
QueryBuilders.matchQuery("smsContent", "收貨安裝"),
QueryBuilders.matchQuery("smsContent", "王五")
).negativeBoost(0.5f);
builder.query(boostingQuery);
request.source(builder);
//3. 執行查詢
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 輸出結果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
6.7 filter查詢
query,根據你的查詢條件,去計算文件的匹配度得到一個分數,並且根據分數進行排序,不會做快取的。
filter,根據你的查詢條件去查詢文件,不去計算分數,而且filter會對經常被過濾的資料進行快取。
# filter查詢
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"corpName": "盒馬鮮生"
}
},
{
"range": {
"fee": {
"lte": 4
}
}
}
]
}
}
}
程式碼實現方式
// Java實現filter操作
@Test
public void filter() throws IOException {
//1. SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 查詢條件
SearchSourceBuilder builder = new SearchSourceBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.filter(QueryBuilders.termQuery("corpName","盒馬鮮生"));
boolQuery.filter(QueryBuilders.rangeQuery("fee").lte(5));
builder.query(boolQuery);
request.source(builder);
//3. 執行查詢
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 輸出結果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
6.8 高亮查詢【重點
】
高亮查詢就是你使用者輸入的關鍵字,以一定的特殊樣式展示給使用者,讓使用者知道為什麼這個結果被檢索出來。
高亮展示的資料,本身就是文件中的一個Field,單獨將Field以highlight的形式返回給你。
ES提供了一個highlight屬性,和query同級別的。
- fragment_size:指定高亮資料展示多少個字元回來。
- pre_tags:指定字首標籤,舉個栗子< font color="red" >
- post_tags:指定字尾標籤,舉個栗子< /font >
- fields:指定哪幾個Field以高亮形式返回
效果圖 |
---|
RESTful實現
# highlight查詢
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"match": {
"smsContent": "盒馬"
}
},
"highlight": {
"fields": {
"smsContent": {}
},
"pre_tags": "<font color='red'>",
"post_tags": "</font>",
"fragment_size": 10
}
}
程式碼實現方式
// Java實現高亮查詢
@Test
public void highLightQuery() throws IOException {
//1. SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定查詢條件(高亮)
SearchSourceBuilder builder = new SearchSourceBuilder();
//2.1 指定查詢條件
builder.query(QueryBuilders.matchQuery("smsContent","盒馬"));
//2.2 指定高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("smsContent",10)
.preTags("<font color='red'>")
.postTags("</font>");
builder.highlighter(highlightBuilder);
request.source(builder);
//3. 執行查詢
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 獲取高亮資料,輸出
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getHighlightFields().get("smsContent"));
}
}
6.9 聚合查詢【重點
】
ES的聚合查詢和MySQL的聚合查詢類似,ES的聚合查詢相比MySQL要強大的多,ES提供的統計資料的方式多種多樣。
# ES聚合查詢的RESTful語法
POST /index/type/_search
{
"aggs": {
"名字(agg)": {
"agg_type": {
"屬性": "值"
}
}
}
}
6.9.1 去重計數查詢
去重計數,即Cardinality,第一步先將返回的文件中的一個指定的field進行去重,統計一共有多少條
# 去重計數查詢 北京 上海 武漢 山西
POST /sms-logs-index/sms-logs-type/_search
{
"aggs": {
"agg": {
"cardinality": {
"field": "province"
}
}
}
}
程式碼實現方式
// Java程式碼實現去重計數查詢
@Test
public void cardinality() throws IOException {
//1. 建立SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定使用的聚合查詢方式
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.aggregation(AggregationBuilders.cardinality("agg").field("province"));
request.source(builder);
//3. 執行查詢
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 獲取返回結果
Cardinality agg = resp.getAggregations().get("agg");
long value = agg.getValue();
System.out.println(value);
}
6.9.2 範圍統計
統計一定範圍內出現的文件個數,比如,針對某一個Field的值在 0100,100200,200~300之間文件出現的個數分別是多少。
範圍統計可以針對普通的數值,針對時間型別,針對ip型別都可以做相應的統計。
range,date_range,ip_range
數值統計
# 數值方式範圍統計
POST /sms-logs-index/sms-logs-type/_search
{
"aggs": {
"agg": {
"range": {
"field": "fee",
"ranges": [
{
"to": 5
},
{
"from": 5, # from有包含當前值的意思
"to": 10
},
{
"from": 10
}
]
}
}
}
}
時間範圍統計
# 時間方式範圍統計
POST /sms-logs-index/sms-logs-type/_search
{
"aggs": {
"agg": {
"date_range": {
"field": "createDate",
"format": "yyyy",
"ranges": [
{
"to": 2000
},
{
"from": 2000
}
]
}
}
}
}
ip統計方式
# ip方式 範圍統計
POST /sms-logs-index/sms-logs-type/_search
{
"aggs": {
"agg": {
"ip_range": {
"field": "ipAddr",
"ranges": [
{
"to": "10.126.2.9"
},
{
"from": "10.126.2.9"
}
]
}
}
}
}
程式碼實現方式
// Java實現數值 範圍統計
@Test
public void range() throws IOException {
//1. 建立SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定使用的聚合查詢方式
SearchSourceBuilder builder = new SearchSourceBuilder();
//---------------------------------------------
builder.aggregation(AggregationBuilders.range("agg").field("fee")
.addUnboundedTo(5)
.addRange(5,10)
.addUnboundedFrom(10));
//---------------------------------------------
request.source(builder);
//3. 執行查詢
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 獲取返回結果
Range agg = resp.getAggregations().get("agg");
for (Range.Bucket bucket : agg.getBuckets()) {
String key = bucket.getKeyAsString();
Object from = bucket.getFrom();
Object to = bucket.getTo();
long docCount = bucket.getDocCount();
System.out.println(String.format("key:%s,from:%s,to:%s,docCount:%s",key,from,to,docCount));
}
}
6.9.3 統計聚合查詢
他可以幫你查詢指定Field的最大值,最小值,平均值,平方和等
使用:extended_stats
# 統計聚合查詢
POST /sms-logs-index/sms-logs-type/_search
{
"aggs": {
"agg": {
"extended_stats": {
"field": "fee"
}
}
}
}
程式碼實現方式
// Java實現統計聚合查詢
@Test
public void extendedStats() throws IOException {
//1. 建立SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定使用的聚合查詢方式
SearchSourceBuilder builder = new SearchSourceBuilder();
//---------------------------------------------
builder.aggregation(AggregationBuilders.extendedStats("agg").field("fee"));
//---------------------------------------------
request.source(builder);
//3. 執行查詢
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 獲取返回結果
ExtendedStats agg = resp.getAggregations().get("agg");
double max = agg.getMax();
double min = agg.getMin();
System.out.println("fee的最大值為:" + max + ",最小值為:" + min);
}
其他的聚合查詢方式檢視官方文件:https://www.elastic.co/guide/en/elasticsearch/reference/6.5/index.html
6.10 地圖經緯度搜索
ES中提供了一個數據型別 geo_point,這個型別就是用來儲存經緯度的。
建立一個帶geo_point型別的索引,並新增測試資料
# 建立一個索引,指定一個name,locaiton
PUT /map
{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1
},
"mappings": {
"map": {
"properties": {
"name": {
"type": "text"
},
"location": {
"type": "geo_point"
}
}
}
}
}
# 新增測試資料
PUT /map/map/1
{
"name": "天安門",
"location": {
"lon": 116.403981,
"lat": 39.914492
}
}
PUT /map/map/2
{
"name": "海淀公園",
"location": {
"lon": 116.302509,
"lat": 39.991152
}
}
PUT /map/map/3
{
"name": "北京動物園",
"location": {
"lon": 116.343184,
"lat": 39.947468
}
}
6.10.1 ES的地圖檢索方式
語法 | 說明 |
---|---|
geo_distance | 直線距離檢索方式 |
geo_bounding_box | 以兩個點確定一個矩形,獲取在矩形內的全部資料 |
geo_polygon | 以多個點,確定一個多邊形,獲取多邊形內的全部資料 |
6.10.2 基於RESTful實現地圖檢索
geo_distance
# geo_distance
POST /map/map/_search
{
"query": {
"geo_distance": {
"location": { # 確定一個點
"lon": 116.433733,
"lat": 39.908404
},
"distance": 3000, # 確定半徑
"distance_type": "arc" # 指定形狀為圓形
}
}
}
geo_bounding_box
# geo_bounding_box
POST /map/map/_search
{
"query": {
"geo_bounding_box": {
"location": {
"top_left": { # 左上角的座標點
"lon": 116.326943,
"lat": 39.95499
},
"bottom_right": { # 右下角的座標點
"lon": 116.433446,
"lat": 39.908737
}
}
}
}
}
geo_polygon
# geo_polygon
POST /map/map/_search
{
"query": {
"geo_polygon": {
"location": {
"points": [ # 指定多個點確定一個多邊形
{
"lon": 116.298916,
"lat": 39.99878
},
{
"lon": 116.29561,
"lat": 39.972576
},
{
"lon": 116.327661,
"lat": 39.984739
}
]
}
}
}
}
6.10.3 Java實現geo_polygon
// 基於Java實現geo_polygon查詢
@Test
public void geoPolygon() throws IOException {
//1. SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
//2. 指定檢索方式
SearchSourceBuilder builder = new SearchSourceBuilder();
List<GeoPoint> points = new ArrayList<>();
points.add(new GeoPoint(39.99878,116.298916));
points.add(new GeoPoint(39.972576,116.29561));
points.add(new GeoPoint(39.984739,116.327661));
builder.query(QueryBuilders.geoPolygonQuery("location",points));
request.source(builder);
//3. 執行查詢
SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
//4. 輸出結果
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}