Elasticsearch - 基础入门篇

目录

基础入门

Elasticsearch 是一个开源的搜索引擎,建立在一个全文搜索引擎库 Apache Lucene™ 基础之上。

Elasticsearch 也是使用 Java 编写的,它的内部使用 Lucene 做索引与搜索,但是它的目的是使全文检索变得简单, 通过隐藏 Lucene 的复杂性,取而代之的提供一套简单一致的 RESTful API。

本文以下Elasticsearch,皆简称为'es'

安装并运行

安装es之前,需要先安装一个比较新版本的Java,可以从官方去下载:https://www.java.com/,之后可以从es的官网:https://www.elastic.co/downloads/elasticsearch去下载新版本的es。

解压安装好后,找到安装目录下,输入启动es

shell 复制代码
cd elasticsearch-<version>
./bin/elasticsearch  

如果想把es作为一个守护进程在后台运行,可以在后面添加参数 -d

如果是在windows上面运行es,应该运行的是bin\elasticsearch.bat

测试es是否启动成功,可以打开另一个终端,执行以下操作

shell 复制代码
curl 'http://localhost:9200/?pretty'

如果是在windows运行es,可以访问http://curl.haxx.se/download.html 中下载cURL,cURL给你提供了一种将请求提交到es的便捷方式

成功响应会是如下返回:

json 复制代码
{
  "name" : "Tom Foster",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.1.0",
    "build_hash" : "72cd1f1a3eee09505e036106146dc1949dc5dc87",
    "build_timestamp" : "2015-11-18T22:40:03Z",
    "build_snapshot" : false,
    "lucene_version" : "5.3.1"
  },
  "tagline" : "You Know, for Search"
}

这意味着已经启动并运行了一个es节点,可以在这个单节点去实验一些基本操作。

Java与es的交互

es不仅支持Java语言进行交互,还支持Groovy、JavaScript、Python等语言的客户端插件,可以在https://www.elastic.co/guide/en/elasticsearch/client/index.html中找到

注意:Java客户端作为节点必须和es有相同的主要版本,否则,它们之间将无法互相理解。

Java API

Java与es的交互中,在代码里可以使用es内置的两个客户端

  • 节点客户端(Node client)
    • 节点客户端作为一个非数据节点加入本地集群中。换句话说,它本身不保存任何数据,但是它知道数据在集群中的哪个节点中,并且可以吧请求转发到正确的节点,至于如何进行转发,后续实现原理会讲到。
  • 传输客户端(Transport client)
    • 轻量级的传输客户端可以将请求发送到远程集群。它本身不加入集群,但是她可以将请求转发到集群的一个节点上。

两个Java客户端都是通过9300端口并使用es原生传输协议和集群交互。集群的节点通过9300彼此通信,如果这个端口没有打开,节点将无法形成一个集群。

http方式请求es

一个es请求和任何http请求一样由若干相同的部件组成

json 复制代码
curl -X<VERB> '<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>' -d '<BODY>'

被<>标记的部件说明:

  • VERB:适当的HTTP方法或谓词:GET、POST、PUT、HEAD或DELETE
  • PROTOCOL:http或者https(如果你在es前面有一个https代理)
  • HOST:es集群中任意节点的主机名,本机节点用localhost代表
  • PORT:运行es http服务的端口号,默认9200
  • PATH:API的终端路径(例如_count将返回集群中文档数量),path可能包含多个组件,例如:_cluster/stats和_nodes/stats/jvm等,这些都是es内置的函数,后续会详细介绍
  • QUERY_STRING:任意可选的查询字符串参数(例如?pretty将格式化的输出json返回值)
  • BODY:一个JSON格式的请求体(如果请求需要的话)

案例说明:

计算集群中文档的数量

json 复制代码
curl -XGET 'http://localhost:9200/_count?pretty' -d '
{
    "query": {
        "match_all": {}
    }
}
'

es返回的一个http状态码(例如:200 ok)和(除HEAD请求)一个json格式的返回值,前面的curl请求返回一个像下面一样的json体

