ELasticsearch笔记

Scroll Down

Elasticsearch 可以横向扩展至数百(甚至数千)的服务器节点,同时可以处理PB级数据。我们的教程给出了一些使用 Elasticsearch 的示例,但并不涉及任何内部机制。Elasticsearch 天生就是分布式的,并且在设计时屏蔽了分布式的复杂性。

Elasticsearch 在分布式方面几乎是透明的。教程中并不要求了解分布式系统、分片、集群发现或其他的各种分布式概念。可以使用笔记本上的单节点轻松地运行教程里的程序,但如果你想要在 100 个节点的集群上运行程序,一切依然顺畅。

Elasticsearch 尽可能地屏蔽了分布式系统的复杂性。这里列举了一些在后台自动执行的操作:

分配文档到不同的容器 或 分片 中,文档可以储存在一个或多个节点中
按集群节点来均衡分配这些分片,从而对索引和搜索过程进行负载均衡
复制每个分片以支持数据冗余,从而防止硬件故障导致的数据丢失
将集群中任一节点的请求路由到存有相关数据的节点
集群扩容时无缝整合新节点,重新分配分片以便从离群节点恢复
当阅读本书时,将会遇到有关 Elasticsearch 分布式特性的补充章节。这些章节将介绍有关集群扩容、故障转移(集群内的原理) 、应对文档存储(分布式文档存储) 、执行分布式搜索(执行分布式检索) ,以及分区(shard)及其工作原理(分片内部原理) 。
ElasticSearch 的主旨是随时可用和按需扩容。 而扩容可以通过购买性能更强大( 垂直扩容 ,或 纵向扩容 ) 或者数量更多的服务器( 水平扩容 ,或 横向扩容 )来实现
yellow green red

bully,选主 当前状况下 谁的id最大谁就是老大 其余进来的都是小弟 当出现master下线的时候 会在触发选主,选主过程为 都会向目标ip发出ping信号。得到返回则加入活跃集合,然后从小到大排序 获取第一个为master节点,然后由master通知slave节点 完成选举

默认一个索引是5个分片 每个分片都是一个lucene的实例。主分片在创建索引时候就指定了 副本的数量可以通过_settingAPI去修改,新加入节点会导致重新分片 相同分片的副本不会放在同一节点

(p1 r2)(p0,r1)(p2,r0)

UPDATE。

分档在ES中是不可变的,所以更新文档呢是把就文档cerated设置为false,同时会修改version信息,删除了旧文档。然后把就文档的数据加上更新的数据构建创建新索引的json,然后发起一个新的索引

DETETE
删除文档不会立即将文档从磁盘中删除,只是将文档标记为已删除状态。随着你不断的索引更多的数据,Elasticsearch 将会在后台清理标记为已删除的文档。会备注标注为“.del” 在分片内部的处理则是 每个提交点会包含一个 .del 文件,文件中会列出这些被删除文档的段信息。

当一个文档被 “删除” 时,它实际上只是在 .del 文件中被 标记 删除。一个被标记删除的文档仍然可以被查询匹配到, 但它会在最终结果被返回前从结果集中移除。

文档更新也是类似的操作方式:当一个文档被更新时,旧版本文档被标记删除,文档的新版本被索引到一个新的段中。 可能两个版本的文档都会被一个查询匹配到,但被删除的那个旧版本文档在结果集返回前就已经被移除。

更新的旧文档和删除的文档在查询时候依旧会被查询到,但是在fetch阶段会被排除出去。

基于这种更新机制就会有并发访问的隐患,如果把es仅仅做为一个读取源,是数据库的副本,只是用于提高查询效率,这点可以忽略,但是如果是作为一个有写入操作的存储的话,就要做好并发控制,悲观方式就不多说了,读取到这一行就把这一行的数据锁住操作完毕之后当前client再去释放了,其余的client之前在等待,然后再去抢占锁,乐观方式es天生自带版本信息,根据查询出来的version在写入时候传入,如果version不一致,es会返回409的状态码,一致的话,即可更新成功。

