仅一分钟学会ElasticSearch

本文深入讲解Elasticsearch的使用场景、安装配置、数据管理和查询技巧,涵盖了从文档管理到复杂查询的全过程,适合初学者快速上手及进阶学习。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一,认识ElasticSearch

  1. 为什么要使用ElasticSearch

虽然全文搜索领域,Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。

但是,Lucene只是一个库。想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,更糟糕的是,Lucene的配置及使用非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的。

实际项目中,我们建立一个网站或应用程序,并要添加搜索功能,令我们受打击的是:搜索工作是很难的。我们希望我们的搜索解决方案要快,我们希望有一个零配置和一个完全免费的搜索模式,我们希望能够简单地使用JSON/XML通过HTTP的索引数据,我们希望我们的搜索服务器始终可用,我们希望能够从一台开始并在需要扩容时方便地扩展到数百,我们要实时搜索,我们要简单的多租户,我们希望建立一个云的解决方案。

    2.ElasticSearch(简称ES

ES即为了解决原生Lucene使用的不足,优化Lucene的调用方式,并实现了高可用的分布式集群的搜索方案,其第一个版本于2010年2月出现在GitHub上并迅速成为最受欢迎的项目之一。

首先,ES的索引库管理支持依然是基于Apache Lucene(TM)的开源搜索引擎。

ES也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的 RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。

不过,ES的核心不在于Lucene,其特点更多的体现为:

分布式的实时文件存储,每个字段都被索引并可被搜索

分布式的实时分析搜索引擎

可以扩展到上百台服务器,处理PB级结构化或非结构化数据

高度集成化的服务,你的应用可以通过简单的 RESTful API、各种语言的客户端甚至命令行与之

交互。

上手Elasticsearch非常容易。它提供了许多合理的缺省值,并对初学者隐藏了复杂的搜索引擎理论。它拥有开瓶即饮的效果(安装即可使用),只需很少的学习既可在生产环境中使用。

3.ES的使用者及类似框架

典型使用案例

①Github使用Elasticsearch搜索20TB的数据,包括13亿的文件和1300亿行的代码.

Foursquare实时搜索5千万地点信息?Foursquare每天都用Elasticsearch做这样的事.

德国SoundCloud使用Elasticsearch来为1.8亿用户提供即时精准的音乐搜索服务.

④Mozilla公司以火狐著名,它目前使用 WarOnOrange 这个项目来进行单元或功能测试,测试的结果以 json的方式索引到elasticsearch中,开发人员可以非常方便的查找 bug.

⑤Sony公司使用elasticsearch 作为信息搜索引擎.

类似框架

 Solr(重量级对手)

Apache Lucene项目的开源企业搜索平台。其主要功能包括全文检索、命中标示、分面搜索、动态聚类、数据库集成,以及富文本(如Word、PDF)的处理。Solr是高度可扩展的,并提供了分布式搜索和索引复制。Solr是最流行的企业级搜索引擎,Solr4 还增加了NoSQL支持。

Solr和ES比较:

Solr 利用 Zookeeper 进行分布式管理,支持更多格式的数据(HTML/PDF/CSV),官方提供的功能更多在传统的搜索应用中表现好于 ES,但实时搜索效率低。

 ES自身带有分布式协调管理功能,但仅支持json文件格式,本身更注重于核心功能,高级功能多有第三方插件提供,在处理实时搜索应用时效率明显高于 ES。

② Katta

基于 Lucene 的,支持分布式,可扩展,具有容错功能,准实时的搜索方案。

优点:开箱即用,可以与 Hadoop 配合实现分布式。具备扩展和容错机制。

缺点:只是搜索方案,建索引部分还是需要自己实现。在搜索功能上,只实现了最基本的需求。成功案例较少,项目的成熟度稍微差一些。

③ HadoopContrib

Map/Reduce 模式的,分布式建索引方案,可以跟 Katta 配合使用。

优点:分布式建索引,具备可扩展性。

缺点:只是建索引方案,不包括搜索实现。工作在批处理模式,对实时搜索的支持不佳。

二,ES安装及使用说明

     1.包含的内容

ES的安装比较简单,只需要官方下载ES的运行包,然后启动ES服务即可。

ES的使用主要是通过能够发起HTTP请求的终端来接入,比如Poster插件、CURL、kibana5等。

     2.安装ES

ES服务只依赖于JDK,推荐使用JDK1.7+。

① 下载ES安装包

官方下载地址:https://www.elastic.co/downloads/elasticsearch

这里以在window环境下,ES 5.2.2版本为例,下载对应的ZIP文件

② 运行ES

bin/elasticsearch.bat

③ 验证

访问:http://localhost:9200/

看到上图信息,恭喜你,你的ES集群已经启动并且正常运行.

     3.ES交互方式

① 基于RESTful API

ES和所有客户端的交互都是使用JSON格式的数据.

其他所有程序语言都可以使用RESTful API,通过9200端口的与ES进行通信,在开发测试阶段,你可以使用你喜欢的WEB客户端, curl命令以及火狐的POSTER插件方式和ES通信。

Curl命令方式:

默认windows下不支持curl命令,在资料中有curl的工具及简单使用说明。

② Java API

ES为Java用户提供了两种内置客户端:

节点客户端(node client):

节点客户端以无数据节点(none data node)身份加入集群,换言之,它自己不存储任何数据,但是它知道数据在集群中的具体位置,并且能够直接转发请求到对应的节点上。

传输客户端(Transport client):

这个更轻量的传输客户端能够发送请求到远程集群。它自己不加入集群,只是简单转发请求给集群中的节点。

两个Java客户端都通过9300端口与集群交互,使用ES传输协议(ES Transport Protocol)。集群中的节点

之间也通过9300端口进行通信。如果此端口未开放,你的节点将不能组成集群。

注意

Java客户端所在的ES版本必须与集群中其他节点一致,否则,它们可能互相无法识别。

     4.辅助管理工具Kibana5

① Kibana5.2.2下载地址:https://www.elastic.co/downloads/kibana

② 解压并编辑config/kibana.yml,设置elasticsearch.url的值为已启动的ES

③ 启动Kibana5 : bin\kibana.bat

④ 默认访问地址:http://localhost:5601

Discover:可视化查询分析器

Visualize:统计分析图表

Dashboard:自定义主面板(添加图表)

Timelion:Timelion是一个kibana时间序列展示组件(暂时不用)

Dev Tools :Console(同CURL/POSTER,操作ES代码工具,代码提示,很方便)

Management:管理索引库(index)、已保存的搜索和可视化结果(save objects)、设置 kibana 服务器属性。

三,数据管理

1.什么是ES中的文档

​​​​​​​

Document(List<Field>)  Field {name value store....}

ES是面向文档(document oriented)的,这意味着它可以存储整个对象或文档(document)。然而它不仅仅是存储,还会索引(index)每个文档的内容使之可以被搜索。在ES中,你可以对文档(而非成行成列的数据)进行索引、搜索、排序、过滤。

ES使用Javascript对象符号(JavaScript Object Notation),也就是JSON,作为文档序列化格式。JSON现在已经被大多语言所支持,而且已经成为NoSQL领域的标准格式。

ES存储的一个员工文档的格式示例:

{

_index : crm,

_type : user,

_id : 1,

_source : {

"email": "nxh@itsource.cn",

"name": "倪先华",

"info": {

     "addr": "四川省成都市",

     "age": 30,

     "interests": [ "美食", "美女" ]

},

"join_date": "2016-06-01"

}

}

尽管原始的 employee对象很复杂,但它的结构和对象的含义已经被完整的体现在JSON中了,在ES中将对象转化为JSON并做索引要比在表结构中做相同的事情简单的多。

一个文档不只有数据。它还包含元数据(metadata)—关于文档的信息。三个必须的元数据节点是:

_index:索引库,类似于关系型数据库里的“数据库”—它是我们存储和索引关联数据的地方。

_type:在应用中,我们使用对象表示一些“事物”,例如一个用户、一篇博客、一个评论,或者一封邮件。可以是大写或小写,不能包含下划线或逗号。我们将使用 employee 做为类型名。

_id:与 _index  和 _type  组合时,就可以在ELasticsearch中唯一标识一个文档。当创建一个文档,你可以自定义 _id  ,也可以让Elasticsearch帮你自动生成。

另外还包括:_uid文档唯一标识(_type#_id)

_source:文档原始数据

_all:所有字段的连接字符串

2.​​​​​​​文档的增删改

 

我们以员工对象为例,我们首先要做的是存储员工数据,每个文档代表一个员工。在ES中存储数据的行为就叫做索引(indexing),文档归属于一种类型(type),而这些类型存在于索引(index)中,我们可以简单的对比传统数据库和ES的对应关系:

关系数据库(MYSQL) -> 数据库DB-> 表TABLE-> 行ROW-> 列Column

Elasticsearch -> 索引库Indices -> 类型Types -> 文档Documents -> 字段Fields

ES集群可以包含多个索引(indices)(数据库),每一个索引库中可以包含多个类型(types)(表),每一个类型包含多个文档(documents)(行),然后每个文档包含多个字段(Fields)(列)。

创建索引文档

①使用自己的ID创建:

PUT {index}/{type}/{id}

{

  "field": "value",

  ...

}

②ES内置ID创建:

POST {index}/{type}/

{

  "field": "value",

  ...

}

①②ES响应内容:

{

"_index": "itsource",

"_type": "employee",

"_id": xxxxxx,

"_version": 1, //文档版本号

"created": true //是否新增

}

 

③ 获取指定ID的文档

GET itsource/employee/123?pretty

③返回的内容:

{

"_index" : "itsource",

"_type" : "employee",

"_id" : "123",

"_version" : 1,

"found" : true,

"_source" : {

   "email": "nxh@itsource.cn",

   "fullName": "倪先华",

   ...

   "joine_date": "2016-06-01"

}

}

返回文档的部分字段:

GET默认返回整个文档,通过GET /itsource/employee/123?_source=fullName,email

只返回文档内容,不要元数据:

GET itsource/employee/123/_source

    只检查文档是否存在(查询头信息):

curl -i -X HEAD http://localhost:9200/itsource/employee/123

④ 修改文档

更新整个文档

同PUT {index}/{type}/{id}

在响应中,我们可以看到Elasticsearch把 _version  增加了。

{

...

"_version" : 2,

"created": false

}

created  标识为 false  因为同索引、同类型下已经存在同ID的文档。

在内部,Elasticsearch已经标记旧文档为删除并添加了一个完整的新文档。旧版本文档不会立即消失,但你也不能去访问它。Elasticsearch会在你继续索引更多数据时清理被删除的文档。

局部更新文档

接受一个局部文档参数 doc,它会合并到现有文档中,对象合并在一起,存在的标量字段被覆盖,新字段被添加。

POST itsource/employee/123/_update

{

“doc” : {

"email" : "nixianhua@itsource.cn", 

"salary": 1000

}

}

email会被更新覆盖,salary会新增。

这个API 似乎 允许你修改文档的局部,但事实上Elasticsearch

遵循与之前所说完全相同的过程,这个过程如下:

1. 从旧文档中检索JSON

2. 修改它

3. 删除旧文档

4. 索引新文档

脚本更新文档

也可以通过使用简单的脚本来进行。这个例子使用一个脚本将age加5:

POST itsource/emploee/123/_update

{

"script" : "ctx._source.age += 5"

}

在上面的例子中, ctx._source指向当前被更新的文档。

注意,目前的更新操作只能一次应用在一个文档上。

删除文档

DELETE {index}/{type}/{id}

存在文档的返回:

{

"found" : true,

"_index" : "website",

"_type" : "blog",

"_id" : "123",

"_version" : 3

}

不存在的返回:

{

"found" : false,

"_index" : "website",

"_type" : "blog",

"_id" : "123",

"_version" : 4

}

注意:尽管文档不存在,但_version依旧增加了。这是内部记录的一部分,它确保在多节点间不同操作可以有正确的顺序。

批量操作bulk  API

使用单一请求来实现多个文档的create、index、update 或 delete。

Bulk请求体格式:

{ action: { metadata }}\n

{ request body }\n

{ action: { metadata }}\n

{ request body }\n

每行必须以 "\n"  符号结尾,包括最后一行。这些都是作为每行有效的分离而做的标记。

create当文档不存在时创建之。

index创建新文档或替换已有文档。

update局部更新文档。

delete删除一个文档。

例如:

POST _bulk

{ "delete": { "_index": "itsource", "_type": "employee", "_id": "123" }}

{ "create": { "_index": "itsource", "_type": "blog", "_id": "123" }}

{ "title": "我发布的博客" }

{ "index": { "_index": "itsource", "_type": "blog" }}

{ "title": "我的第二博客" }

 

注意:delete后不需要请求体,最后一行要有回车

 

3.​​​​​​​​​​​​​​文档的简单查询

           ①批量获取

mget  API参数是一个 docs数组,数组的每个节点定义一个文档的 _index  、 _type  、 _id  元数据。如果你只想检索一个或几个确定的字段,也可以定义一个 _source  参数:

方式1:GET _mget

{

"docs" : [

{

"_index" : "itsource",

"_type" : "blog",

"_id" : 2

},

{

"_index" : "itsource",

"_type" : "employee",

"_id" : 1,

"_source": ["name","age"]

}

]

}

方式2:同一个索引库的同一个类型下

GET itsource/blog/_mget

{

"ids" : [ "2", "1" ]

}

            ②​​​​​​​空搜索

没有指定任何的查询条件,只返回集群索引中的所有文档: GET _search

             ③​​​​​​​​​​​​​​分页搜索

和SQL使用 LIMIT  关键字返回只有一页的结果一样,Elasticsearch接受 from  和 size  参数:

size  : 每页条数,默认 10

from  : 跳过开始的结果数,默认 0

如果你想每页显示5个结果,页码从1到3,那请求如下:

GET _search?size=5

GET _search?size=5&from=5

GET _search?size=5&from=10

             ④​​​​​​​查询字符串搜索

一个搜索可以用纯粹的uri来执行查询。在这种模式下使用搜索,并不是所有的选项都是暴露的。它可以方便快速进行 curl 测试。

 

查询年龄为25岁的员工

GET itsource/employee/_search?q=age:25

如果q后的参数不指定Fileds则默认查询_all字段(隐含的文档所有字段的连接内容)

类似的查询语法参考lucene,如:

+name:john +tweet:mary

+name:(mary john) +date:>2014-09-10 +(aggregations geo)

age[20 TO 30]

四,DSL查询与过滤

1.​​​​​​​什么是DSL查询

 

由ES提供丰富且灵活的查询语言叫做DSL查询(Query DSL),它允许你构建更加复杂、强大的查询。

DSL(Domain Specific Language特定领域语言)以JSON请求体的形式出现。我们可以这样表示之前关于“倪先华”的查询:

查询字符串模式:GET itsource/employee/_search?q=fullName:倪先华

DSL模式

GET itsource/employee/_search

{

"query" : {

   "match" : {

    "fullName" : "倪先华"

}

}

}

对于简单查询,使用查询字符串比较好,但是对于复杂查询,由于条件多,逻辑嵌套复杂,查询字符串不易组织与表达,且容易出错,因此推荐复杂查询通过DSL使用JSON内容格式的请求体代替。

2.​​​​​​​DSL查询

 

使用DSL查询,必须要传递query参数给ES。

GET _search

{"query": YOUR_QUERY_HERE}

一个常用的相对完整的DSL查询:

GET itsource/employee/_search

{

"query": {

   "match": {"sex":"女"}

},

"from": 20,

"size": 10,

" _source": ["fullName", "age", "email"],

"sort": [{"join_date": "desc"},{"age": "asc"}]

}

上面的DSL查询语句代表:查询公司员工性别为女的员工,并按照加入时间降序、年龄升序排列,最终返回第21条至30条数据(只返回名字、年龄和email字段)

3.​​​​​​​DSL过滤

 

DSL过滤语句和DSL查询语句非常相似,但是它们的使用目的却不同:

DSL过滤查询文档的方式更像是对于我的条件“有”或者“没有”,而DSL查询语句则像是“有多像”。

DSL过滤和DSL查询在性能上的区别:

  • 过滤结果可以缓存并应用到后续请求。
  • 查询语句同时匹配文档,计算相关性,所以更耗时,且不缓存。
  • 过滤语句可有效地配合查询语句完成文档过滤。

原则上,使用DSL查询做全文本搜索或其他需要进行相关性评分的场景,其它全用DSL过滤。

2.0以上的用法

{

"query": {

   "bool": {

          "must": [

{"match": {"description": "search" }}

],

           "filter": {

               "term": {"tags": "lucene"}

           }

    }

}

}

2.0以前的用法

{

"query": {

   "filtered": {

          "query": {

                 "match": {"description": "search" }

            },

           "filter": {

               "term": {"tags": "lucene"}

           }

    }

}

}

 

 

4.​​​​​​​使用DSL查询与过滤

 

① 全匹配(match_all)

普通搜索(匹配所有文档):

{

"query" : {

"match_all" : {}

}

}

如果需要使用过滤条件(在所有文档中过滤,红色部分默认可不写):

{

"query" : {

"bool" : {

"must" : [{

"match_all":{}

}],

"filter":{....}

}

}

}

标准查询(match和multi_match)

match查询是一个标准查询,不管你需要全文本查询还是精确查询基本上都要用到它。

如果你使用match查询一个全文本字段,它会在真正查询之前用分析器先分析查询字符:

{

"query": {

"match": {

"fullName": "Steven King"

}

}

}

上面的搜索会对Steven King分词,并找到包含Steven或King的文档,然后给出排序分值。

如果用 match  下指定了一个确切值,在遇到数字,日期,布尔值或者 not_analyzed的字符串时,它将为你搜索你给定的值,如:

{ "match": { "age": 26 }}

{ "match": { "date": "2014-09-01" }}

{ "match": { "public": true }}

{ "match": { "tag": "full_text" }}

multi_match  查询允许你做 match查询的基础上同时搜索多个字段:

{

"query":{

"multi_match": {

"query": "Steven King",

"fields": [ "fullName", "title" ]

}

}

}

上面的搜索同时在fullName和title字段中匹配。

提示:match一般只用于全文字段的匹配与查询,一般不用于过滤。

 

③单词搜索与过滤(Term和Terms)

{

"query": {

"bool": {

"must": {

"match_all": {}

},

"filter": {

"term": {

"tags": "elasticsearch"

}

}

}

}

}

Terms搜索与过滤

{

"query": {

"terms": {

"tags": ["jvm", "hadoop", "lucene"],

"minimum_match": 2

}

}

}

minimum_match:至少匹配个数,默认为1

 

④ 组合条件搜索与过滤(Bool)

组合搜索bool可以组合多个查询条件为一个查询对象,查询条件包括must、should和must_not。

例如:查询爱好有美女,同时也有喜欢游戏或运动,且出生于1990-06-30及之后的人。

{

"query": {

"bool": {

"must": [{"term": {"hobby": "美女"}}],

"should": [{"term": {"hobby": "游戏"}},

 {"term": {"hobby": "运动"}}

],

"must_not": [

{"range" :{"birth_date":{"lt": "1990-06-30"}}}

],

        "filter": [...],

"minimum_should_match": 1

}

}

}

提示: 如果 bool 查询下没有must子句,那至少应该有一个should子句。但是 如果有 must子句,那么没有 should子句也可以进行查询。

⑤ 范围查询与过滤(range)

range过滤允许我们按照指定范围查找一批数据:

{

"query":{

"range": {

"age": {

"gte": 20,

"lt": 30

}

}

}

}

上例中查询年龄大于等于20并且小于30。

gt:>    gte:>=   lt:<  lte:<=

⑥ 存在和缺失过滤器(exists和missing)

{

"query": {

"bool": {

"must": [{

"match_all": {}

}],

"filter": {

"exists": { "field": "gps" }

}

}

}

}

提示:exists和missing只能用于过滤结果。

⑦ 前匹配搜索与过滤(prefix)

和term查询相似,前匹配搜索不是精确匹配,而是类似于SQL中的like ‘key%’

{

"query": {

"prefix": {

"fullName": "倪"

}

}

}

上例即查询姓倪的所有人。

⑧ 通配符搜索(wildcard)

使用*代表0~N个,使用?代表1个。

{

"query": {

"wildcard": {

"fullName": "倪*华"

}

}

}

五,分词与映射

 

​​​​​​​1.为什么要使用分词与映射

在全文检索理论中,文档的查询是通过关键字查询文档索引来进行匹配,因此将文本拆分为有意义的单词,对于搜索结果的准确性至关重要,因此,在建立索引的过程中和分析搜索语句的过程中都需要对文本串分词。

ES中分词需要对具体字段指定分词器等细节,因此需要在文档的映射中明确指出。

​​​​​​​2.IK分词器

ES默认对英文文本的分词器支持较好,但和lucene一样,如果需要对中文进行全文检索,那么需要使用中文分词器,同lucene一样,在使用中文全文检索前,需要集成IK分词器。

ES的IK分词器插件源码地址:https://github.com/medcl/elasticsearch-analysis-ik

① Maven打包IK插件

② 解压target/releases/elasticsearch-analysis-ik-5.2.2.zip文件

并将其内容放置于ES根目录/plugins/ik

③ 配置插件:(可默认不改)

   插件配置:plugin-descriptor.properties

④ 分词器(可默认不改)

词典配置:config/IKAnalyzer.cfg.xml

⑤ 重启ES

⑥ 测试分词器

POST _analyze

{

  "analyzer":"ik_smart",

  "text":"中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"

}

注意:IK分词器有两种类型,分别是ik_smart分词器和ik_max_word分词器。

ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”。

ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合;

 

提示:也可以直接使用已集成好各种插件的elasticsearch-rtf-master.zip中文发行版,但ES版本为5.1.1。​​​​​​​

3.文档映射Mapper

ES的文档映射(mapping)机制用于进行字段类型确认,将每个字段匹配为一种确定的数据类型。

​​​​​​​(1)ES字段类型

① 基本字段类型

字符串:text,keyword

        text默认为全文文本,keyword默认为非全文文本

数字:long,integer,short,double,float

日期:date

逻辑:boolean

② 复杂数据类型

对象类型:object

数组类型:array

地理位置:geo_point,geo_shape

​​​​​​​(2)默认映射

查看索引类型的映射配置:GET {indexName}/{typeName}/_mapping

ES在没有配置Mapping的情况下新增文档,ES会尝试对字段类型进行猜测,并动态生成字段和类型的映射关系。

JSON type

Field type

Boolean: true or false

"boolean"

Whole number: 123

"long"

Floating point: 123.45

"float"

String, valid date:"2014-09-15"

"date"

String: "foo bar"

"text"

 

 

 

(3)​​​​​​​简单类型映射

字段映射的常用属性配置列表

type

类型:基本数据类型,integer,long,date,boolean,keyword,text...

enable

是否启用:默认为true。 false:不能索引、不能搜索过滤,仅在_source中存储

boost

权重提升倍数:用于查询时加权计算最终的得分。

format

格式:一般用于指定日期格式,如 yyyy-MM-dd HH:mm:ss.SSS

ignore_above

长度限制:长度大于该值的字符串将不会被索引和存储。

ignore_malformed

转换错误忽略:true代表当格式转换错误时,忽略该值,被忽略后不会被存储和索引。

include_in_all

是否将该字段值组合到_all中。

_all : 虚拟字段,每个文档都有该字段,代表所有字段的组合信息,作用方便直接对整个文档的所有信息进行搜索。

{id:1,name:zs,age:20,_all:1,zs,20}

null_value

默认控制替换值。如空字符串替换为”NULL”,空数字替换为-1

store

是否存储:默认为false。true意义不大,因为_source中已有数据

index

索引模式:analyzed (索引并分词,text默认模式), not_analyzed (索引不分词,keyword默认模式),no(不索引)

analyzer

索引分词器:索引创建时使用的分词器,如ik_smart,ik_max_word,standard

search_analyzer

搜索分词器:搜索该字段的值时,传入的查询内容的分词器。

fields

多字段索引:当对该字段需要使用多种索引模式时使用。

如:城市搜索New York

"city": {

     "type": "text",

     "analyzer": "ik_smart",

     "fields": {

            "raw": {

                "type":  "keyword"

             }

     }

 

}

那么以后搜索过滤和排序就可以使用city.raw字段名

 

① 针对单个类型的映射配置方式

POST {indexName}/{typeName}/_mapping

{

    "{typeName}": {

        "properties": {

            "id": {

                "type": "long"

            },

            "content": {

                "type": "text",

                "analyzer": "ik_smart",

                "search_analyzer": "ik_smart"

            }

        }

    }

}

注意:你可以在第一次创建索引的时候指定映射的类型。此外,你也可以晚些时候为新字段添加映射(或者为已有的类型更新映射)。

你可以向已有映射中增加字段,但你不能修改它。如果一个字段在映射中已经存在,这可能意味着那个字段的数据已经被索引。如果你改变了字段映射,那已经被索引的数据将错误并且不能被正确的搜索到。

我们可以更新一个映射来增加一个新字段,但是不能把已有字段的类型那个从 analyzed  改到 not_analyzed。

② 同时对多个类型的映射配置方式(推荐)

PUT {indexName}

{

  "mappings": {

    "user": {

      "properties": {

        "id": {

          "type": "integer"

        },

        "info": {

          "type": "text",

          "analyzer": "ik_smart",

          "search_analyzer"

        }

      }

    },

    "dept": {

      "properties": {

        "id": {

          "type": "integer"

        },

        ....更多字段映射配置

      }

    }

  }

}​​​​​​​

(4)对象及数组类型映射

① 对象的映射与索引

{

“id” : 1,

“girl” : {

    “name” : “王小花”,

    “age”  : 22

}

}

对应的mapping配置:

{

"properties": {

            "id": {"type": "long"},

            "girl": {

"properties":{

"name": {"type": "keyword"},

"age": {"type": "long"}

}

}

     }

}

注意:Lucene不理解内置对象,一个lucene文档包含键值对的一个扁平化列表,以便于ES索引内置对象,它把文档转换为类似这样:

{

    "id": 1,

    "girl.name":"王小花",

    "girl.age":26

}

内置字段与名字相关,区分两个字段中相同的名字,可以使用全路径,例如user.girl.name

② 数组与对象数组

注意:数组中元素的类型必须一致。

Java : Object[] objects = [];

Js   : var objs = [1,true,”haha”];

{

“id” : 1,

“hobby” : [“王小花”,“林志玲”]

}

对应的mapping配置是:

{

"properties": {

            "id": {"type": "long"},

            "hobby": {"type": "keyword"}

     }

}

 

对象数组的映射

{

"id" : 1,

"girl":[{"name":"林志玲","age":32},{"name":"赵丽颖","age":22}]

}

对应的映射配置为:

"properties": {

"id": {

            "type": "long"

        },

        "girl": {

            "properties": {

              "age": { "type": "long" },

              "name": { "type": "text" }

            }

        }

}

注意:同内联对象一样,对象数组也会被扁平化索引

{

    "user.girl.age":    [32, 22],

    "user.girl.name":   ["林志玲", "赵丽颖"]

}

注意:扁平化后,对象属性的相关性已经丢失,因为每个多值字段只是一个数值集,不是排序的数组。

比如查询:哪个女朋友的年龄是22岁? 这个是无法查询到答案,如果需要保留关系,需要使用嵌套对象nested objects。

​​​​​​​(5)全局映射

全局映射可以通过动态模板和默认设置两种方式实现。

默认方式:_default_

索引下所有的类型映射配置会继承_default_的配置,如:

PUT {indexName}

{

  "mappings": {

    "_default_": { 

      "_all": {

        "enabled": false

      }

    },

 

    "user": {}, 

    "dept": { 

      "_all": {

        "enabled": true

      }

    }

  }

}

上例中:user和dept都会继承_default_的配置,user类型的文档中将不会合并所有字段到_all,而dept会。

动态模板:dynamic_templates

注意:ES会默认把string类型的字段映射为text类型(默认使用标准分词器)和对应的keyword类型,如:

"name": {

     "type": "text",

     "fields": {

         "keyword": {

             "type": "keyword",

             "ignore_above": 256

          }

      }

}

在实际应用场景中,一个对象的属性中,需要全文检索的字段较少,大部分字符串不需要分词,因此,需要利用全局模板覆盖自带的默认模板:

PUT _template/global_template  //创建/修改名为global_template的模板

{

  "template":   "*",  //匹配所有索引库

  "settings": { "number_of_shards": 1 }, //匹配到的索引库只创建1个主分片

  "mappings": {

    "_default_": {

      "_all": { 

        "enabled": false //关闭所有类型的_all字段

      },

      "dynamic_templates": [

        {

          "string_as_text": {

            "match_mapping_type": "string",//匹配类型string

            "match":   "*_txt", //匹配字段名字以_txt结尾

            "mapping": {

              "type": "text",//将类型为string的字段映射为text类型

              "analyzer": "ik_max_word",

              "search_analyzer": "ik_max_word",

              "fields": {

                "raw": {

                  "type":  "keyword",

                  "ignore_above": 256

                }

              }

            }

          }

        },

        {

          "string_as_keyword": { 

            "match_mapping_type": "string",//匹配类型string

            "mapping": {

              "type": "keyword"//将类型为string的字段映射为keyword类型

             }

          }

        }

      ]

    }

  }}

说明:上例中定义了两种动态映射模板string_as_text和string_as_keyword.

在实际的类型字段映射时,会依次匹配:

①字段自定义配置、②全局dynamic_templates[string_as_text、string_as_keyword]、

③索引dynamic_templates[...]、④ES自带的类型映射。 以最先匹配上的为准。

注意:索引库在创建的时候会继承当前最新的dynamic_templates,索引库创建后,修改动态模板,无法应用到已存在的索引库。

​​​​​​​(6)最佳实践

映射的配置会影响到后续数据的索引过程,因此,在实际项目中应遵循如下顺序规则:

① 配置全局动态模板映射(覆盖默认的string映射)

② 配置自定义字段映射(由于基本类型主要用于过滤和普通查询,因此,字段映射主要对需要全文检索的字段进行配置)

③ 创建、更新和删除文档

④ 搜索

六,Java API

​​​​​​​1.什么是Java API

ES对Java提供一套操作索引库的工具包,即Java API。所有的ES操作都使用Client对象执行。

ES的Maven引入

<dependency>

    <groupId>org.elasticsearch.client</groupId>

    <artifactId>transport</artifactId>

    <version>5.2.2</version>

</dependency>

<dependency>

    <groupId>org.apache.logging.log4j</groupId>

    <artifactId>log4j-api</artifactId>

    <version>2.7</version>

</dependency>

<dependency>

    <groupId>org.apache.logging.log4j</groupId>

    <artifactId>log4j-core</artifactId>

    <version>2.7</version>

</dependency>

       <dependency>

            <groupId>junit</groupId>

            <artifactId>junit</artifactId>

            <version>4.12</version>

            <scope>test</scope>

        </dependency>

​​​​​​​2.连接ES获取Client对象

TransportClient  利用transport模块远程连接一个ES集群。它并不加入到集群中,只是简单的获得一个或者多个初始化的transport地址,并以轮询的方式与这些地址进行通信。

// on startup

TransportClient client = new PreBuiltTransportClient(Settings.EMPTY)

        .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("localhost"), 9300))

        .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("host2"), 9300));