json 复制代码
{
    "count" : 0,
    "_shards" : {
        "total" : 5,
        "successful" : 5,
        "failed" : 0
    }
}

在返回结果里没有看到http头部信息是因为我们没有要求curl显示他们,可以在curl加-i参数使用,如下:

json 复制代码
curl -i -XGET 'localhost:9200/'

以上是完整的请求方式,还可以用缩写格式来展示这些curl示例,缩写格式就是省略请求中所有相同的部分,例如主机名、端口号以及curl命令本身,如下:

json 复制代码
完整curl命令:
curl -XGET 'localhost:9200/_count?pretty' -d '
{
    "query": {
        "match_all": {}
    }
}'

缩写curl命令:
GET /_count
{
    "query": {
        "match_all": {}
    }
}

面向文档

es是面向文档,意味着它存储整个对象或文档,es不仅存储文档,而且索引每个文档的内容,可以被检索到,如果一个结构内容非常丰富的对象,像传统关系型数据库,要将这个对象扁平化尽可能的每个字段设计对应列中,每次查询后又需要重新构造成对象,而es则不需要,因此这也是es其中强大功能之一,另外es对存储的文档内容还可支持复杂的全文检索,这也是传统数据库比较难处理的部分。

json

es使用json作为文档的序列化格式,下面这个json文档代表了一个user对象:

json 复制代码
{
    "email":      "john@smith.com",
    "first_name": "John",
    "last_name":  "Smith",
    "info": {
        "bio":         "Eco-warrior and defender of the weak",
        "age":         25,
        "interests": [ "dolphins", "whales" ]
    },
    "join_date": "2014/05/01"
}

文档操作

在这里将会带领大家对es文档有一个简单的基本的入门练习,在练习前,我们先对这些名词有个概念

索引、类型、文档ID

一个索引类似于传统关系数据库中的一个数据库,是一个存储关系型文档的地方,一个es集群可以包含多个索引,对应每个索引可以包含多个类型,文档的唯一性是通过索引、类型、文档唯一标识三要素去确定的。

  • _index(索引):文档在哪里存放
  • _type(类型):文档标识的对象类别
  • _id(唯一标识):文档的唯一标识
json 复制代码
格式:
PUT /{index}/{type}/{id}
{
  "field": "value",
  ...
}
使用自定义的ID

示例:我们的索引是website,类型是blog,并且选择123作为ID,那么索引请求如下

json 复制代码
PUT /website/blog/123
{
  "title": "My first blog entry",
  "text":  "Just trying this out...",
  "date":  "2014/01/01"
}

es响应:
{
   "_index":    "website",
   "_type":     "blog",
   "_id":       "123",
   "_version":  1,
   "created":   true
}

响应表明文档已经成功创建,响应中_version是es中每个文档都会有一个版本号,每次对文档进行修改(包括删除),_version都会递增,在处理冲突的时候可以用_version保证程序一部分修改不会影响到另一部分的修改,_created为true表示创建成功

es自动生成的ID

如果你的数据没有自然的ID,es可以帮我们自动生成ID,请求结构需要调整为POST。PUT是对数据的一部分修改,如果不存在的属性会自动加到原文档中,存在的属性会覆盖原来的值,而POST是创建一个完全新的文档,视情况选择使用。

json 复制代码
POST /website/blog/
{
  "title": "My second blog entry",
  "text":  "Still trying this out...",
  "date":  "2014/01/01"
}

es响应:
{
   "_index":    "website",
   "_type":     "blog",
   "_id":       "AVFgSgVHUP18jI2wRx0w",
   "_version":  1,
   "created":   true
}

可以看见除了_id是es自动生成的,其他响应部分与前面类似,自动生成的 ID 是 URL-safe、 基于 Base64 编码且长度为20个字符的 GUID 字符串。 这些 GUID 字符串由可修改的 FlakeID 模式生成,这种模式允许多个节点并行生成唯一 ID ,且互相之间的冲突概率几乎为零。

创建新文档

当我们索引一个文档时候,怎么确认我们是创建一个完全新的文档,而不是覆盖现有的呢,最简单的方法就是使用POST形式让es自动生成一个唯一_id

示例如下:

json 复制代码
POST /website/blog/
{
  "title": "My second blog entry",
  "text":  "Still trying this out...",
  "date":  "2014/01/01"
}

es响应:
{
   "_index":    "website",
   "_type":     "blog",
   "_id":       "AVFgSgVHUP18jI2wRx0w",
   "_version":  1,
   "created":   true
}

然而,如果已经有自己的_id的情况,因为文档的唯一性是_index、_type、_id来确定的,如果_index、_type两个不存在,可以使用PUT请求后面追加参数表示创建一个新文档,如下:

1.使用op_type查询-字符串参数

json 复制代码
PUT /website/blog/123?op_type=create
{ ... }

2.在URL末端使用/_create

json 复制代码
PUT /website/blog/123/_create
{ ... }

如果创建新文档请求成功,es会返回源数据和一个201 Created的http响应码

如果_index、_type和_id文档都存在,es会返回409的响应码,如下:

json 复制代码
{
   "error": {
      "root_cause": [
         {
            "type": "document_already_exists_exception",
            "reason": "[blog][123]: document already exists",
            "shard": "0",
            "index": "website"
         }
      ],
      "type": "document_already_exists_exception",
      "reason": "[blog][123]: document already exists",
      "shard": "0",
      "index": "website"
   },
   "status": 409
}

更新文档

在es中文档是不可改变 的,不能修改它们,相反,如果想更新现有的文档,需要重建索引 或者进行替换

json 复制代码
PUT /website/blog/123
{
  "title": "My first blog entry",
  "text":  "I am starting to get the hang of this...",
  "date":  "2014/01/02"
}

es响应:
{
  "_index" :   "website",
  "_type" :    "blog",
  "_id" :      "123",
  "_version" : 2,
  "created":   false 
}

created标志设置为false,因为相同的索引、类型、id的文档已经存在,且_version已经递增为2

在es内部,已经将旧文档标记为已删除,并新增一个全新的文档,尽管你不能对旧版本的文档进行访问,但它不会立马消失,当继续索引更多的数据,在es内部的会自动清理已删除的文档

流程:

  1. 从旧文档构建json
  2. 更新该json
  3. 删除旧文档
  4. 索引一个新文档
部分更新

在上面的案例中,我们使用了PUT请求去更新文档的信息,前面提到过POST会建立全新的文档,但也可以利用_update的api去对文档进行一个局部的更新。<font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">update</font> 请求最简单的一种形式是接收文档的一部分作为 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">doc</font> 的参数, 它只是与现有的文档进行合并。对象被合并到一起,覆盖现有的字段,增加新的字段。 例如,我们增加字段 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">tags</font><font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">views</font> 到我们的博客文章,如下所示:

json 复制代码
POST /website/blog/1/_update
{
  "doc" : {
    "tags" : [ "testing" ],
    "views": 0
  }
}

es正确响应如下:
{
  "_index" :   "website",
  "_id" :      "1",
  "_type" :    "blog",
  "_version" : 3
}

查询更新后的内容,发现新字段已经更新进去
{
  "_index":    "website",
  "_type":     "blog",
  "_id":       "1",
  "_version":  3,
  "found":     true,
  "_source": {
    "title":  "My first blog entry",
    "text":   "Starting to get the hang of this...",
    "tags": [ "testing" ], 
    "views":  0 
  }
}
使用脚本更新文档

脚本可以在 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">update</font> API中用来改变 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">_source</font> 的字段内容, 它在更新脚本中称为 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">ctx._source</font> 。 例如,我们可以使用脚本来增加博客文章中 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">views</font> 的数量:

json 复制代码
POST /website/blog/1/_update
{
  "script" : "ctx._source.views+=1"
}

我们也可以通过使用脚本给 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">tags</font> 数组添加一个新的标签。在这个例子中,我们指定新的标签作为参数,而不是硬编码到脚本内部。 这使得es可以重用这个脚本,而不是每次我们想添加标签时都要对新脚本重新编译:

json 复制代码
POST /website/blog/1/_update
{
  "script" : "ctx._source.tags+=new_tag",
  "params" : {
    "new_tag" : "search"
  }
}

