大话ElasticSearch

449 阅读23分钟

这是我参与8月更文挑战的第25天,活动详情查看:8月更文挑战

搜索引擎简述

什么是搜索?

搜索:就是在任何场景下,找寻你想要的信息,这个时候,会输入一段你要搜索的关键字,然后就期望找到这个关键字相关的有些信息。

搜索分类: 普通的网页搜索、垂直搜索引擎等

网页搜索

网页搜索主要是Google,百度,搜狗,必应这类网站。

垂直搜索(站内搜索)

垂直搜索主要分为互联网的搜索和IT系统的搜索。

互联网的搜索:电商网站,招聘网站,新闻网站,各种app,如电商网站搜索”牙膏“,”童装“等。

IT系统的搜索:OA软件,办公自动化软件,会议管理,日程管理,项目管理,员工管理。如管理系统搜索员工,“张三”,“张三儿”,“张小三”等;

如果用数据库做搜索会怎么样?

我们都知道数据都是存储在数据库里面的,比如说电商网站的商品信息,招聘网站的职位信息,新闻网站的新闻信息等。

如果说从技术的角度去考虑,如何实现电商网站内部的搜索功能的话,就可以考虑,去使用数据库去进行搜索。

每条记录的指定字段的文本,可能会很长,比如说“商品描述”字段的长度,有长达数千个,甚至数万个字符,这个时候,每次都要对每条记录的所有文本进行扫描,你包不包含我指定的这个关键词(比如说“电动牙刷”) 还不能将搜索词拆分开来,尽可能去搜索更多的符合你的期望的结果,比如输入“电动刷”,就搜索不出来“电动牙刷”。

用数据库来实现搜索,是不太靠谱的。通常来说,性能会很差的。

正排索引与倒排索引

正排与倒排.png

正排索引

以文档的ID为关键字,表中记录文档中每个字的位置信息,查找时扫描表中每个文档中字的信息直到找出所有包含查询关键字的文档。

倒排索引

以字或词为关键字进行索引,表中关键字所对应的记录表项记录了出现这个字或词的所有文档,一个表项就是一个字表段,它记录该文档的ID和字符在该文档中出现的位置情况。

倒排2.png

倒排.jpg

图中倒排列表栏中的(1:1:<1>)以冒号分割分别表示关键词所在文档,关键词在文档中的频数,关键词所在位置。

Lucene

简述

一个开放源代码的全文检索引擎工具包(jar包),里面包含了封装好的各种建立倒排索引,以及进行搜索的代码,包括各种算法。直接基于lucene开发,非常复杂,api复杂(实现一些简单的功能,写大量的java代码),需要深入理解原理(各种索引结构)。

TF-IDF

TF-IDF.png

TF-词频

每篇文档中关键词的频率(该文档关键词t数目/该文档单词总数)

IDF-逆文档频率

逆文档频率 = 文档总数/关键词t出现的文档数目

可以看到,TF-IDF与一个词在文档中的出现次数成正比,与该词在整个语言中的出现次数成反比

TF-IDF的特点

TF-IDF算法的优点是简单快速,结果比较符合实际情况。

缺点是单纯以"词频"衡量一个词的重要性,不够全面,有时重要的词可能出现次数并不多。而且,这种算法无法体现词的位置信息,出现位置靠前的词与出现位置靠后的词,都被视为重要性相同,这是不正确的。(一种解决方法是,对全文的第一段和每一段的第一句话,给予较大的权重。)

还有停止词、同义词和反义语句需要考虑,例如:

停止词:出现次数最多的词是"的"、"是"、"在",这一类最常用的词,对找到结果毫无帮助,这是必须要过滤掉的词。

同义词:表达同一种含义的词,比如西红柿和番茄、土豆和洋芋。

反义语句: 两者的TFIDF可能差不多,但是却表达的相反的含义。

句子A:我喜欢看电视,不喜欢看电影。

句子B:我不喜欢看电视,也不喜欢看电影。

Lucene存在的问题

  • API复杂
  • 单机瓶颈
  • 无法高可用

ElasticSearch

简述

ElasticSearch是一个高度可扩展的分布式全文搜索和分析引擎。基于lucene,隐藏复杂性,提供简单易用的restful api接口、java api接口(还有其他语言的api接口)

ElasticSearch在2010年发布, Elastic公司2018年上市。同时,ElasticSearch版本迭代快,文档全面。

版本功能:Elasticsearch各版本升级核心内容必看

推荐文档:官方文档