// on shutdown

client.close();

注意,如果你有一个与 ES集群不同的集群,你可以设置机器的名字。

Settings settings = Settings.builder()

        .put("cluster.name", "myClusterName").build();

TransportClient client = new PreBuiltTransportClient(settings);

//添加地址到client中

client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("host2"), 9300));

你可以设置client.transport.sniff为true来使客户端去嗅探整个集群的状态,把集群中其它机器的ip地址加到客户端中,这样做的好处是一般你不用手动设置集群里所有集群的ip到连接客户端,它会自动帮你添加,并且自动发现新加入集群的机器。代码实例如下:

Settings settings = Settings.builder()

        .put("client.transport.sniff", true).build();

TransportClient client = new PreBuiltTransportClient(settings);

//添加地址到client中

client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("host1"), 9300));

3.​​​​​​​索引API

(1)创建文档索引

ES索引文档非常方便,只需要构建好需要索引的JSON格式数据,然后调用API:

import static org.elasticsearch.common.xcontent.XContentFactory.*;

IndexResponse response = client.prepareIndex("crm", "vip", "1")

.setSource(jsonDataText).get();

或使用ES自动ID,不提供ID值1也OK。

返回的response对象可以获取到_index,_type,_version,_id等元数据。

​​​​​​​(2)获取文档

