分布式搜索引擎elasticsearch的架构原理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式搜索引擎elasticsearch的架构原理相关的知识,希望对你有一定的参考价值。

参考技术A 分布式搜索引擎:把大量的索引数据拆散成多块,每台机器放一部分,然 后利用多台机器对分散之后的数据进行搜索,所有操作全部是分布在多台机器上进行,形成了 完整的分布式的架构。

近实时,有两层意思:

集群包含多个节点,每个节点属于哪个集群都是通过一个配置来决定的,
Node 是集群中的一个节点,节点也有一个名称,默认是随机分配的。默认节点会去加入一个名 称为 elasticsearch 的集群。如果直接启动一堆节点,那么它们会自动组成一个elasticsearch 集群,当然一个节点也可以组成 elasticsearch 集群。

文档是 es 中最小的数据单元,一个 document 可以是1条客户数据、1条商品分类数据、1条 订单数据,通常用json 数据结构来表示。每个 index 下的 type,都可以存储多条 document。
1个 document 里面有多个 field,每个 field 就是1个数据字段。

es 集群多个节点,会自动选举1个节点为 master 节点,这个 master 节点其实就是干一些管理 的工作的,比如维护索引元数据、负责切换 primary shard 和 replica shard 身份等。要是 master 节点宕机了,那么会重新选举1个节点为 master 节点。 如果是非 master节点宕机了,那么会由 master 节点,让那个宕机节点上的 primary shard 的身 份转移到其他机器上的 replica shard。接着你要是修复了那个宕机机器,重启了之后,master 节点会控制将缺失的 replica shard 分配过去,同步后续修改的数据之类的,让集群恢复正常。 说得更简单1点,就是说如果某个非 master 节点宕机了,那么此节点上的 primary shard 不就 没了。那好,master 会让 primary shard 对应的 replica shard(在其他机器上)切换为 primary shard。如果宕机的机器修复了,修复后的节点也不再是 primary shard,而是 replica shard。

索引可以拆分成多个 shard ,每个 shard 存储部分数据。拆分多个 shard是有好处的,一是支持横向扩展,比如你数据量是 3T,3 个 shard,每个 shard 就 1T 的数据, 若现在数据量增加到 4T,怎么扩展,很简单,重新建1个有 4 个 shard 的索引,将数据导进 去;二是提高性能,数据分布在多个 shard,即多台服务器上,所有的操作,都会在多台机器 上并行分布式执行,提高了吞吐量和性能。 接着就是这个 shard 的数据实际是有多个备份,就是说每个 shard 都有1个 primary shard ,负责写入数据,但是还有多个 replica shard 。 primary shard 写入数据之后, 会将数据同步到其他几个 replica shard上去。
通过这个 replica 的方案,每个 shard 的数据都有多个备份,如果某个机器宕机了,没关系啊, 还有别的数据副本在别的机器上,这样子就高可用了。

总结:分布式就是两点,1.通过shard切片实现横向扩展;2.通过replica副本机制,实现高可用

基本概念

写数据过程:客户端通过hash选择一个node发送请求,这个node被称做coordinating node(协调节点),协调节点对docmount进行路由,将请求转发给到对应的primary shard,primary shard 处理请求,将数据同步到所有的replica shard,此时协调节点,发现primary shard 和所有的replica shard都处理完之后,就反馈给客户端。

客户端发送get请求到任意一个node节点,然后这个节点就称为协调节点,协调节点对document进行路由,将请求转发到对应的node,此时会使用随机轮询算法,在primary shard 和replica shard中随机选择一个,让读取请求负载均衡,接收请求的node返回document给协调节点,协调节点,返回document给到客户端

es最强大的是做全文检索,就是比如你有三条数据
1.java真好玩儿啊
2.java好难学啊
3.j2ee特别牛

你根据java关键词来搜索,将包含java的document给搜索出来。

更新/删除数据过程,首先还是write、merge操作,然后flush过程中:
1、write过程和上面的一致;
2、refresh过程有点区别

所谓的倒排索引,就是把你的数据内容先分词,每句话分成一个一个的关键词,然后记录好每一个关键词对应出现在了哪些 id 标识的数据。
然后你可以从其他地根据这个 id 找到对应的数据就可以了,这个就是倒排索引的数据格式 以及搜索的方式,这种利倒排索引查找数据的式,也被称之为全文检索。