批量操作 bulkapi
{ "create": { "_index": "website", "_type": "blog", "_id": "123" }}
{ "title": "My first blog post" }
{action/metadata} 换行符
{请求体,也
就是type的内容}

路由分片算法
shard = hash(routing) % number_of_primary_shards
新建 更新 删除

在写入操作发生时 根据那个路由分片的方法,可以找到一个对应的主分片,在上面执行完毕之后,他会把请求转发到所有的副本,当前主分片的所有的副本都返回成功了 他会把向master报告,然后由master返回给client

当主分片把更改转发到副本分片时, 它不会转发更新请求。 相反,它转发完整文档的新版本。请记住,这些更改将会异步转发到副本分片,并且不能保证它们以发送它们相同的顺序到达。 如果Elasticsearch仅转发更改请求,则可能以错误的顺序应用更改,导致得到损坏的文档。

字符串排序和被索引,可以使用fields子映射,排序可以使用 tweet.rqw 这样不会被分词。会按照字符串的形式去排序 query则是使用tweet主字段是全文字段。

"tweet": {
"type": "string",
"analyzer": "english",
"fields": {
"raw": {
"type": "string",
"index": "not_analyzed"
}
}
}

查询过程,query and fetch
会在各个节点上查询,然后查询到之后会放到默认的队列中去,然后根据获取的内容,去取回需要的文档,舍弃队列中的其他文档 深入到分片中的查询逻辑是 当一个查询被触发,所有已知的段按顺序被查询。词项统计会对所有段的结果进行聚合
{
"from":0,
"size":10
}
lucene的原理
倒排索引被写入磁盘后是 不可改变 的:它永远不会修改。 不变性有重要的价值:不需要锁。如果你从来不更新索引,你就不需要担心多进程同时修改数据的问题。
一旦索引被读入内核的文件系统缓存,便会留在哪里,由于其不变性。只要文件系统缓存中还有足够的空间,那么大部分读请求会直接请求内存,而不会命中磁盘。这提供了很大的性能提升。
其它缓存(像filter缓存),在索引的生命周期内始终有效。它们不需要在每次数据改变时被重建,因为数据不会变化。
写入单个大的倒排索引允许数据被压缩,减少磁盘 I/O 和 需要被缓存到内存的索引的使用量。
当然,一个不变的索引也有不好的地方。主要事实是它是不可变的! 你不能修改它。如果你需要让一个新的文档 可被搜索,你需要重建整个索引。这要么对一个索引所能包含的数据量造成了很大的限制,要么对索引可被更新的频率造成了很大的限制。
下一个需要被解决的问题是怎样在保留不变性的前提下实现倒排索引的更新? 答案是: 用更多的索引。

通过增加新的补充索引来反映新近的修改,而不是直接重写整个倒排索引。每一个倒排索引都会被轮流查询到--从最早的开始--查询完后再对结果进行合并。

luceen的查询是按段搜索 文档会转换为段 每段写入内存缓存之后 之后都有个提交点,内存中缓存的数据达到了之后就会进行磁盘同步,然后清空内存缓存

当一个查询被触发,所有已知的段按顺序被查询。词项统计会对所有段的结果进行聚合,以保证每个词和每个文档的关联都被准确计算。 这种方式可以用相对较低的成本将新文档添加到索引。

sync(意指Synchronize,即“同步”)为UNIX操作系统的标准系统调用,功能为将内核文件系统缓冲区的所有数据(也即预定将通过低级I/O系统调用[注 1]写入存储介质的数据)写入存储介质(如硬盘)

UNIX中还有一些与sync相似的系统调用,如fsync与fdatasync。其中fsync负责写入所有与特定文件描述符相关的缓冲区数据[1];fdatasync功能与fsync相似,但只负责写入文件中被变更的数据,而不会修改文件的元数据(如文件属性)[2]。

我们用第一个示例来解释使用 match 查询搜索全文字段中的单个词:

GET /my_index/my_type/_search
{
"query": {
"match": {
"title": "QUICK!"
}
}
}
拷贝为 CURL在 SENSE 中查看
Elasticsearch 执行上面这个 match 查询的步骤是:

检查字段类型 。

标题 title 字段是一个 string 类型( analyzed )已分析的全文字段,这意味着查询字符串本身也应该被分析。

分析查询字符串 。

将查询的字符串 QUICK! 传入标准分析器中,输出的结果是单个项 quick 。因为只有一个单词项,所以 match 查询执行的是单个底层 term 查询。

查找匹配文档 。

用 term 查询在倒排索引中查找 quick 然后获取一组包含该项的文档,本例的结果是文档:1、2 和 3 。

为每个文档评分 。

用 term 查询计算每个文档相关度评分 _score ,这是种将 词频(term frequency,即词 quick 在相关文档的 title 字段中出现的频率)和反向文档频率(inverse document frequency,即词 quick 在所有文档的 title 字段中出现的频率),以及字段的长度(即字段越短相关度越高)相结合的计算方式。

任何文档只要 title 字段里包含 指定词项中的至少一个词 就能匹配,被匹配的词项越多,文档就越相关。
提高精度编辑
用 任意 查询词项匹配文档可能会导致结果中出现不相关的长尾。 这是种散弹式搜索。可能我们只想搜索包含 所有 词项的文档,也就是说,不去匹配 brown OR dog ,而通过匹配 brown AND dog 找到所有文档。

match 查询还可以接受 operator 操作符作为输入参数,默认情况下该操作符是 or 。我们可以将它修改成 and 让所有指定词项都必须匹配:

GET /my_index/my_type/_search
{
"query": {
"match": {
"title": {
"query": "BROWN DOG!",
"operator": "and"
}
}
}
}
拷贝为 CURL在 SENSE 中查看

match 查询的结构需要做稍许调整才能使用 operator 操作符参数。

这个查询可以把文档 1 排除在外,因为它只包含两个词项中的一个。

控制精度编辑
在 所有 与 任意 间二选一有点过于非黑即白。 如果用户给定 5 个查询词项,想查找只包含其中 4 个的文档,该如何处理?将 operator 操作符参数设置成 and 只会将此文档排除。

有时候这正是我们期望的,但在全文搜索的大多数应用场景下,我们既想包含那些可能相关的文档,同时又排除那些不太相关的。换句话说,我们想要处于中间某种结果。

match 查询支持 minimum_should_match 最小匹配参数, 这让我们可以指定必须匹配的词项数用来表示一个文档是否相关。我们可以将其设置为某个具体数字,更常用的做法是将其设置为一个百分数,因为我们无法控制用户搜索时输入的单词数量:

GET /my_index/my_type/_search
{
"query": {
"match": {
"title": {
"query": "quick brown dog",
"minimum_should_match": "75%"
}
}
}
}
当给定百分比的时候, minimum_should_match 会做合适的事情:在之前三词项的示例中, 75% 会自动被截断成 66.6% ,即三个里面两个词。无论这个值设置成什么,至少包含一个词项的文档才会被认为是匹配的。

增加了操作符和精度控制之后,就有助于我们更好的理解组合查询。

GET /my_index/my_type/_search
{
"query": {
"bool": {
"must": { "match": { "title": "quick" }},
"must_not": { "match": { "title": "lazy" }},
"should": [
{ "match": { "title": "brown" }},
{ "match": { "title": "dog" }}
]
}
}
}
以上的查询结果返回 title 字段包含词项 quick 但不包含 lazy 的任意文档。目前为止,这与 bool 过滤器的工作方式非常相似。

区别就在于两个 should 语句,也就是说:一个文档不必包含 brown 或 dog 这两个词项,但如果一旦包含,我们就认为它们 更相关 :