GetResponse response = client.prepareGet("crm", "vip", "1").get();

​​​​​​​(3)更新文档

client.prepareUpdate("crm", "vip", "1").setDoc("{\"sex\":0}").get();

或client.prepareUpdate("crm", "vip", "1")

.setScript(new Script("ctx._source.sex = 1"  , ScriptService.ScriptType.INLINE,null, null))

            .get();

java API也支持使用 upsert。如果文档还不存在,会根据 upsert内容创建一个新的索引。

IndexRequest indexRequest = new IndexRequest("crm", "vip", "1")

.source(originalJsonData);

UpdateRequest updateRequest = new UpdateRequest("crm", "vip", "1")

.doc(updateJsonData).upsert(indexRequest);

client.update(updateRequest).get();

​​​​​​​(4)删除文档

DeleteResponse response = client.prepareDelete("crm", "vip", "1").get();

 

​​​​​​​(5)批量操作

BulkRequestBuilder bulkRequest = client.prepareBulk();

bulkRequest.add(client.prepareIndex("crm", "vip", "1")

.setSource(vip1JsonData));

bulkRequest.add(client.prepareIndex("crm", "vip", "2")

.setSource(vip2JsonData));

BulkResponse bulkResponse = bulkRequest.get();

if (bulkResponse.hasFailures()) {

//处理错误

}