Inverted Index就是我们常见的倒排索引, 主要包括两部分:
一个有序的数据字典 Dictionary(包括单词 Term 和它出现的频率)。
与单词 Term 对应的 Postings(即存在这个单词的文件)
当我们搜索的时候,首先将搜索的内容分解,然后在字典里找到对应 Term,从而查找到与搜索相关的文件内容。

本质上,Stored Fields 是一个简单的键值对 key-value。默认情况下,Stored Fields是为false的,ElasticSearch 会存储整个文件的 JSON source。

哪些情形下需要显式的指定store属性呢?大多数情况并不是必须的。从_source中获取值是快速而且高效的。如果你的文档长度很长,存储 _source或者从_source中获取field的代价很大,你可以显式的将某些field的store属性设置为yes。缺点如上边所说:假设你存 储了10个field,而如果想获取这10个field的值,则需要多次的io,如果从Stored Field 中获取则只需要一次,而且_source是被压缩过 的。

这个时候你可以指定一些字段store为true,这意味着这个field的数据将会被单独存储(实际上是存两份,source和 Stored Field都存了一份)。这时候,如果你要求返回field1(store:yes),es会分辨出field1已经被存储了,因此不会从_source中加载,而是从field1的存储块中加载。

Doc_values 本质上是一个序列化的 列式存储,这个结构非常适用于聚合(aggregations)、排序(Sorting)、脚本(scripts access to field)等操作。而且,这种存储方式也非常便于压缩,特别是数字类型。这样可以减少磁盘空间并且提高访问速度,ElasticSearch 可以将索引下某一个 Document Value 全部读取到内存中进行操作.

Doc_values是存在磁盘的

在es中text类型字段默认只会建立倒排索引,其它几种类型在建立倒排索引的时候还会建立正排索引,当然es是支持自定义的。在这里这个正排索引其实就是Doc Value。

即上文所描述的动态索引

往 es 写的数据,实际上都写到磁盘文件里去了,查询的时候,操作系统会将磁盘文件里的数据自动缓存到 filesystem cache 中去。

es 的搜索引擎严重依赖于底层的 filesystem cache ,你如果给 filesystem cache 更多的 内存,尽量让内存可以容纳所有的 idx segment file 索引数据文件,那么你搜索的时候就 基本都是走内存的,性能会非常高。 性能差距究竟可以有多大?我们之前很多的测试和压测,如果走磁盘一般肯定上秒,搜索性能 绝对是秒级别的,1秒、5秒、10秒。但如果是走 filesystem cache ,是走纯内存的,那么一 般来说性能比走磁盘要高一个数量级,基本上就是毫秒级的,从几毫秒到几百毫秒不等。

那如何才能节约filesystem cache这部分的空间呢?
当写数据到ES时就要考虑到最小化数据,当一行数据有30几个字段,并不需要把所有的数据都写入到ES,只需要把关键的需要检索的几列写入。这样能够缓存的数据就会越多。 所以需要控制每台机器写入的数据最好小于等于或者略大于filesystem cache空间最好。 如果要搜索海量数据,可以考虑用ES+Hbase架构。用Hbase存储海量数据,然后ES搜索出doc id后,再去Hbase中根据doc id查询指定的行数据。

当每台机器写入的数据大于cache os太多时,导致太多的数据无法放入缓存,那么就可以把一部分热点数据刷入缓存中。

对于那些你觉得比较热的、经常会有人访问的数据,最好做个专门的缓存预热系统,就是 对热数据每隔一段时间,就提前访问一下,让数据进入 filesystem cache 里去。这样下 次别人访问的时候,性能肯定会好很多。

把热数据和冷数据分开,写入不同的索引里,然后确保把热索引数据刷到cache里。

在ES里最好不要用复杂的关联表的操作。当需要这样的场景时,可以在创建索引的时候,就把数据关联好。比如在mysql中需要根据关联ID查询两张表的关联数据:select A.name ,B.age from A join B where A.id = B.id,在写入ES时直接去把相关联数据放到一个document就好。