适用场景

  • 维基百科,类似百度百科,牙膏,牙膏的维基百科,全文检索,高亮,搜索推荐
  • The Guardian(国外新闻网站),类似搜狐新闻,用户行为日志(点击,浏览,收藏,评论)+社交网络数据(对某某新闻的相关看法),数据分析,给到每篇新闻文章的作者,让他知道他的文章的公众反馈(好,坏,热门,垃圾,鄙视,崇拜)
  • Stack Overflow(国外的程序异常讨论论坛),IT问题,程序的报错,提交上去,有人会跟你讨论和回答,全文检索,搜索相关问题和答案,程序报错了,就会将报错信息粘贴到里面去,搜索有没有对应的答案
  • GitHub(开源代码管理),搜索上千亿行代码
  • 电商网站,检索商品
  • 日志数据分析,logstash采集日志,ES进行复杂的数据分析(ELK技术,elasticsearch+logstash+kibana)
  • 商品价格监控网站,用户设定某商品的价格阈值,当低于该阈值的时候,发送通知消息给用户,比如说订阅牙膏的监控,如果高露洁牙膏的家庭套装低于50块钱,就通知我,我就去买。
  • BI系统,商业智能,Business Intelligence。比如说有个大型商场集团,BI,分析一下某某区域最近3年的用户消费金额的趋势以及用户群体的组成构成,产出相关的数张报表,某某区,最近3年,每年消费金额呈现100%的增长,而且用户群体85%是高级白领,开一个新商场。ES执行数据分析和挖掘,Kibana进行数据可视化。

国内常见的应用场景:

  • 站内搜索(电商,招聘,门户,等等)
  • IT系统搜索(OA,CRM,ERP,等等)
  • 数据分析(ES热门的一个使用场景)

特点

  • 可以作为一个大型分布式集群(数百台服务器)技术,处理PB级数据,服务大公司;也可以运行在单机上,服务小公司。
  • Elasticsearch不是什么新技术,主要是将全文检索、数据分析以及分布式技术,合并在了一起,才形成了独一无二的ES;lucene(全文检索),商用的数据分析软件(友盟+、百度统计),分布式数据库(tidb/mycat)。
  • 对用户而言,是开箱即用的,非常简单,作为中小型的应用,直接3分钟部署一下ES,就可以作为生产环境的系统来使用了,数据量不大,操作不是太复杂。
  • 数据库的功能面对很多领域是不够用的(事务,还有各种联机事务型的操作);特殊的功能,比如全文检索,同义词处理,相关度排名,复杂数据分析,海量数据的近实时处理;Elasticsearch作为传统数据库的一个补充,提供了数据库所不不能提供的很多功能。

Elasticsearch目前最核心的两个应用领域:垂直搜索引擎,实时数据分析

核心概念

  • Near Realtime(NRT):近实时,两个意思,从写入数据到数据可以被搜索到有一个小延迟(大概1秒);基于es执行搜索和分析可以达到秒级。
  • Cluster:集群,包含多个节点,每个节点属于哪个集群是通过一个配置(集群名称,默认是elasticsearch,建议修改)来决定的,对于中小型应用来说,刚开始一个集群就一个节点很正常。
  • Node:节点,集群中的一个节点,节点也有一个名称(默认是随机分配的),节点名称很重要(在执行运维管理操作的时候),默认节点会去加入一个名称为“elasticsearch”的集群,如果直接启动一堆节点,那么它们会自动组成一个elasticsearch集群,当然一个节点也可以组成一个elasticsearch集群。
  • Document&field:文档,es中的最小数据单元,一个document可以是一条客户数据,一条商品分类数据,一条订单数据,通常用JSON数据结构表示,每个index下的type中,都可以去存储多个document。一个document里面有多个field,每个field就是一个数据字段。
  • Index:索引,包含一堆有相似结构的文档数据,比如可以有一个客户索引,商品分类索引,订单索引,索引有一个名称。一个index包含很多document,一个index就代表了一类类似的或者相同的document。比如说建立一个product index,商品索引,里面可能就存放了所有的商品数据,所有的商品document。
  • Type:类型,每个索引里都可以有一个或多个type,type是index中的一个逻辑数据分类,一个type下的document,都有相同的field,比如博客系统,有一个索引,可以定义用户数据type,博客数据type,评论数据type(es7正式废除单个索引下多Type的支持,es6时,官方就提到了es7会删除type,并且es6时已经规定每一个index只能有一个type。在es7中使用默认的_doc作为type,官方说在8.x版本会彻底移除type。 api请求方式也发送变化,如获得某索引的某ID的文档:GET index/_doc/id其中index和id为具体的值)。
  • shard:单台机器无法存储大量数据,es可以将一个索引中的数据切分为多个shard,分布在多台服务器上存储。有了shard就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。每个shard都是一个lucene index。
  • replica:任何一个服务器随时可能故障或宕机,此时shard可能就会丢失,因此可以为每个shard创建多个replica副本。replica可以在shard故障时提供备用服务,保证数据不丢失,多个replica还可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,不能修改,默认5个),replica shard(随时修改数量,默认1个),默认每个索引10个shard,5个primary shard,5个replica shard,最小的高可用配置,是2台服务器。

