ElasticSearch

  • ElasticSearch
  • term查询
  • match查询
  • Elasticsearch架构原理
  • Master节点
  • DataNode节点
  • 分片(Shard)
  • 副本
  • 指定分片、副本数量
  • Elasticsearch重要工作流程
  • Elasticsearch文档写入原理
  • Elasticsearch检索原理
  • Elasticsearch准实时索引实现
  • 手工控制搜索结果精准度
  • match 的底层转换
  • boost权重控制
  • 基于dis_max实现best fields策略进行多字段搜索
  • 基于tie_breaker参数优化dis_max搜索效果
  • 使用multi_match简化dis_max+tie_breaker
  • cross fields搜索
  • copy_to组合fields
  • 近似匹配
  • match phrase
  • match phrase原理 -- term position
  • 前缀搜索 prefix search
  • 正则搜索
  • fuzzy模糊搜索技术
  • 通配符搜索
  • 总结


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文档写入原理

es搜索电商 更精准_字段

  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检索原理

es搜索电商 更精准_数据_02

  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的数据)

es搜索电商 更精准_搜索_03

手工控制搜索结果精准度

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

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

es搜索电商 更精准_数据_04


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

用下述语法

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

es搜索电商 更精准_搜索_05


上述语法中,如果将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%"
      }
    }
  }
}

es搜索电商 更精准_字段_06


如果使用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
    }
  }
}

es搜索电商 更精准_es搜索电商 更精准_07

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
            }
          }
        }
      ]
    }
  }
}

es搜索电商 更精准_字段_08

基于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 进阶"
        }
      }]
    }
  }
}

es搜索电商 更精准_搜索_09

基于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"      
    }
  }
}

es搜索电商 更精准_es搜索电商 更精准_10


上述语法代表的是,搜索条件中的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"
      }
    }
  }
}

es搜索电商 更精准_es搜索电商 更精准_11

上述的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进阶"
    }
  }
}

es搜索电商 更精准_字段_12

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

es搜索电商 更精准_elasticsearch_13

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搜索电商 更精准_搜索_14


从上述结果中,可以看到。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"
      }
    }
  }
}

es搜索电商 更精准_数据_15


注意:针对前缀搜索,是对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搜索电商 更精准_字段_16

通配符搜索

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

? - 一个任意字符
 * - 0~n个任意字符
GET /users2/_search
{
  "query": {
    "wildcard": {
      "name": {
        "value": "*o*"
      }
    }
  }
}

es搜索电商 更精准_es搜索电商 更精准_17

总结

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