ES的使用进阶

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下许可使用,可以免费下载、使用和修改。

使用场景

  1. 网站搜索
  2. ELK 日志采集,存储,分析
  3. 地理信息系统分析

特点

  1. ES 是一个分布式文档存储,存储的数据都是序列化为 JSON documents 。
  2. 使用倒排索引存储数据,倒排索引比较适合全文本搜索。
  3. 基于 Apache Lucene 搜索引擎库,可以存储,检索文档及元数据。
  4. 支持 JSON 样式的查询语言——Query DSL,也支持 SQL 样式的查询。
  5. 集群部署,易于扩展。节点(node)分片(shard),将新的 node 添加到集群时,ES 会自动迁移 shard 到新 node 上,重新平衡集群。
    1. shard 分为两种 主分片(primary shard)和 副本分片 (replica shard)
    2. replica shard 存放的是 primary shard 的冗余副本 —— 可以防止集群故障,数据丢失,同时可以提高搜索或检索速度。
    3. 在创建索引时 primary shard 数量是固定的,而 replica shard 数量是可以更改的。
    4. 分片由索引配置,分片越多,维护索引则开销则越大,分片大小越大,则 ES 在增减节点重新平衡集群时,分片移动时间越长。
  1. 集群恢复: 跨集群复制 (CCR),可以自动将索引从主集群同步到热备份的辅助远程集群。

核心概念

倒排索引

倒排索引也可以称为反向索引。

作为开发咱们经常接触到的就是 MySql,假设有一堆技术书籍,并且已经编上号。

  1. Java 并发编程之美
  2. Java 开发手册
  3. 深入分布式缓存
  4. Java 并发程序设计
  5. 算法
  6. 数据结构与算法

如果放在 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 有什么用?

  1. 增加故障转移:如果主副本发生故障,副本副本可以提升为主副本
  2. 提高性能:获取和搜索请求可以由主或副本分片处理。默认情况下,每个主分片都有一个副本,但是可以在现有索引上动态更改副本的数量。副本分片永远不会与其主分片在同一节点上启动。

除了定义索引应具有的主分片和副本分片的数量外,您无需直接引用分片。相反,您的代码应仅处理索引。

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 的执行需要三部分组成:

  1. RestHighLevelClient :执行bulk请求并拿到响应对象。
  2. BulkProcessor.Listener:在执行bulk request之前、之后和当bulk response发生错误时调用。
  3. 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

  1. Client 将请求发送到任意节点 node,此时 node 节点就是协调节点(coordinating node)。
  2. 协调节点对 id 进行路由,从而判断该数据在哪个shard。
  3. 在 primary shard 和 replica shard 之间 随机选择一个,请求获取 doc。
  4. 接收请求的节点会将数据返回给协调节点,协调节点会将数据返回给Client。

可以通过 preference 参数指定执行操作的节点或分片。默认为随机。

检索流程

GET /my-index/_search

  1. Client 将请求发送到任意节点 node,此时 node 节点就是协调节点(coordinating node)
  2. 协调节点进行分词等操作后,去查询所有的 shard (primary shard 和 replica shard 选择一个)
  3. 所有 shard 将满足条件的数据 id 排序字段 等信息返回给路由节点
  4. 路由节点重新进行排序,截取数据后,获取到真正需要返回的数据的 id
  5. 路由节点再次请求对应的 shard (此时有 id 了,可以直接定位到对应shard)
  6. 获取到全量数据,返回给 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"

实际开发流程与常用命令

开发流程

  1. 设计好数据结构;
  2. 新增索引名;
  3. 新增索引结构;
  4. 查看索引结构是否新增成功;
  5. 添加别名;(若原本存在就切换索引)
  6. 写入数据;(需考虑有重刷全量数据的地方)
  7. 使用索引查询进行业务操作。

常用命令

##创建索引
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"
    }
  }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值