es 的分页是较坑的,为啥呢?举个例子吧,假如你每页是 10 条数据,你现在要查询第 100 页,实际上是会把每个 shard 上存储的前 1000 条数据都查到1个协调节点上,如果你有个 5 个 shard,那么就有 5000 条数据,接着协调节点对这 5000 条数据进行一些合并、处理,再获取到 最终第 100 页的 10 条数据。
分布式的,你要查第 100 页的 10 条数据,不可能说从 5 个 shard,每个 shard 就查 2 条数据, 最后到协调节点合并成 10 条数据吧?你必须得从每个 shard 都查 1000 条数据过来,然后根据 你的需求进行排序、筛选等等操作,最后再次分页,拿到里面第 100 页的数据。你翻页的时 候,翻的越深,每个 shard 返回的数据就越多,而且协调节点处理的时间越长,非常坑爹。所 以用 es 做分页的时候,你会发现越翻到后面,就越是慢。

我们之前也是遇到过这个问题,用 es 作分页,前几页就几十毫秒,翻到 10 页或者几十页的时 候,基本上就要 5~10 秒才能查出来一页数据了。

解决方案吗?
1)不允许深度分页:跟产品经理说,你系统不允许翻那么深的页,默认翻的越深,性能就越差;
2)在APP或者公众号里,通过下拉来实现分页,即下拉时获取到最新页,可以通过scroll api来实现;
scroll 会1次性给你生成所有数据的1个快照,然后每次滑动向后翻页就是通过游标 scroll_id 移动获取下一页,性能会比上面说的那种分页性能要高很多很 多,基本上都是毫秒级的。 但是,唯1的缺点就是,这个适合于那种类似微博下拉翻页的,不能随意跳到任何一页的场 景。也就是说,你不能先进到第 10 页,然后去第 120 页,然后再回到第 58 页,不能随意乱跳 页。所以现在很多APP产品,都是不允许你随意翻页的,也有一些网站,做的就是你只能往 下拉,一页一页的翻。
初始化时必须指定 scroll 参数,告诉 es 要保存此次搜索的上下文多长时间。你需要确保用户不会持续不断翻页翻几个小时,否则可能因为超时而失败。
除了用 scroll api ,也可以用 search_after 来做, search_after 的思想是使用前一页的结果来帮助检索下一页的数据,显然,这种方式也不允许你随意翻页,你只能一页一页往后 翻。初始化时,需要使用一个唯1值的字段作为 sort 字段。

分布式搜索引擎ElasticSearch---ElasticSearch进阶使用深入理解搜索技术集群架构原理

ElasticSearch

term查询

  • term查询keyword字段。
    term不会分词。而keyword字段也不分词。需要完全匹配才可。
  • term查询text字段。
    因为text字段会分词,而term不分词,所以term查询的条件必须是text字段分词后的某一个。

match查询

  • match查询keyword字段
    match会被分词,而keyword不会被分词,match的需要跟keyword的完全匹配可以。
  • match查询text字段
    match分词,text也分词,只要match的分词结果和text的分词结果有相同的就匹配。

Elasticsearch架构原理

Elasticsearch的节点类型分为两种节点:一类是Master,一类是DataNode;

Master节点

在ElasticSearch集群启动时,会选举出来一个Master节点,当某个节点启动后,然后使用Zen Discovery机制找到集群中的其他节点,并建立连接。
discovery.seed_hosts: [“192.168.21.130”, “192.168.21.131”, “192.168.21.132”]
并从候选主节点中选举出一个主节点。
cluster.initial_master_nodes: [“node1”, “node2”,“node3”]

Master节点的主要负责:

  • 管理索引(创建索引,删除索引),分配分片
  • 维护元数据(映射信息);
  • 管理集群节点状态
  • 不负责数据的写入和查询

一个ElasticSearch集群中,只有一个Master节点。在生产环境中,内存可以相对
小一点,但机器要稳定。

DataNode节点

在ElasticSearch集群中,会有N个DataNode节点。
主要负责 数据的写入,数据检索,大部分压力都在DataNode节点上。
因此,在生产环境中,内存最好配置大一些。

分片(Shard)

ES索引的数据也是分成若干部分,分布在不同的服务器节点中。
分布在不同服务器中的索引数据,就是分片;
ES会自动管理分片,如果发现分片分布不均匀,就会自动迁移;
一个索引由多个shard组成,而分片分布在不同服务器上。

副本