​​​​​​​(6)搜索

ES的查询是通过执行json格式的查询条件,在java中就是构造QueryBuilder对象,ES完全支持queryDSL风格的查询方式,QueryBuilder的构建类是QueryBuilders,filters的构建需要基于BoolQuery来设置filter过滤条件。

查询示例:

SearchResponse response = client.prepareSearch("index1", "index2")

.setTypes("type1", "type2")

.setSearchType(SearchType.DFS_QUERY_THEN_FETCH)

.setQuery(QueryBuilders.termQuery("sex", "0")) //① Query and Filter

.setFrom(0).setSize(60).setExplain(true)

.get();

 

注意:新版中已取消FiltersBuilders的设置方式,统一采用BoolQueryBuilder.filter()添加

Eg. 标记①中改为过滤+查询的模式

// 查询条件

BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

//搜索条件通过must设置

List<QueryBuilder> must = boolQuery.must();

// 关键字搜索

must.add(QueryBuilders.matchQuery(title, itsource));

//过滤条件通过filter设置

// 条件过滤器

List<QueryBuilder> filters = boolQuery.filter();

filters.add(QueryBuilders.termQuery(type, 2));

SearchResponse response = client.prepareSearch().setQuery(boolQuery).get();

 

for(SearchHit hit: response.getHits().getHits()) {

System.out.println(hit.getSource());

  }

