Neil Zhu,简书ID Not_GOD,University AI 创始人 & Chief Scientist,致力于推进世界人工智能化进程。制定并实施 UAI 中长期增长战略和目标,带领团队快速成长为人工智能领域最专业的力量。
作为行业领导者,他和UAI一起在2014年创建了TASA(中国最早的人工智能社团), DL Center(深度学习知识中心全球价值网络),AI growth(行业智库培训)等,为中国的人工智能人才建设输送了大量的血液和养分。此外,他还参与或者举办过各类国际性的人工智能峰会和活动,产生了巨大的影响力,书写了60万字的人工智能精品技术内容,生产翻译了全球第一本深度学习入门书《神经网络与深度学习》,生产的内容被大量的专业垂直公众号和媒体转载与连载。曾经受邀为国内顶尖大学制定人工智能学习规划和教授人工智能前沿课程,均受学生和老师好评。
探索elasticsearch
by Andrew Cholakian
相比较官方文档,这本书很好看。
第二章 建模数据
2.1 文档和字段的基础知识
在elasticsearch中的最小独立单元是字段(field),字段有一个已定义的类型(type),并且拥有一个或者多个该类型的值。字段包含一个简单的数据块,如数字42
或者字符串"Hello, World!"
,亦或一个同样类型数据的简单表(single list),如[5,6,7,8]
。
文档(document)是字段的聚合(collection),并且包含了elasticsearch中的基本存储单元,就像在传统的RDBMS中的行(row)那样。将文档作为一个基本的存储单元的原因是,与Lucene不同的是,所有完全更新的字段重写一个给定的文档进存储设备(同时保留哪些没有变更的字段)。所以,当我们从一个API角度看,字段是最小的单元,而文档则是从存储角度的最小单元。
2.1.1 JSON——ES的语言
在ES中,所有的文档必须为合法的JSON值。例如
{
"_id": 1,
"handle": "ron",
"age": 28,
"hobbies": ["hacking", "the great outdoors"],
"computer": {"cpu": "pentium pro", "mhz": 200}
}
Elasticsearch reserves some fields for special use. We’ve specified one of these fields in this example: the _id
field. A document’s id is unique, and if unassigned will be created automatically. An elasticsearch id would be a primary key in RDBMS parlance.
While elasticsearch deals with JSON exclusively, internally, the JSON is converted to flat fields for Lucene’s key/value API. Arrays in documents are mapped to Lucene multi-values.
2.2 基本类型
2.2.1 概述
每个ES中的文档肯定要遵循一个用户定义的类型映射,类似于数据库的模式(schema)。一个类型的映射同时包含了字段的类型(integer、string……)和这些属性将被如何索引的方式(这较复杂的部分在后面会介绍)。
多个在一个索引的文档可能拥有相同的ID只要这些文档的类型是不同的。
类型使用Mapping API定义,其将type name和property定义进行关联。图2.1最小版本的映射如下:
{
"user": {
"properties": {
"handle": {"type": "string"},
"age": {"type": "integer"},
"hobbies": {"type": "string"},
"computer": {
"properties": {
"cpu": {"type": "string"},
"speed": {"type": "integer"}}}}}}
这里相当于数据库模式的定义
2.2.2 数据类型
在一个mapping的属性部分,每个字段都与一个不同的core类型关联。合法的core数据类型如下表所示。除了关联数据类型到字段外,类型映射定义了诸如analysis设定和默认的boosting值的属性;后续章节会介绍。
Type | Definition |
---|---|
string | Text |
integer | 32 bit integers |
long | 64 bit integers |
float | IEEE float |
double | Double precision floats |
boolean | true or false |
date | UTC Date/Time (JodaTime) |
geo_point | Latitude / Longitude |
2.2.3 数组,对象和高级类型
复杂JSON类型同样被ES支持,使用数组和对象表示。另外的,ES文档可以处理更加复杂的关系,诸如parent/child关系,和一个嵌套的闻到那个类型。
注意ES中数组不支持混合类型的元素。如果一个field被声明为一个整数,它可以存储一个或者多个整数,不过不能参杂其他类型的元素。
An important thing to remember, however, is that elasticsearch arrays cannot store mixed types. If a field is declared as an integer, it can store one or many integers, but never a mix of types.
As seen in figure 2.2, mappings may describe objects which contain other objects, as in the computer field in that figure. The key thing to remember with these objects is that their properties are declared in the properties field in the mapping, and that the type field is omitted.
当使用对象的时候,type字段被省略。
子对象仍然存储在同样的物理文档中。所以,还有一个特殊的nested
类型,尽管相似,但是拥有不同的性能和查询特征,因为被分别存储在不同的文档中。(也就是不同的记录row中)。后面将介绍parent/child文档。
2.3 索引的基础
在ES中最大的独立的数据单元是index
。索引是文档的逻辑和物理划分(partition)。文档和文档类型对于index来说是唯一的。索引不知道包含在其他索引中的数据。从可操作角度看,需要性能和持久性(durability)相关的选项(option)被设置为per-index层面。
从查询的角度,当ES支持交叉索引搜索时,通常in practice it usually makes more organizational sense to design for searches against individual indexes.
ES索引是在一个独立的运行时服务器实例一个完全划分的universe。文档和类型映射是以index范围内进行的。所以跨索引重用名称和id是安全的。索引同样拥有供集群复制,sharding,定制文本分析和其他考虑的本身的设置。
ES的索引不是与Lucene索引一一对应的。实际上,他们是一个Lucene索引的集合(通常设为5)对shard有一个备份。一个单一的机器可能相比较其他的机器对同一个索引有或多或少的shards。ES尽可能地保持所有索引的全部数据在所有的机器上都一致,即使那意味着某些索引可能会在某台机器上不成比例地出现。每个shard拥有一个可配置数目地全replica,这总是存储在唯一地实例上。如果cluster不足够大来支撑指定数量地replica,cluster health会被报告为恶化的(‘yellow’)状态。基本的ES开发安装因此在使用默认索引运行。一个单一运行的实例没有peer来复制其数据。注意这对于开发过程没有实际的影响。然后,建议大家ES在产品环境中运行在多个服务器上。正如一个clustered数据库,数据的保证依赖于多个节点的可用上。
我们在下个section:Basic CRUD介绍基本使用index的操作命令
2.4 CRUD基础
Let’s perform some basic operations on data. Elasticsearch is RESTish in design and tends to match HTTP verbs up to the Create, Read, Update, and Delete operations that are fundamental to most databases. We’ll create an index, then a type, and finally a document within that index using that type. Open up elastic-hammer, and we’ll issue the following operations from figure 2.3.
F 2.3 Simple CRUD
// Create a type called 'hacker'
PUT /planet/hacker/_mapping
{
"hacker": {
"properties": {
"handle": {"type": "string"},
"age": {"type": "long"}}}}
// Create a document
PUT /planet/hacker/1
{"handle": "jean-michel", "age": 18}
// Retrieve the document
GET /planet/hacker/1
// Update the document's age field
POST /planet/hacker/1/_update
{"doc": {"age": 19}}
// Delete the document
DELETE /planet/hacker/1
fig-simplecrud
// Create an index named 'planet'
PUT /planet
在上面的例子中,我们可以看到ES全部的CRUD生命周期。现在我们可以对数据执行一些基本的操作。现在就到了最为关键的地方了——搜索。注意URL模式与这些操作是一致的,大多数的URL形如/index/type/docid
,使用一个下划线前缀作为特定操作的名字空间。
database ACID
- atomicity;
- consistency;
- isolation;
- durability:
* if a flight booking reports that a seat has successfully been booked, then the seat will remain booked even if the system crashes. In distributed transactions, all participating servers must coordinate before commit can be acknowledged. This is usually done by a two-phase commit protocol.
* Many DBMSs implement durability by writing transactions into a transaction log that can be reprocessed to recreate the system state right before any later failure. A transaction is deemed committed only after it is entered in the log.
shard
shard是Lucene的独立的实例,这个工作单元是由elasticsearch自动管理的。一个index是一个逻辑的名字空间,其指向主从shards。但是我们不需要关注一个索引如何定义主从shard的数量,不会直接接触到shard,只需要直接处理index。ES将shards分发给cluster中所有的节点。,并且可以在node失败时或者新增nodes时自动地将shards从一个node移向另外一个。
第三章 搜索数据
3.1 搜索API
搜索API通常使用/index
和index/type
后接_search
作为路径。索引搜索可能是/myidx/_search,而对独立文档类型的范围搜索可以是/myidx/mytype/_search
。搜索API的作用是调用一个查询,其参数可以是结果集合的最大数,结果偏移位置,和一些性能指标选项。搜索API同样提供Faceting和过滤(Filtering),这些在后面会介绍到。
下面展示了搜索API的骨架。例子中只有size和query参数被设置,没有facet或者filter被应用。例子很简单。_search
后缀可以使用GET
和POST
HTTP方法。
Simple Query
{
"size": 3,
"query": {
"match": {"hobbies": "skateboard"}
}}
Query DSL用来确定哪些文档匹配了指定的限定。同样对文档进行排序,通过他们的相似度来做排序的基准,这在Lucene中是标准的技术术语。相似度度值通常被称文档的打分(score)。The Query DSL is employed as the contents of either the query key in JSON posted to the _search endpoint as in the example above.
3.1.1 Mixing查询、过滤器和Facets
- http://www.slideshare.net/medcl/elastic-search-training1-brief-tutorial#btnLast
- http://www.slideshare.net/endless_yy/ss-27849480
transportclient, node client
3.2 分析
3.2.1 文本分析
分析自然是最为重要的和神奇的特性了,可以处理自然语言及复杂数据。
下面出现的问题,就是当数据存储在文档中时,知道哪里和什么时候使用分析,然后在每次查询过来时,根据那个字段的分析规则进行匹配。文档进行分析过程如下:
- 文档的更新和创建使用
PUT
或者POST
- 文档中字段的值通过一个分析器后被转化成零个、1个或者多个可索引的token
- 被token化的值存放在索引中个,指向了这个文档的全文
3.2.2 分析API
The easiest way to see analysis in action is with the Analyze API, which lets you test pieces of text against any analyzer. To test the words “candles” and “candle” for instance, against a snowball analyzer, you would issue the query in Figure 3.6.
使用分析API是看分析过程的最佳方法,你可以对分析器测试各种文档输入。例如测试词“candles”和“candle”,使用一个snowball分析器:
F 3.6 使用分析API
GET '/_analyze?analyzer=snowball&text=candles%20candle&pretty=true'
在这种情形下,我们可以分析“candles candle”来展示两个相似的词是如何被分析的。你会得到下面的结果:
F 3.7 Analysis API Output
{
"tokens" : [ {
"token" : "candl",
"start_offset" : 0,
"end_offset" : 7,
"type" : "<ALPHANUM>",
"position" : 1
}, {
"token" : "candl",
"start_offset" : 8,
"end_offset" : 14,
"type" : "<ALPHANUM>",
"position" : 2
} ]
}
分析API在试着区分为何一些词完全一样的token化还有其他没有的时候是最有价值的。如果你被不知道如何解决没有匹配的查询难住,首先你可以使用分析API来试试一些文本
定制分析器
分析器是典型的三步走:
- Character Filtering: 将输入字符串转化为一个不同的字符串
- Tokenization: 将char过滤后的字符串转化为一个token的数组
- Token Filtering: 将过滤过的token转化为一个mutated token数组
3.3 基于相似度的排名
现在我们走入了ES的核心部分。搜索是两大步:
- 匹配所有满足要求的文档。这是IR领域的布尔搜索,因为这步只是存在与否的的衡量。
- 第二步则是基于与查询的相似度进行打分,然后以打分的倒序展示结果。
有多个可配置的相似度算法。本章介绍默认的TFIDF相似度类。这是相当著名的打分算法,在Lucene的TFIDFSimilarity
类中实现。
基本思想:
文档的分数高当:
- 被匹配的term是“罕见的”,指这个term比其他term在更少的文档中出现。
- term以一个很高的频率出现在该文档中
- 如果多个term在查询中,该文档比其他文档包含更多的查询term
- 字段field或文档在索引时指定了boost因子或者查询时间(query time)。
上面的列表是极其简化的相似度计算的思想。请牢记的是,所有的打分因子之间关系不都是线性的,还有一些比较微妙的地方。然而,大多数时间,Lucene已经做了你想要做的,而不要你自己计算TF/IDF。而且,ES支持可配置的相似度算法,如BM25算法。如果给你的算法包对你作用不大,你自己可以写一个script
查询,这样可以根据自己的想法对文档进行打分,或者写一个实现你自己的打分算法的java插件。
一般来说,TF/IDF、显式的排序(如按日期降序)、和脚本查询,将给你很好的效果。
分面
3.4.1 什么是分面(Faceting)
聚合统计是ES的一个核心部分,可以通过Search API进行调用。分面(facet)总是跟一个查询一起,让我们返回正常查询结果和聚合统计。想象一个用户使用电影名来查询电影。使用分面搜索你可以给出结果中不同类属的聚合数量。如果你曾经在一个电商网站上进行过搜索,你可能会在sidebar处看到一个下拉选项。
分面是高度可定制的,同样也是可以复合的。除了统计不同字段的值外,分面可以使用更加复杂的群组特性,例如时间跨度、嵌套过滤器、甚至全面、嵌套的elasticsearch查询。
Filtering
filter对搜索的执行path有着明显的影响。当查询给出哪些文档出现在结果中,以及他们如何被打分,过滤器只会给出哪些文档出现在结果中。这个可以获得一个极其快速的查询。另外,一些限制可以通过过滤器指定。过滤器可以将结果集进行切分,且不需要执行代价昂贵的打分计算。过滤器也可以用来,当一个term必须要匹配但是其对文档整体分数的影响应该是一个确定的量而不管TF/IDF分值。最后,不同与查询的是,filters可以被缓存,当filter被重复使用时,导致了一个明显的性能提升。
elasticsearch有三种filter出现方式。控制filter应用在query和factes上,查询或者自查询,或者facet。下面列表说明
- Queries of the filtered/constant_score类型:这些都内嵌在query字段中,filters将影响查询结果和facet计数。
- top-level filter element: 确定一个filter在搜索的根,只会过滤查询,但不会影响facet
- facets with facet_filter选项:给每个facet添加可选的facet_filter元素,这个可以用来在数据被聚合之前预先过滤数据。这个过滤器将只会影响定义在其里面的facet,不会影响查询结果。
3.5.1 过滤的三种不同过程
使用filtered和constant_score_quries
这两种类型均允许嵌套一个regular查询。过滤器先运行,然后查询和任何的的facets,如果filter足够强地限制了结果集合,这将千载地提供了一个有效的安全的速度提升。
// Load Dataset: products.eloader
POST /products/_search
{
"facets": {"department": {"terms": {"field": "department_name"}}},
"query": {
"filtered": {
"query": {"match": {"name": "fake"}},
"filter": {"term": {"department_name": "Books"}}}}}
网友评论