为了对ES的分片进行容错,假设某个节点不可用,会导致整个索引库都不可用。
所以,需要对分片进行副本容错,每一个分片都会有对应的副本。
在ES中,默认创建的所有为一个分片,每个分片有1个主分片,一个副本分片。
Primary Shard和Replica Shard不在同一个节点上

指定分片、副本数量

PUT /job_shard
{
  "mappings": {
    "properties": {
      "id":{
        "type":"long","store": true
      },
      "area":{
        "type": "keyword","store": true
      },
       "exp":{
        "type": "keyword","store": true
      },
       "edu":{
        "type": "keyword","store": true
      },
       "salary":{
        "type": "keyword","store": true
      },
       "job_type":{
        "type": "keyword","store": true
      },
       "cmp":{
        "type": "keyword","store": true
      },
       "pv":{
        "type": "keyword","store": true
      },
       "title":{
        "type": "text","store": true
      },
       "jb":{
        "type": "text","store": true
      }
      
      
    }
  },
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 2
  }
}
//查看分片、副本信息
GET /_cat/indices?v

Elasticsearch重要工作流程

Elasticsearch文档写入原理

  1. 选择任意一个DataNode发送请求,例如:node2。此时,node2就成为一个
    coordinating node(协调节点)
  2. 计算得到文档要写入的分片
    shard = hash(routing) % number_of_primary_shards
    routing 是一个可变值,默认是文档的 _id
  3. coordinating node会进行路由,将请求转发给对应的primary shard所在的
    DataNode(假设primary shard在node1、replica shard在node2)
  4. node1节点上的Primary Shard处理请求,写入数据到索引库中,并将数据同步到
    Replica shard
  5. Primary Shard和Replica Shard都保存好了文档,返回client

Elasticsearch检索原理

  1. client发起查询请求,某个DataNode接收到请求,该DataNode就会成为协调节点
    (Coordinating Node)
  2. 协调节点(Coordinating Node)将查询请求广播到每一个数据节点,这些数据节
    点的分片会处理该查询请求
  3. 每个分片进行数据查询,将符合条件的数据放在一个优先队列中,并将这些数据
    的文档ID、节点信息、分片信息返回给协调节点。
  4. 协调节点将所有的结果进行汇总,并进行全局排序
  5. 协调节点向包含这些文档ID的分片发送get请求,对应的分片将文档数据返回给协
    调节点,最后协调节点将数据返回给客户端

Elasticsearch准实时索引实现

  • 溢写到文件系统缓存
    当数据写入到ES分片时,会首先写入到内存中,然后通过内存的buffer生成一个
    segment,并刷到文件系统缓存中,数据可以被检索
    (注意不是直接刷到磁盘)
    ES中默认1秒,refresh一次
  • 写translog保障容错
    在写入到内存中的同时,也会记录translog日志。
    在refresh期间出现异常,会根据translog进行数据恢复。等到文件系统缓存中的segment数据刷磁盘中,清空translog文件。
  • flush刷盘
    ES默认每隔30分钟会将文件系统缓存的数据刷入到磁盘
  • segment合并
    Segment太多时,ES定期会将多个segment合并成为大的segment,减少索引查询时
    IO开销,此阶段ES会真正的物理删除(之前执行过的delete的数据)

手工控制搜索结果精准度

下述搜索中,如果document中的book字段包含java或泛型词组,都符合搜索条件。

GET /users2/_search
{
  "query": {
    "match": {
      "book": "java 泛型"
    }
  }
}


如果需要搜索的document中的book字段,包含java和泛型词组,则需要使
用下述语法

GET /users2/_search
{
  "query": {
    "match": {
      "book":{
      "query":"java 泛型",
      "operator": "and"
      }
    }
  }
}


上述语法中,如果将operator的值改为or。则与第一个案例搜索语法效果一致。默
认的ES执行搜索的时候,operator就是or。

如果在搜索结果的document中,需要remark字段中包含一定比例的搜索词。则可以使用minimum_should_match,其可以使用百分比或固定数字。百分比代表query搜索条件中词条百分比,如果无法整除,向下匹配(如,query条件有3个单词,如果使用百分比提供精准度计算,那么是无法除尽的,如果需要至少匹配两个单词,则需要用67%来进行描述。如果使用66%描述,ES
则认为匹配一个单词即可。)。固定数字代表query搜索条件中的词条,至少需要匹配多少个。

POST /users2/_search
{
  "query": {
    "match": {
      "book":{
      "query":"java 线程 泛型",
      "minimum_should_match": "68%"
      }
    }
  }
}


如果使用should+bool搜索的话,也可以控制搜索条件的匹配度。具体如下:下述
案例代表搜索的document中的remark字段中,必须匹配java、developer、
assistant三个词条中的至少2个。


POST /users2/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "book": "java"
          }
        },
        {
          "match": {
            "book": "线程"
          }
        },
         {
          "match": {
            "book": "泛型"
          }
         }
      ],
      "minimum_should_match": 2
    }
  }
}

match 的底层转换

其实在ES中,执行match搜索的时候,ES底层通常都会对搜索条件进行底层转换,
来实现最终的搜索结果。如:


POST /users2/_search
{
  "query": {
    "match": {
      "book":{
      "query":"java 线程"
      }
    }
  }
}
//转化
POST /users2/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "book": {
              "value": "java"
            }
          }
        },
        {
          "term": {
            "book": {
              "value": "线程"
            }
          }
        }
      ]
    }
  }
}

POST /users2/_search
{
  "query": {
    "match": {
      "book":{
      "query":"java 进阶", 
      "operator": "and"
      }
    }
  }
}


GET /users2/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "book": {
              "value": "java"
            }
          } 
        },
           {
          "term": {
            "book": {
              "value": "进阶"
            }
          } 
        }
      ]
    }
  }
}


建议,如果不怕麻烦,尽量使用转换后的语法执行搜索,效率更高。
如果开发周期短,工作量大,使用简化的写法。

boost权重控制

搜索document中remark字段中包含java的数据,如果remark中包含developer
或architect,则包含architect的document优先显示。(就是将architect数据匹
配时的相关度分数增加)。
一般用于搜索时相关度排序使用。如:电商中的综合排序。将一个商品的销
量,广告投放,评价值,库存,单价比较综合排序。在上述的排序元素中,广告投
放权重最高,库存权重最低。

GET /users2/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "book": "java"
          }
        }
      ],
      "should": [
        {
          "match": {
            "book": {
              "query": "线程",
              "boost": 2
            }
          }
        },
         {
          "match": {
            "book": {
              "query": "集合",
              "boost": 1
            }
          }
        }
      ]
    }
  }
}

基于dis_max实现best fields策略进行多字段搜索

  • best fields策略: 搜索的document中的某一个field,尽可能多的匹配搜索
    条件。

  • 与之相反的是,尽可能多的字段匹配到搜索条件(most fields策略)。如
    百度搜索使用这种策略。

  • 优点:精确匹配的数据可以尽可能的排列在最前端,且可以通过
    minimum_should_match来去除长尾数据,避免长尾数据字段对排序结果的影响。

  • 长尾数据比如说我们搜索4个关键词,但很多文档只匹配1个,也显示出来了,这些文
    档其实不是我们想要的
    缺点:相对排序不均匀。

  • dis_max语法: 直接获取搜索的多条件中的,单条件query相关度分数最
    高的数据,以这个数据做相关度排序。

下述的案例中,就是找name字段中rod匹配相关度分数或remark字段中java
developer匹配相关度分数,哪个高,就使用哪一个相关度分数进行结果排序。

GET /users2/_search
{
  "query": {
    "dis_max": {
      "queries": [{
        "match": {
          "address": "深"
        }
      },{
        "match": {
          "book": "java 进阶"
        }
      }]
    }
  }
}

基于tie_breaker参数优化dis_max搜索效果

dis_max是将多个搜索query条件中相关度分数最高的用于结果排序,忽略其他
query分数,在某些情况下,可能还需要其他query条件中的相关度介入最终的结果
排序,这个时候可以使用tie_breaker参数来优化dis_max搜索。tie_breaker参数
代表的含义是:将其他query搜索条件的相关度分数乘以参数值,再参与到结果排
序中。如果不定义此参数,相当于参数值为0。所以其他query条件的相关度分数被
忽略。


GET /users2/_search
{
  "query": {
    "dis_max": {
      "tie_breaker": 0.7, 
      "queries": [{
        "match": {
          "address": "广州"
        }
      },{
        "match": {
          "book": "java 进阶"
        }
      }]
    }
  }
}

使用multi_match简化dis_max+tie_breaker

ES中相同结果的搜索也可以使用不同的语法语句来实现。不需要特别关注,只
要能够实现搜索,就是完成任务!

POST /users2/_search
{
  "query": {
    "multi_match": {
      "query": "Java 进阶 线程 广州",
      "fields": ["address","book"],
      "type": "best_fields",
      "tie_breaker": 0.7,
      "minimum_should_match": 4
    }
  }
}

cross fields搜索

cross fields : 一个唯一的标识,分部在多个fields中,使用这种唯一标识
搜索数据就称为cross fields搜索
(类似联合索引)。
如:人名可以分为姓和名,地址可以分为省、市、区县、街道等。那么使用人名或地址来搜索document,就称为cross fields搜索。
实现这种搜索,一般都是使用most fields搜索策略。因为这就不是一个field
的问题。
**Cross fields搜索策略,是从多个字段中搜索条件数据。默认情况下,和most
fields搜索的逻辑是一致的,计算相关度分数是和best fields策略一致的。**一般
来说,如果使用cross fields搜索策略,那么都会携带一个额外的参数operator。
用来标记搜索条件如何在多个字段中匹配。
当然,在ES中也有cross fields搜索策略。具体语法如下:


POST /users2/_search
{
  "query": {
    "multi_match": {
      "query": "Java 进阶 线程 广州",
      "fields": ["address","book"],
      "type": "cross_fields",
      "operator": "and"      
    }
  }
}


上述语法代表的是,搜索条件中的java必须在book或address字段中匹配,
“进阶”也必须在book或book字段中匹配。“线程”也必须在book或book字段中匹配。
“广州”也必须在book或book字段中匹配。

most field策略问题:most fields策略是尽可能匹配更多的字段,所以会导致
精确搜索结果排序问题。又因为cross fields搜索,不能使用
minimum_should_match来去除长尾数据。
所以在使用most fields和cross fields策略搜索数据的时候,都有不同的缺
陷。所以商业项目开发中,都推荐使用best fields策略实现搜索

copy_to组合fields

京东中,如果在搜索框中输入“手机”,点击搜索,那么是在商品的类型名
称、商品的名称、商品的卖点、商品的描述等字段中,哪一个字段内进行数据的匹
配?如果使用某一个字段做搜索不合适,那么使用_all做搜索是否合适?也不合
适,因为_all字段中可能包含图片,价格等字段。
假设,有一个字段,其中的内容包括(但不限于):商品类型名称、商品名称、
商品卖点等字段的数据内容。是否可以在这个特殊的字段上进行数据搜索匹配?

在这里插入代码片

copy_to : 就是将多个字段,复制到一个字段中,实现一个多字段组合。copy_to
可以解决cross fields搜索问题,在商业项目中,也用于解决搜索条件默认字段问
题。

如果需要使用copy_to语法,则需要在定义index的时候,手工指定mapping映射策
略。

copy_to语法:

PUT /escopy
{
  "mappings": {
    "properties": {
      "province":{
        "type":"text","store": true,"analyzer": "standard","copy_to": "address"
      },
      "city":{
        "type": "text","store": true,"analyzer": "standard","copy_to": "address"
      },
       "street":{
        "type": "text","store": true,"analyzer": "standard","copy_to": "address"
      }, "address":{
        "type": "text","store": true,"analyzer": "standard"
      }
    }
  }
}

上述的escopy定义中,是新增了4个字段,分别是provice、city、street、
address,其中provice、city、street三个字段的值,会自动复制到address字段
中,实现一个字段的组合。那么在搜索地址的时候,就可以在address字段中做条
件匹配,从而避免most fields策略导致的问题。在维护数据的时候,不需对
address字段特殊的维护。因为address字段是一个组合字段,是由ES自动维护的。
类似java代码中的推导属性。在存储的时候,未必存在,但是在逻辑上是一定存在
的,因为address是由3个物理存在的属性province、city、street组成的。

近似匹配

前文都是精确匹配。如doc中有数据java assistant,那么搜索jave是搜索不到
数据的。因为jave单词在doc中是不存在的。
如果搜索的语法是:

GET _search
{
  "query": {
    "match": {
      "name": "jave"
    }
  }
}