es响应,search 标签已追加到 tags 数组中,views 字段已递增。
{
  "_index":    "website",
  "_type":     "blog",
  "_id":       "1",
  "_version":  5,
  "found":     true,
  "_source": {
    "title":  "My first blog entry",
    "text":   "Starting to get the hang of this...",
    "tags":  ["testing", "search"], 
    "views":  1 
  }
}

我们甚至可以选择通过设置 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">ctx.op</font><font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">delete</font> 来删除基于其内容的文档:

json 复制代码
POST /website/blog/1/_update
{
  "script" : "ctx.op = ctx._source.views == count ? 'delete' : 'none'",
  "params" : {
    "count": 1
  }
}
更新的文档不存在时

假设我们需要在 es 中存储一个页面访问量计数器。 每当有用户浏览网页,我们对该页面的计数器进行累加。但是,如果它是一个新网页,我们不能确定计数器已经存在。 如果我们尝试更新一个不存在的文档,那么更新操作将会失败。

在这样的情况下,我们可以使用 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">upsert</font> 参数,指定如果文档不存在就应该先创建它:

json 复制代码
POST /website/pageviews/1/_update
{
  "script" : "ctx._source.views+=1",
  "upsert": {
    "views": 1
  }
}

我们第一次运行这个请求时, <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">upsert</font> 值作为新文档被索引,初始化 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">views</font> 字段为 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">1</font> 。 在后续的运行中,由于文档已经存在, <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">script</font> 更新操作将替代 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">upsert</font> 进行应用,对 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">views</font> 计数器进行累加。

更新和冲突

假如在 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">update</font> 设法重新索引之前,来自另一进程的请求修改了文档,就会产生冲突问题。

为了避免数据丢失, <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">update</font> API 在 检索 步骤时检索得到文档当前的 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">_version</font> 号,并传递版本号到 重建索引 步骤的 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">index</font> 请求。 如果另一个进程修改了处于检索和重新索引步骤之间的文档,那么 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">_version</font> 号将不匹配,更新请求将会失败。

对于部分更新的很多使用场景,文档已经被改变也没有关系。 例如,如果两个进程都对页面访问量计数器进行递增操作,它们发生的先后顺序其实不太重要; 如果冲突发生了,我们唯一需要做的就是尝试再次更新。

这可以通过设置参数 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">retry_on_conflict</font> 来自动完成, 这个参数规定了失败之前 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">update</font> 应该重试的次数,它的默认值为 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">0</font>

以下案例:失败之前重试该更新5次。

plain 复制代码
POST /website/pageviews/1/_update?retry_on_conflict=5 
{
   "script" : "ctx._source.views+=1",
   "upsert": {
       "views": 0
   }
}

删除文档

使用DELETE方法,如下

json 复制代码
DELETE /website/blog/123

es响应:
{
  "found" :    true,
  "_index" :   "website",
  "_type" :    "blog",
  "_id" :      "123",
  "_version" : 3
}

注意,字段_version值已经增加,如果文档没有找到,会是会是下面的响应码和内容,即使文档不存在,_version也是会增加,这是es内部记录本的一部分,用来确保这些改变跨多节点时正确的顺序执行

json 复制代码
{
  "found" :    false,
  "_index" :   "website",
  "_type" :    "blog",
  "_id" :      "123",
  "_version" : 4
}

检索文档

先从一个最简单的查询开始,我们使用下列请求来搜索所有雇员

json 复制代码
GET /megacorp/employee/_search

es响应:
{
   "took":      6,
   "timed_out": false,
   "_shards": { ... },
   "hits": {
      "total":      3,
      "max_score":  1,
      "hits": [
         {
            "_index":         "megacorp",
            "_type":          "employee",
            "_id":            "3",
            "_score":         1,
            "_source": {
               "first_name":  "Douglas",
               "last_name":   "Fir",
               "age":         35,
               "about":       "I like to build cabinets",
               "interests": [ "forestry" ]
            }
         },
         {
            "_index":         "megacorp",
            "_type":          "employee",
            "_id":            "1",
            "_score":         1,
            "_source": {
               "first_name":  "John",
               "last_name":   "Smith",
               "age":         25,
               "about":       "I love to go rock climbing",
               "interests": [ "sports", "music" ]
            }
         },
         {
            "_index":         "megacorp",
            "_type":          "employee",
            "_id":            "2",
            "_score":         1,
            "_source": {
               "first_name":  "Jane",
               "last_name":   "Smith",
               "age":         32,
               "about":       "I like to collect rock albums",
               "interests": [ "music" ]
            }
         }
      ]
   }
}

