安装好ElasticSearch及其Kibana,Marvel,Sense插件之后,就可以简单的使用ES了.
1. 使用简述
使用ES,是基于HTTP协议以及JSON为数据交互格式的.
使用Linux终端命令curl对ES进行HTTP请求,格式:
curl -X<VERB> '<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>' -d '<BODY>'
其中,
VERB,是HTTP请求的方法,有GET,POST,PUT,HEAD,DELETE.
PROTOCOL,有http,或者https.
HOST,部署在ES集群系统中的主机地址,本地节点使用localhost
PORT,ES服务器所使用的端口号,默认为9200
PATH,路径,不同的路径对应不同的功能,例如_count返回集群中文件的数量
QUERY_STRING,可选的查询参数,例如pretty参数,可以使返回的数据更加美观,在linux终端中可以体现.
BODY,是基于JSON格式的请求体.
例如,在linux终端输入命令:
curl -XGET 'http://localhost:9200/_count?pretty' -d '
{
"query": {
"match_all": {}
}
}
'
该命令查询集群中文件的数量,ES返回一个类似于200 OK的HTTP状态码,以及基于JSON格式的响应主体,响应主体如下:
{
"count" : 32330,
"_shards" : {
"total" : 4,
"successful" : 4,
"failed" : 0
}
}
通过在curl命令加参数i,可以得到响应的头部和主体:
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 99
{
"count" : 32700,
"_shards" : {
"total" : 4,
"successful" : 4,
"failed" : 0
}
}
在Sense中对ES进行交互时,命令格式简写如下:
GET /_count
{
"query": {
"match_all": {}
}
}
2. 面向文档及序列化
ElasticSearch是面向文档的,它可以存储整个对象或文档,而不是是像传统数据库一样只能存储行列的表,不仅仅是存储,ES还会索引每个文档的内容使之可以被搜索.
在ES中,可以对文档进行索引,检索,排序,过滤.这种理解数据的方式与以往完全不同,这也是ES能够执行复杂的全文搜索的原因之一.
ES对文档进行序列化,使用Java对象符号JSON(JavaScript Object Notation)作为文档序列化后的格式.
几乎所有的语言都有相应的模块将任意数据结构转换为JSON格式.
通过例子,介绍概念:索引indexing,搜索search以及聚合aggregations
例:为公司Megacorp创建一个员工目录,实现如下需求:
数据能够包含多个值的标签、数字和纯文本。
检索任何员工的所有信息。
支持结构化搜索,例如查找30岁以上的员工。
支持简单的全文搜索和更复杂的短语(phrase)
搜索高亮搜索结果中的关键字
能够利用图表管理分析这些数据
3. 索引
首先,中文索引分两个含义:名词和动词.名词对应英文的index,动词对应英文的indexing.
ES存储中是按照:索引index->类型type->文档document->字段field 级别层次划分的.
这可以和传统的数据库做类比:数据库Database->表Table->行,列
Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices -> Types -> Documents -> Fields
ES集群可以包含多个索引,每个索引可以包含多个类型,每个类型可以包含多个文档,每个文档可以包含多个字段.
动词索引,索引一个文档,表示把一个文档存储到索引Index里,可以用来查询和检索,
索引一个文档时,如果文档已经存在,新的文档将覆盖旧的文档
默认情况下,文档中的所有字段都会被索引,只有这样才可以被用来检索.ES使用一种称为"倒排索引"的数据结构,加速ES的检索,如同传统数据库中,为数据表的列增加索引一样.
现在执行例子,根据需求,作出如下操作:
为每个员工的文档建立索引,每个文档中包含了员工的所有信息;
定义每个文档所属的类型Type为employee;
employee类型归属与索引megacorp;
megacorp索引存储在ES集群中;
通过使用PUT的方式,为ES集群添加文档,加入三个员工的文档:
员工1
PUT /megacorp/employee/1
{
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
员工2,3
PUT /megacorp/employee/2
{
"first_name" : "Jane",
"last_name" : "Smith",
"age" : 32,
"about" : "I like to collect rock albums",
"interests": [ "music" ]
}
PUT /megacorp/employee/3
{
"first_name" : "Douglas",
"last_name" : "Fir",
"age" : 35,
"about": "I like to build cabinets",
"interests": [ "forestry" ]
}
在使用 /megacorp/employee/1 这样的层次方式中,也就定义了索引,类型,文档的信息.
4. 搜索
现在ES集群中已经存储了一些员工的信息,下边进行各种业务的需求的操作.
检索单个员工的信息,也就是检索一个文档,使用HTTP GET请求+文档地址:索引/类型/文档 的方式:
GET /megacorp/employee/1
返回原始的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" ]
}
}
通过GET方法获取文档,通过DELETE请求删除文档,HEAD请求检查文档是否存在,更新文档只需再PUT一次.
如果需要返回所有员工的文档,使用_search替代GET方法中的文档名:
GET /megacorp/employee/_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"的员工信息,通过为HTTP请求加参数的方式,成为查询字符串query string的搜索
GET /megacorp/employee/_search?q=last_name:Smith
q=字段:检索值
响应:
{
"took": 58,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 0.30685282,
"hits": [
{
"_index": "megacorp",
"_type": "employee",
"_id": "2",
"_score": 0.30685282,
"_source": {
"first_name": "Jane",
"last_name": "Smith",
"age": 32,
"about": "I like to collect rock albums",
"interests": [
"music"
]
}
},
{
"_index": "megacorp",
"_type": "employee",
"_id": "1",
"_score": 0.30685282,
"_source": {
"first_name": "John",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [
"sports",
"music"
]
}
}
]
}
}
DSL语句查询
DSL(Domian in Specific Language特定领域语言)以JSON格式请求体的形式.
使用DSL替代上边查询字符串的方法,查询信息写在请求体内:
GET /megacorp/employee/_search
{
"query" : {
"match" : {
"last_name" : "Smith"
}
}
}
query , match都是特定的语句.
结构化查询
稍微复杂点的查询,得到年龄大于30岁的姓氏为Smith的员工,使用过滤器filter,进行结构化搜索:
GET /megacorp/employee/_search
{
"query" : {
"filtered" : {
"filter" : {
"range" : {
"age" : { "gt" : 30 } <1>
}
},
"query" : {
"match" : {
"last_name" : "smith" <2>
}
}
}
}
}
可以看出这样的语句层次:query->filtered->filter+query
其中,filter部分属于区间过滤器,用于查询所有年龄大于30岁的员工,"gt"为"greater than"的缩写
filter后的query->match语句就是在信息经过过滤器之后,进行匹配查询.
响应结果,只得到了一个年龄为32岁的姓氏Smith的员工:
{
"took": 15,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 0.30685282,
"hits": [
{
"_index": "megacorp",
"_type": "employee",
"_id": "2",
"_score": 0.30685282,
"_source": {
"first_name": "Jane",
"last_name": "Smith",
"age": 32,
"about": "I like to collect rock albums",
"interests": [
"music"
]
}
}
]
}
}
全文搜索
全文搜索是一种更高级的搜索,传统数据库很难实现这样的功能.
搜索所有喜欢"rock climbing"的员工,使用之前的qury->match语句,为about字段设定检索值:
GET /megacorp/employee/_search
{
"query" : {
"match" : {
"about" : "rock climbing"
}
}
}
响应结果给出了两个匹配文档:
{ ...
"hits": {
"total": 2,
"max_score": 0.16273327,
"hits": [
{
"_index": "megacorp",
"_type": "employee",
"_id": "1",
"_score": 0.16273327,
"_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": 0.016878016,
"_source": {
"first_name": "Jane",
"last_name": "Smith",
"age": 32,
"about": "I like to collect rock albums",
"interests": [
"music"
]
}
}
]
}
}
默认情况下,ES根据相关性评分对结果集进行排序,相关性就是文档与查询条件匹配的程度.John Smith和Jane Smith都在结果集中,John的about字段因为有"rock climbing",所以其相关性分数_score最高,而Jane中的about字段提到了"rock",所以也被检索出,但相关性低.
相关性的概念在ES中非常重要.
短语搜索:前边通过语句match进行,有时候需要精确匹配若干个单词或者一个短语phrase,使用match_phrase语句.
查询同时包含rock和climbing的(并且是相邻的)员工记录:
GET /megacorp/employee/_search
{
"query" : {
"match_phrase" : {
"about" : "rock climbing"
}
}
}
那么就只有John被匹配:
{
...
"hits": {
"total": 1,
"max_score": 0.23013961,
"hits": [
{
"_index": "megacorp",
"_type": "employee",
"_id": "1",
"_score": 0.23013961,
"_source": {
"first_name": "John",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [
"sports",
"music"
]
}
}
]
}
}
高亮关键字很多应用需要在搜索结果中高亮highlight匹配到的关键字,这样用户可以知道为什么这些文档和查询相匹配.
ES中通过highlight语句的方式高亮片段:
GET /megacorp/employee/_search
{
"query" : {
"match_phrase" : {
"about" : "rock climbing"
}
},
"highlight": {
"fields" : {
"about" : {}
}
}
}
响应结果中,增加了highlight字段,这里包含了about字段的文本,并用<em></em>来标识匹配到的单词.
{
...
"hits": {
"total": 1,
"max_score": 0.23013961,
"hits": [
{
"_index": "megacorp",
"_type": "employee",
"_id": "1",
"_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>"
]
}
}
]
}
}
5. 聚合
聚合aggregation是指对搜索的结果进行分析,得到需要的统计数据,就像传统数据库中GROUP BY语句一样.
查询所有员工中最大的共同兴趣爱好是什么:
GET /megacorp/employee/_search
{
"aggs": {
"all_interests": {
"terms": { "field": "interests" }
}
}
}
暂时忽略不理解的语法,后边逐渐学习,现主要直观的认识ES所提供的功能.
响应结果不但返回匹配的文档信息,还对文档信息进行了分析,分析结果在aggregations字段中:
{
...
"hits": {
"total": 3,
"max_score": 1,
"hits": [
{
"_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"
]
}
},
{
"_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": "3",
"_score": 1,
"_source": {
"first_name": "Douglas",
"last_name": "Fir",
"age": 35,
"about": "I like to build cabinets",
"interests": [
"forestry"
]
}
}
]
},
"aggregations": {
"all_interests": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "music",
"doc_count": 2
},
{
"key": "forestry",
"doc_count": 1
},
{
"key": "sports",
"doc_count": 1
}
]
}
}
}
从aggregations字段中,可以看出喜欢music的职员有2个,喜欢forestry的一个,喜欢sports的一个
如果我们想知道所有姓Smith的职员中,兴趣爱好的分析,只需在查询Smith的语句后,加上聚合语句:
GET /megacorp/employee/_search
{
"query": {
"match": {
"last_name": "smith"
}
},
"aggs": {
"all_interests": {
"terms": {
"field": "interests"
}
}
}
}
分级汇总
分级汇总,在聚合一层次之后,再进行聚合.
查询所有职员的兴趣爱好,并得到每种兴趣爱好的职员的平均年龄:
GET /megacorp/employee/_search
{
"aggs" : {
"all_interests" : {
"terms" : { "field" : "interests" },
"aggs" : {
"avg_age" : {
"avg" : { "field" : "age" }
}
}
}
}
}
响应结果:
{
...
"hits": {
...
},
"aggregations": {
"all_interests": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"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
}
}
]
}
}
}
从结果看,爱好music的员工有2个,这两个人的平均年龄为28.5.
ES这个特性可以完成相当复杂的聚合工作.
6. 分布式
ES可以扩展到上百甚至上千的数量的服务器上,处理PB级别的数据.
ES为分布式而生,它的设计隐藏了分布式本身的复杂性.
Elasticsearch在分布式概念上做了很大程度上的透明化,在教程中你不需要知道任何关于分布式系统、分片、集群发现或者其他大量的分布式概念。
所有的教程你即可以运行在你的笔记本上,也可以运行在拥有100个节点的集群上,其工作方式是一样的。
ES致力于隐藏分布式系统的复杂特性,这些操作都是在底层完成的:
1. 将文档分区到不同的容器或者分片Shards中,它们可以存储在一个或多个节点中.
2. 将分片均匀的分配到各个节点中,对索引和搜索做负载均衡
3. 冗余每一个分片,防止因硬件损坏造成的数据丢失
4. 将集群中任意一个节点上的请求,路由到相应数据所在的节点
5. 无论是增加节点,还是移除节点,分片都能做到无缝的扩展和迁移
关于分布式的内容,教给你如何扩展集群,故障转移,处理文档存储,执行分布式搜索,分片是什么以及如何工作.
这些关于分布式的内容不是必读的,这些都是内部机制,不懂这些内部机制也可以使用ES,但是这些机制可以是你更深入和完整的了解ES.
可以暂时忽略它们,在需要深入理解的时候,再回头翻阅.