Elasticsearch简介
Elasticsearch是一个实时分布式搜索和分析引擎。它让你以前所未有的速度处理大数据成为可能。
使用场景
- 全文检索
- 结构化搜索
- 分析
应用
- 维基百科使用Elasticsearch提供全文搜索并高亮关键字,以及输入实时搜索(search-as-you-type)和搜索纠错(did-youmean)等搜索建议功能。
- 英国卫报使用Elasticsearch结合用户日志和社交网络数据提供给他们的编辑以实时的反馈,以便及时了解公众对新发表的文章的回应。
- StackOverflow结合全文搜索与地理位置查询,以及more-like-this功能来找到相关的问题和答案。
- Github使用Elasticsearch检索1300亿行的代码。
Elasticsearch vs Lucene
Lucene只是一个库。想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,更糟糕的是,Lucene非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的。Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的 RESTfulAPI 来隐藏Lucene的复杂性,从而让全文搜索变得简单。
与Elasticsearch交互
Elasticsearch为Java用户提供了两种内置客户端
节点客户端(node client)
节点客户端以无数据节点(none data node)身份加入集群,换言之,它自己不存储任何数据,但是它知道数据在集群中的具体位置,并且能够直接转发请求到对应的节点上。
传输客户端(Transport client)
这个更轻量的传输客户端能够发送请求到远程集群。它自己不加入集群,只是简单转发请求给集群中的节点。
两个Java客户端都通过9300端口与集群交互,使用Elasticsearch传输协议(Elasticsearch Transport Protocol)。集群中的节点
之间也通过9300端口进行通信。如果此端口未开放,你的节点将不能组成集群。
注意:Java客户端所在的Elasticsearch版本必须与集群中其他节点一致,否则,它们可能互相无法识别。
向Elasticsearch发出的请求的组成部分与其它普通的HTTP请求是一样的:
curl -X<VERB> '<PROTOCOL>://<HOST>/<PATH>?<QUERY_STRING>' -d '<BODY>'
- VERB表示HTTP方法,例如: GET,POST,PUT,HEAD,DELETE
- PROTOCOL表示http或者https协议(只有在Elasticsearch前面有https代理的时候可用)
- HOST表示Elasticsearch集群中的任何一个节点的主机名,如果是在本地的节点,那么就叫localhost
- PORT表示Elasticsearch HTTP服务所在的端口,默认为9200
- QUERY_STRING表示一些可选的查询请求参数,例如 ?pretty 参数将使请求返回更加美观易读的JSON数据
- BODY表示request body,一个JSON格式的请求主体(如果请求需要的话)
关系型数据库 vs Elasticsearch
Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices(好比数据库) -> Types(好比表) -> Documents(好比记录) -> Fields(好比列)
集群内部工作方式
集群健康:green、yellow或者red
GET /_cluster/health
{
"cluster_name": "elasticsearch",
"status": "green", <1>
"timed_out": false,
"number_of_nodes": 1,
"number_of_data_nodes": 1,
"active_primary_shards": 0,
"active_shards": 0,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0
}
颜色 | 意义 |
---|---|
green | 所有主要分片和复制分片都可用 |
yellow | 所有主要分片可用,但不是所有复制分片都可用 |
red | 不是所有的主要分片都可用 |
一个分片(shard)是一个最小级别“工作单元(worker unit)”,它只是保存了索引中所有数据的一部分。
文档元数据
节点 | 说明 |
---|---|
_index | 文档存储的地方 |
_type | 文档代表的对象的类 |
_id | 文档的唯一标识 |
_index
索引,类似数据库
_type
类似于”类”
空搜索
GET /_search
返回集群索引中的所有文档
一些例子:
- /_search
在所有索引的所有类型中搜索
- /gb/_search
在索引 gb 的所有类型中搜索
- /gb,us/_search
在索引 gb 和 us 的所有类型中搜索
- /g*,u*/_search
在以 g 或 u 开头的索引的所有类型中搜索
- /gb/user/_search
在索引 gb 的类型 user 中搜索
- /gb,us/user,tweet/_search
在索引 gb 和 us 的类型为 user 和 tweet 中搜索
- /_all/user,tweet/_search
在所有索引的 user 和 tweet 中搜索 search types user and tweet in all indices
分词
表征化(断词)和标准化的过程叫做分词。
结构化查询 Query DSL
查询
- match_all 查询
使用 match_all 可以查询到所有文档,是没有查询条件下的默认语句
{
"match_all": {}
}
- match 查询
match 查询是一个标准查询,不管你需要全文本查询还是精确查询基本上都要用到它。
{
"match": {
"tweet": "About Search"
}
}
- multi_match 查询
multi_match 查询允许你做 match 查询的基础上同时搜索多个字段
{
“multi_match”: {
“query”: “full text search”,
“fields”: [ “title”, “body” ]
}
}
- bool 查询
bool 查询与 bool 过滤相似,用于合并多个查询子句。不同的是, bool 过滤可以直接给出是否匹配成功, 而 bool 查询要计算每一个查询子句的 _score (相关性分值)
语句 | 含义 |
---|---|
must | 查询指定文档一定要被包含。 |
must_not | 查询指定文档一定不要被包含。 |
should | 查询指定文档,有则可以为文档相关性加分。 |
{
“bool”: {
“must”: { “match”: { “title”: “how to make millions” }},
“must_not”: { “match”: { “tag”: “spam” }},
“should”: [
{ “match”: { “tag”: “starred” }},
{ “range”: { “date”: { “gte”: “2014-01-01” }}}
]
}
}
过滤
- term 过滤
term 主要用于精确匹配哪些值,比如数字,日期,布尔值或 not_analyzed 的字符串(未经分析的文本数据类型)
{
"term": {
"age": 26
}
}
- terms 过滤
terms 跟 term 有点类似,但 terms 允许指定多个匹配条件。 如果某个字段指定了多个值,那么文档需要一起去做匹配
{
"terms": {
"tag": ["search", "full_text", "nosql"]
}
}
- range 过滤
range 过滤允许我们按照指定范围查找一批数据
{
"range": {
"age": {
"gte": 20,
"lt": 30
}
}
}
范围操作符包含:
符号 | 含义 |
---|---|
gt | 大于 |
gte | 大于等于 |
lt | 小于 |
lte | 小于等于 |
- exists 和 missing 过滤
exists 和 missing 过滤可以用于查找文档中是否包含指定字段或没有某个字段,类似于SQL语句中的 IS_NULL 条件
{
"exists": {
"field": "title"
}
}
- bool 过滤
bool 过滤可以用来合并多个过滤条件查询结果的布尔逻辑,它包含一下操作符:
语句 | 含义 |
---|---|
must | 多个查询条件的完全匹配,相当于 and 。 |
must_not | 多个查询条件的相反匹配,相当于 not 。 |
should | 至少有一个查询条件匹配, 相当于 or 。 |
这些参数可以分别继承一个过滤条件或者一个过滤条件的数组:
{
"bool": {
"must": { "term": { "folder": "inbox" }},
"must_not": { "term": { "tag": "spam" }},
"should": [
{ "term": { "starred": true }},
{ "term": { "unread": true }}
]
}
}
查询与过滤条件的合并
举例:
查询语句
{
"match": {
"email": "business opportunity"
}
}
然后我们想要让这条语句加入 term 过滤,在收信箱中匹配邮件
{
"term": {
"folder": "inbox"
}
}
search API中只能包含 query 语句,所以我们需要用 filtered 来同时包含 “query” 和 “filter” 子句
{
"filtered": {
"query": { "match": { "email": "business opportunity" }},
"filter": { "term": { "folder": "inbox" }}
}
}
理解错误信息
想知道语句非法的具体错误信息,需要加上 explain 参数
例如:
GET /gb/tweet/_validate/query?explain
返回的结果中valid为false表示不合法。假设合法的话,返回true,explanations就是查询的过程了
{
"valid" : false,
"_shards" : { ... },
"explanations" : [ {
"index" : "gb",
"valid" : false,
"error" : "org.elasticsearch.index.query.QueryParsingException:[gb] No query registered for [tweet]"
} ]
}
相关性排序
- 字段值排序
对结果集按照时间排序,这也是最常见的情形,将最新的文档排列靠前。 我们使用 sort 参数进行排序
GET /_search
{
"query" : {
"filtered" : {
"filter" : { "term" : { "user_id" : 1 }}
}
},
"sort": { "date": { "order": "desc" }}
}
有了sort后,就不需要对_score进行计算了。计算 _score 是比较消耗性能的, 而且通常主要用作排序 – 我们不是用相关性进行排序的时候,就不需要统计其相关性。
- 默认排序
字段值默认以顺序排序,_score默认以倒序排序
- 多级排序
结果集会先用第一排序字段来排序,当用用作第一字段排序的值相同的时候, 然后再用第二字段对第一排序值相同的文档进行排序,以此类推。
GET /_search
{
"query" : {
"filtered" : {
"query": { "match": { "tweet": "manage text search" }},
"filter" : { "term" : { "user_id" : 2 }}
}
},
"sort": [
{ "date": { "order": "desc" }},
{ "_score": { "order": "desc" }}
]
}
正向相关和反向相关
- 检索词频率:出现频率越高,相关性也越高
- 反向文档频率:频率越高,相关性越低
- 字段长度准则:长度越长,相关性越低
重要: 输出 explain 结果代价是十分昂贵的,它只能用作调试工具 –千万不要用于生产环境。
扫描和滚屏
scan(扫描) 搜索类型是和 scroll(滚屏) API一起使用来从Elasticsearch里高效地取回巨大数量的结果而不需要付出深分页的代价。
- scroll(滚屏)
一个滚屏搜索允许我们做一个初始阶段搜索并且持续批量从Elasticsearch里拉取结果直到没有结果剩下。这有点像传统数据
库里的cursors(游标)。
- scan(扫描)
深度分页代价最高的部分是对结果的全局排序,但如果禁用排序,就能以很低的代价获得全部返回结果。为达成这个目的,可以采用 scan(扫描) 搜索模式。扫描模式让Elasticsearch不排序,只要分片里还有结果可以返回,就返回一批结果。
配置
number_of_shards:定义一个索引的主分片个数
number_of_replicas:每个主分片的复制分片个数
配置分析器
standard 分析器是用于全文字段的默认分析器,对于大部分西方语系来说是一个不错的选择。它考虑了以下几点
- standard 分词器,在词层级上分割输入的文本。
- standard 表征过滤器,被设计用来整理分词器触发的所有表征(但是目前什么都没做)。
- lowercase 表征过滤器,将所有表征转换为小写。
- stop 表征过滤器,删除所有可能会造成搜索歧义的停用词,如 a , the , and , is 。
- stemmer 表征过滤器将单词转化为他们的根形态
将 & 替换成 and ,使用一个自定义的 mapping 字符过滤器
"char_filter": {
"&_to_and": {
"type": "mapping",
"mappings": [ "&=> and "]
}
}
索引别名
索引 别名 就像一个快捷方式或软连接,可以指向一个或多个索引,也可以给任何需要索引名的 API 使用。别名带给我们极大的灵活性,允许我们做到:
- 在一个运行的集群上无缝的从一个索引切换到另一个
- 给多个索引分类(例如, last_three_months )
- 给索引的一个子集创建 视图
在应用中使用别名而不是索引。然后你就可以在任何时候重建索引。别名的开销很小,应当广泛使用。