可以看到,我们仍然使用索引库 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">megacorp</font> 以及类型 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">employee</font>,但与指定一个文档 ID 不同,这次使用 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">_search</font> 。返回结果包括了所有三个文档,放在数组 <font style="color:rgb(85, 85, 85);background-color:rgb(248, 248, 248);">hits</font> 中。一个搜索默认返回十条结果。

注意:返回结果不仅告知匹配了哪些文档,还包含了整个文档本身:显示搜索结果给最终用户所需的全部信息。

接下来,尝试下搜索姓氏为 Smith 的雇员。为此,我们将使用一个 高亮 搜索,很容易通过命令行完成。这个方法一般涉及到一个 查询字符串query-string) 搜索,因为我们通过一个URL参数来传递查询信息给搜索接口:

json 复制代码
GET /megacorp/employee/_search?q=last_name:Smith

es响应:
{
   ...
   "hits": {
      "total":      2,
      "max_score":  0.30685282,
      "hits": [
         {
            ...
            "_source": {
               "first_name":  "John",
               "last_name":   "Smith",
               "age":         25,
               "about":       "I love to go rock climbing",
               "interests": [ "sports", "music" ]
            }
         },
         {
            ...
            "_source": {
               "first_name":  "Jane",
               "last_name":   "Smith",
               "age":         32,
               "about":       "I like to collect rock albums",
               "interests": [ "music" ]
            }
         }
      ]
   }
}

相信到这里,大家对es基础入门使用有一定了解,那么es提供一个丰富灵活的查询语言叫做查询表达式, 它支持构建更加复杂和健壮的查询,后续es专栏中将会对大家进行持续更新,基于查询表达式上,对文档进行分组、聚合、过滤等案例讲解。

相关文献

就先说到这 \color{#008B8B}{ 就先说到这} 就先说到这
在下 A p o l l o \color{#008B8B}{在下Apollo} 在下Apollo
一个爱分享 J a v a 、生活的小人物, \color{#008B8B}{一个爱分享Java、生活的小人物,} 一个爱分享Java、生活的小人物,
咱们来日方长,有缘江湖再见,告辞! \color{#008B8B}{咱们来日方长,有缘江湖再见,告辞!} 咱们来日方长,有缘江湖再见,告辞!

相关推荐
airuike12314 小时前
以微见著,精准护航:MEMS IMU助力高铁轨道智能检测
大数据·人工智能·科技
青稞社区.15 小时前
Claude Code 源码深度解析:运行机制与 Memory 模块详解
大数据·人工智能·elasticsearch·搜索引擎·agi
T062051415 小时前
【面板数据】地级市及区县人口空心化数据(2000-2024年)
大数据
Aktx20FNz16 小时前
iFlow CLI 完整工作流指南
大数据·elasticsearch·搜索引擎
LaughingZhu17 小时前
Anthropic 收购 Oven 后,Claude Code 用运行时写了一篇护城河文章
大数据·人工智能·经验分享·搜索引擎·语音识别
学习3人组17 小时前
TortoiseGit冲突解决实战上机练习
大数据·elasticsearch·搜索引擎
Ln5x9qZC217 小时前
Flink SQL 元数据持久化实战
大数据·sql·flink
OYpBNTQXi18 小时前
Flink Agents 源码解读 --- (6) --- ActionTask
大数据·flink
A__tao18 小时前
Elasticsearch Mapping 一键生成 Go Struct,支持嵌套解析
elasticsearch·es
中金快讯19 小时前
济民健康医疗服务占比提升至46%!业务结构调整初见成效
大数据·人工智能