ES是什么
Elasticsearch(以下简称ES)是一个基于Apache Lucene™的开源搜索引擎,无论在开源还是专有领域,Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。 但是,Lucene只是一个库。想要发挥其强大的作用,你需使用Java并要将其集成到你的应用中。Lucene非常复杂,你需要深入的了解检索相关知识来理解它是如何工作的。 ES也是使用Java编写并使用Lucene来建立索引并实现搜索功能,但是它的目的是通过简单连贯的RESTful API让全文搜索变得简单并隐藏Lucene的复杂性。 另外ES不仅仅是Lucene和全文搜索引擎,它还提供:
分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索。
- 实时分析的分布式搜索引擎。
- 可以扩展到上百台服务器,处理PB级结构化或非结构化数据。
- 而且,所有的这些功能被集成到一台服务器,你的应用可以通过简单的RESTful API、各种语言的客户端甚至命令行与之交互。上手Elasticsearch非常简单,它提供了许多合理的缺省值,并对初学者隐藏了复杂的搜索引擎理论。它开箱即用(安装即可使用),只需很少的学习既可在生产环境中使用。Elasticsearch在Apache 2 license下许可使用,可以免费下载、使用和修改。
使用场景
- 网站搜索
- ELK 日志采集,存储,分析
- 地理信息系统分析
特点
- ES 是一个分布式文档存储,存储的数据都是序列化为 JSON documents 。
- 使用倒排索引存储数据,倒排索引比较适合全文本搜索。
- 基于 Apache Lucene 搜索引擎库,可以存储,检索文档及元数据。
- 支持 JSON 样式的查询语言——Query DSL,也支持 SQL 样式的查询。
- 集群部署,易于扩展。节点(node)分片(shard),将新的 node 添加到集群时,ES 会自动迁移 shard 到新 node 上,重新平衡集群。
-
- shard 分为两种 主分片(primary shard)和 副本分片 (replica shard)
- replica shard 存放的是 primary shard 的冗余副本 —— 可以防止集群故障,数据丢失,同时可以提高搜索或检索速度。
- 在创建索引时 primary shard 数量是固定的,而 replica shard 数量是可以更改的。
- 分片由索引配置,分片越多,维护索引则开销则越大,分片大小越大,则 ES 在增减节点重新平衡集群时,分片移动时间越长。
- 集群恢复: 跨集群复制 (CCR),可以自动将索引从主集群同步到热备份的辅助远程集群。
核心概念
倒排索引
倒排索引也可以称为反向索引。
作为开发咱们经常接触到的就是 MySql,假设有一堆技术书籍,并且已经编上号。
- Java 并发编程之美
- Java 开发手册
- 深入分布式缓存
- Java 并发程序设计
- 算法
- 数据结构与算法
如果放在 MySql 里面就是这样
id | book_name |
1 | Java 并发编程之美 |
2 | Java 开发手册 |
3 | 深入分布式缓存 |
4 | Java 并发程序设计 |
5 | 算法 |
6 | 数据结构与算法 |
此时我想查询所有关于 并发 的书籍。
select * from table_book where book_name like %并发%;
然后会开始遍历表格,查找到 1和4两条记录。
如果是倒排索引处理的话
首先会将每个名称进行分词,比如 Java 并发编程之美 会被分为 Java 并发 编程 之 美。
分词结束之后按照词关联书籍的编号。
term | ids |
Java | 1、2、4 |
并发 | 1、4 |
编程 | 1 |
算法 | 5、6 |
分布式 | 3 |
… | … |
在倒排索引中搜索并发,然后进行检索,就很容易定位到关于并发书籍的编号。
名词解释
名词 | 解释 |
cluster | 一个或者多个 node 指定相同的 cluster name,则它们会组成集群,并且自动选举 master,以及在故障时自动选举。 |
node | 节点是属于集群的Elasticsearch的运行实例 。在启动时,节点将使用单播来发现具有相同集群名称的现有集群,并将尝试加入该集群。 |
index | 类似关系数据库的表,映射一个或者多个主分片,同时拥有零个或多个副本分片。 |
index alias | 索引别名是用于引用一个或多个现有索引的辅助名称。大多数Elasticsearch API接受索引别名代替索引名称。 |
mapping | 每个 index 都有一个 mapping ,定义一个 type 以及许多索引范围的设置。mapping 可以明确定义,也可以在为文档建立索引后自动生成。 |
shard | 分片是单个Lucene实例。最小的工作单位,由Elasticsearch自动管理。索引是指向主分片和副本分片的逻辑命名空间。 |
primary shard | 每个文档都存储在一个主分片中。当您为文档建立索引时,将首先在主 shard 上建立索引,然后在主 shard 的所有副本上建立索引。默认情况下,索引具有一个主分片。您可以指定更多的主要分片来扩展 索引可以处理的文档数量。创建索引后,您将无法更改索引中的主要分片数量。但是,可以使用split API将索引拆分为新索引 。 |
replica shard | 每个主分片可以具有零个或多个副本。副本是 primary shard 的副本。 |
document | document 是存储在 Elasticsearch 中的 JSON 文档。每个 document 都存储在索引中,并且有 type 和 id。被索引的 JSON 文档 将存储在 _source 字段中,该字段在获取或搜索文档时默认返回。 |
id | 每个 document 都有不同的 id,没有指定的话,会自动生成。 |
field | 一个 document 包含字段或键值对的列表。字段类似于关系数据库中表中的列。 |
source field | 默认情况下,索引的JSON文档存储在 _source 字段中,并且将由所有 get 和 search 请求返回。这样,可以直接从搜索结果中访问原始对象,而无需执行第二步来从 ID 中检索对象。 |
画图出来就是下面这个样子
replica shard 有什么用?
- 增加故障转移:如果主副本发生故障,副本副本可以提升为主副本
- 提高性能:获取和搜索请求可以由主或副本分片处理。默认情况下,每个主分片都有一个副本,但是可以在现有索引上动态更改副本的数量。副本分片永远不会与其主分片在同一节点上启动。
除了定义索引应具有的主分片和副本分片的数量外,您无需直接引用分片。相反,您的代码应仅处理索引。
Elasticsearch 在 集群中的所有节点之间分配分片,并且在节点发生故障或添加新节点的情况下,可以自动将分片从一个节点移动到另一个节点。
分片 默认是 5个,副本默认为 1个。
ES数据类型
1、分隔数据
ElasticSearch“真正用于分隔数据的结构“只有index,而没有type,type实际上作为了一个元数据(类似SQL中的id,作为额外的标识数据)来实现逻辑划分。
2、mapping
在index中还有一个mapping,mapping管理了整个index的各个字段的属性,也就是定义了整个index中document的结构。
GET test_alias/_mapping
3、ES数据类型
ES常用的数据类型可分为3大类:核⼼数据类型、复杂数据类型、专⽤数据类型
1)核心数据类型
(1)字符串类型: text, keyword
text类型:
a. 支持分词,全文检索,支持模糊、精确查询,不支持聚合,排序操作;
b. test类型最大支持的字符长度无限制,适合大字段存储;
使用场景:
存储全文搜索数据, 例如: 邮箱内容、地址、代码块、博客文章内容等。
默认结合standard analyzer(标准解析器)对文本进行分词、倒排索引。
默认结合标准分析器进行词命中、词频相关度打分。
keyword类型:
a. 不进行分词,直接索引,支持模糊、支持精确匹配,支持聚合、排序操作。
b. keyword类型的最大支持的长度为——32766个UTF-8类型的字符,可以通过设置ignore_above指定自持字符长度,超过给定长度后的数据将不被索引,无法通过term精确匹配检索返回结果。
使用场景:
存储邮箱号码、url、name、title,手机号码、主机名、状态码、邮政编码、标签、年龄、性别等数据。
用于筛选数据(例如: select * from x where status='open')、排序、聚合(统计)。
直接将完整的文本保存到倒排索引中。
(2)数字类型:long, integer, short, byte, double, float, half_float, scaled_float
(3)日期:date
(4)日期 纳秒:date_nanos
(5)布尔型:boolean
(6)Binary:binary
(7)Range: integer_range, float_range, long_range, double_range, date_range
ES分词器
分词器介绍
分词器是es中的一个组件,通俗意义上理解,就是将一段文本按照一定的逻辑,分析成多个词语,同时对这些词语进行常规化的一种工具;ES会将text格式的字段按照分词器进行分词,并编排成倒排索引,正是因为如此,es的查询才如此之快;
es本身就内置有多种分词器,他们的特性与作用梳理如下:
分词器 | 作用 |
Standard | ES默认分词器,按单词分类并进行小写处理 |
Simple | 按照非字母切分,然后去除非字母并进行小写处理 |
Stop | 按照停用词过滤并进行小写处理,停用词包括the、a、is |
Whitespace | 按照空格切分 |
Language | 提供了30多种常见语言的分词器 |
Patter | 按照正则表达式进行分词,默认是\W+ ,代表非字母 |
Keyword | 不进行分词,作为一个整体输出 |
这些分词器用于处理单词和字母,那功能基本已经覆盖,可以说是相当全面了!但对于中文而言,不同汉字组合成词语,往往多个字符组合在一起表达一种意思,显然,上述分词器无法满足需求;对应于中文,目前也有许多对应分词器,例如:IK,jieba,THULAC等,使用最多的即是IK分词器。
除了中文文字以外,我们也经常会使用拼音,例如各类输入法,百度的搜索框等都支持拼音的联想搜索,那么假如将数据存入到es中,如何通过拼音搜索我们想要的数据呢,这个时候对应的拼音分词器可以有效帮助到我们,它的开发者也正是ik分词器的创始人。
不同分词器的效果对比
种分词器的功能介绍令人眼花缭乱,那么,在业务的应用与开发中,我们该如何选择合适的分词器来满足我们的业务需求呢?具体可以根据分词器的分词效果酌情选择;接下来就具体看看各个分词器的分词效果吧~
以 “text” : “白兔万岁A*” 为例:
standard分词器 —— ES默认分词器,对于中文会按每个字分开处理,会忽略特殊字符
{
"tokens": [
{
"token": "白",
"start_offset": 0,
"end_offset": 1,
"type": "<IDEOGRAPHIC>",
"position": 0
},
{
"token": "兔",
"start_offset": 1,
"end_offset": 2,
"type": "<IDEOGRAPHIC>",
"position": 1
},
{
"token": "万",
"start_offset": 2,
"end_offset": 3,
"type": "<IDEOGRAPHIC>",
"position": 2
},
{
"token": "岁",
"start_offset": 3,
"end_offset": 4,
"type": "<IDEOGRAPHIC>",
"position": 3
},
{
"token": "a",
"start_offset": 4,
"end_offset": 5,
"type": "<ALPHANUM>",
"position": 4
}
]
}
ik 分词器 —— 适用于根据词语查询整个内容信息,同样忽略其他特殊字符以及英文字符
{
"tokens": [
{
"token": "白兔",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "万岁",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 1
},
{
"token": "万",
"start_offset": 2,
"end_offset": 3,
"type": "TYPE_CNUM",
"position": 2
},
{
"token": "岁",
"start_offset": 3,
"end_offset": 4,
"type": "COUNT",
"position": 3
}
]
}
pinyin 分词器 —— 适用于通过拼音查询到对应字段信息,同时忽略特殊字符
{
"tokens": [
{
"token": "bai",
"start_offset": 0,
"end_offset": 0,
"type": "word",
"position": 0
},
{
"token": "btwsa",
"start_offset": 0,
"end_offset": 0,
"type": "word",
"position": 0
},
{
"token": "tu",
"start_offset": 0,
"end_offset": 0,
"type": "word",
"position": 1
},
{
"token": "wan",
"start_offset": 0,
"end_offset": 0,
"type": "word",
"position": 2
},
{
"token": "sui",
"start_offset": 0,
"end_offset": 0,
"type": "word",
"position": 3
},
{
"token": "a",
"start_offset": 0,
"end_offset": 0,
"type": "word",
"position": 4
}
]
}
自定义分词器
不同分词器的分词效果各有不同,那么,假如我们需要完成一个模糊查询的搜索功能,以多种形式查询es中的同一个字段,例如类似于百度搜索框那样,既想通过简单词语或者单个字去搜索,又想根据拼音去搜索,很明显,单一种类的分词器是非常难以满足业务需求的;
此时,可以考虑构建索引字段中不同的field去适配多个分词器,例如:我们可以将字段设置多个分词器:
mapping:
{
"properties":{
"name":{
"type":"text",
"analyzer":"ik_max_word"
},
"fields":{
"PY":{
"type":"text",
"analyzer":"pinyin"
}
}
}
}
如果想要更加自由地使用es的分词功能,也许还能打开另一扇通往成功的大门 —— 自定义分词器,自定义分词器,顾名思义,就是通过不同分词器的组合以及相关属性设置,去创建符合自己心意的分词器,例如,如果我们既想通过词语联想一句话,又想享受拼音自动拼写转成词语的便捷,那么何不定义一个专属的分词器呢?例如:定义一个ik与拼音结合的分词器:
{
"analysis":{
"analyzer":{
"my_max_analyzer":{
"tokenizer":"ik_max_word",
"filter":"py"
},
"my_smart_analyzer":{
"tokenizer":"",
"filter":"py"
}
},
"filter":{
"py":{
"type":"pinyin",
"first_letter":"prefix",
"keep_separate_first_letter":true,
"keep_full_pinyin":true,
"keep_joined_full_pinyin":true,
"keep_original":true,
"limit_first_letter_length":16,
"lowercase":true,
"remove_duplicated_term":true
}
}
}
}
此时,对应 “白兔万岁A*" 分词效果如下:
{
"tokens": [
{
"token": "b",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "bai",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "白兔",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "baitu",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "bt",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "t",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 1
},
{
"token": "tu",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 1
},
{
"token": "w",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 2
},
{
"token": "wan",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 2
},
{
"token": "s",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 3
},
{
"token": "sui",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 3
},
{
"token": "万岁",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 3
},
{
"token": "wansui",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 3
},
{
"token": "ws",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 3
},
{
"token": "w",
"start_offset": 2,
"end_offset": 3,
"type": "TYPE_CNUM",
"position": 4
},
{
"token": "wan",
"start_offset": 2,
"end_offset": 3,
"type": "TYPE_CNUM",
"position": 4
},
{
"token": "万",
"start_offset": 2,
"end_offset": 3,
"type": "TYPE_CNUM",
"position": 4
},
{
"token": "s",
"start_offset": 3,
"end_offset": 4,
"type": "COUNT",
"position": 5
},
{
"token": "sui",
"start_offset": 3,
"end_offset": 4,
"type": "COUNT",
"position": 5
},
{
"token": "岁",
"start_offset": 3,
"end_offset": 4,
"type": "COUNT",
"position": 5
}
]
}
如何使用
引入POM
<!--elasticsearch-->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>${elasticsearch.version}</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>${elasticsearch.version}</version>
<exclusions>
<exclusion>
<artifactId>elasticsearch-rest-client</artifactId>
<groupId>org.elasticsearch.client</groupId>
</exclusion>
<exclusion>
<artifactId>elasticsearch</artifactId>
<groupId>org.elasticsearch</groupId>
</exclusion>
</exclusions>
</dependency>
加载连接
private void init() throws Exception {
credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(username, password));
String url = urls.substring(0, urls.lastIndexOf(":"));
port = Integer.parseInt(urls.substring(urls.lastIndexOf(":") + 1, urls.length()));
RestClientBuilder builder = RestClient.builder(
new HttpHost(url, port))
.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
@Override
public HttpAsyncClientBuilder customizeHttpClient(
HttpAsyncClientBuilder httpClientBuilder) {
HttpAsyncClientBuilder httpAsyncClientBuilder = httpClientBuilder
.setDefaultCredentialsProvider(credentialsProvider)
// .setConnectionManager(getConnectManager())//使用默认连接池 default:30个
;
return httpAsyncClientBuilder;
}
});
builder.setRequestConfigCallback(requestConfigBuilder ->
requestConfigBuilder
.setConnectTimeout(10 * 1000)// 连接超时(默认为1秒),现10秒
.setSocketTimeout(60 * 1000)// 客户端的超时限制(默认为30秒),现1分钟
.setConnectionRequestTimeout(30 * 1000)//30秒
);
this.client = new RestHighLevelClient(builder);
//查不到也没关系,默认会懒加载
if (!LAZY_LOAD_DOC_INFO) {
Map<String, Object> beansWithAnnotationESDoc = applicationContext.getBeansWithAnnotation(ESDoc.class);
beansWithAnnotationESDoc.forEach((key, val) -> {
loadDocInfos(val.getClass());
});
Map<String, Object> beansWithAnnotationESNested = applicationContext.getBeansWithAnnotation(ESNested.class);
beansWithAnnotationESNested.forEach((key, val) -> {
loadNestedInfo(val.getClass());
});
}
}
index api
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("user", "laimailai");
jsonMap.put("postDate", new Date());
jsonMap.put("message", "trying out Elasticsearch");
IndexRequest indexRequest = new IndexRequest("index", "type", "1")
.source(jsonMap);
IndexResponse indexResponse = client.index(request);
get api
GetRequest getRequest = new GetRequest(
"index",
"type",
"1");
GetResponse getResponse = client.get(request);
update api
UpdateRequest request = new UpdateRequest(
"index",
"type",
"1");
UpdateResponse updateResponse = client.update(request);
delete api
DeleteRequest request = new DeleteRequest(
"index",
"type",
"1");
bulk api
bulk接口是批量index/update/delete操作
在API中,只需要一个bulk request就可以完成一批请求。
//1.bulk
BulkRequest request = new BulkRequest();
request.add(new IndexRequest("index", "type", "1")
.source(XContentType.JSON, "field", "foo"));
request.add(new IndexRequest("index", "type", "2")
.source(XContentType.JSON, "field", "bar"));
request.add(new IndexRequest("index", "type", "3")
.source(XContentType.JSON, "field", "baz"));
//同步
BulkResponse bulkResponse = client.bulk(request);
//异步
client.bulkAsync(request, new ActionListener<BulkResponse>() {
@Override
public void onResponse(BulkResponse bulkResponse) {
}
@Override
public void onFailure(Exception e) {
}
});
bulkprocessor
BulkProcessor 简化bulk API的使用,并且使整个批量操作透明化。
BulkProcessor 的执行需要三部分组成:
- RestHighLevelClient :执行bulk请求并拿到响应对象。
- BulkProcessor.Listener:在执行bulk request之前、之后和当bulk response发生错误时调用。
- ThreadPool:bulk request在这个线程池中执行操作,这使得每个请求不会被挡住,在其他请求正在执行时,也可以接收新的请求。
@Service
public class ElasticSearchUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ElasticSearchUtil.class);
@Autowired
private RestHighLevelClient restHighLevelClient;
private BulkProcessor bulkProcessor;
@PostConstruct
public void init() {
BulkProcessor.Listener listener = new BulkProcessor.Listener() {
@Override
public void beforeBulk(long executionId, BulkRequest request) {
//重写beforeBulk,在每次bulk request发出前执行,在这个方法里面可以知道在本次批量操作中有多少操作数
int numberOfActions = request.numberOfActions();
LOGGER.info("Executing bulk [{}] with {} requests", executionId, numberOfActions);
}
@Override
public void afterBulk(long executionId, BulkRequest request, BulkResponse response) {
//重写afterBulk方法,每次批量请求结束后执行,可以在这里知道是否有错误发生。
if (response.hasFailures()) {
LOGGER.error("Bulk [{}] executed with failures,response = {}", executionId, response.buildFailureMessage());
} else {
LOGGER.info("Bulk [{}] completed in {} milliseconds", executionId, response.getTook().getMillis());
}
BulkItemResponse[] responses = response.getItems();
}
@Override
public void afterBulk(long executionId, BulkRequest request, Throwable failure) {
//重写方法,如果发生错误就会调用。
LOGGER.error("Failed to execute bulk", failure);
}
};
//在这里调用build()方法构造bulkProcessor,在底层实际上是用了bulk的异步操作
BulkProcessor bulkProcessor = BulkProcessor.builder(restHighLevelClient::bulkAsync, listener)
// 1000条数据请求执行一次bulk
.setBulkActions(1000)
// 5mb的数据刷新一次bulk
.setBulkSize(new ByteSizeValue(5L, ByteSizeUnit.MB))
// 并发请求数量, 0不并发, 1并发允许执行
.setConcurrentRequests(0)
// 固定1s必须刷新一次
.setFlushInterval(TimeValue.timeValueSeconds(1L))
// 重试5次,间隔1s
.setBackoffPolicy(BackoffPolicy.constantBackoff(TimeValue.timeValueSeconds(1L), 5))
.build();
this.bulkProcessor = bulkProcessor;
}
@PreDestroy
public void destroy() {
try {
bulkProcessor.awaitClose(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
LOGGER.error("Failed to close bulkProcessor", e);
}
LOGGER.info("bulkProcessor closed!");
}
/**
* 修改
*
* @param request
* @throws IOException
*/
public void update(UpdateRequest request) {
this.bulkProcessor.add(request);
}
/**
* 新增
*
* @param request
*/
public void insert(IndexRequest request) {
this.bulkProcessor.add(request);
}
}
bulkProcessor使用用例:
//新建三个 index 请求
IndexRequest one = new IndexRequest("posts", "doc", "1").
source(XContentType.JSON, "title", "In which order are my Elasticsearch queries executed?");
IndexRequest two = new IndexRequest("posts", "doc", "2")
.source(XContentType.JSON, "title", "Current status and upcoming changes in Elasticsearch");
IndexRequest three = new IndexRequest("posts", "doc", "3")
.source(XContentType.JSON, "title", "The Future of Federated Search in Elasticsearch");
//新的三条index请求加入到上面配置好的bulkProcessor里面。
bulkProcessor.add(one);
bulkProcessor.add(two);
bulkProcessor.add(three);
// add many request here.
//bulkProcess必须被关闭才能使上面添加的操作生效
bulkProcessor.close(); //立即关闭
//关闭bulkProcess的两种方法:
try {
//2.调用awaitClose.
//简单来说,就是在规定的时间内,是否所有批量操作完成。全部完成,返回true,未完成返//回false
boolean terminated = bulkProcessor.awaitClose(30L, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
upsert api
update --当id不存在时将会抛出异常:
UpdateRequest request = new UpdateRequest(index, type, "1").doc(jsonMap);
UpdateResponse response = restHighLevelClient.update(request);
upsert--id不存在时就插入:
UpdateRequest request = new UpdateRequest(index, type, "1").doc(jsonMap).upsert(jsonMap);
UpdateResponse response = restHighLevelClient.update(request);
search api
Search API提供了对文档的查询和聚合的查询。
它的基本形式:
SearchRequest searchRequest = new SearchRequest(); //构造search request .在这里无参,查询全部索引
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();//大多数查询参数要写在searchSourceBuilder里
searchSourceBuilder.query(QueryBuilders.matchAllQuery());//增加match_all的条件。
SearchRequest searchRequest = new SearchRequest("posts"); //指定posts索引
searchRequest.types("doc"); //指定doc类型
使用SearchSourceBuilder
大多数的查询控制都可以使用SearchSourceBuilder实现。
举一个简单例子:
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); //构造一个默认配置的对象
sourceBuilder.query(QueryBuilders.termQuery("user", "kimchy")); //设置查询
sourceBuilder.from(0); //设置从哪里开始
sourceBuilder.size(5); //每页5条
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS)); //设置超时时间
配置好searchSourceBuilder后,将它传入searchRequest里:
SearchRequest searchRequest = new SearchRequest();
searchRequest.source(sourceBuilder);
//全量搜索
SearchRequest searchRequest = new SearchRequest();
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
searchRequest.source(searchSourceBuilder);
SearchRequest searchRequest = new SearchRequest("index");
//根据多个条件搜索
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
for (String id: ids) {
TermQueryBuilder termQueryBuilder = new TermQueryBuilder("id", id);
boolQueryBuilder.should(termQueryBuilder);
}
SearchRequest searchRequest = new SearchRequest(index);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(boolQueryBuilder);
searchRequest.source(searchSourceBuilder);
SearchResponse response = null;
response = restHighLevelClient.search(searchRequest);
return response;
search scroll api
//scroll 分页搜索
final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L));
SearchRequest searchRequest = new SearchRequest("posts");
searchRequest.scroll(scroll);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(matchQuery("title", "Elasticsearch"));
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest);
String scrollId = searchResponse.getScrollId();
SearchHit[] searchHits = searchResponse.getHits().getHits();
while (searchHits != null && searchHits.length > 0) {
SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
scrollRequest.scroll(scroll);
searchResponse = client.searchScroll(scrollRequest);
scrollId = searchResponse.getScrollId();
searchHits = searchResponse.getHits().getHits();
}
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
clearScrollRequest.addScrollId(scrollId);
ClearScrollResponse clearScrollResponse = client.clearScroll(clearScrollRequest);
boolean succeeded = clearScrollResponse.isSucceeded();
排序
SearchSourceBuilder可以添加一种或多种SortBuilder。
有四种特殊的排序实现:
-
- field
- score
- GeoDistance
- scriptSortBuilder
sourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC)); //按照score倒序排列
sourceBuilder.sort(new FieldSortBuilder("_uid").order(SortOrder.ASC)); //并且按照id正序排列
过滤
默认情况下,searchRequest返回文档内容,与REST API一样,这里你可以重写search行为。例如,你可以完全关闭"_source"检索。
sourceBuilder.fetchSource(false);
该方法还接受一个或多个通配符模式的数组,以更细粒度地控制包含或排除哪些字段。
String[] includeFields = new String[] {"title", "user", "innerObject.*"};
String[] excludeFields = new String[] {"_type"};
sourceBuilder.fetchSource(includeFields, excludeFields);
聚合
通过配置适当的 AggregationBuilder ,再将它传入SearchSourceBuilder里,就可以完成聚合请求了。
@Test
public void test2(){
RestClient lowLevelRestClient = RestClient.builder(
new HttpHost("172.16.73.50", 9200, "http")).build();
RestHighLevelClient client =
new RestHighLevelClient(lowLevelRestClient);
SearchRequest searchRequest = new SearchRequest("bank");
searchRequest.types("account");
TermsAggregationBuilder aggregation = AggregationBuilders.terms("group_by_state")
.field("state.keyword");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.aggregation(aggregation);
searchSourceBuilder.size(0);
searchRequest.source(searchSourceBuilder);
try {
SearchResponse searchResponse = client.search(searchRequest);
System.out.println(searchResponse.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
Search response
Search response返回对象与其在API里的一样,返回一些元数据和文档数据。
首先,返回对象里的数据十分重要,因为这是查询的返回结果、使用分片情况、文档数据,HTTP状态码等
RestStatus status = searchResponse.status();
TimeValue took = searchResponse.getTook();
Boolean terminatedEarly = searchResponse.isTerminatedEarly();
boolean timedOut = searchResponse.isTimedOut();
其次,返回对象里面包含关于分片的信息和分片失败的处理:
int totalShards = searchResponse.getTotalShards();
int successfulShards = searchResponse.getSuccessfulShards();
int failedShards = searchResponse.getFailedShards();
for (ShardSearchFailure failure : searchResponse.getShardFailures()) {
// failures should be handled here
}
取回searchHit
SearchHits hits = searchResponse.getHits();
@Test
public void test2(){
RestClient lowLevelRestClient = RestClient.builder(
new HttpHost("172.16.73.50", 9200, "http")).build();
RestHighLevelClient client =
new RestHighLevelClient(lowLevelRestClient);
SearchRequest searchRequest = new SearchRequest("bank");
searchRequest.types("account");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchRequest.source(searchSourceBuilder);
try {
SearchResponse searchResponse = client.search(searchRequest);
SearchHits searchHits = searchResponse.getHits();
SearchHit[] searchHit = searchHits.getHits();
for (SearchHit hit : searchHit) {
System.out.println(hit.getSourceAsString());
}
} catch (IOException e) {
e.printStackTrace();
}
}
根据需要,还可以转换成其他数据类型:
String sourceAsString = hit.getSourceAsString();
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
String documentTitle = (String) sourceAsMap.get("title");
List<Object> users = (List<Object>) sourceAsMap.get("user");
Map<String, Object> innerObject = (Map<String, Object>) sourceAsMap.get("innerObject");
查询返回参数说明
{
"took":4, 请求花了多少时间
"time_out":false, 有没有超时
"_shards":{ 执行请求时查询的分片信息
"total":5, 查询的分片数量
"successful":5, 成功返回结果的分片数量
"failed":0 失败的分片数量
},
"hits":{
"total":2, 查询返回的文档总数
"max_score":0.625, 计算所得的最高分
"hits":[ 返回文档的hits数组
{
"_index":"books", 索引
"_type":"es", 属性
"_id":"1", 标志符
"_score":0.625, 得分
"_source":{ 发送到索引的JSON对象
"title":"Elasticsearch Server",
"publish":2013
}
},
{
"_index":"books",
"_type":"es",
"_id":"2",
"_score":0.19178301,
"_source":{
"title":"Mastering Elasticsearch",
"published":2013
}
}
]
}
}```
查询流程
GET my-index/_doc/0
- Client 将请求发送到任意节点 node,此时 node 节点就是协调节点(coordinating node)。
- 协调节点对 id 进行路由,从而判断该数据在哪个shard。
- 在 primary shard 和 replica shard 之间 随机选择一个,请求获取 doc。
- 接收请求的节点会将数据返回给协调节点,协调节点会将数据返回给Client。
可以通过 preference 参数指定执行操作的节点或分片。默认为随机。
检索流程
GET /my-index/_search
- Client 将请求发送到任意节点 node,此时 node 节点就是协调节点(coordinating node)
- 协调节点进行分词等操作后,去查询所有的 shard (primary shard 和 replica shard 选择一个)
- 所有 shard 将满足条件的数据 id 排序字段 等信息返回给路由节点
- 路由节点重新进行排序,截取数据后,获取到真正需要返回的数据的 id
- 路由节点再次请求对应的 shard (此时有 id 了,可以直接定位到对应shard)
- 获取到全量数据,返回给 Client
cat api 说明
GET /_cat/XXX?v
GET /_cat/XXX?v&format=json
v 是指带着列信息
支持指定返回内容的格式 默认为text
?format=text(json/smile/yaml/cbor)
查看节点别名
GET /_cat/aliases?v
curl -X GET "192.168.xxx.xxx:9200/_cat/aliases?v"
每个节点分配了几个shard,对磁盘的占用空间大小,使用率
GET /_cat/allocation?v
curl -X GET "192.168.xxx.xxx:9200/_cat/allocation?v"
群集或单个索引的document计数
GET /_cat/count?v
curl -X GET "192.168.xxx.xxx:9200/_cat/count?v
GET /_cat/count/index_name?v
curl -X GET "192.168.xxx.xxx:9200/_cat/count/index_name?v"
显示集群中每个数据节点上fielddata当前正在使用的堆内存量
GET /_cat/fielddata?v
curl -X GET "192.168.xxx.xxx:9200/_cat/fielddata?v"
查看集群健康情况
GET /_cat/health?v
curl -X GET "192.168.xxx.xxx:9200/_cat/health?v"
查看索引的信息
GET _cat/indices?v
GET _cat/indices/index_name?v
curl -X GET "192.168.xxx.xxx:9200/_cat/indices/twi*?v&s=index"
查看master信息
GET /_cat/master?v
curl -X GET "192.168.xxx.xxx:9200/_cat/master?v"
查看node信息
GET /_cat/nodes?v
curl -X GET "192.168.xxx.xxx:9200/_cat/nodes?v"
当前pending没执行完的task的具体情况,执行的是什么操作
创建索引,更新映射,分配或失败分片的列表
GET /_cat/pending_tasks?v
curl -X GET "192.168.xxx.xxx:9200/_cat/pending_tasks?v"
查看安装的插件
GET /_cat/plugins?v&s=component&h=name,component,version,description
curl -X GET "192.168.xxx.xxx:9200/_cat/plugins?v&s=component&h=name,component,version,description"
shard recovery恢复的过程情况
GET /_cat/recovery?v
curl -X GET "192.168.xxx.xxx:9200/_cat/recovery?v"
查看在群集中注册的快照存储库
GET /_cat/repositories?v
curl -X GET "192.168.xxx.xxx:9200/_cat/repositories?v
查看线程池使用
GET /_cat/thread_pool
curl -X GET "192.168.xxx.xxx:9200/_cat/thread_pool"
查看shard情况
GET _cat/shards?v
GET _cat/shards/index_name?v
curl -X GET "192.168.xxx.xxx:9200/_cat/shards/index_name?v
索引segment文件的情况,在哪个node上,有多少个document,占用了多少磁盘空间,有多少数据在内存中,是否可以搜索
GET /_cat/segments?v
GET _cat/segments/index_name?v
curl -X GET "192.168.xxx.xxx:9200/_cat/segments/index_name?v
查看tempalte
GET /_cat/templates?v&s=name
curl -X GET "192.168.xxx.xxx:9200/_cat/templates?v&s=name"
实际开发流程与常用命令
开发流程
- 设计好数据结构;
- 新增索引名;
- 新增索引结构;
- 查看索引结构是否新增成功;
- 添加别名;(若原本存在就切换索引)
- 写入数据;(需考虑有重刷全量数据的地方)
- 使用索引查询进行业务操作。
常用命令
##创建索引
PUT /your_index_name
## 也可以附加一些属性
## number_of_replicas 是数据备份数,如果只有一台机器,设置为0
## number_of_shards 是数据分片数,默认为5,有时候设置为3
PUT /your_index_name
{
"settings": {
"index":{
"number_of_shards" : 1,
"number_of_replicas" : 0
}
}
}
##查看索引状态
GET /your_index_name
aliases 别名
mappings 映射
settings 配置
settings.index.creation_date 创建时间
settings.index.number_of_shards 数据分片数
settings.index.number_of_replicas 数据备份数
settings.index.uuid 索引id
settings.index.provided_name 名称
##创建索引结构
POST /your_index_name/_doc/_mapping
{
"dynamic" : "strict",
"properties" : {
"storeId" : {
"type" : "keyword"
}
}
}
##查看索引结构
GET /your_index_name/_mapping
##查看所有索引与结构
GET /_all
##删除索引
DELETE /your_index_name
##关闭索引 当你不想删除该索引,可能只是想该索引暂停写入
POST /your_index_name/_close
##开启索引
POST /your_index_name/_open
##指定索引别名
POST /_aliases
{
"actions": [
{
"add": {
"index": "your_index_name",
"alias": "my_index_name"
}
}
]
}
##存在的索引结构新增字段
PUT /your_index_name/_mapping/_doc
{
"properties": {
"store_name" : {
"type" : "text"
}
}
}