如果需要的结果是有特殊要求,如:hello world必须是一个完整的短语,不
可分割;或document中的field内,包含的hello和world单词,且两个单词之间离
的越近,相关度分数越高。那么这种特殊要求的搜索就是近似搜索。包括hell搜索
条件在hello world数据中搜索,包括h搜索提示等都数据近似搜索的一部分。
如何上述特殊要求的搜索,使用match搜索语法就无法实现了。

match phrase

短语搜索。就是搜索条件不分词。代表搜索条件不可分割。
如果hello world是一个不可分割的短语,我们可以使用前文学过的短语搜索
match phrase来实现。语法如下:

POST /users2/_search
{
  "query":{
    "match_phrase": {
      "book": "java进阶"
    }
  }
}

POST /users2/_search
{
  "query":{
    "match_phrase": {
      "book": "java进阶线程"
    }
  }
}

match phrase原理 – term position

ES是如何实现match phrase短语搜索的?其实在ES中,使用match phrase做搜
索的时候,也是和match类似,首先对搜索条件进行分词-analyze。将搜索条件拆
分成hello和world
。既然是分词后再搜索,ES是如何实现短语搜索的?
这里涉及到了倒排索引的建立过程。在倒排索引建立的时候,ES会先对
document数据进行分词,如:

GET _analyze
{
  "text": "hello world , java  thread",
  "analyzer": "standard"
}


从上述结果中,可以看到。ES在做分词的时候,除了将数据切分外,还会保留
一个position。position代表的是这个词在整个数据中的下标。当ES执行match
phrase搜索的时候,首先将搜索条件hello world分词为hello和world。然后在倒
排索引中检索数据,如果hello和world都在某个document的某个field出现时,那
么检查这两个匹配到的单词的position是否是连续的,如果是连续的,代表匹配成
功,如果是不连续的,则匹配失败。

前缀搜索 prefix search

使用前缀匹配实现搜索能力。通常针对keyword类型字段,也就是不分词的字
段。

GET /users2/_search
{
  "query": {
    "prefix": {
      "name": {
        "value": "d"
      }
    }
  }
}


注意:针对前缀搜索,是对keyword类型字段而言。而keyword类型字段数据大小
写敏感。
前缀搜索效率比较低。前缀搜索不会计算相关度分数。前缀越短,效率越低。
如果使用前缀搜索,建议使用长前缀。因为前缀搜索需要扫描完整的索引内容,所
以前缀越长,相对效率越高。

正则搜索

ES支持正则表达式。可以在倒排索引或keyword类型字段中使用。
常用符号:
[] - 范围,如: [0-9]是0~9的范围数字
. - 一个字符

    • 前面的表达式可以出现多次。
GET /users2/_search
{
  "query": {
    "regexp": {
      "name":"[A‐z].+" 
    }
  }
}

fuzzy模糊搜索技术

搜索的时候,可能搜索条件文本输入错误,如:hello world -> hello
word。这种拼写错误还是很常见的。fuzzy技术就是用于解决错误拼写的(在英文
中很有效,在中文中几乎无效。)。其中fuzziness代表value的值word可以修改多
少个字母来进行拼写错误的纠正
(修改字母的数量包含字母变更,增加或减少字
母)。f代表要搜索的字段名称。

GET /users2/_search
{
  "query": {
    "fuzzy": {
      "name": {
        "value": "toa",
        "fuzziness": 2
      }
    }
  }
}

通配符搜索

ES中也有通配符。但是和java还有数据库不太一样。通配符可以在倒排索引中
使用,也可以在keyword类型字段中使用。
常用通配符:

? - 一个任意字符
 * - 0~n个任意字符

GET /users2/_search
{
  "query": {
    "wildcard": {
      "name": {
        "value": "*o*"
      }
    }
  }
}

总结

个人感觉ES是实战性较强的技术,没有动手实践,很难记得住,记住一些常用的即可。

以上是关于分布式搜索引擎elasticsearch的架构原理的主要内容,如果未能解决你的问题,请参考以下文章

Elasticsearch 分布式架构原理

ElasticSearch概念与架构原理

ElasticSearch概念与架构原理

Day123.ElasticSearch:CAP定理集群搭建架构原理及分片倒排索引面试题

突击Java面试-分布式搜索引擎的架构原理

es分布式架构原理