浅谈ES技术与实践

2,691 阅读17分钟
原文链接: mp.weixin.qq.com

    ES是基于Lucene的分布式存储.Lucene提供了全文检索的功能,ES在此之上加入索引分布式的机制,提供了数据分片,数据副本,数据同步等功能,保证数据的安全性

    ES存储的基本单位是Document,相当于数据库中的一条记录,一个Doc包括多个Field(相当于字段),Doc可以随时添加或者删除字段

    另外,ES对于需要检索的字段可以生命为需要索引,这样就可以很方便的对Document进行检索

API接口:

目前支持:

1.原生ES客户端访问(速度较快)ES 1.7版本 要求JDK 1.7或以上版本,从2016-01-20起,再发邮件申请的ES集群为2.1.0版本。

2.HTTP方式访问(速度较慢);

索引文档

    一个文档的_index,_type和_id唯一标识一个文档,我们可以提供自定义的id,或者让index自动生成

指定ID:

PUT /website/blog/123

自动生成ID:

POST /{index}/{type}/{id}

       在ElasticSearch中每一个文档都有一个版本号,每次对文档进行修改时(包括删除),_version的值会递增,

搜索一个文档

GET/website/blog/123?pretty

搜索文档的一部分

GET/website/blog/123?_source=title,text

只获取_source字段,不需要任何元数据

GET /website/blog/123/_source

    如果只想检查一个文档是否存在 --根本不想关心内容--那么用 HEAD 方法来代替 GET 方法。 HEAD 请求没有返回体,只返回一个 HTTP 请求报头:

curl -i -XHEAD http://localhost:9200/website/blog/123

如果文档存在, Elasticsearch 将返回一个 200 ok 的状态码:

若文档不存在, Elasticsearch 将返回一个 404 Not Found 的状态码:

更新整个文档:

    在内部,Elasticsearch 已将旧文档标记为已删除,并增加一个全新的文档。 尽管你不能再对旧版本的文档进行访问,但它并不会立即消失.实际上ES按前述完全相同的方式执行了以下过程:

  1. 从旧文档构建了JSON

  2. 更改了JSON

  3. 删除了旧文档

  4. 索引一个新的文档

唯一的区别在于, update API 仅仅通过一个客户端请求来实现这些步骤,而不需要单独的 get 和 index 请求。

创建新的文档

确认创建的是一个新的文档(不能覆盖现有的文档)

    确保创建一个新文档的最简单办法是,使用索引请求的 POST 形式让 Elasticsearch 自动生成唯一 _id 

    如果已经有了ID,那么久必须告诉ES只有在相同的_index和_type和_id不存在的时候才能接受我们的索引请求.两种方式:

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

  1. 是在 URL 末端使用 /_create :

    如果创建新文档的请求执行成功,ES会返回元数据的一个201 Created的HTTP响应吗.另一方面,如果具有相同的文档,ES将会返回409 Conflict响应码

删除文档

DELETE /website/blog/123

    如果找到该文档,Elasticsearch 将要返回一个 200 ok 的 HTTP 响应码,和一个类似以下结构的响应体。注意,字段 _version 值会增加

如果文档没有 找到,我们将得到 404 Not Found 的响应码和类似这样的响应体:

_version仍然会增加

处理冲突

    无论最后哪一个文档被索引,都将被唯一存储在 Elasticsearch 中。如果其他人同时更改这个文档,他们的更改将丢失。虽然两人同时更改相同的文档几率很小.或者对于我们的业务来说偶尔丢失并不是很严重的问题.但是有时丢失一个变更时很严重的(限时抢购,促销之类的)

悲观并发控制:

    这种方法在关系型数据库广泛使用,假定变更冲突可能发生,因此阻塞访问防止冲突.典型的例子就是读取一行数据之前现将其锁住,确保只有放置锁的线程能够对这行数据进行修改

乐观并发控制:

    ES中使用这种方法假定变更冲突不可能发生,并且不会阻塞正在尝试的操作.然而,如果源数据在读写当中被修改,更新将会失败.应用程序接下来将决定该如何解决冲突.可以重试更新,使用新数据或者将情况报告给用户

应用:

    利用 _version 号来确保 应用中相互冲突的变更不会导致数据丢失。我们通过指定想要修改文档的 version 号来达到这个目的。 如果该版本不是当前版本号,我们的请求将会失败。

文档的部分更新

1.他们不能被修改,只能被替换。 update API 必须遵循同样的规则。 从外部来看,我们在一个文档的某个位置进行部分更新。然而在内部, update API 简单使用与之前描述相同的 检索-修改-重建索引 的处理过程。 区别在于这个过程发生在分片内部,这样就避免了多次请求的网络开销。通过减少检索和重建索引步骤之间的时间,我们也减少了其他进程的变更带来冲突的可能性。

POST /website/blog/1/_update

{

  "doc" : {

     "tags" : [ "testing" ],

     "views": 0

  }

}

2,用Groovy脚本编程

分布式的文档存储

文档存储算法

shard=hash(routing) % number_of_primary_shards