elasticsearch vs 关系数据库

Elasticsearch数据库
Document
Type
Index

ElasticSearch安装

单机模式

零配置,开箱即用。

下载elasticsearch:

wget artifacts.elastic.co/downloads/e…

解压缩安装包:

tar -zxvf elasticsearch-5.5.2.tar.gz

启动elasticsearch:

cd /usr/local/elasticsearch-5.5.2

bin/elasticsearch -d # 后台运行

查看集群信息:

curl http://localhost:9200/

{
  "name" : "4onsTYV",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "nKZ9VK_vQdSQ1J0Dx9gx1Q",
  "version" : {
    "number" : "5.2.0",
    "build_hash" : "24e05b9",
    "build_date" : "2017-01-24T19:52:35.800Z",
    "build_snapshot" : false,
    "lucene_version" : "6.4.0"
  },
  "tagline" : "You Know, for Search"
}

集群模式

如果是分布式安装,则需要修改配置文件elasticsearch.yml

# 修改配置文件elasticsearch.yml

cluster.name: es-cluster    # 集群名称,所有节点一致
node.name: node-data-104    # 节点名称,每个节点不同

node.master: true       # 是否作为主节点
node.data: true         # 是否作为数据节点
node.ingest: true       # ingest节点,可以在ES内部对数据进行加工

path.data: /home/lgd/es/data    # 数据存放目录
path.logs: /home/lgd/es/log     # 日志存放目录

bootstrap.memory_lock: true     # 锁定内存
network.host: 10.10.10.104    
http.enabled: true              # 启用http端口,对外提供http服务的端口
http.port: 9200                 
transport.tcp.port: 9300        # 启用tcp端口,节点间交互
discovery.zen.ping.unicast.hosts: ["10.10.10.104","10.10.10.105","10.10.10.106"] # 集群自动发现节点
bootstrap.system_call_filter: false # 禁用系统调用过滤器检查
transport.tcp.compress: true
thread_pool.index.queue_size: 800   # 写索引线程池大小
thread_pool.bulk.queue_size: 800    # 批量插入线程池大小

ElasticSearch集群监控

_cat系列提供了一系列查询elasticsearch集群状态的接口。

在es老版本,有一个很好用的插件,叫做head,但是5.x之后都收口了,不让做这种插件了,主推自己的x-pack(收费)。

GET /_cat

=^.^=
/_cat/allocation
/_cat/shards
/_cat/shards/{index}
/_cat/master
/_cat/nodes # 节点统计
/_cat/tasks
/_cat/indices # 索引统计
/_cat/indices/{index}
/_cat/segments
/_cat/segments/{index}
/_cat/count
/_cat/count/{index}
/_cat/recovery
/_cat/recovery/{index}
/_cat/health # 集群健康状态
/_cat/pending_tasks
/_cat/aliases
/_cat/aliases/{alias}
/_cat/thread_pool
/_cat/thread_pool/{thread_pools}
/_cat/plugins # 集群插件
/_cat/fielddata
/_cat/fielddata/{fields}
/_cat/nodeattrs
/_cat/repositories
/_cat/snapshots/{repository}
/_cat/templates

集群健康状态

GET /_cat/health?v

cluster   status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
smart-es  green           2         2    233 121    0    0        0             0                  -                100.0%

状态说明:

  • green:每个索引的primary shard和replica shard都是active状态的。
  • yellow:每个索引的primary shard都是active状态的,但是部分replica shard不是active状态,处于不可用的状态。
  • red:不是所有索引的primary shard都是active状态的,部分索引有数据丢失了。

查看集群节点

查看master节点:

GET /_cat/master?v

id                     host           ip             node
T2bwdF_5TWqWfA1C0bmKLg 10.xxx.150.231 10.xxx.150.231 node-231

查看所有节点:

GET /_cat/nodes?v

ip             heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
10.xxx.xxx.231           64          94   5    2.33    2.66     2.90 mdi       *      node-231
10.xxx.xxx.208           64          99   8    1.10    1.13     1.14 mdi       -      node-208

查看集群所有索引

GET /_cat/indices?v



health status index             uuid                   pri rep docs.count docs.deleted store.size pri.store.size
green  open   news              EylgtpJ7TlWwJF4EIMYj3g   5   1          3            0     30.8kb         15.4kb
green  open   student           6Kydo3Y0TkyP_SmeR136xA   5   1          1            0       12kb            6kb
green  open   test              4PMOT7xvS_SYJIbKXGwtyw   5   1          2            0     14.9kb          7.4kb
green  open   workorder_tet     SbrdY8HLQPOQFIoxdNL7Gg   5   1          0            0      1.5kb           810b


查看某个索引

GET /_cat/indices/news

查看索引文档数量

GET /_cat/count/news?v

epoch      timestamp count
1550218453 16:14:13  3

或者使用如下方式查看索引文档数量:

GET news/news/_count?pretty

GET news/_count?pretty

{
  "count": 3,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  }
}

查看插件

GET /_cat/plugins?v&s=component&h=name,component,version,description

name     component   version description
node-231 analysis-ik 5.5.2   IK Analyzer for Elasticsearch
node-208 analysis-ik 5.5.2   IK Analyzer for Elasticsearch

查询各个节点分配资源

GET /_cat/allocation?v

shards disk.indices disk.used disk.avail disk.total disk.percent host           ip             node
   117      427.3mb    42.7gb    153.9gb    196.7gb           21 10.250.xxx.208 10.250.xxx.208 node-208
   116      435.8mb    39.1gb      9.9gb       49gb           79 10.250.xxx.231 10.250.xxx.231 node-231

基于案例讲解ES文档CRUD及搜索

背景

有一个电商网站,需要基于ES构建一个后台系统,提供以下功能:

  1. 对商品信息进行CRUD(增删改查)操作
  2. 执行简单的结构化查询
  3. 可以执行简单的全文检索,以及复杂的phrase(短语)检索
  4. 对于全文检索的结果,可以进行高亮显示

索引结构

# 创建订单索引库
PUT /order_detail
{
 "aliases": {},
 "mappings": {
   "default": {
     "properties": {
       "name": {
         "type": "text",
         "fields": {
           "keyword": {
             "type": "keyword",
             "ignore_above": 256
           }
         },
         "analyzer": "ik_max_word",
         "search_analyzer": "ik_smart"
       },
       "desc": {
         "type": "keyword"
       },
       "price": {
         "type": "long"
       },
       "producer": {
         "type": "text",
         "fields": {
           "keyword": {
             "type": "keyword",
             "ignore_above": 256
           }
         },
         "analyzer": "ik_max_word",
         "search_analyzer": "ik_smart"
       },
       "tags": {
         "type": "keyword"
       }
     }
   }
 },
 "settings": {
   "index": {
     "number_of_shards": "5",
     "number_of_replicas": "1"
   }
 }
}


# 查看
GET /order_detail

# 删除
DELETE /order_detail

# 修改(不建议操作)

文档CRUD


# 新增商品
PUT /order_detail/default/1
{
    "name" : "高露洁牙膏",
    "desc" :  "高露洁美白防蛀牙",
    "price" :  30,
    "producer" : "高露洁",
    "tags": [ "美白", "防蛀" ]
}


{
  "_index": "order_detail",
  "_type": "default",
  "_id": "1",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "created": true
}

###############################
PUT /order_detail/default/2
{
    "name" : "佳洁士牙膏",
    "desc" :  "佳洁士有效防蛀牙",
    "price" :  25,
    "producer" :  "佳洁士",
    "tags": [ "防蛀" ]
}

PUT /order_detail/default/3
{
    "name" : "中华牙膏",
    "desc" : "中华牙膏草本植物",
    "price" :  40,
    "producer" : "中华",
    "tags": [ "清新" ]
}


# 查询商品
GET /order_detail/default/1

{
  "_index": "order_detail",
  "_type": "default",
  "_id": "1",
  "_version": 1,
  "found": true,
  "_source": {
    "name": "高露洁牙膏",
    "desc": "高露洁美白防蛀牙",
    "price": 30,
    "producer": "高露洁",
    "tags": [
      "美白",
      "防蛀"
    ]
  }
}

###############################
# 修改商品
PUT /order_detail/default/1
{
    "name" : "高露洁加强版牙膏",
    "desc" :  "高露洁美白防蛀牙",
    "price" :  30,
    "producer" : "高露洁",
    "tags": [ "美白", "防蛀" ]
}

{
  "_index": "order_detail",
  "_type": "default",
  "_id": "1",
  "_version": 2,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "created": false
}


PUT /order_detail/default/1
{
    "name" : "高露洁终极版牙膏"
}


GET /order_detail/default/1

{
  "_index": "order_detail",
  "_type": "default",
  "_id": "1",
  "_version": 2,
  "found": true,
  "_source": {
    "name": "高露洁加强版牙膏"
  }
}

# 这种方式有一个不好,即使必须带上所有的field,才能去进行信息的修改

# 更新部分文档

POST /order_detail/default/1/_update
{
  "doc": {
    "name": "高露洁变态版牙膏"
  }
}

{
  "_index": "order_detail",
  "_type": "default",
  "_id": "1",
  "_version": 5,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  }
}



GET /order_detail/default/1

{
  "_index": "order_detail",
  "_type": "default",
  "_id": "1",
  "_version": 5,
  "found": true,
  "_source": {
    "name": "高露洁变态版牙膏",
    "desc": "高露洁美白防蛀牙",
    "price": 30,
    "producer": "高露洁",
    "tags": [
      "美白",
      "防蛀"
    ]
  }
}


###############################
# 删除商品
DELETE /order_detail/default/1

{
  "found": true,
  "_index": "order_detail",
  "_type": "default",
  "_id": "1",
  "_version": 3,
  "result": "deleted",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  }
}

分析器

分析器可以是内置分析器或者每个索引定制的自定义分析器。

分析器承担以下四种角色:

  • 文本拆分为单词: The quick brown foxes → [ The, quick, brown, foxes]
  • 大写转小写: Thethe
  • 移除常用的停用词: [ The, quick, brown, foxes] → [ quick, brown, foxes]
  • 将变型词(例如复数词,过去式)转化为词根:foxesfox

内置分析器-标准分词器

内置的分析器如下: Standard Analyzer(默认),Simple Analyzer,Whitespace Analyzer,Stop Analyzer,Keyword Analyzer,Pattern Analyzer,Language Analyzers,Fingerprint Analyzer,具体说明请参考官网文档(analysis-analyzers)

标准分词器包含如下:

Tokenizer:Standard Tokenizer

Token Filters:Standard Token Filter,Lower Case Token Filter,Stop Token Filter (disabled by default)

# 使用标准分析器分词
POST _analyze
{
  "analyzer": "standard",
  "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}


{
  "tokens": [
    {
      "token": "the",
      "start_offset": 0,
      "end_offset": 3,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "2",
      "start_offset": 4,
      "end_offset": 5,
      "type": "<NUM>",
      "position": 1
    },
    {
      "token": "quick",
      "start_offset": 6,
      "end_offset": 11,
      "type": "<ALPHANUM>",
      "position": 2
    },
    {
      "token": "brown",
      "start_offset": 12,
      "end_offset": 17,
      "type": "<ALPHANUM>",
      "position": 3
    },
    {
      "token": "foxes",
      "start_offset": 18,
      "end_offset": 23,
      "type": "<ALPHANUM>",
      "position": 4
    },
    {
      "token": "jumped",
      "start_offset": 24,
      "end_offset": 30,
      "type": "<ALPHANUM>",
      "position": 5
    },
    {
      "token": "over",
      "start_offset": 31,
      "end_offset": 35,
      "type": "<ALPHANUM>",
      "position": 6
    },
    {
      "token": "the",
      "start_offset": 36,
      "end_offset": 39,
      "type": "<ALPHANUM>",
      "position": 7
    },
    {
      "token": "lazy",
      "start_offset": 40,
      "end_offset": 44,
      "type": "<ALPHANUM>",
      "position": 8
    },
    {
      "token": "dog's",
      "start_offset": 45,
      "end_offset": 50,
      "type": "<ALPHANUM>",
      "position": 9
    },
    {
      "token": "bone",
      "start_offset": 51,
      "end_offset": 55,
      "type": "<ALPHANUM>",
      "position": 10
    }
  ]
}

###############################
# 添加停止词过滤
PUT my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_english_analyzer": {
          "type": "standard",
          "max_token_length": 5,
          "stopwords": "_english_"
        }
      }
    }
  }
}

###############################

POST my_index/_analyze
{
  "analyzer": "my_english_analyzer",
  "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}


[ 2, quick, brown, foxes, jumpe, d, over, lazy, dog's, bone ]

中文分析器

中文分析器中使用最广泛的是IK分析器。

IK分析器包括2种分词方式:

  • ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合;
  • ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”。

ik_max_word 建议索引使用,ik_smart 建议搜索使用


POST /_analyze
{
  "analyzer": "ik_max_word",
  "text": "中华人民共和国国歌"
}

{
  "tokens": [
    {
      "token": "中华人民共和国",
      "start_offset": 0,
      "end_offset": 7,
      "type": "CN_WORD",
      "position": 0
    },
    {
      "token": "中华人民",
      "start_offset": 0,
      "end_offset": 4,
      "type": "CN_WORD",
      "position": 1
    },
    {
      "token": "中华",
      "start_offset": 0,
      "end_offset": 2,
      "type": "CN_WORD",
      "position": 2
    },
    {
      "token": "华人",
      "start_offset": 1,
      "end_offset": 3,
      "type": "CN_WORD",
      "position": 3
    },
    {
      "token": "人民共和国",
      "start_offset": 2,
      "end_offset": 7,
      "type": "CN_WORD",
      "position": 4
    },
    {
      "token": "人民",
      "start_offset": 2,
      "end_offset": 4,
      "type": "CN_WORD",
      "position": 5
    },
    {
      "token": "共和国",
      "start_offset": 4,
      "end_offset": 7,
      "type": "CN_WORD",
      "position": 6
    },
    {
      "token": "共和",
      "start_offset": 4,
      "end_offset": 6,
      "type": "CN_WORD",
      "position": 7
    },
    {
      "token": "国",
      "start_offset": 6,
      "end_offset": 7,
      "type": "CN_CHAR",
      "position": 8
    },
    {
      "token": "国歌",
      "start_offset": 7,
      "end_offset": 9,
      "type": "CN_WORD",
      "position": 9
    }
  ]
}




当然还有一些其他分析器,比如ansj,它包含的分词策略如下:

index_ansj (建议索引使用)

query_ansj (建议搜索使用)

dic_ansj 用户自定义词典优先的分词方式

自定义分析器

当然,我们也可以自定义分析器,适当的组合自定义分析器,包括下面三个部分:


# 使用自定义(分词器 字符过滤器 分词过滤器)
PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_custom_analyzer": {
          "type": "custom",
          "char_filter": [
            "emoticons" 
          ],
          "tokenizer": "punctuation", 
          "filter": [
            "lowercase",
            "english_stop" 
          ]
        }
      },
      "tokenizer": {
        "punctuation": { 
          "type": "pattern",
          "pattern": "[ .,!?]"
        }
      },
      "char_filter": {
        "emoticons": { 
          "type": "mapping",
          "mappings": [
            ":) => _happy_",
            ":( => _sad_"
          ]
        }
      },
      "filter": {
        "english_stop": { 
          "type": "stop",
          "stopwords": "_english_"
        }
      }
    }
  }
}

POST /my_index/_analyze
{
  "analyzer": "my_custom_analyzer",
  "text":     "I'm a :) person, and you?"
}


{
  "tokens": [
    {
      "token": "i'm",
      "start_offset": 0,
      "end_offset": 3,
      "type": "word",
      "position": 0
    },
    {
      "token": "_happy_",
      "start_offset": 6,
      "end_offset": 8,
      "type": "word",
      "position": 2
    },
    {
      "token": "person",
      "start_offset": 9,
      "end_offset": 15,
      "type": "word",
      "position": 3
    },
    {
      "token": "you",
      "start_offset": 21,
      "end_offset": 24,
      "type": "word",
      "position": 5
    }
  ]
}

搜索


# 搜索全部文档
GET /order_detail/default/_search?pretty

{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 3,
    "max_score": 1,
    "hits": [
      {
        "_index": "order_detail",
        "_type": "default",
        "_id": "2",
        "_score": 1,
        "_source": {
          "name": "佳洁士牙膏",
          "desc": "佳洁士有效防蛀牙",
          "price": 25,
          "producer": "佳洁士",
          "tags": [
            "防蛀"
          ]
        }
      },
      {
        "_index": "order_detail",
        "_type": "default",
        "_id": "1",
        "_score": 1,
        "_source": {
          "name": "高露洁变态版牙膏",
          "desc": "高露洁美白防蛀牙",
          "price": 30,
          "producer": "高露洁",
          "tags": [
            "美白",
            "防蛀"
          ]
        }
      },
      {
        "_index": "order_detail",
        "_type": "default",
        "_id": "3",
        "_score": 1,
        "_source": {
          "name": "中华牙膏",
          "desc": "中华牙膏草本植物",
          "price": 40,
          "producer": "中华",
          "tags": [
            "清新"
          ]
        }
      }
    ]
  }
}

took:耗费了几毫秒
timed_out:是否超时,这里是没有
_shards:数据拆成了5个分片,所以对于搜索请求,会打到所有的primary shard或者是它的某个replica shard也可以
hits.total:查询结果的数量,3个document
hits.max_score:score的含义,就是document对于一个search的相关度的匹配分数,越相关,就越匹配,分数也高
hits.hits:包含了匹配搜索的document的详细数据

# 通过json构建语法
GET /order_detail/default/_search?pretty
{
  "query": { "match_all": {} }
}

# 查询名称包含"牙膏"的商品,同时按照价格降序排序
GET /order_detail/default/_search?pretty
{
    "query" : {
        "match" : {
            "name" : "牙膏"
        }
    },
    "sort": [
        { "price": "desc" }
    ]
}


# 分页查询商品,总共3条商品,假设每页就显示1条商品,现在显示第2页,所以就查出来第2个商品
GET /order_detail/default/_search
{
  "query": { "match_all": {} },
  "from": 1,
  "size": 1
}

# 指定要查询出来商品的名称和价格(更加适合生产环境的使用,可以构建复杂的查询)
GET /order_detail/default/_search
{
  "query": { "match_all": {} },
  "_source": ["name", "price"]
}

{
  "took": 5,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 3,
    "max_score": 1,
    "hits": [
      {
        "_index": "order_detail",
        "_type": "default",
        "_id": "2",
        "_score": 1,
        "_source": {
          "price": 25,
          "name": "佳洁士牙膏"
        }
      },
      {
        "_index": "order_detail",
        "_type": "default",
        "_id": "1",
        "_score": 1,
        "_source": {
          "price": 30,
          "name": "高露洁变态版牙膏"
        }
      },
      {
        "_index": "order_detail",
        "_type": "default",
        "_id": "3",
        "_score": 1,
        "_source": {
          "price": 40,
          "name": "中华牙膏"
        }
      }
    ]
  }
}



# 搜索商品名称包含"牙膏",而且售价大于25元的商品
# filter,仅仅只是按照搜索条件过滤出需要的数据而已,不计算任何相关度分数,对相关度没有任何影响
# query,会去计算每个document相对于搜索条件的相关度,并按照相关度进行排序

GET /order_detail/default/_search
{
    "query" : {
        "bool" : {
            "must" : {
                "match" : {
                    "name" : "牙膏"
                }
            },
            "filter" : {
                "range" : {
                    "price" : { "gt" : 25 } 
                }
            }
        }
    }
}

# 短语搜索
GET /order_detail/_search
{
  "query" : {
      "match_phrase" : {
          "name" : "中华牙膏草本植物"
      }
  }
}


{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 1.1899844,
    "hits": [
      {
        "_index": "order_detail",
        "_type": "default",
        "_id": "4",
        "_score": 1.1899844,
        "_source": {
          "name": "中华牙膏草本植物精华",
          "desc": "中华牙膏草本植物",
          "price": 15,
          "producer": "中华",
          "tags": [
            "清新"
          ]
        }
      },
      {
        "_index": "order_detail",
        "_type": "default",
        "_id": "5",
        "_score": 0.8574782,
        "_source": {
          "name": "中华牙膏草本植物精华清新",
          "desc": "中华牙膏草本植物",
          "price": 11,
          "producer": "中华",
          "tags": [
            "清新"
          ]
        }
      }
    ]
  }
}


# slop 如果slop的值足够大,那么单词的顺序可以是任意的。
GET /order_detail/_search
{
    "query": {
        "match_phrase": {
            "name": {
                "query": "中华牙膏清新",
                "slop":  50
            }
        }
    }
}

{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 0.26850328,
    "hits": [
      {
        "_index": "order_detail",
        "_type": "default",
        "_id": "5",
        "_score": 0.26850328,
        "_source": {
          "name": "中华牙膏草本植物精华清新",
          "desc": "中华牙膏草本植物",
          "price": 11,
          "producer": "中华",
          "tags": [
            "清新"
          ]
        }
      }
    ]
  }
}



GET /order_detail/_search
{
    "query" : {
        "match" : {
            "name" : "中华牙膏"
        }
    },
    "highlight": {
        "fields" : {
            "name" : {}
        }
    }
}

{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 5,
    "max_score": 0.6641487,
    "hits": [
      {
        "_index": "order_detail",
        "_type": "default",
        "_id": "4",
        "_score": 0.6641487,
        "_source": {
          "name": "中华牙膏草本植物精华",
          "desc": "中华牙膏草本植物",
          "price": 15,
          "producer": "中华",
          "tags": [
            "清新"
          ]
        },
        "highlight": {
          "name": [
            "<em>中华</em><em>牙膏</em>草本植物精华"
          ]
        }
      },
      {
        "_index": "order_detail",
        "_type": "default",
        "_id": "5",
        "_score": 0.5716521,
        "_source": {
          "name": "中华牙膏草本植物精华清新",
          "desc": "中华牙膏草本植物",
          "price": 11,
          "producer": "中华",
          "tags": [
            "清新"
          ]
        },
        "highlight": {
          "name": [
            "<em>中华</em><em>牙膏</em>草本植物精华清新"
          ]
        }
      },
      {
        "_index": "order_detail",
        "_type": "default",
        "_id": "3",
        "_score": 0.51623213,
        "_source": {
          "name": "中华牙膏",
          "desc": "中华牙膏草本植物",
          "price": 40,
          "producer": "中华",
          "tags": [
            "清新"
          ]
        },
        "highlight": {
          "name": [
            "<em>中华</em><em>牙膏</em>"
          ]
        }
      },
      {
        "_index": "order_detail",
        "_type": "default",
        "_id": "1",
        "_score": 0.2824934,
        "_source": {
          "name": "高露洁变态版牙膏",
          "desc": "高露洁美白防蛀牙",
          "price": 30,
          "producer": "高露洁",
          "tags": [
            "美白",
            "防蛀"
          ]
        },
        "highlight": {
          "name": [
            "高露洁变态版<em>牙膏</em>"
          ]
        }
      },
      {
        "_index": "order_detail",
        "_type": "default",
        "_id": "2",
        "_score": 0.21380994,
        "_source": {
          "name": "佳洁士牙膏",
          "desc": "佳洁士有效防蛀牙",
          "price": 25,
          "producer": "佳洁士",
          "tags": [
            "防蛀"
          ]
        },
        "highlight": {
          "name": [
            "佳洁士<em>牙膏</em>"
          ]
        }
      }
    ]
  }
}

