搜索引擎简述
什么是搜索?
搜索:就是在任何场景下,找寻你想要的信息,这个时候,会输入一段你要搜索的关键字,然后就期望找到这个关键字相关的有些信息。
搜索分类: 普通的网页搜索、垂直搜索引擎等
网页搜索
网页搜索主要是Google,百度,搜狗,必应这类网站。
垂直搜索(站内搜索)
垂直搜索主要分为互联网的搜索和IT系统的搜索。
互联网的搜索:电商网站,招聘网站,新闻网站,各种app,如电商网站搜索”牙膏“,”童装“等。
IT系统的搜索:OA软件,办公自动化软件,会议管理,日程管理,项目管理,员工管理。如管理系统搜索员工,“张三”,“张三儿”,“张小三”等;
如果用数据库做搜索会怎么样?
我们都知道数据都是存储在数据库里面的,比如说电商网站的商品信息,招聘网站的职位信息,新闻网站的新闻信息等。
如果说从技术的角度去考虑,如何实现电商网站内部的搜索功能的话,就可以考虑,去使用数据库去进行搜索。
每条记录的指定字段的文本,可能会很长,比如说“商品描述”字段的长度,有长达数千个,甚至数万个字符,这个时候,每次都要对每条记录的所有文本进行扫描,你包不包含我指定的这个关键词(比如说“电动牙刷”) 还不能将搜索词拆分开来,尽可能去搜索更多的符合你的期望的结果,比如输入“电动刷”,就搜索不出来“电动牙刷”。
用数据库来实现搜索,是不太靠谱的。通常来说,性能会很差的。
正排索引与倒排索引
正排索引
以文档的ID为关键字,表中记录文档中每个字的位置信息,查找时扫描表中每个文档中字的信息直到找出所有包含查询关键字的文档。
倒排索引
以字或词为关键字进行索引,表中关键字所对应的记录表项记录了出现这个字或词的所有文档,一个表项就是一个字表段,它记录该文档的ID和字符在该文档中出现的位置情况。
图中倒排列表栏中的(1:1:<1>)
以冒号分割分别表示关键词所在文档,关键词在文档中的频数,关键词所在位置。
Lucene
简述
一个开放源代码的全文检索引擎工具包(jar包),里面包含了封装好的各种建立倒排索引,以及进行搜索的代码,包括各种算法。直接基于lucene开发,非常复杂,api复杂(实现一些简单的功能,写大量的java代码),需要深入理解原理(各种索引结构)。
TF-IDF
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版本迭代快,文档全面。
推荐文档:官方文档
适用场景
- 维基百科,类似百度百科,牙膏,牙膏的维基百科,全文检索,高亮,搜索推荐
- 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:
解压缩安装包:
tar -zxvf elasticsearch-5.5.2.tar.gz
启动elasticsearch:
cd /usr/local/elasticsearch-5.5.2
bin/elasticsearch -d # 后台运行
查看集群信息:
{ "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构建一个后台系统,提供以下功能:
- 对商品信息进行CRUD(增删改查)操作
- 执行简单的结构化查询
- 可以执行简单的全文检索,以及复杂的phrase(短语)检索
- 对于全文检索的结果,可以进行高亮显示
索引结构
# 创建订单索引库 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
] - 大写转小写:
The
→the
- 移除常用的停用词: [
The
,quick
,brown
,foxes
] → [quick
,brown
,foxes
] - 将变型词(例如复数词,过去式)转化为词根:
foxes
→fox
内置分析器-标准分词器
内置的分析器如下: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 用户自定义词典优先的分词方式