routing 是一个可变值,默认是文档的 _id ,也可以设置成一个自定义的值。 routing 通过 hash 函数生成一个数字,然后这个数字再除以 number_of_primary_shards (主分片的数量)后得到 余数 。这个分布在 0 到 number_of_primary_shards-1 之间的余数,就是我们所寻求的文档所在分片的位置。

主分片和副分片如何交互

相同分片的副本不会放在同一节点,

我们可以发送请求到集群中的任一节点。 每个节点都有能力处理任意请求。 每个节点都知道集群中任一文档位置,所以可以直接将请求转发到需要的节点上。 在下面的例子中,将所有的请求发送到 Node 1 ,我们将其称为 协调节点(coordinating node) 。

搜索

空搜索

搜索API的最基础的形式是没有指定任何查询的空搜索 ,它简单地返回集群中所有索引下的所有文档:

GET /_search

        返回的结果中最重要的是hits,他包含了total字段来表示匹配的文档总数,并且一个hits数组锁查询结果的前十个文档

在hits数组中每个结果包含文档的_index,_type,_id加上_source字段.这意味着我们可以直接从返回的搜索结果中使用整个文档.不像其他的搜索引擎,仅仅返回文档的ID,需要你去获取文档

    每个结果还有一个_source,他衡量了文档与查询的匹配程度.默认情况下,首先返回最相关的文档结果,就是说,返回文档是按照_score降序排列的.

took 值告诉我们执行整个搜索请求耗费了多少毫秒。

timed_out 值告诉我们查询是否超时。默认情况下,搜索请求不会超时。 如果低响应时间比完成结果更重要,你可以指定 timeout 为10或者 10ms(10毫秒),或者 1s(1秒)

GET /_search?timeout=10ms

    应当注意的是 timeout 不是停止执行查询,它仅仅是告知正在协调的节点返回到目前为止收集的结果并且关闭连接。在后台,其他的分片可能仍在执行查询即使是结果已经被发送了。使用超时是因为 SLA(服务等级协议)对你是很重要的,而不是因为想去中止长时间运行的查询。

多索引搜索

    我们可以通过在URL中指定特殊的索引和类型达到这种效果,如下所示:

/_search在所有的索引中搜索所有的类型

/gb/_search在 gb 索引中搜索所有的类型

/gb,us/_search在 gb 和 us 索引中搜索所有的文档

/g*,u*/_search在任何以 g 或者 u 开头的索引中搜索所有的类型

/gb/user/_search在 gb 索引中搜索 user 类型

/gb,us/user,tweet/_search在 gb 和 us 索引中搜索 user 和 tweet 类型

/_all/user,tweet/_search在所有的索引中搜索 user 和 tweet 类型

分页:

和SQL使用 LIMIT 关键字返回单个page结果的方法相同,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

轻量搜索:

查询字符串搜索非常适用于通过命令行做即席查询。例如,查询在 tweet 类型中 tweet 字段包含 elasticsearch 单词的所有文档:

GET/_all/tweet/_search?q=tweet:elasticsearch

_all这个简单搜索返回包含 mary 的所有文档:

GET/_search?q=mary

在刚开始开发一个应用时,_all 字段是一个很实用的特性。之后,你会发现如果搜索时用指定字段来代替 _all 字段,将会更好控制搜索结果。当 _all 字段不再有用的时候,可以将它置为失效

精确值

Elasticsearch 中的数据可以概括的分为两类:精确值和全文。

对于精确值来讲,Foo 和 foo 是不同的,2014 和 2014-09-15 也是不同的。

精确值很容易查询。结果是二进制的:要么匹配查询,要么不匹配。

我们很少对全文类型的域做精确匹配。相反,我们希望在文本类型的域中搜索。不仅如此,我们还希望搜索能够理解我们的 意图 :

  • 搜索 UK ,会返回包含 United Kindom 的文档。

  • 搜索 jump ,会匹配 jumped , jumps , jumping ,甚至是 leap 。

  • 搜索 johnny walker 会匹配 Johnnie Walker , johnnie depp 应该匹配 Johnny Depp 。

  • fox news hunting 应该返回福克斯新闻(Foxs News )中关于狩猎的故事,同时, fox hunting news 应该返回关于猎狐的故事。

为了促进这类在全文域中的查询,Elasticsearch 首先 分析 文档,之后根据结果创建 倒排

索引 。

什么时候使用分词器:

1.当你查询一个 全文 域时, 会对查询字符串应用相同的分析器,以产生正确的搜索词条列表。

2.当你查询一个 精确值 域时,不会分析查询字符串,而是搜索你指定的精确值。

3.date 域包含一个精确值:单独的词条 `2014-09-15`。

4._all 域是一个全文域,所以分词进程将日期转化为三个词条: `2014`, `09`,,和 `15`。

当我们在 _all 域查询 2014`,它匹配所有的12条推文,因为它们都含有 `2014 :

最重要的查询:match_all查询

match_all 查询简单的 匹配所有文档。在没有指定查询方式时,它是默认的查询

match查询

无论你在任何字段上进行的是全文搜索还是精确查询,match 查询是你可用的标准查询。

如果你在一个全文字段上使用 match 查询,在执行查询前,它将用正确的分析器去分析查询字符串:

如果你在一个全文字段上使用 match 查询,在执行查询前,它将用正确的分析器去分析查询字符串:

{ "match": { "tweet": "About Search" }}

如果在一个精确值的字段上使用它, 例如数字`日期,布尔或者一个 not_analyzed 字符串字段,那么它将会精确匹配给定的值:

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

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

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

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

Multi_match查询

multi_match 查询可以在多个字段上执行相同的 match 查询

{    "multi_match": {        "query": "full text search",        "fields": [            "title",            "body"        ]    }

}

Range查询

range 查询找出那些落在指定区间内的数字或者时间:

{

        "range": {

        "age": {

        "gte":  20,

        "lt":   30

        }

    }

}

被允许的操作符如下:

gt大于

gte大于等于

lt小于

lte小于等于

term查询

term 查询被用于精确值 匹配,这些精确值可能是数字、时间、布尔或者那些 not_analyzed 的字符串

{ "term": { "age":    26           }}

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

{ "term": { "public": true         }}

{ "term": { "tag":    "full_text"  }}

Terms查询

terms 查询和 term 查询一样,但它允许你指定多值进行匹配。如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件:

{ "terms": { "tag": [ "search", "full_text", "nosql" ] }}

和 term 查询一样,terms 查询对于输入的文本不分析.它查询那些精确匹配的值(包括在大小写、重音、空格等方面的差异).

Exists查询和missing查询

exists 查询和 missing 查询被用于查找那些指定字段中有值 (exists) 或无值 (missing) 的文档。这与SQL中的 IS_NULL (missing) 和 NOT IS_NULL (exists) 在本质上具有共性:

{    "exists": {        "field": "title"    }

}

组合多查询

Boolquery

这种查询将多查询组合在一起,成为用户自己想要的布尔查询.它接收以下参数:

must文档 必须 匹配这些条件才能被包含进来。

must_not文档 必须不 匹配这些条件才能被包含进来。

should如果满足这些语句中的任意语句,将增加 _score ,否则,无任何影响.它们主要用于修正每个文档的相关性得分.

filter必须 匹配,但它以不评分,过滤模式来进行.这些语句对评分没有贡献,只是根据过滤标准来排除或包含文档。

{    "bool": {        "must": {            "match": {                "title": "how to make millions"            }        },        "must_not": {            "match": {                "tag": "spam"            }        },        "should": [            {                "match": {                    "tag": "starred"                }            },            {                "range": {                    "date": {                        "gte": "2014-01-01"                    }                }            }        ]    }

}

如果没有 must 语句,那么至少需要能够匹配其中的一条 should 语句。但,如果存在至少一条 must 语句,则对 should 语句的匹配没有要求。

Filtering:增加带过滤器的查询

如果我们不想因为文档的时间而影响得分,可以用 filter 语句来重写前面的例子:

{    "bool": {        "must": {            "match": {                "title": "how to make millions"            }        },        "must_not": {            "match": {                "tag": "spam"            }        },        "should": [            {                "match": {                    "tag": "starred"                }            }        ],        "filter": {            "range": {                "date": {                    "gte": "2014-01-01"                }            }        }    }

}

    通过将 range 查询移到 filter 语句中,我们将它转成不评分的查询,将不再影响文档的相关性排名。由于它现在是一个不评分的查询,可以使用各种对 filter 查询有效的优化手段来提升性能。

    所有查询都可以借鉴这种方式。将查询移到 bool 查询的 filter 语句中,这样它就自动的转成一个不评分的 filter 了。

    如果你需要通过多个不同的标准来过滤你的文档,bool 查询本身也可以被用做不评分的查询。简单地将它放置到 filter 语句中并在内部构建布尔逻辑:

{    "bool": {        "must": {            "match": {                "title": "how to make millions"            }        },        "must_not": {            "match": {                "tag": "spam"            }        },        "should": [            {                "match": {                    "tag": "starred"                }            }        ],        "filter": {            "bool": {                "must": [                    {                        "range": {                            "date": {                                "gte": "2014-01-01"                            }                        }                    },                    {                        "range": {                            "price": {                                "lte": 29.99                            }                        }                    }                ],                "must_not": [                    {                        "term": {                            "category": "ebooks"                        }                    }                ]            }        }    }

}

Constant_score查询

它将一个不变的常量评分应用于所有匹配的文档。它被经常用于你只需要执行一个 filter 而没有其它查询(例如,评分查询)的情况下。

可以使用它来取代只有 filter 语句的 bool 查询。在性能上是完全相同的,但对于提高查询简洁性和清晰度有很大帮助。

{    "constant_score": {        "filter": {            "term": {                "category": "ebooks"            }        }    }

}

term 查询被放置在 constant_score 中,转成不评分的 filter。这种方式可以用来取代只有 filter 语句的 bool 查询。

ES与solr区别:

http://blog.csdn.net/AlvinNoending/article/details/45534177

https://www.cnblogs.com/huajiezh/p/6033656.html