bool查询的评分规则
bool 查询运行每个 match 查询,再把评分加在一起,然后将结果与所有匹配的语句数量相乘,最后除以所有的语句数量。处于同一层的每条语句具有相同的权重。依托于bool查询,因为评分相加再相除的计算方式,导致无法实现最佳匹配,最佳匹配可以使用dis_max查询
dis_max查询 相当于操作符是and,但是 dis_max 查询只会简单地使用 单个 最佳匹配语句的评分 _score 作为整体评分,所以可以使用 tie_breaker 来优化查询匹配
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
tie_breaker 参数提供了一种 dis_max 和 bool 之间的折中选择,它的评分方式如下:
获得最佳匹配语句的评分 _score 。
将其他匹配语句的评分结果与 tie_breaker 相乘。
对以上评分求和并规范化。
有了 tie_breaker ,会考虑所有匹配语句,但最佳匹配语句依然占最终结果里的很大一部分。

dis_max 查询:

{
"dis_max": {
"queries": [
{
"match": {
"title": {
"query": "Quick brown fox",
"minimum_should_match": "30%"
}
}
},
{
"match": {
"body": {
"query": "Quick brown fox",
"minimum_should_match": "30%"
}
}
},
],
"tie_breaker": 0.3
}
}
上面这个查询用 multi_match 重写成更简洁的形式:

{
"multi_match": {
"query": "Quick brown fox",
"type": "best_fields",
"fields": [ "title", "body" ],
"tie_breaker": 0.3,
"minimum_should_match": "30%"
}
}

multi_match多字段匹配 (模糊匹配,^ 字符语法为单个字段提升权重)

  • 模糊匹配
    "multi_match": {
        "query":  "Quick brown fox",
        "fields": "*_title"
    }
}
  • ^ 字符语法为单个字段提升权重
    "multi_match": {
        "query":  "Quick brown fox",
        "fields": [ "*_title", "chapter_title^2" ] 
    }
}

跨字段实体搜索(cross-fields entity search)。 在如 person 、 product 或 address (人、产品或地址)这样的实体中,需要使用多个字段来唯一标识它的信息。

可以使用bool加多个match 不过简洁的是使用multi_match

{
  "query": {
    "multi_match": {
      "query":       "Poland Street W1V",
      "type":        "most_fields",
      "fields":      [ "street", "city", "country", "postcode" ]
    }
  }
}

most_fields 方式的问题编辑
用 most_fields 这种方式搜索也存在某些问题,这些问题并不会马上显现:

它是为多数字段匹配 任意 词设计的,而不是在 所有字段 中找到最匹配的。
它不能使用 operator 或 minimum_should_match 参数来降低次相关结果造成的长尾效应。
词频对于每个字段是不一样的,而且它们之间的相互影响会导致不好的排序结果。

可以使用在索引时候冗余字段的方式来进行

短语匹配

GET /my_index/my_type/_search
{
"query": {
"match_phrase": {
"title": "quick brown fox"
}
}
}
优化
GET /my_index/my_type/_search
{
"query": {
"match_phrase": {
"title": {
"query": "quick fox",
"slop": 1
}
}
}
}
slop 参数告诉 match_phrase 查询词条相隔多远时仍然能将文档视为匹配 。 相隔多远的意思是为了让查询和文档匹配你需要移动词条多少次?

对多值字段使用短语匹配

PUT /my_index/groups/1
{
"names": [ "John Abraham", "Lincoln Smith"]
}

GET /my_index/groups/_search
{
"query": {
"match_phrase": {
"names": "Abraham Lincoln"
}
}
}

ElasticSearch会把数组索引的值变成一张大的序列表,每一个短语都会被按照顺序slop排列,当我们使用上述查询时候,这个不属于任何一个name,但是是可以查询出来的,就是因为这个数组索引导致的
上面的文档name会被索引为 【john,Abraham,Lincoln,Smith】,然后进行短语匹配的时候,【Abraham,Lincoln】,发现存在,则返回文档,处理方式,则是增大slop的值即可。默认是0,可以使用position_increment_gap 参数解决该问题,这个参数会导致存放的位置发生改变。

PUT /my_index/_mapping/groups
{
"properties": {
"names": {
"type": "string",
"position_increment_gap": 100
}
}
}

虽然设置了slop参数,但是查询匹配出很近的是会得到很好的评分并返回。短语查询和邻近查询都很好用,但仍有一个缺点。它们过于严格了:为了匹配短语查询,所有词项都必须存在,即使使用了 slop 。