elasticsearch 中文
运行elasticsearch
network.host:0.0.0.0
curl ‘http://localhost:9200/?pretty’
集群和节点
节点(node)是一个运行着的elasticsearch实例。集群(cluster)是一组具有相同cluster.name的节点集合,他们协同工作,共享数据并提供故障转移和扩展功能,当然一个及诶单也可以组成一个集群。
java api
节点客户端
节点客户端以无数据节点加入集群,它自己不存储任何数据,但是它知道数据在集群中的具体位置。
传输客户端
这个更轻量的传输客户端能够发送请求到远程集群。它自己不加入集群,只是简单转发请求给集群中的节点。
9300此端口未开放,你的节点将不能组成集群。
VERB HTTP:GET,POST,PUT,HEAD,DELETE
简单搜索
GET /megacorp/employee/_search
搜索全部员工的请求
GET /megacorp/employee/_search?q=last_name:Smith
使用DSL语句查询
elasticsearch提供丰富且灵活的查询语句叫做DSL查询(Query DSL)
GET /megacorp/employee/_search
{
“query”:{
“match”:{
“last_name”:”Smih”
}
}
}
更复杂的搜索
GET /megocorp/employee/_search
{
“query”:{
“filtered”:{
“filter”:{
“range”:{
“age”:{“gt”:30}
}
},
“query”:{
“match”:{
“last_name”:”smith”
}
}
}
}
}
全文搜索
GET /megacorp/employee/_search
{
“query”:{
“match”:{
“about”:”rock climbing”
}
}
}
短语搜索
GET /megacorp/employee/_search
{
“query”:{
“match_phrase”:{
“about”:”rock climbing”
}
}
}
高亮我们的搜索
GET /megacorp/employee/_search
{
“query”:{
“match_phrase”:{
“about”:”rock climbing”
}
},
“highlight”:{
“fields”:{
“about”:{}
}
}
}
分析
我们还有一个需求需要完成:允许管理者在职员目录中进行一些分析。elasticsearch有一个功能叫做聚合,它允许你在数据上面生产复杂的分析统计。它就像sql中的group by 但是功能更强大。
让我们找到所有职员中最大的共同点(兴趣爱好)是什么:
GET /megacorp/employee/_search
{
“aggs”:{
“all_interests”:{
“terms”:{“field”:”interests”}
}
}
}
聚合也允许分级汇总。例如,让我们统计每种兴趣下职员的平均年龄:
GET /megacorp/employee/_search
{
“aggs”:{
“all_interests”:{
“iterms”:{“field”:”interests”},
“aggs”:{
“avg_age”:{
“avg”:{“filed”:”age”}
}
}
}
}
}
分布式的特性
elasticsearch致力于隐藏分布式系统的复杂性:
- 将你的文档分区到不同的容器或者分片(shards)中,它们可以存在于一个或多个节点中
- 将分片均匀的分配到各个及诶点,对索引和搜索做负载均衡
- 用于每一个分片,防止硬件故障造成的数据丢失。
- 将集群中任意一个及诶点上的请求路由到相应数据所在的节点
- 无论是增加点,还是移除节点,分片都可以做到无缝的扩展和迁移
集群中一个及诶单会被选举为主节点,它将临时管理集群级别的一些变更,例如新建或删除索引、增加或移除节点等。主及诶单不存于文档级别的变更和搜索,者以为这在流量增长的时候,该主及诶点不会成为集群的瓶颈。任何节点都可以成为主节点。
每一个节点都知道文档存在于哪个节点上,我们可以转发请求到某一个节点上。我们访问的节点负责收集各节点返回的数据,最后一起返回给客户端。
集群健康
GET /_cluster/health
添加索引
索引只是一个用来指向一个或多个分片的逻辑命名空间
一个分片是一个最小级别工作单元,它只是保存了索引中所有数据的一部分。现在我们只要知道分片就是一个luence实例,并且它本身就是一个完整的搜索引擎。我们的文档存储在分片中,并且在分片中被索引,但是我们的应用程序不会直接与它们通信,取而代之的是,直接与索引通信。
分片可以是主分片或者是复制分片。你索引中的每个文档属于一个单独的主分片,所以主分片的数据决定了索引最多能存储多少数据。
复制分片只是主分片的一个副本,它可以防止硬件故障导致的数据丢失,同时可以提供读请求.
当索引创建完成的时候,主分片的数量就固定了,但是复制分片的数量可以随时调整。
默认情况下,一个索引被分片5个主分片,但是为了演示的目的,我们只分配3个主分片和一个复制分片(每个主分片都有一个复制分片)
集群的健康状态yellow表示所有的主分片启动而且正常运行了–集群已经可以正常处理任何请求–但是集群分片还没有全部可用。事实上所有的是哪个复制分片现在都是unassigned状态–它们还未被分配给节点。
增加故障转移
继续扩展
GET /blogs/_settings{
“number_of_replicas”:2
}
应对故障
一个集群必须要有一个主节点才鞥呢使其功能正常,所以集群做的第一件事情就是各节点选举一个新的主节点:node-2
幸运的是丢失的两个主分片的完整拷贝存在于其他节点上,所以新主节点做的第一件事是把这些在node2和node3上复制分片升级为主分片。
数据吞吐
什么是文档?
程序中大多的实体或对象能够被序列化为包含键值对的json对象,键是字段或属性的名字,值可以是字符串、数字、布尔类型、另一个对象、值数组或者其他特殊类型。
在elasticsearch中,文档这个术语有着特殊含义。它特指最顶层结构或者根对象序列化成的json数据。
文档元数据
它还包含元数据–关于文档的信息:_index:文档存储的地方,_type:文档代表的对象的类,_id 文档的唯一标识
索引一个文档
使用自己的id
put /website/blog/123
{
“title”:”My first blog entry”,
“text”:”Just trying this out..”,
“date”:”2014/01/01”
}
检索文档
GET /website/blog/123?pretty
检索文档的一部分
GET /website/blog/123?_source=title,text
GET /wensite/blog/123/_source
更新整个文档
elasticsearch 将文档中_version 增加了
创建一个新文档
put /website/blog/123?op_type=create{}
put /website/blog/123/_create{}
conflict 会有一个409的错误 code
处理冲突
悲观并发控制
乐观并发控制
文档局部更新
最简单的update请求表单接收一个局部文档参数doc,它会合并到现有文档中–对象合并在一起,存在的标量字段被覆盖,新字段被添加。
post /website/blog/1/_update
{
“doc”:{
“tags”:[“testing”],
“views”:0
}
}
我们的新添加的字段已经被添加到_source字段中
使用脚本局部刷新
我们可以使用脚本增加博客的views数量
post /website/blog/1/_update
{
“script”:”ctx._source.views+=1”
}
我们还可以使用脚本增加一个新标签到tags数组中。
post /website/blog/1/_update
{
“script”:”ctx._source.tags+=new_tag”,
“params”:{
“new_tag”:”searhc”
}
}
更新可能不存在的文档
我们可以使用upsert参数定义文档来使其不存在时被创建
post /website/pageviews/1/_update
{
“script”:”ctx._source.views+=1”,
“upsert”:{
“views”:1
}
}
更新和冲突
这些可以通过retry_on_conflict参数设置重试次数来自动完成,这样update操作将会在发生错误前重试–这个值默认为0.
post /website/pageviews/1/_update?retry_on_conflict=5<1>
{
“script”:”ctx._souce.views+=1”,
“upsert”:{
“views”:0
}
}
检索多个文档
相对于一个一个的检索,更快的方式是在一个请求中使用multi-get或者mget API。
mget API参数是一个doc数组,数组的每个节点定义一个文档的_index,_type,_id元数据。如果你只想检索一个或几个确定的字段,也可以定义一个_source参数。
POST /_mget
{
“docs”:[
{
“_index”:”website”,
“_type”:”blog”,
“_id”:2
},
{
“_index”:”website”,
“_type”:”pageviews”,
“_id”:1,
“_source”:”views”
}
]
}
如果所有文档具有相同_index和_type,你可以通过简单的ids数组来代替完整的docs数组:
POST /website/blog/_mget
{
“ids”:[“2”,”1”]
}
事实上第二个文档不存在并不影响第一个文档的检索。每个文档的检索和报告都是独立的。
更新时的批量操作
就像mget允许我们一次性检索多个文档一样,bulkAPI允许我们使用单一请求来实现多个文档的create,index,update或delete。这对索引类似于日志活动这样的数据流非常有用,它们可以成百上千的数据为一个批次按序进行索引。
{action:{metadata}}\n
{request body}\n
{action:{metadata}}\n
{request body}\n
* 每行必须以“\n”符号结尾,包括最后一行。这些都是作为每行有效的分离而做的标记。
* 每一行的数据不能包含未被转移的换行符,它们会干扰分析–这意味着json不能被美化打印。
行为 | 解释 |
---|---|
create | 当问到不存在时创建 |
index | 创建新文档或替换已有文档 |
update | 局部刷新 |
delete | 删除一个文档 |
路由文档到分片
shard=hash(routing)%number_of_primary_shards
routing值是一个任意字符串,它默认是_id但也可以自定义。这个_routing字符串通过哈希函数生产一个数字,然后除以主切片的数量得到一个余数,余数的范围永远是0到number_of_primary_shards-1,这个数字就是特定问所在的分片。
consistency
默认主分片在尝试写入时需要规定数量或过半的分片可用。这是防止数据被写入到错的网络分区。规定的数量计算公司如下:
int((primary+number_of_replicas)/2)+1
注意number_of_replicas 是在索引中的设置,用来定义复制分片的数量,而不是现在活动的复制节点的数量。如果你定义了索引有3个复制节点,那规定数量是:
int((primary+3replicas)/2)+1=3
但是如果你只有2个节点,那你的活动分片不够规定数量,也就不能索引或者删除文档。
timeout
当分片副本不足时会怎么样?elasticsearch会等待更多的分片出现。默认等待一分钟。如果需要,你可以设置timeout参数让它终止的更早:100表示
检索文档
- 客户端给node1发送get请求
- 节点使用文档的_id确定文档属于分片0。分片-对应的复制分片在三个节点都有。
- node2返回文档给node1然后返回给客户端
为什么是奇怪的格式?
elasticsearch则是从网络缓存区中一行一行的直接读取数据。它使用换行符识别和解析action/metadata行,以决定哪些分片来处理这个请求。
搜索–基本的工具
elasticsearch真正强大之处在于可以从混乱的数据中找出有意义的信息–从大数据到全面的信息
A search can be :搜索可以:
- 在类似于generator或者age这样的字段上面使用结构化查询,join_date这样的字段上面使用排序
- 全文搜索,可以使用所有字段来匹配关键字,然后按照关联性排序返回结果
很多搜索都是开箱即用的,为了充分挖掘elasticsearch的潜力,你需要理解:
概念 | 解释 |
---|---|
映射 | 数据在每个字段中的解释说明 |
分析 | 全文是如何处理的可以被搜索的 |
领域特定语言查询 | elastics使用的灵活的、强大的查询语言 |
空搜索
GET /_search
hits
响应中最重要的部分是hits,它包含了total字段来表示匹配到的文档总数,hits数组还包含了匹配到的前10条数据。
hits数组中的每个结果都包含_index、_type和文档的_id字段,被加入到_source字段中这意味着在搜索结果中我们将可以直接使用全部文档。这不像其他搜索引擎只返回文档ID,需要你单独去获取文档。
每个节点都有一个_score字段,这是相关性得分,它衡量了文档与查询的匹配程度。默认的,返回的结果中关联性最大的文档排在首位;这意味着,它是按照_score降序排列的。
max_score指的是所有文档匹配查询中_score的最大值
took
took 告诉我们整个搜索请求花费的毫秒数
shards
_shards节点告诉我们参与查询的分片数,有多少是成功的,有多少是失败的。通常我们不希望分片失败,不过这个有可能发生。
timeout
timeout值告诉我们查询超时是否。一般的,搜索请求不会超时。如果响应速度比完整的结果更重要,你可以定义timeout参数为10或者10ms。
timeout不会停止执行查询,它仅仅告诉你目前顺利返回结果的节点然后关闭连接。在后台,其它分片可能依旧执行查询。
多索引和多类别
你注意到空搜索的结果中不同类型的文档–user和tweet–来自不同的索引–us和gb。
我们能通过定义url中的索引或类型达到这个目的:
`
/_search
/gb/_search
/gb,us/_search
/g*,u*/_search
/gb/user/_search
/gb,us/user,tweet/_search
/_all/user,tweet/_search
在所有索引的user和tweet中搜索search types user和tweet in all indices
`
当你搜索包含单一索引是,elasticsearch转发搜索请求到这个索引的主分别或每个分片的复制分片上,然后聚集每个分片的结果。搜索包含多个索引也是同样的方式–只不过或有更多的分片被关联
分页
GET /_search?size=5
GET /_search?size=5&from=5
GET /_search?size=5&from=10
简易搜索
search api有两种表单:一种是简易的查询字符串 将所有参数通过查询字符串定义,另一种版本使用json完整的表示请求体,这种富搜索语句叫做结构化查询语句(DSL)
_all 字段
GET /_search?q=mary
_all 字段对于开始一个新应用时是一个有用的特性。之后,如果你定义字段来代替_all字段,你的搜索结果将更加可控。当_all字段不再使用,你可以停用它。
更复杂的语句
+name:(mary john)+date:>2014-09-10+(aggregation geo)
映射机制用于进行字段类型确认,将每个字段匹配为一个确定的数据类(String,number,booleans,date等)
分析机制用于进行全文文本的分词,以建立供搜索用的反向索引。
映射及分析
elasticsearch为对字段类型进行猜测,动态生成了字段和类型的映射关系。返回的信息显示了date字段被识别为date类型。_all因为默认字段所以没有在此显示,不过我们知道它是string类型。
date 类型的字段和String类型的字段的索引方式是不同的,因此导致查询结果的不同,这并不会让我们觉得惊讶。
但是更大的区别在于确定值(exact values)及全文文本(full text)之间。
全文文本,从另一个角度来说是文本话的数据,比如一篇推文或邮件正文。
全文文本常常称为非结构化数据,其实是一种用词不当的称谓,实际上自然语言是高度结构化的。
对于全文数据的查询来说,却有微妙。我们不会去询问 这篇文档是否匹配查询要求?但是,我们会询问这边文档和查询的匹配程度如何?。换句话说,对于查询条件,这边文档的相关性有多高?
不仅如此,我们还期望搜索引擎能理解我们的意图:
- 一个针对uk的查询即返回涉及“united kingdom”的文档
倒排索引
elasticsearch 使用一种叫做倒排索引的结构来做快速的全文搜索。倒排索引由在文档中出现的唯一的单词表,以及对于每个单词在文档中出现的位置组成。
1 quick和quick被认为是不同的单词,但是用户可能认为它们是相同的
2 fox和foxes很相似,就像dog和dogs 它们都是同根词
3 jumped和leap不是同根词,但意思相似–它们是同义词
这个标记话和标准化的过程叫做分词。
分词和分析器
分析是这样的一个过程
- 首先,标记化一个文本块为适用于倒排索引单独的词
- 然后标准化这些词为标准形式,提高它们的可搜索性或查全率
一个分析器只是一个包装用于将三个功能放到一个包里:
字符过滤器
首先字符串经过字符过滤器,它们的工作是在标记化前处理字符串。字符过滤器能够去掉html标记,或者转义
分词器
分词器被标记成为独立的词。一个简单的分词器可以根据空格或逗号将单词分开
标记过滤
每个词都通过所有标记过滤,它可以修改词。
内建的分析器
标准分析器
标准分析器是elasticsearch默认的使用的分析器。对于文本分析,它对于任何语言都是最佳选择。
简单分析器
空格分析器
语言分析器
当分析器被使用
- 当你查询全文字段,查询将使用相同的分析器来分析查询字符串,以产生正确的词列表
- 当你查询一个确切值字段,查询将不分析查询字符串,但是你可以自己指定。
当我们在_all字段查询2014,它一个匹配到12条推文,因为这些推文都包含词2014:
GET /_search?q=2014
当我们在date字段查询2014-09-15,它查询一个确切的日期,然后值找到一个推文
当我们在date字段查询中查询2014,没有找到文档,因为没有文档包含那个确切的日期:
GET /_search?q=date:2014 # 0 results
测试分析器
GET /_analyze?analyzer=standard&text=Text to ananlyze
token 是一个实际被存储在索引中的词。potion指明词在原文本中第几个出现。start_offset 和end_offset 表示此在原文本中占据的位置。
指定分析器
你不可能总是想要这样做。也许你想使用一个更适合这个数据的语言分析器。或者,你只想把字符串字段当作一个普通的字段–不做任何分析,只存储确切值,就像字符串类型的用户ID或者内部状态字段或者标签。
映射
核心简单字段类型
类型 | 表示的数据类型 |
---|---|
String | string |
whole number | byte,short,integer,long |
Floating point | float,double |
Boolean | boolean |
Date | date |
查看映射
GET /gb/_mapping/tweet
自定义字段映射
对于stirng字段,两个最重要的映射参数是index和analyzer。
index
index参数控制字符串以何种方式索引。
值 | 解释 |
---|---|
analyzed | 首先分析这个字符串,然后索引. |
not_analyzed | 索引这个字段,使之可以被搜索,但是索引内容和指定值一样 |
no | 不索引这个字段 |
{
“tag”:{
“type”:”string”,
“index”:”not_analyzed”
}
}
分析
更新映射
你可以在第一次创建索引的时候指定映射的类型。此外,你也可以晚些时候为新类型添加映射
我们可以更新一个映射来添加一个新字段,但是不能把已有字段的类型那个从analyzed改not_analyzed
PUT /gb/_mapping/tweet
{
“properties”:{
“tag”:{
“type”:”string”,
“index”:”not_analyzed”
}
}
}
测试映射
你可以通过名字使用analyze API测试字符串字段的映射。
`
GET /gb/_analyzer?field=tweet&text=Black-cats
GET /gb/_analyzer?field=tag&text=Black-cats
`
复合核心字段类型
多值字段
我们想让tag字段包含多个字段,这非常有可能发生。我们可以索引一个标签数组来代替单一字符串
多层对象
我们需要讨论的最后一个自然json数据类时对象–在其他语言中叫做hash、hashmap、dictionary或者associative array。
内部对象经常用于在另一个对象中嵌入一个实体或对象。
内部对象的映射
elasticsearch会动态的检测新对象的字段,并且映射它们为object类型,将每个字段加到properties字段下:
内部对象是怎么样被索引的
lucene并不了解内部对象。一个lucene文件包含一个键-值对应的扁平表单。问了让elasticsearch可以有效的所有内部对象,将文件转换为一下格式:
{
“tweet”:[elasticsearch,flexible,very],
“user.id”:[@johnsmith],
“user.gender”:[male],
“user.age”:[26],
“user.name.full”:[john,smith]
\...
}
请求体查询
空查询
GET /_search
elasticsearch的作者们倾向于使用get提交查询请求,没因为它们觉得这个词相比post来说,能更好的描述这种行为。然而,因为携带交互数据的get请求并不被广泛支持,所以search API同样支持post请求
结构化查询query DSL
结构化查询是一种灵活的,多表现形式的查询语句。嗯啦提出search在一个简单的json接口中用结构化查询来展现lucene绝大多数能力。你应该在你的产品中采用这种方式进行查询。
GET /_search
{
“query”:your_query_here
}
查询子句
{
“query“:{
“match”:{
“tweet”:”elaticsearch”
}
}
}
合并多子句
查询子句就像搭积木一样,可以合并简单的子句为一个浮渣的查询语句,比如:
- 叶子子句(比如match子句)用以在将查询字符串与一个字段(或多字段)进行比较
- 复合子句用以合并其他的字段。例如,bool子句允许你合并其他的合法字段,must,must_not或者should,
{
”bool“:{
“must”:{“match”:{“tweet”:”elasticsearch”}},
“must_not”:{“match”:{“name”:”mary”}},
“should”:{“mathc”:{“tweet”:”full text”}}
}
}
复合子句能合并任意其他查询子句,包括其他的复合子句。这就意味着复合子句可以相互嵌套,从而实现非常复杂的逻辑。
邮件正文中含有”business opportunity“字样的星标邮件或收件箱中文中含有”business oppprtunity“字段的非垃圾邮件。
{
”bool“:{
“must”:{“match“:{“email”:”business opportunity”}},
“should”:[
{“match”:{“starrted”:true}},
{“bool”:{
“must”:{“folder”:”inbox”},
“must_not”:{“spam”:true}
}}
],
“minium_should_match”:1
}
}
查询与过滤
事实上我们可以使用两种结构化语句:结构化查询和结构化过滤。查询与过滤语句非常相似。
一条过滤语句会询问每个文档的字段值是否包含着特定值:
- created的日期范围是否在2013到2014?
- status的字段中是否包含单词”published“
- lat_lon字段中的地理位置与目标点相距是否不超过10km?
一条查询语句与过滤语句相似,但问法不同:
查询语句的典型用法是为了找到文档:
- 查找与full text search 这个词语最佳匹配的文档
- 查找包含单词run,但是也包含runs,running,jog或sprint的文档
- 同时包含着quick,brown和fox–单词间离得越近,该文档的相关性越高
- 标识着lucene,search或java–标识词越多,该文档的相关性越高
性能差异
使用过滤语句得到的结果集–一个简单的文档列表,快速匹配运算并存入内存是十分方便的,每个文档仅需要1个字节。这些荤菜的过滤结果集与后续请求册结合使用时非常高效的。
查询语句不仅要查找相匹配的文档,还需要计算每个文档的相关性,所以一般来说查询语句的要比过滤语句更耗时,并且查询结果也不可缓存。
幸亏有了倒排索引,一个只匹配少量文档的简单查询语句在百万级文档中的查询效率会与一条经过缓存的过滤语句旗鼓相当,甚至略占上风。但是一般情况下,一条经过缓存的过滤查询要远胜一条查询语句的执行效率。
过滤语句的目的就是缩小匹配的文档结果集,所以要仔细检查过滤条件。
什么情况下使用
原则上啦说,使用查询语句做全文本搜索或其他需要进行相关性评分的时候,剩下的全部用过滤语句。