注意,所有的参数都是可选的。下面是最简洁的形式。(查询所有)

SearchResponse response = client.prepareSearch().get();

遍历结果:

for(SearchHit hit: response.getHits().getHits()) {

System.out.println(hit.getSource());

}

 

① match_all

queryBuilder = QueryBuilders.matchAllQuery();

② match

queryBuilder = QueryBuilders.matchQuery("message", "a quick brown fox")

.operator(Operator.AND);

③ 分页

SearchResponse response = client.prepareSearch("aigou")

.setTypes("product")

.setQuery(QueryBuilders.termQuery("sex", "0"))

.addSort("id",SortOrder.DESC)

.setFrom(20).setSize(10).get();

④ 排序

SearchResponse response = client.prepareSearch("crm")

.setTypes("product")

.setQuery(QueryBuilders.matchAllQuery())

.addSort("age",SortOrder.ASC)

.addSort("_score", SortOrder.DESC)

.get();

⑤ term/terms

⑥ boolean

⑦ range

⑧ highlight

HighlightBuilder highlight = new HighlightBuilder()

          .highlighterType("plain")

          .field("name")

          .preTags("<b>").postTags("</b>");

 

  SearchResponse searchResponse = client.prepareSearch("aigou")

          .setTypes("product")

          .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)

          .setQuery(QueryBuilders.termQuery("name", "连衣裙"))

          .setFrom(0).setSize(10)

          .highlighter(highlight)

          .addSort("id", SortOrder.ASC).get();

输出结果:

import org.elasticsearch.common.text.Text;

for(SearchHit hit: searchResponse.getHits().getHits()) {

  HighlightField hField = hit.getHighlightFields().get("name");

  for (Text t : hField.fragments()) {

  System.out.println(t.string());

  }

}

⑨ DSL查询

String dsl = "{}";//dsl json格式查询条件

try {

XContentBuilder builder = jsonBuilder()

.startObject()

.field("query")

.startObject()

.field("match")

.startObject()

.field("id","{{id}}")

.endObject()

.endObject()

.endObject();

dsl = builder.string();

} catch (IOException e) {}

 

SearchResponse response = new SearchTemplateRequestBuilder(client)

        .setScript(dsl)

        .setScriptType(ScriptType.INLINE)    

        .setScriptParams(params)                  

        .setRequest(new SearchRequest())                   

        .get()                                             

        .getResponse();

 

for(SearchHit hit: response.getHits().getHits()) {

System.out.println(hit.getSource());

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值