本文为 Cheatsheet 类型文章,用于记录我在日常编程中经常使用的 ElasticSearch 相关和命令。
最早使用 ElasticSearch 是两年前了。最近准备用 Django 写一个全栈式的应用,借用强大的 ES 来做搜索。
这是我在写程序之余写这篇笔记的原因。最近因为换工作的事情耽误了教程更新,就把这篇笔记放出来吧。不定期更新。
官网介绍 ElasticSearch 不仅仅是全文搜索,也可以结构化搜索(这里用结构化查询会更准确一些),处理人类语言,地理位置,以及关系。
然而,我在项目使用过程中还是主要用到了全文搜索以及推荐。
不用其他的主要原因是因为 ES 尺有所短寸有所长:
我对 ElasticSearch 在后台组件里的作用在于搜索与推荐:
知乎的文章居然不支持 toc, 实在是太蛋疼了。
文章目录如下
▼ 0x00 前言 : section
▼ 0x01 安装,配置,基本 shell 命令 : section
1. 安装 : section
2. 配置 : section
3. 插件 : section
0x02 ElasticSearch 配套工具 : section
▼ 0x03 ElasticSearch 基础概念 : section
▼ 3.1 Elasticsearch CRUDE 以及基本操作 : section
CURDE : section
普通搜索 : section
聚集搜索 : section
▼ 0x04 全文搜索的基本概念 : section
4.1 全文搜索遇到的挑战 : section
▼ 4.2 全文搜索的索引时与查询时 : section
1. 索引时 ES 做了什么? : section
2. 查询时 ES 做了什么? : section
3. 全文搜索调优之中文分词 : section
4. 全文搜索调优之停止词 : section
5. 全文搜索调优之同义词 : section
6. 全文搜索调优之拼写错误 : section
▼ 7. 全文搜索调优之相关性 : section
索引时三因素 : section
查询时 : section
计算公式 : section
0x05 搜索语法 : section
0x06 Python SDK : section
0x07 踩坑集 : section
0xEE 参考链接 : section
具体在项目中的配置建议看一下我写的配置文章 https://zhuanlan.zhihu.com/p/33920401 和并且参考现有代码 https://github.com/twocucao/YaDjangoBlog
# 执行如下的命令
curl 'http://localhost:9200/?pretty'
# 输出结果
{
"name" : "XOGvo8a",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "fAwp341bQzalzBxRFyD1YA",
"version" : {
"number" : "6.2.1",
"build_hash" : "7299dc3",
"build_date" : "2018-02-07T19:34:26.990113Z",
"build_snapshot" : false,
"lucene_version" : "7.2.1",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}
配置略
ES 的插件有很多,截止笔者写这篇文章的时候,ES 最新的版本是 6.2.1 版本。
PS: 两年前我用的还是 2.3.3 版本。新版本有很多插件配置起来已经有所不同了。比如说 head 现在已经被独立出来作为一个单纯的网页,chrome 商店可以直接下载。
需要配 ik-analyser. 如果你在 YaDjangoBlog 中起了这个命令,则已经配置完毕。
建议使用 Head 插件来进行简单的查询与调试。
详细的搜索见 Python SDK
ES 使用的是 RESTFUL API 接口
这也就意味着:
ES 写复杂查询的时候,语法乱,这个过程需要多翻看 guide 和手册。
https://www.elastic.co/guide/en/elasticsearch/guide/current/structured-search.html
全文搜索包含两个重要方面:
https://www.elastic.co/guide/en/elasticsearch/guide/current/full-text-search.html
https://www.elastic.co/guide/en/elasticsearch/guide/current/aggregations.html
在最初开源搜索引擎技术还不是很成熟的时候,我们一般都会使用 RDBMS 进行简单搜索。
简单搜索,也就是我们常常使用的 like 查询(当然,有的数据库可以使用正则表达式)
这种方式是简单暴力的查询方式,优点是实现起来简单暴力。缺点是在这个场景下性能和准确度很差。
举例:
是的,我们需要一种新的姿势,来进行搜索。也就是本文所说的全文搜索。
本小节先搞清楚两个点,
这里我们略过定义 index,type,document 仅仅指某个 field 被赋值 document 被保存的时候针对这个被赋值的 text 类型 field 的处理。
先看第一步:
通常在定义 field 的时候显式指定 analyzer(分析器).
这个 analyzer 一般的作用如下:
不同的 analyzer 作用大同小异,拿我们常用的 https://github.com/medcl/elasticsearch-analysis-ik 的话,则也是类似的步骤(下面步骤是我猜测的,没看源码)
可以定义字段的时候可以指定 analyzer(索引时) 与 search_analyzer(查询时)
先看经过第一步之后,就可以进入第二步形成倒排索引了,此时,倒排索引之于 ElasticSearch 可以类比于 btree 之于 MySQL 或者 Gist 之于 PostgreSQL.
那么,倒排索引包含哪些东西呢?
减少停止词仅仅可以减少少部分 terms dictionary 和 postings list , 但是 positions 和 offsets data 对 index 的影响则是非常大的。
其实搜索的就是这个玩意。
于是,我们就必须关注如何更好的查询文档了。下面几个小节,你就知道全文搜索是比较难调优的了。好,一个一个来。
中文分词以前是个难点,现在基本有成熟的解决方案,在没有更加牛逼的分词技术解决方案之前,现在分词效果主要是拼词典。
TODO: 这个话题可能比较大,先挖坑,以后填
使用停止词是减少索引大小的一种方式(减小索引效果不明显),那么,哪些词语可以呗当做停止词呢?
当然,是否是高频词语依据个人经验主要依据两点来判断:
是不是用上停止词就好了呢?并不是。
比如:
同义词也有很多种:
随着场景的不同,上面有些同义词也是不能轻而易举同义的。
- | 索引时 | 查询时 |
---|---|---|
索引大小 | 耗时变多,同义词被索引,大小更大 | 耗时几乎不变 |
相关性 | 准确度下降,所有同义词相同 IDF, 则在所有文档的索引记录中,常用词和冷门词权重相同 | 准确度提升,每个同义词的 IDF 将被校正 |
性能 | 性能下降,查询需要涨到 | 性能下降,查询被重写,用于查找同义词 |
灵活性 | 变差,同义词法则不改变已存在记录,需重新索引 | 不变,同义词法则可被更新,无需重新索引 |
由此可见,大部分场景下的索引时如果没有特别的需求,谨慎使用同义词。
同义词使用自定义 filter , 并且在新建 analyzer 并指定 filter 即可。
有的时候,用户也会输入错误:
这个时候,搜索引擎应该提示一下,您搜索的是不是『周杰伦』呢?
这里面就遇到了一个问题,我们显然知道周杰棍和周杰伦是是相似的,为什么呢?或者说,直观上感知的详细,能用数学方式表达出来吗?
有人说,正则匹配 / 通配符匹配呗。这是一个思路。
Vladimir Levenshtein 和 frederic damerau 给出了一种相似度算法 https://en.wikipedia.org/wiki/Damerau–Levenshtein_distance
一个词组通过转换到另一个词的步数就是其距离:
用法:
指定 “fuzziness”: step 即可
当 step >=2 的时候,ES 进行查询的时候,每次查询都会遍历 terms 字典,所以,如果 fuzziness 大于 2 的时候遍历 terms 的数量则非常惊人了。
fuzzy match query 也是支持的,比如说,假如你指定 “fuzziness” 为 1, 搜索周杰棍,则将周杰伦,周杰全搜索出来了。似乎搜索的很全面呀,但是问题来了:
依据 TF/IDF 的高频低权重,低频高权重的计算方式,周杰棍由于出现次数极少,反而获得了极高的权重。
跑个题,这种因为『出现次数少,查询的时候反而显得权重较高』的情况。并不仅仅出现在 TF/IDF 算法上。
这方面搜索引擎和人是一样一样的
回到正题
所以,一般情况下还是建议拼写错误主要还是用于:
我们在接触 RDBMS 的时候系统是没有相关性的说法的,比如说,2017 年 12 月份 xxx 用户的订单,就是直接 select 出来这些订单。因为 where 语句后面包含了界限明确的条件,而全文搜索则不然。
这个时候一个人拍着桌子站起来,说:不对呀,我要搜索包含周杰伦的所有文章。这咋没有条件边界。
嗯,稍等,『选出所有包含周杰伦的文章』条件很清晰。但问题是,排序怎么做?按照日期排?按照点击率排?这篇文章上周已经在在搜索靠前了,已经『长江后浪推前浪了』上了,这周是不是该差不多『前浪死在沙滩上』了?
Elasticsearch 中使用的计算 score 的公式叫做 practical scoring function, 这个公式借鉴于 TF/TDF 以及 矢量空间模型,但有更多的特征比如,条件因素,字段长度正态化,term / query clause boosting
全文搜索不仅仅找到匹配的 documents, 并且按照相关性进行排序(其实就是打分 score)。
为什么需要打分呢?从相亲角度来说,上海内环有房肯定是个超级大加分项。同样是录入信息,在上海内环有房的权重值可是设置的高一些。
嗯,其实相关性的调优是最难的部分。
先看前两个因素 TF/IDF
再看后一个因素 Field-Length norm
标题越短,这个词对这个 field 的代表性越强
几个词 -> 几维度 -> 寻求最佳匹配以及近似匹配
这个公式调优的时候需要用到
score(q,d) = queryNorm(q)
·coord(q,d)
·∑(tf(t in d)·idf(t)²·t.getBoost()·norm(t,d)) (t in q)
Single document APIs
Multi Get API
官方提供了两个 SDK 方便我们进行日常的开发:
我更喜欢 elasticsearch , 而不是 elasticsearch_dsl, 因为写起来更容易结合 elasticsearch-head 进行 profile
前者偏底层一些,后者偏高层一些,高底层关系的有点类似于 sql 和 sqlalchemy core 之间的关系。
ChangeLog: