typora-copy-images-to: img
typora-root-url: img
Elasticsearch实战与原理解析读书笔记
Elasticsearch的简介
功能
- 搜索
- 分析
- 存储数据
特点
- 分布式
- 零配置
- 易装易用
- 自动发现
- 索引自动分片
- 索引副本机制
- RESTful风格接口
- 多数据源
- 自动搜索负载
Lucene简介
Lucene默认实现了布尔操作,模糊查询,分组查询。
Lucene的模块
- Analisis模块:主要负责词法分析及语言处理。
- Index模块:主要负责索引的创建工作。
- Store模块:主要负责索引的读和写,主要对文件的一些操作,其主要目的是抽象出和平台文件系统无关的存储。
- QueryParser模块:主要负责语法分析,把查询语句生成Lucene底层可以识别的条件。
- Search模块:主要负责对索引的搜索工作。
- Similarity模块:主要负责相关性打分和排序的实现。
Lucene的专业术语
- Term
- 词典
- 倒排表
- 正向信息
- 段
数据搜索方式
-
结构化数据
-
顺序扫描
-
关键词精确匹配
-
关键词部分匹配
- 较为复杂的关键词部分匹配,通常需要借助like关键字来实现,如左匹配关键词“TAL”时需要使用like “TAL%”,右匹配关键词“TAL”时需要使用like “%TAL”,完全模糊匹配关键词“TAL”时需要使用like “%TAL%”。
-
-
非结构化数据
- 顺序扫描(效率低)
- 全文检索
搜索引擎的工作原理分为两个阶段
- 网页数据爬取和索引阶段
- 网络爬虫
- 数据预处理
- 数据索引
- 搜索阶段
- 搜索关键词
- 输入内容预处理
- 搜索关键词查询
倒排索引
倒排索引源于实际应用中需要根据属性的值来查找记录。这种索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引(inverted index)。带有倒排索引的文件我们称为倒排索引文件,简称倒排文件。
倒排索引中的名词介绍
- 词条:是索引里面最小的存储和查询单元。
- 词典:是词典的集合
- 倒排表:
Elasticsearch的核心概念
- Node:节点
- Cluster:集群
- Green:所有的主分片和副本分片都可以正常工作
- Yellow:所有的主分片都可以正常工作,但至少有一个副本分片不可以正常工作
- Red:至少有一个分片的主分片以及它的全部副本分片都不可正常工作
- Shards:默认为一个索引创建5个主分片,并分别为每个主分片创建一个副本
- Replicas
- Index:索引,由一个或多个分片组成。在使用索引时,需要通过索引名称在集群内进行唯一标识。
- Type
- Document
- Setting
- Mapping
- Analyzer
Elasticsearch中的索引Index如果对标关系型数据库中的数据库database的话,则表table与类型type对应,一个数据库下面可以有多张表table,就像一个索引index下面有多种类型type一样。
Elasticsearch | 关系数据库 |
---|---|
索引Indices | 库Databases |
类型Types | 表Tables |
文档Documents | 行Rows |
字段Fields | 列Columns |
映射Mapping | schema |
Put/Post/Delete/Update/Get | 增删改查 |
副本的发现策略
- 中心化方式
- 去中心化方式
客户端的主要功能
- 跨所有可用节点的负载平衡
- 在节点故障和特定响应代码时的故障转移
- 失败连接的惩罚机制
- 持久连接
- 请求和响应的跟踪日志记录
- 自动发现集群节点,该功能可选
文档索引API
四种构建JSON文档的方法
-
基于String构建IndexRequest
-
public void buildIndexRequestWithString(String indexName, String document) { // 索引名称 IndexRequest request = new IndexRequest(indexName); // 文档ID request.id(document); // String类型的文档 String jsonString = "{" + "\"user\":\"niudong\"," + "\"postDate\":\"2019-07-30\"," + "\"message\":\"Hello Elasticsearch\"" + "}"; request.source(jsonString, XContentType.JSON); }
-
-
基于Map构建IndexRequest
-
public void buildIndexRequestWithMap(String indexName, String document) { Map<String, Object> jsonMap = new HashMap<>(); jsonMap.put("user", "niudong"); jsonMap.put("postDate", new Date()); jsonMap.put("message", "Hello Elasticsearch"); // 作为自动转换为JSON格式的MAP提供的文档源 IndexRequest indexRequest = new IndexRequest(indexName).id(document).source(jsonMap); }
-
-
基于XContentBuilder构建IndexRequest
-
public void buildIndexRequestWithXContentBuilder(String indexName, String document) { try { XContentBuilder builder = XContentFactory.jsonBuilder(); builder.startObject(); { builder.field("user", "niudong"); builder.timeField("postDate", new Date()); builder.field("message", "Hello Elasticsearch"); } builder.endObject(); // 作为XContentBuilder对象提供的文档源,ElasticSearch内置的帮助器用于生成JSON内容 IndexRequest indexRequest = new IndexRequest(indexName).id(document).source(builder); } catch (Exception e) { e.printStackTrace(); } }
-
-
基于Key-value 构建IndexRequest
-
public void buildIndexRequestWithKV(String indexName, String document) { // 作为Key-value对提供的文档源,转换为JSON格式 IndexRequest indexRequest = new IndexRequest(indexName).id(document).source("user", "niudong", "postDate", new Date(), "message", "Hello Elasticsearch"); }
-
构建IndexRequest的其他参数配置
public void buildIndexRequestWithParam(String indexName, String document) {
// 作为Key-value对提供的文档源,转换为JSON格式
IndexRequest request = new IndexRequest(indexName).id(document).source("user", "niudong",
"postDate", new Date(), "message", "Hello Elasticsearch");
request.routing("routing");// 路由值
// 设置超时时间
request.timeout(TimeValue.timeValueSeconds(1));
request.timeout("1s");
// 设置超时策略
request.setRefreshPolicy(WriteRequest.RefreshPolicy.WAIT_UNTIL);
request.setRefreshPolicy("wait_for");
// 设置版本
request.version(2);
// 设置版本类型
request.versionType(VersionType.EXTERNAL);
// 设置操作类型
request.opType(DocWriteRequest.OpType.CREATE);
request.opType("create");
// 索引文档之前要执行的接收管道的名称
request.setPipeline("pipeline");
}
文档索引请求
-
同步方式
-
IndexResponse indexResponse = restClient.index(indexRequest, RequestOptions.DEFAULT);
-
public void indexDocuments(String indexName, String document) { // 作为Key-value对提供的文档源,转换为JSON格式 IndexRequest indexRequest = new IndexRequest(indexName).id(document).source("user", "niudong", "postDate", new Date(), "message", "Hello Elasticsearch!北京时间8月1日凌晨2点,美联储公布7月议息会议结果。一如市场预期,美联储本次降息25个基点,将联邦基金利率的目标范围调至2.00%-2.25%。此次是2007-2008年间美国为应对金融危机启动降息周期后,美联储十年多以来首次降息。美联储公布利率决议后,美股下跌,美元上涨,人民币汇率下跌"); try { IndexResponse indexResponse = restClient.index(indexRequest, RequestOptions.DEFAULT); // 解析索引结果 processIndexResponse(indexResponse); } catch (Exception e) { e.printStackTrace(); } }
-
-
异步方式
-
restClient.indexAsync(indexRequest, RequestOptions.DEFAULT, listener);
-
public void indexDocumentsAsync(String indexName, String document) { // 作为Key-value对提供的文档源,转换为JSON格式 IndexRequest indexRequest = new IndexRequest(indexName).id(document).source("user", "niudong", "postDate", new Date(), "message", "Hello Elasticsearch"); ActionListener listener = new ActionListener<IndexResponse>() { @Override public void onResponse(IndexResponse indexResponse) { } @Override public void onFailure(Exception e) { } }; try { restClient.indexAsync(indexRequest, RequestOptions.DEFAULT, listener); } catch (Exception e) { e.printStackTrace(); } }
-
解析索引结果IndexResponse
private void processIndexResponse(IndexResponse indexResponse) {
String index = indexResponse.getIndex();
String id = indexResponse.getId();
log.info("index is " + index + ", id is " + id);
if (indexResponse.getResult() == DocWriteResponse.Result.CREATED) {
// 文档创建时
log.info("Document is created!");
} else if (indexResponse.getResult() == DocWriteResponse.Result.UPDATED) {
// 文档更新时
log.info("Document has updated!");
}
ReplicationResponse.ShardInfo shardInfo = indexResponse.getShardInfo();
if (shardInfo.getTotal() != shardInfo.getSuccessful()) {
// 处理成功shards 小于总shards 的情况
log.info("Successed shards are not enough!");
}
if (shardInfo.getFailed() > 0) {
for (ReplicationResponse.ShardInfo.Failure failure : shardInfo.getFailures()) {
String reason = failure.reason();
log.info("Fail reason is " + reason);
}
}
}
文档索引查询 即 Get API
1 构建文档索引请求GetRequest
GetRequest getRequest = new GetRequest(indexName, document);
public void buildGetRequest(String indexName, String document) {
GetRequest getRequest = new GetRequest(indexName, document);
// 可选配置参数
// 禁用源检索,默认情况下启用
getRequest.fetchSourceContext(FetchSourceContext.DO_NOT_FETCH_SOURCE);
// 为特定字段配置源包含
String[] includes = new String[] {"message", "*Date"};
String[] excludes = Strings.EMPTY_ARRAY;
FetchSourceContext fetchSourceContext = new FetchSourceContext(true, includes, excludes);
getRequest.fetchSourceContext(fetchSourceContext);
// 为特定字段配置源排除
includes = Strings.EMPTY_ARRAY;
excludes = new String[] {"message"};
fetchSourceContext = new FetchSourceContext(true, includes, excludes);
getRequest.fetchSourceContext(fetchSourceContext);
getRequest.storedFields("message"); // 为特定存储字段配置检索(要求在映射中单独存储字段)
try {
GetResponse getResponse = restClient.get(getRequest, RequestOptions.DEFAULT);
String message = getResponse.getField("message").getValue();// 检索消息存储字段(要求该字段单独存储在映射中)
log.info("message is " + message);
} catch (Exception e) {
e.printStackTrace();
}
// 路由值
getRequest.routing("routing");
// 偏好值
getRequest.preference("preference");
// 将实时标志设置为假(默认为真)
getRequest.realtime(false);
// 在检索文档之前执行刷新(默认为false)
getRequest.refresh(true);
// 配置版本号
getRequest.version(2);
// 配置版本类型
getRequest.versionType(VersionType.EXTERNAL);
}
2 执行文档索引查询请求
-
同步方式
-
public void getIndexDocuments(String indexName, String document) { GetRequest getRequest = new GetRequest(indexName, document); try { GetResponse getResponse = restClient.get(getRequest, RequestOptions.DEFAULT); // 处理GetResponse processGetResponse(getResponse); } catch (Exception e) { e.printStackTrace(); } // 关闭ES连接 closeEs(); }
-
-
异步方式
-
public void getIndexDocumentsAsync(String indexName, String document) { GetRequest getRequest = new GetRequest(indexName, document); ActionListener<GetResponse> listener = new ActionListener<GetResponse>() { @Override public void onResponse(GetResponse getResponse) { String id = getResponse.getId(); String index = getResponse.getIndex(); log.info("id is " + id + ", index is " + index); } @Override public void onFailure(Exception e) { } }; try { restClient.getAsync(getRequest, RequestOptions.DEFAULT, listener); } catch (Exception e) { e.printStackTrace(); } // 关闭ES连接 closeEs(); }
-
3 返回结果的解析
// 处理GetResponse
private void processGetResponse(GetResponse getResponse) {
String index = getResponse.getIndex();
String id = getResponse.getId();
log.info("id is " + id + ", index is " + index);
if (getResponse.isExists()) {
long version = getResponse.getVersion();
String sourceAsString = getResponse.getSourceAsString(); // 以字符串形式检索文档
Map<String, Object> sourceAsMap = getResponse.getSourceAsMap(); // 以Map<String, Object>形式检索文档
byte[] sourceAsBytes = getResponse.getSourceAsBytes(); // 以byte[]形式检索文档
log.info("version is " + version + ", sourceAsString is " + sourceAsString);
} else {
// 找不到文档时在此处处理。请注意,尽管返回的响应具有404状态代码,但返回的是有效的getResponse,而不是引发异常。这样的响应不包含任何源文档,并且其isexists方法返回false。
}
}
文档存在性校验
-
同步方式
-
// 同步方式校验索引文档是否存在 public void checkExistIndexDocuments(String indexName, String document) { GetRequest getRequest = new GetRequest(indexName, document); // 禁用提取源 getRequest.fetchSourceContext(new FetchSourceContext(false)); // 禁用提取存储字段 getRequest.storedFields("_none_"); try { boolean exists = restClient.exists(getRequest, RequestOptions.DEFAULT); log.info("索引" + indexName + "下的" + document + "文档的存在性是" + exists); } catch (Exception e) { e.printStackTrace(); } // 关闭ES连接 closeEs(); }
-
-
异步方式
-
// 异步方式校验索引文档是否存在 public void checkExistIndexDocumentsAsync(String indexName, String document) { GetRequest getRequest = new GetRequest(indexName, document); // 禁用提取源 getRequest.fetchSourceContext(new FetchSourceContext(false)); // 禁用提取存储字段 getRequest.storedFields("_none_"); // 定义监听器 ActionListener<Boolean> listener = new ActionListener<Boolean>() { @Override public void onResponse(Boolean exists) { log.info("索引" + indexName + "下的" + document + "文档的存在性是" + exists); } @Override public void onFailure(Exception e) { } }; try { restClient.existsAsync(getRequest, RequestOptions.DEFAULT, listener); } catch (Exception e) { e.printStackTrace(); } // 关闭ES连接 closeEs(); }
-
删除文档索引
-
构建DeleteRequest
-
public void buildDeleteRequestIndexDocuments(String indexName, String document) { DeleteRequest request = new DeleteRequest(indexName, document); // 设置路由 request.routing("routing"); // 设置超时 request.timeout(TimeValue.timeValueMinutes(2)); request.timeout("2m"); // 设置刷新策略 request.setRefreshPolicy(WriteRequest.RefreshPolicy.WAIT_UNTIL); request.setRefreshPolicy("wait_for"); // 设置版本 request.version(2); // 设置版本类型 request.versionType(VersionType.EXTERNAL); }
-
-
同步方式
-
public void deleteIndexDocuments(String indexName, String document) { DeleteRequest request = new DeleteRequest(indexName, document); try { DeleteResponse deleteResponse = restClient.delete(request, RequestOptions.DEFAULT); // 处理DeleteResponse processDeleteRequest(deleteResponse); } catch (Exception e) { e.printStackTrace(); } // 关闭ES连接 closeEs(); }
-
// 处理DeleteResponse private void processDeleteRequest(DeleteResponse deleteResponse) { String index = deleteResponse.getIndex(); String id = deleteResponse.getId(); long version = deleteResponse.getVersion(); log.info("delete id is " + id + ", index is " + index + ",version is " + version); ReplicationResponse.ShardInfo shardInfo = deleteResponse.getShardInfo(); if (shardInfo.getTotal() != shardInfo.getSuccessful()) { log.info("Success shards are not enough"); } if (shardInfo.getFailed() > 0) { for (ReplicationResponse.ShardInfo.Failure failure : shardInfo.getFailures()) { String reason = failure.reason(); log.info("Fail reason is " + reason); } } }
-
-
异步方式
-
public void deleteIndexDocumentsAsync(String indexName, String document) { DeleteRequest request = new DeleteRequest(indexName, document); ActionListener listener = new ActionListener<DeleteResponse>() { @Override public void onResponse(DeleteResponse deleteResponse) { String id = deleteResponse.getId(); String index = deleteResponse.getIndex(); long version = deleteResponse.getVersion(); log.info("delete id is " + id + ", index is " + index + ",version is " + version); } @Override public void onFailure(Exception e) { } }; try { restClient.deleteAsync(request, RequestOptions.DEFAULT, listener); } catch (Exception e) { e.printStackTrace(); } // 关闭ES连接 closeEs(); }
-
-
结果解析
获取文档索引的词向量
词向量 Term Vectors:关于词的一些统计信息的统称
-
构建词向量请求 TermVectorsRequest
-
索引的名称
-
检索信息的字段
-
文档ID
-
public void buildTermVectorsRequest(String indexName,String document,String field){ TermVectorRequest request = new TermVectorsRequest(indexName,document); request.setFields(fields); }
-
public void buildTermVectorsRequest(String indexName, String document, String field) { // 方式1:索引中存在的文档 TermVectorsRequest request = new TermVectorsRequest(indexName, document); request.setFields(field); // 方式2:索引中不存在的文档, 也可以人工为文档生成词向量 try { XContentBuilder docBuilder = XContentFactory.jsonBuilder(); docBuilder.startObject().field("user", "niudong").endObject(); request = new TermVectorsRequest(indexName, docBuilder); } catch (Exception e) { e.printStackTrace(); } /* * 可选参数 */ // 将FieldStatistics设置为false(默认为true)可忽略文档计数、文档频率总和、总术语频率总和。 request.setFieldStatistics(false); // 将termstatistics设置为true(默认值为false),以显示术语总频率和文档频率。 request.setTermStatistics(true); // 将“位置”设置为“假”(默认为“真”)以忽略位置的输出。 request.setPositions(false); // 将“偏移”设置为“假”(默认为“真”)以忽略偏移的输出。 request.setOffsets(false); // 将“有效载荷”设置为“假”(默认为“真”)以忽略有效载荷的输出。 request.setPayloads(false); Map<String, Integer> filterSettings = new HashMap<>(); filterSettings.put("max_num_terms", 3); filterSettings.put("min_term_freq", 1); filterSettings.put("max_term_freq", 10); filterSettings.put("min_doc_freq", 1); filterSettings.put("max_doc_freq", 100); filterSettings.put("min_word_length", 1); filterSettings.put("max_word_length", 10); // 设置filtersettings,根据tf-idff分数筛选可返回的词条。 request.setFilterSettings(filterSettings); Map<String, String> perFieldAnalyzer = new HashMap<>(); perFieldAnalyzer.put("user", "keyword"); // 设置PerFieldAnalyzer,指定与字段具有的分析器不同的分析器。 request.setPerFieldAnalyzer(perFieldAnalyzer); // 将realtime设置为false(默认值为true)以在realtime附近检索术语向量。 request.setRealtime(false); // 设置路由 request.setRouting("routing"); }
-
文档的索引过程
写入磁盘的倒排索引是不可变的
- 读写操作轻量级,不需要锁
- 一旦索引被读入文件系统的内存,它就会一直在哪儿,因为不会改变。当文件系统内存有足够大的空间时,大部分的索引读写操作是可以直接访问内存,而不是磁盘就能实现的,有利于提升Elasticsearch的性能
- 当写入单个大的倒排索引时,Elasticsearch可以压缩数据,以减少磁盘I/O和需要存储索引的内存大小
一个per-segment search的工作流程如下所示:
- 新的文档首先被写入内存区的索引
- 内存中的索引不断被提交,新段不断产生。当新的提交点产生时就将这些新段的数据写入磁盘,包括新段的名称。写入磁盘时文件同步写入的,也就是说,所有的写操作都需要等待文件系统内存的数据同步到磁盘,确保它们可以被物理写入。
- 新段被打开,于是它包含的文档就可以被检索到。
- 内存被清除,等待接收新的文档。
当一个文档被删除时,它实际上只是在.del文件被标记为删除。在进行文档查询时,被删除的文档依然可以被匹配查询,但是在最终返回之前会从结果中删除
当一个文档被更新时,旧版本的文档会被标记为删除,新版本的文档在新的段中被索引。当对文档进行查询时,该文档的不同版本都会匹配一个查询请求,当时较旧的版本会从结果中被删除。
在段合并过程中,小段被合并成大段,大段再合并成更大的段,在合并段时,被删除的文档不会被合并到大段中。
构建批量请求
-
构建批量请求
-
public void buildBulkRequest(String indexName, String field) { /* * 方式1:添加同型请求 */ BulkRequest request = new BulkRequest(); // 添加第一个IndexRequest request.add(new IndexRequest(indexName).id("1").source(XContentType.JSON, field, "事实上,自今年年初开始,美联储就已传递出货币政策或将转向的迹象")); // 添加第二个IndexRequest request.add(new IndexRequest(indexName).id("2").source(XContentType.JSON, field, "自6月起,市场对于美联储降息的预期愈发强烈")); // 添加第三个IndexRequest request.add(new IndexRequest(indexName).id("3").source(XContentType.JSON, field, "从此前美联储降息历程来看,美联储降息将打开全球各国央行的降息窗口")); /* * 方式2:添加异型请求 */ // 添加一个 DeleteRequest request.add(new DeleteRequest(indexName, "3")); // 添加一个 UpdateRequest request.add(new UpdateRequest(indexName, "2").doc(XContentType.JSON, field, "自今年初美联储暂停加息以来,全球范围内的降息大幕就已拉开,不仅包括新兴经济体,发达经济体也加入降息阵营,仅7月份一个月内,就有6国央行降息")); // 添加一个IndexRequest request.add(new IndexRequest(indexName).id("4").source(XContentType.JSON, field, "在此次美联储降息后,央行或不会立即跟进降息z")); /* * 以下是可选参数的配置 */ // 设置超时时间 request.timeout(TimeValue.timeValueMinutes(2)); request.timeout("2m"); // 设置数据刷新策略 request.setRefreshPolicy(WriteRequest.RefreshPolicy.WAIT_UNTIL); request.setRefreshPolicy("wait_for"); // 设置在继续执行索引/更新/删除操作之前必须处于活动状态的碎片副本数。 request.waitForActiveShards(2); request.waitForActiveShards(ActiveShardCount.ALL); // 用于所有子请求的全局pipelineid request.pipeline("pipelineId"); // 用于所有子请求的全局路由ID request.routing("routingId"); }
-
-
执行批量请求
-
同步方式
-
// 同步方式执行BulkRequest public void executeBulkRequest(String indexName, String field) { BulkRequest request = new BulkRequest(); // 添加第一个IndexRequest request.add(new IndexRequest(indexName).id("1").source(XContentType.JSON, field, "事实上,自今年年初开始,美联储就已传递出货币政策或将转向的迹象")); // 添加第二个IndexRequest request.add(new IndexRequest(indexName).id("2").source(XContentType.JSON, field, "自6月起,市场对于美联储降息的预期愈发强烈")); // 添加第三个IndexRequest request.add(new IndexRequest(indexName).id("3").source(XContentType.JSON, field, "从此前美联储降息历程来看,美联储降息将打开全球各国央行的降息窗口")); try { BulkResponse bulkResponse = restClient.bulk(request, RequestOptions.DEFAULT); // 解析BulkResponse processBulkResponse(bulkResponse); } catch (Exception e) { e.printStackTrace(); } // 关闭ES连接 closeEs(); } // 解析BulkResponse private void processBulkResponse(BulkResponse bulkResponse) { if (bulkResponse == null) { return; } for (BulkItemResponse bulkItemResponse : bulkResponse) { DocWriteResponse itemResponse = bulkItemResponse.getResponse(); switch (bulkItemResponse.getOpType()) { // 索引状态 case INDEX: // 索引生成 case CREATE: IndexResponse indexResponse = (IndexResponse) itemResponse; String index = indexResponse.getIndex(); String id = indexResponse.getId(); long version = indexResponse.getVersion(); log.info("create id is " + id + ", index is " + index + ",version is " + version); break; // 索引更新 case UPDATE: UpdateResponse updateResponse = (UpdateResponse) itemResponse; break; // 索引删除 case DELETE: DeleteResponse deleteResponse = (DeleteResponse) itemResponse; } } }
-
异步方式
-
// 异步方式执行BulkRequest public void executeBulkRequestAsync(String indexName, String field) { BulkRequest request = new BulkRequest(); // 添加第一个IndexRequest request.add(new IndexRequest(indexName).id("1").source(XContentType.JSON, field, "事实上,自今年年初开始,美联储就已传递出货币政策或将转向的迹象")); // 添加第二个IndexRequest request.add(new IndexRequest(indexName).id("2").source(XContentType.JSON, field, "自6月起,市场对于美联储降息的预期愈发强烈")); // 添加第三个IndexRequest request.add(new IndexRequest(indexName).id("3").source(XContentType.JSON, field, "从此前美联储降息历程来看,美联储降息将打开全球各国央行的降息窗口")); // 构建监听器 ActionListener<BulkResponse> listener = new ActionListener<BulkResponse>() { @Override public void onResponse(BulkResponse bulkResponse) { } @Override public void onFailure(Exception e) { } }; try { restClient.bulkAsync(request, RequestOptions.DEFAULT, listener); } catch (Exception e) { e.printStackTrace(); } // 关闭ES连接 closeEs(); }
-
-
解析批量请求的响应结果
批量处理器BulkProcessor
BulkProcessor想要依赖如下组件
- RestHighLevelClient:客户端用于执行BulkRequest和检索BulkResponse
- BulkProcessor.Listener:在每次执行BulkRequest之前和之后,或者当BulkRequest失败时,都会调用此监听器
Bulk构建代码
// 构建BulkProcessor
public void buildBulkRequestWithBulkProcessor(String indexName, String field) {
BulkProcessor.Listener listener = new BulkProcessor.Listener() {
@Override
public void beforeBulk(long executionId, BulkRequest request) {
// 批量处理前的动作
}
@Override
public void afterBulk(long executionId, BulkRequest request, BulkResponse response) {
// 批量处理后的动作
}
@Override
public void afterBulk(long executionId, BulkRequest request, Throwable failure) {
// 批量处理后的动作
}
};
BulkProcessor bulkProcessor = BulkProcessor.builder((request, bulkListener) -> restClient
.bulkAsync(request, RequestOptions.DEFAULT, bulkListener), listener).build();
/*
* BulkProcessor的配置
*/
BulkProcessor.Builder builder = BulkProcessor.builder((request, bulkListener) -> restClient
.bulkAsync(request, RequestOptions.DEFAULT, bulkListener), listener);
// 根据当前添加的操作数设置刷新批量请求的时间(默认值为1000,使用-1表示禁用)
builder.setBulkActions(500);
// 根据当前添加的操作大小设置刷新批量请求的时间(默认为5MB,使用-1表示禁用)
builder.setBulkSize(new ByteSizeValue(1L, ByteSizeUnit.MB));
// 设置允许执行的并发请求数(默认为1,使用0时表示仅允许执行单个请求)
builder.setConcurrentRequests(0);
// 设置刷新间隔刷(默认为未设置)
builder.setFlushInterval(TimeValue.timeValueSeconds(10L));
// 设置一个恒定的后退策略,该策略最初等待1秒,最多重试3次。
builder.setBackoffPolicy(BackoffPolicy.constantBackoff(TimeValue.timeValueSeconds(1L), 3));
/**
* 添加索引请求
*/
IndexRequest one = new IndexRequest(indexName).id("6").source(XContentType.JSON, "title",
"8月1日,中国空军发布强军宣传片《初心伴我去战斗》,通过歼-20、轰-6K等新型战机练兵备战的震撼场景,展现新时代空军发展的新气象,彰显中国空军维护国家主权、保卫国家安全、保障和平发展的意志和能力。");
IndexRequest two = new IndexRequest(indexName).id("7").source(XContentType.JSON, "title",
"在2分钟的宣传片中,中国空军现役先进战机悉数亮相,包括歼-20、歼-16、歼-11、歼-10B/C、苏-35、苏-27、轰-6K等机型");
IndexRequest three = new IndexRequest(indexName).id("8").source(XContentType.JSON, "title",
"宣传片发布正逢八一建军节,而今年是新中国成立70周年,也是人民空军成立70周年。70年来,中国空军在各领域取得全面发展,战略打击、战略预警、空天防御和战略投送等能力得到显著进步。");
bulkProcessor.add(one);
bulkProcessor.add(two);
bulkProcessor.add(three);
}
MultiGet批量处理
在单个HTTP请求中并执行多个Get请求
-
构建MultiGet批量处理请求
-
// 构建MultiGetRequest public void buildMultiGetRequest(String indexName, String[] documentIds) { if (documentIds == null || documentIds.length <= 0) { return; } MultiGetRequest request = new MultiGetRequest(); for (String documentId : documentIds) { // 添加请求 request.add(new MultiGetRequest.Item(indexName, documentId)); } /* * 可选参数使用介绍 */ // 禁用源检索, 默认情况下启用 request.add(new MultiGetRequest.Item(indexName, documentIds[0]) .fetchSourceContext(FetchSourceContext.DO_NOT_FETCH_SOURCE)); // 为特定字段配置 源包含关系 String[] excludes = Strings.EMPTY_ARRAY; String[] includes = {"title", "content"}; FetchSourceContext fetchSourceContext = new FetchSourceContext(true, includes, excludes); request.add( new MultiGetRequest.Item(indexName, documentIds[0]).fetchSourceContext(fetchSourceContext)); // 为特定字段配置 源排除关系 fetchSourceContext = new FetchSourceContext(true, includes, excludes); request.add( new MultiGetRequest.Item(indexName, documentIds[0]).fetchSourceContext(fetchSourceContext)); // 为特定存储字段配置检索( 要求字段在索引中单独存储字段) try { request.add(new MultiGetRequest.Item(indexName, documentIds[0]).storedFields("title")); MultiGetResponse response = restClient.mget(request, RequestOptions.DEFAULT); MultiGetItemResponse item = response.getResponses()[0]; // 检索title存储字段( 要求该字段单独存储在索引中) String value = item.getResponse().getField("title").getValue(); log.info("value is " + value); } catch (Exception e) { e.printStackTrace(); } finally { // 关闭ES连接 closeEs(); } // 配置路由 request.add(new MultiGetRequest.Item(indexName, documentIds[0]).routing("routing")); // 配置版本和版本类型 request.add(new MultiGetRequest.Item(indexName, documentIds[0]) .versionType(VersionType.EXTERNAL).version(10123L)); // 配置偏好值 request.preference("title"); // 将实时标志设置为假( 默认为真) request.realtime(false); // 在检索文档之前执行刷新(默认为false) request.refresh(true); }
-
-
执行MultiGet批量处理请求
-
同步方式
-
// 同步执行MultiGetRequest public void executeMultiGetRequest(String indexName, String[] documentIds) { if (documentIds == null || documentIds.length <= 0) { return; } MultiGetRequest request = new MultiGetRequest(); for (String documentId : documentIds) { // 添加请求 request.add(new MultiGetRequest.Item(indexName, documentId)); } try { MultiGetResponse response = restClient.mget(request, RequestOptions.DEFAULT); // 解析MultiGetResponse processMultiGetResponse(response); } catch (Exception e) { e.printStackTrace(); } finally { // 关闭ES连接 closeEs(); } } // 解析MultiGetResponse private void processMultiGetResponse(MultiGetResponse multiResponse) { if (multiResponse == null) { return; } MultiGetItemResponse[] responses = multiResponse.getResponses(); log.info("responses is " + responses.length); for (MultiGetItemResponse response : responses) { GetResponse getResponse = response.getResponse(); String index = response.getIndex(); String id = response.getId(); log.info("index is " + index + ";id is " + id); if (getResponse.isExists()) { long version = getResponse.getVersion(); // 按字符串方式获取内容 String sourceAsString = getResponse.getSourceAsString(); // 按Map方式获取内容 Map<String, Object> sourceAsMap = getResponse.getSourceAsMap(); // 按字节数组方式获取内容 byte[] sourceAsBytes = getResponse.getSourceAsBytes(); log.info("version is " + version + ";sourceAsString is " + sourceAsString); } } }
-
异步方式
-
// 异步执行MultiGetRequest public void executeMultiGetRequestAsync(String indexName, String[] documentIds) { if (documentIds == null || documentIds.length <= 0) { return; } MultiGetRequest request = new MultiGetRequest(); for (String documentId : documentIds) { // 添加请求 request.add(new MultiGetRequest.Item(indexName, documentId)); } // 添加ActionListener ActionListener listener = new ActionListener<MultiGetResponse>() { @Override public void onResponse(MultiGetResponse response) { } @Override public void onFailure(Exception e) { } }; // 执行批量获取 try { MultiGetResponse response = restClient.mget(request, RequestOptions.DEFAULT); } catch (Exception e) { e.printStackTrace(); } finally { // 关闭ES连接 closeEs(); } }
-
-
解析MultiGet批量处理请求的响应结果
文档处理过程解析
Elasticsearch文档分片存储
一个索引一般由多个分片构成,当用户执行添加,删除,修改文档操作时,Elasticsearch需要决定把这个文档存储在那个分片上,这个过程就成为数据路由。
路由算法:
-
shard=hash(routing) % number_of_primary_shards
Elasticsearch的数据分区
- 基于文档的分区
- 每个文档只存一个分区,每个分区持有整个文档集的一个子集,这里所说的分区是指一个功能完整的倒排索引。
- 优点
- 每个分区都可以独立地处理查询
- 可以非常副本地添加以文档为单位的索引信息
- 在搜索过程中网络开销很小,每个节点可以分别独立地执行搜索,执行完之后只需返回文档的ID和评分信息即可。而呈现给用户的结果集是在执行分布式搜索的节点上执行合并操作实现的。
- 基于词条的分区
搜索
Search API
SortBuilder
- FieldSortBuilder
- ScoreSortBuilder
- GeoDistanceSortBuilder
- ScriptSortBuilder
Search Scroll API
滚动搜索有点类似于数据库中的分页查询
Clear Scroll API
Search Template API
Multi-Search-Template API
Multi-Search API
Field Capabilities API
Ranking Evaluation API
Explain API
Count API
当用多个查询条件进行搜索或查询时,需要注意多个查询条件间的匹配方式。
- must子句:文档必须匹配must查询条件,相当于“=”
- should子句:文档应该匹配should子句查询的一个或多个条件
- must_not子句:文档不能匹配该查询条件,相当于"!="
match方法常见的匹配方式还有 term ,text,range等
- term精确匹配
- wildcard通配符匹配
- prefix前缀匹配
- range区间查询
http.cors.enabled:true
http.cors.allow-origin: "*"
附录一:pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<!-- 指定仓库位置,依次为aliyun、cloudera和jboss仓库 -->
<repositories>
<repository>
<id>aliyun</id>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</repository>
<repository>
<id>cloudera</id>
<url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
</repository>
<repository>
<id>jboss</id>
<url>http://repository.jboss.com/nexus/content/groups/public</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 添加elasticsearch依赖 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>7.2.0</version>
</dependency>
<!-- 添加elasticsearch的依赖 begin -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
<version>4.1.4</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore-nio</artifactId>
<version>4.4.11</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.9</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.11</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.12</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- 添加elasticsearch的依赖 end -->
<!-- 添加elasticsearch的嗅探器 begin -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client-sniffer</artifactId>
<version>7.2.1</version>
</dependency>
<!-- 添加elasticsearch的嗅探器 end -->
<!-- 添加elasticsearch的高级客户端 begin -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.2.1</version>
</dependency>
<!-- 添加elasticsearch的高级客户端 end -->
<!-- 添加elasticsearch的高级客户端的依赖 begin -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.2.1</version>
</dependency>
<!-- 添加elasticsearch的高级客户端的依赖 end -->
<!-- 添加guava的依赖 begin -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.0-jre</version>
</dependency>
<!-- 添加guava的依赖 end -->
<!-- 中文分词组件 begin -->
<dependency>
<groupId>org.ansj</groupId>
<artifactId>ansj_seg</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.nlpcn</groupId>
<artifactId>nlp-lang</artifactId>
<version>1.7.6</version>
</dependency>
<!-- 中文分词组件 end -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>