ES
ElasticSearch简介
1. es是什么
ElasticSearch简称ES,是一个高拓展和开源的全文搜索和分析引擎,可以准实时地存储、搜索、分析海量的数据。它和MongoDB、redis等一样是非关系型数据。
应用定位:采用Restful API标准的可扩展和高可用的实时数据分析的全文搜索工具。
可拓展:开源软件,支持很多第三方插件。
高可用:在一个集群的多个节点中进行分布式存储,索引支持shards和复制,即使部分节点down掉,也能自动进行数据恢复和主从切换。
采用RestfulAPI标准:通过http接口使用JSON格式进行操作数据。数据存储的最小单位是文档,本质上是一个JSON 文本。所有资源都共享统一的接口(标准的HTTP方法)比如 GET、PUT、POST 和 DELETE,在客户端和服务器之间传输数据。
2. 什么是全文检索
全文检索是指计算机索引程序通过扫描文章中的每个词,将每个词都生成相应索引,指明该词在文章中的位置和次数,当搜索词语时,搜索引擎就会根据事先建立的索引进行查找,将查找的结果反馈给用户。例如商城搜索商品等。
3.为什么需要es
作为搜索引擎:实际项目开发中,几乎每个系统都会有一个搜索的功能,数据量少时可以直接从主数据库中比如Mysql搜索。
但当搜索做到一定程度时,比如系统数据量上了10亿、100亿条的时候,传统的关系型数据库的I/O性能和统计分析性能就难以满足用户需要了。所以很多公司都会把搜索单独做成一个独立的模块,用ElasticSearch等来实现。
日志分析:Elasticsearch+ Logstash + Kibana是同一家公司开发的3个开源工具,可组合起来搭建海量日志分析平台,目前很多公司都在使用这种方式搭建日志分析平台进行大数据分析。
基本使用
1.创建索引:
PUT /megacorp/employee/1
{
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
名字 | 内容 |
---|---|
megacorp | 索引的名字 |
employee | 类型的名字 |
1 | 当前员工的ID |
2.搜索:
GET /megacorp/employee/1
返回的内容包含了这个文档的元数据信息,而 John Smith 的原始 JSON 文档也在 _source
字段中出现了:
{
"_index" : "megacorp",
"_type" : "employee",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
}
我们通过将HTTP后的请求方式由 PUT
改变为 GET
来获取文档,同理,我们也可以将其更换为 DELETE
来删除这个文档,HEAD
是用来查询这个文档是否存在的。如果你想替换一个已经存在的文档,你只需要使用 PUT
再次发出请求即可。
3.简易搜索:
GET /megacorp/employee/_search
你可以发现我们正在使用 megacorp
索引,employee
类型,但是我们我们并没有指定文档的ID,我们现在使用的是 _search
端口。你可以再返回的 hits
中发现我们录入的三个文档。搜索会默认返回最前的10个数值。
{
"took": 6,
"timed_out": false,
"_shards": { ... },
"hits": {
"total": 3,
"max_score": 1,
"hits": [
{
"_index": "megacorp",
"_type": "employee",
"_id": "3",
"_score": 1,
"_source": {
"first_name": "Douglas",
"last_name": "Fir",
"age": 35,
"about": "I like to build cabinets",
"interests": [ "forestry" ]
}
},
{
"_index": "megacorp",
"_type": "employee",
"_id": "1",
"_score": 1,
"_source": {
"first_name": "John",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
},
{
"_index": "megacorp",
"_type": "employee",
"_id": "2",
"_score": 1,
"_source": {
"first_name": "Jane",
"last_name": "Smith",
"age": 32,
"about": "I like to collect rock albums",
"interests": [ "music" ]
}
}
]
}
}
注意:反馈值中不仅会告诉你匹配到哪些文档,同时也会把这个文档都会包含到其中:我们需要搜索的用户的所有信息。
接下来,我们将要尝试着实现搜索一下哪些员工的姓氏中包含 Smith
。为了实现这个,我们需要使用一种轻量的搜索方法。这种方法经常被称做 查询字符串(query string) 搜索,因为我们通过URL来传递查询的关键字:
GET /megacorp/employee/_search?q=last_name:Smith
我们依旧使用 _search
端口,然后可以将参数传入给 q=
。这样我们就可以得到姓Smith的结果:
{
...
"hits": {
"total": 2,
"max_score": 0.30685282,
"hits": [
{
...
"_source": {
"first_name": "John",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
},
{
...
"_source": {
"first_name": "Jane",
"last_name": "Smith",
"age": 32,
"about": "I like to collect rock albums",
"interests": [ "music" ]
}
}
]
}
}
4.使用Query DSL搜索:
查询字符串是通过命令语句完成 点对点(ad hoc) 的搜索,但是这也有它的局限性(可参阅《搜索局限性》章节)。Elasticsearch 提供了更加丰富灵活的查询语言,它被称作 Query DSL,通过它你可以完成更加复杂、强大的搜索任务。
DSL (Domain Specific Language 领域特定语言) 需要使用 JSON 作为主体,我们还可以这样查询姓 Smith 的员工:
GET /megacorp/employee/_search
{
"query" : {
"match" : {
"last_name" : "Smith"
}
}
}
5.更加复杂的搜索
接下来,我们再提高一点儿搜索的难度。我们依旧要寻找出姓 Smith 的员工,但是我们还将添加一个年龄大于30岁的限定条件。我们的查询语句将会有一些细微的调整来以识别结构化搜索的限定条件 filter(过滤器):
GET /megacorp/employee/_search
{
"query" : {
"filtered" : {
"filter" : {
"range" : {
"age" : { "gt" : 30 } <1>
}
},
"query" : {
"match" : {
"last_name" : "Smith" <2>
}
}
}
}
}
-
- 这一部分的语句是
range
filter ,它可以查询所有超过30岁的数据 --gt
代表 greater than (大于)。 - 这一部分我们前一个操作的
match
query 是一样的
- 这一部分的语句是
先不要被这么多的语句吓到,我们将会在之后带你逐渐了解他们的用法。你现在只需要知道我们添加了一个filter,可以在 match
的搜索基础上再来实现区间搜索。现在,我们的只会显示32岁的名为Jane Smith的员工了:
{
...
"hits": {
"total": 1,
"max_score": 0.30685282,
"hits": [
{
...
"_source": {
"first_name": "Jane",
"last_name": "Smith",
"age": 32,
"about": "I like to collect rock albums",
"interests": [ "music" ]
}
}
]
}
}
6.全文搜索
上面的搜索都很简单:名字搜索、通过年龄过滤。接下来我们来学习一下更加复杂的搜索,全文搜索——一项在传统数据库很难实现的功能。 我们将会搜索所有喜欢 rock climbing 的员工:
GET /megacorp/employee/_search
{
"query" : {
"match" : {
"about" : "rock climbing"
}
}
}
你会发现我们同样使用了 match
查询来搜索 about
字段中的 rock climbing。我们会得到两个匹配的文档:
{
...
"hits": {
"total": 2,
"max_score": 0.16273327,
"hits": [
{
...
"_score": 0.16273327, <1>
"_source": {
"first_name": "John",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
},
{
...
"_score": 0.016878016, <1>
"_source": {
"first_name": "Jane",
"last_name": "Smith",
"age": 32,
"about": "I like to collect rock albums",
"interests": [ "music" ]
}
}
]
}
}
- 相关评分
通常情况下,Elasticsearch 会通过相关性来排列顺序,第一个结果中,John Smith 的 about
字段中明确地写到 rock climbing。而在 Jane Smith 的 about
字段中,提及到了 rock,但是并没有提及到 climbing,所以后者的 _score
就要比前者的低。
这个例子很好地解释了 Elasticsearch 是如何执行全文搜索的。对于 Elasticsearch 来说,相关性的概念是很重要的,而这也是它与传统数据库在返回匹配数据时最大的不同之处。
7.段落搜索
能够找出每个字段中的独立单词固然很好,但是有的时候你可能还需要去匹配精确的短语或者 段落。例如,我们只需要查询到 about
字段只包含 rock climbing 的短语的员工。
为了实现这个效果,我们将对 match
查询变为 match_phrase
查询:
GET /megacorp/employee/_search
{
"query" : {
"match_phrase" : {
"about" : "rock climbing"
}
}
}
这样,系统会没有异议地返回 John Smith 的文档:
{
...
"hits": {
"total": 1,
"max_score": 0.23013961,
"hits": [
{
...
"_score": 0.23013961,
"_source": {
"first_name": "John",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
}
]
}
}
8.高亮我们的搜索
很多程序希望能在搜索结果中 高亮 匹配到的关键字来告诉用户这个文档是 如何 匹配他们的搜索的。在 Elasticsearch 中找到高亮片段是非常容易的。
让我们回到之前的查询,但是添加一个 highlight
参数:
GET /megacorp/employee/_search
{
"query" : {
"match_phrase" : {
"about" : "rock climbing"
}
},
"highlight": {
"fields" : {
"about" : {}
}
}
}
当我们运行这个查询后,相同的命中结果会被返回,但是我们会得到一个新的名叫 highlight
的部分。在这里包含了 about
字段中的匹配单词,并且会被 <em></em>
HTML字符包裹住:
{
...
"hits": {
"total": 1,
"max_score": 0.23013961,
"hits": [
{
...
"_score": 0.23013961,
"_source": {
"first_name": "John",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [ "sports", "music" ]
},
"highlight": {
"about": [
"I love to go <em>rock</em> <em>climbing</em>" <1>
]
}
}
]
}
}
-
- 在原有文本中高亮关键字。
9.统计
最后,我们还有一个需求需要完成:可以让老板在职工目录中进行统计。Elasticsearch 把这项功能称作 汇总 (aggregations),通过这个功能,我们可以针对你的数据进行复杂的统计。这个功能有些类似于 SQL 中的GROUP BY
,但是要比它更加强大。
例如,让我们找一下员工中最受欢迎的兴趣是什么:
GET /megacorp/employee/_search
{
"aggs": {
"all_interests": {
"terms": { "field": "interests" }
}
}
}
请忽略语法,让我们先来看一下结果:
{
...
"hits": { ... },
"aggregations": {
"all_interests": {
"buckets": [
{
"key": "music",
"doc_count": 2
},
{
"key": "forestry",
"doc_count": 1
},
{
"key": "sports",
"doc_count": 1
}
]
}
}
}
我们可以发现有两个员工喜欢音乐,还有一个喜欢森林,还有一个喜欢运动。这些数据并没有被预先计算好,它们是在文档被查询的同时实时计算得出的。如果你想要查询姓 Smith 的员工的兴趣汇总情况,你就可以执行如下查询:
GET /megacorp/employee/_search
{
"query": {
"match": {
"last_name": "smith"
}
},
"aggs": {
"all_interests": {
"terms": {
"field": "interests"
}
}
}
}
这样,all_interests
的统计结果就只会包含满足查询的文档了:
...
"all_interests": {
"buckets": [
{
"key": "music",
"doc_count": 2
},
{
"key": "sports",
"doc_count": 1
}
]
}
汇总还允许多个层面的统计。比如我们还可以统计每一个兴趣下的平均年龄:
GET /megacorp/employee/_search
{
"aggs" : {
"all_interests" : {
"terms" : { "field" : "interests" },
"aggs" : {
"avg_age" : {
"avg" : { "field" : "age" }
}
}
}
}
}
虽然这次返回的汇总结果变得更加复杂了,但是它依旧很容易理解:
...
"all_interests": {
"buckets": [
{
"key": "music",
"doc_count": 2,
"avg_age": {
"value": 28.5
}
},
{
"key": "forestry",
"doc_count": 1,
"avg_age": {
"value": 35
}
},
{
"key": "sports",
"doc_count": 1,
"avg_age": {
"value": 25
}
}
]
}
在这个丰富的结果中,我们不但可以看到兴趣的统计数据,还能针对不同的兴趣来分析喜欢这个兴趣的平均年龄
。
即使你现在还不能很好地理解语法,但是相信你还是能发现,用这个功能来实现如此复杂的统计工作是这样的简单。你的极限取决于你存入了什么样的数据哟!
小结
希望上面的几个小教程可以很好地向你解释 Elasticsearch 可以实现什么功能。为了保持教程简短,这里只提及了一些基础,除此之外还有很多功能,比如建议、地理定位、过滤、模糊以及部分匹配等。但是相信你也发现了,在这里你只需要很简单的操作就可以完成复杂的操作。无需配置,添加数据就可以开始搜索!
可能前面有一些语法会让你觉得很难理解,你可能对如何调整优化它们还有很多疑问。那么,本书之后的章节将会帮助你逐步解开疑问,让你对 Elasticsearch 是如何工作的有一个全面的了解。
分布式
分布式特性
在最开始的章节中,我们曾经提到 Elasticsearch 可以被扩展到上百台(甚至上千台)服务器上,来处理PB级别的数据。我们的教程只提及了如何使用它,但是并没有提及到服务器方面的内容。Elasticsearch 是自动分布的,它在设计时就考虑到可以隐藏分布操作的复杂性。
Elasticsearch 的分布式部分很简单。你甚至不需要关于分布式系统的任何内容,比如分片、集群、发现等成堆的分布式概念。你可能在你的笔记本中运行着刚才的教程,如果你想在一个拥有100个节点的集群中运行教程,你会发现操作是完全一样的。
Elasticsearch 很努力地在避免复杂的分布式系统,很多操作都是自动完成的:
-
- 可以将你的文档分区到不同容器或者 分片 中,这些文档可能被存在一个节点或者多个节点。
-
- 跨节点平衡集群中节点间的索引与搜索负载。
-
- 自动复制你的数据以提供冗余副本,防止硬件错误导致数据丢失。
-
- 自动在节点之间路由,以帮助你找到你想要的数据。
-
- 无缝扩展或者恢复你的集群。
当你在阅读这本书时,你会发现到有关 Elasticsearch 的分布式特性分布式特性的补充章节。在这些章节中你会了解到如何扩展集群以及故障转移(《分布式集群》),如何处理文档存储(《分布式文档》),如何执行分布式搜索(《分布式搜索》)
这一部分不是必须要看的——你不懂它们也能正常使用 Elasticsearch。但是帮助你更加全面完整地了解 Elasticsearch。你也可以在之后需要的时候再回来翻阅它们。
基础操作
索引
/shopping 创建索引(put)
/shopping 获取索引信息(get)
/cat/indices?v 显示索引详细信息(get)
文档
创建
/shopping/_doc 文档创建(post)
/shopping/_create 文档创建(post)
/shopping/_doc/1001 自定义id
{
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
}
查询
/shopping/_doc/1001 根据id获取(get)
/shopping/_search 获取所有数据
{ 匹配'小' '米'所有有关数据
"query":{
"match":{
"category":"小华"
}
}
}
精确所有有关数据
{
"query":{
"match_phrase":{
"category":"小华"
},
"highlight":{
"fields":{
"category":{}
}
}
}
}
修改
/shopping/_doc/1001 全量修改 (put)
{
"first_name" : "John",
"last_name" : "Smith",
"age" : 26,
"about" : "I love to go rock climbing",
}
/shopping/_update/1001 修改 (post)
{
"first_name" : "John",
"last_name" : "Smith",
"age" : 26,
"about" : "I love to go rock climbing",
}
删除
/shopping/_doc/1001 全量修改 (delete)
条件查询
/shopping/_search?q=category:小米 查询 (get)
/shopping/_search 请求体查询 (get)
{
"query":{
"metch":{
"category":"小米"
}
}
}
/shopping/_search 请求体全量查询 (get)
{
"query":{
"metch_all":{
}
}
}
分页
/shopping/_search 请求体全量查询 (get)
{
"query":{
"metch_all":{
}
},
"from":0, 起始位置
"size":2, 每页2条
"_source":["title"] 显示相应的字段
"sort":{
"price":{
"order":"deac"
}
}
}
条件
and
{
"query":{
"bool":{ 条件查询
"must":[ 多个条件同时成立
{
"match":{
"category":"小米"
},
"match":{
"price":1999.00
}
}
]
}
}
}
or
{
"query":{
"bool":{ 条件查询
"should":[
{
"match":{
"category":"小米"
},
"match":{
"price":1999.00
}
}
]
}
}
}
where
{
"query":{
"bool":{ 条件查询
"filter":[
{
"range":{
"price":{
"gt":5000
}
}
}
]
}
}
}
聚合操作
{
"aggs":{ 聚合操作
"price_group":{ 返回结果名称,随便写
"terms":{ 分组
"field":"price" 分组字段
}
}
},
"size":0 返回的原始数据长度
}
求平均值
{
"aggs":{ 聚合操作
"price_avg":{ 返回结果名称,随便写
"avg":{ 分组(平均)
"field":"price" 分组字段
}
}
},
"size":0 返回的原始数据长度
}
映射
/user/_mapping (put)
{
"properties":{
"name":{
"type":"text",
"index":true
},
"sex":{
"type":"keyword",
"index":true
},
"tel":{
"type":"keyword",
"index":false
}
}
}
添加数据
{
"name":"小米",
"sex":"男",
"tel":"2312342341234"
}
type:
keyword 完全匹配
text 分词匹配
index 能否被索引,(没被索引不能查询)