Elasticsearch的搜索内部其实涉及到相关性打分,接下来就讲述下Elasticsearch的打分规则。

Elasticsearch相关性打分

Elasticsearch(或 Lucene)使用 布尔模型(Boolean model)) 查找匹配文档,并用一个名为实用评分函数(practical scoring function)的公式来计算相关度。这个公式借鉴了 词频/逆向文档频率(term frequency/inverse document frequency) 和 向量空间模型(vector space model),同时也加入了一些现代的新特性,如协调因子(coordination factor),字段长度归一化(field length normalization),以及词或查询语句权重提升。

布尔模型

布尔模型又称为精确匹配模型。被检索的文档都能够精确匹配检索需求,不满足要求的文档不会被检索。所有匹配的文档关于相关性都是一样的,不需要对文档进行评分,返回的结果也是无序的。

布尔模型(Boolean Model) 只是在查询中使用 AND 、OR 和 NOT (与、或和非)这样的条件来查找匹配的文档,以下查询:

full AND text AND search AND (elasticsearch OR lucene) 会将所有包括词 full 、 text 和 search ,以及 elasticsearch 或 lucene 的文档作为结果集。

这个过程简单且快速,它将所有可能不匹配的文档排除在外。

向量空间模型

向量空间模型在向量空间模型中,文档和查询语句都表示成高维空间的向量。这里每一个项都是向量的一个维度。文档和查询的相关性通过两个向量的距离来计算,通常采用余弦相似度度量方法。

设想如果查询 “happy hippopotamus” ,常见词 happy 的权重较低,不常见词 hippopotamus 权重较高,假设 happy 的权重是 2 , hippopotamus 的权重是 5 ,可以将这个二维向量—— [2,5] ——在坐标系下作条直线,线的起点是 (0,0) 终点是 (2,5)

现在,设想我们有三个文档:

  • I am happy in summer 。
  • After Christmas I’m a hippopotamus 。
  • The happy hippopotamus helped Harry

可以为每个文档都创建包括每个查询词—— happy 和 hippopotamus ——权重的向量,然后将这些向量置入同一个坐标系中

向量之间是可以比较的,只要测量查询向量和文档向量之间的角度就可以得到每个文档的相关度,文档 1 与查询之间的角度最大,所以相关度低;文档 2 与查询间的角度较小,所以更相关;文档 3 与查询的角度正好吻合,完全匹配。

向量空间模型.png

实用计分函数

score(q,d)  =  #1
            queryNorm(q)  #2
          · coord(q,d)    #3
          · ∑ (           #4
                tf(t in d)   #5
              · idf(t)²      #6
              · t.getBoost() #7
              · norm(t,d)    #8
            ) (t in q)    #9

公式说明如下:

  • #1 score(q, d) 是文档 d 与 查询 q 的相关度分数
  • #2 queryNorm(q) 是查询正则因子(query normalization factor)
  • #3 coord(q, d) 是协调因子(coordination factor)
  • #4~#9 查询 q 中每个术语 t 对于文档 d 的权重和
  • #5 tf(t in d) 是术语 t 在文档 d 中的词频
  • #6 idf(t) 是术语 t 的逆向文档频次
  • #7 t.getBoost() 是查询中使用的 boost
  • #8 norm(t,d) 是字段长度正则值,与索引时字段级的boost的和(如果存在)

总结

  1. 讲述了搜索,es 基本概念,索引结构,文档增删改查,分析器,全文检索,短语搜索,高亮显示等
  2. 未涉及es父子索引,聚合分析,数据建模,实时数据分析, 文档自动补全,高级搜索(multi_match, boost等),分布式原理,集群维护升级,Elasticsearch SQL(支持 REST 、 JDBC 以及命令行)等

参考文档