ElasticSearch 是一个分布式,高性能、高可用、可伸缩、RESTful 的搜索和数据分析引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。通常作为 Elastic Stack 的核心来使用。
Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的 RESTful API来隐藏 Lucene的复杂性,从而让全文搜索变得简单。Lucene只是一个库,想要使用它,必须使用Java来作为开发语言并将其直接集成到你的应用中。
RESTful 的四个关键字:PUT(修改),POST(添加),DELETE(删除),GET(查询)。其中在ES里面PUT和POST的界限并不是很分明,有时候PUT也作为添加。
Elastic Stack 大致是如下这样组成的:
ES 是一个近实时(NRT)的搜索引擎,一般从添加数据到能被搜索到只有很少的延迟(大约是1s),而查询数据是实时的。一般我们可以把ES配合logstash,kibana 来做日志分析系统,或者是搜索方面的系统功能。
当 ElasticSearch的节点启动后,它会利用多播(multicast)(或者单播,如果用户更改了配置) 寻找集群中的其它节点,并与之建立连接。这个过程如下图所示:
ELK = Elasticsearch + Logstash + Kibana
ELK 架构为数据分布式存储、可视化查询和日志解析创建了一个功能强大的管理连。三者相互配合,取长补短,共同完成分布式大数据处理工作。
全文检索就是对一篇文章进行索引,可以根据关键字搜索,类似于mysql里的like语句。
全文索引就是把内容根据词的意义进行分词,然后分别创建索引,例如”你们下周末干什么“可能会被分词成: ”你们“,”下周末“,”干什么“ 等,这样当你搜索”你们“或者”下周末“ 都会把这句搜出来。
一个表格将ES和mysql 相关的基本概念进行对比:
ES | MySQL |
---|---|
字段 | 列 |
文档 | 一行记录 |
类型(已废弃) | 表 |
索引 | 数据库 |
对比:
mySQL里的数据其实是数据库下的表里面的一行记录。
ES里的数据其实是指索引下的类型里面的JSON格式的数据。
注意:虽然在实际存储上,文档存在于某个索引里,但是文档必须被赋予一个索引下的类型才可以。
类型就相当于 MySQL 里的表,我们知道MySQL里的一个库下可以有很多表,最原始的时候ES也是这样,一个索引下可以有很多类型,但是从 6.0版本开始,type 已经被逐渐废弃,但是这时候一个索引仍然可以设置多个类型,一直到 7.0版本开始,一个索引就只能创建一个类型了。
ES 可以作为一个独立的单个搜索服务器。不过,为了处理大型数据集,实现容错和高可用性,ES可以运行在许多互相合作的服务器上。这些服务器的集合称为集群。
在ES里面默认有一个配置,clustername 默认值是 ElasticSearch,如果这个值是一样的就属于同一个集群,不一样的值就是不一样的集群。
集群中有多个节点,其中一个为主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来说的。ES的一个概念就是去中心化,字面上理解就是无中心节点,这是对于集群外部来说的,因为从外部来看ES集群,在逻辑上是个整体,你与任何一个节点的通信和与整个ES集群通信是等价的。
-E node.name=repledata
指定,默认是随机分配的。建议自己指定,因为节点名称对于管理目的很重要,我们可以通过节点名称确定网络中的哪些服务器对应于ES集群中的哪些节点;node.master: false
来禁止。Master Eligible 可以参加选主流程,并成为 Master 节点(当第一个节点启动后,它会将自己选为Master节点);注意:每个节点都保存了集群的状态,只有Master节点才能修改集群的状态信息。注意:每个节点默认都起到了 Coordinating node 的职责。一般在开发华宁中一个节点可以承担多个角色,但是在生产环境中,还是设置单一的角色比较好,因为有助于提高性能。
ES 里面的索引可能存储大量数据,这些数据可能会超出单个节点的硬件限制。当有大量的文档时,由于内存的限制、磁盘处理能力不足、无法足够快的响应客户端的请求等,一个节点可能不够。
**为了解决这个问题,ES提供了将索引细分为多个碎片的功能,这就是分片。**简单理解,在创建索引时,只需要定义所需的碎片数量,其实每个分片都可以看作是一个完全功能性和独立的索引,可以托管在集群中的任何节点上。
问题:分片有什么好处和注意事项呢?
- 通过分片技术,可以水平拆分数据量,同时他还支持跨碎片(可能在多个节点上)分布和并行操作,从而提高性能/吞吐量;
- ES可以完全自动管理分片的分配和文档的聚合来完成搜索请求,并且对用户完全透明;
- 主分片数在索引创建时指定,后续只能通过 Reindex修改,但是较麻烦,一般不进行修改。
为了实现高可用、遇到问题时实现分片的故障转移机制,ElasticSearch允许将索引分片的一个或多个复制成所谓的副本分片。
问题:副本分片有什么作用和注意事项呢?
- 当分片或者节点发生故障时提供高可靠性。因此,需要注意的是,副本分片永远不会分配到复制它的原始或主分片所在的节点上(拥有相同数据的分片,是不会在同一个节点上面的);
- 可以提高扩展搜索量和吞吐量,因为ES允许在所有副本上并行执行搜索;提高系统的容错性,当某个节点某个分片损坏或丢失时可以从副本中恢复;提高ES的查询效率,ES 会自动对搜索请求进行负载均衡。
- 副本时一个分片的精确复制,每个分片可以有零个或多个副本。ES中可以有许多相同的分片,其中之一被选择更给索引操作,这种特殊的分片成为主分片。
- 默认情况下,ES 中的每个索引都分配 5 个主分片,并为每个主分片分配1 个副本分片 (5主5副)。主分片在创建索引时指定,不能修改,副本分片可以修改。
- recovery:代表数据恢复或叫数据重新分布,ES在有节点加入或退出时会根据机器的负载均衡对索引分片进行重新分配,挂掉的节点重新启动时也会进行数据恢复。
- river:代表ES的一个数据源,也是其他存储方式(如:数据库)同步数据到ES的一个方法。它是以插件方式存在的一个ES服务,通过读取 river 中的数据并把他索引到ES中,官方的river有couchDB的,Rabbit MQ的,Twitter的,Wikipedia的。
- gateway:代表ES索引快照的存储方式,ES默认是先把索引存放到内存中,当内存满了时在持久化到本地硬盘。gateway对索引快照进行存储,当这个ES集群关闭再重新启动时就会从gateway中读取索引备份数据。ES支持多种类型的gateway,有本地文件系统(默认),分布式文件系统,Hadoop 的HDFS和amazon 的s3云存储服务。
- discovery.zen:代表ES的自动发现节点机制,ES是一个基于p2p的系统,它先通过广播寻找存在的节点,再通过多播协议来进行节点之间的通信,同时也支持点对点的交互。
- Transport:代表ES内部节点或集群与客户端的交互方式,默认内部是使用TCP协议进行交互,同时它支持http协议(json格式)、thrift、servlet、memecached、zeroMQ等传输协议(通过插件方式集成)。
下载网址:https://www.elastic.co/cn/downloads/elasticsearch
第一步:进入下载网址,根据自己需求选择适合的版本安装,在此学习使用7.8.1版本。
第二步:下载后解压,进入bin 目录。
第三步:启动。在bin目录中输入cmd代开命令窗口并输入elasticsearch 或者 直接运行bin目录下的elasticsearch.bat 文件
第四步:成功启动后,在浏览器中输入“127.0.0.1:9200” 或者 “localhost:9200”,出现“You Know, for Search” 说明启动成功。
下载地址:https://www.elastic.co/cn/downloads/past-releases#kibana
第一步:进入下载网址,根据自己需求选择适合的版本安装,在此与elasticsearch保持一致,选择7.8.1版本。
第二步:下载后解压,进入bin 目录。
第三步:直接运行bin目录下的kibana.bat 文件。
第四步:成功启动后,在浏览器中输入“127.0.0.1:5601” 后,会自动拼接为“http://127.0.0.1:5601/app/kibana#/home”,界面如下:(注意:启动kibana前,先把elasticsearch跑起来)
学习期间,只要使用 Dev Tools就可
点击后,出现如下界面:
ES 是 RESTful风格 的系统,所以需要先掌握RESTful 的四个关键词:PUT(修改),POST(添加),DELETE(删除),GET(查询)。其中在ES里面PUT和POST的界限不是很分明,有时候PUT也作为添加。
创建一个0副本2分片的bjydemo索引,如下:
PUT /bjydemo
{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 0
}
}
运行结果如下:
如果对刚才创建的索引副本数量不满意,可以进行修改,注意:分片不允许修改。
PUT bjydemo/_settings
{
"number_of_replicas": 2
}
运行结果如下:
当这个索引不想用了,可以进行删除,执行如下命令即可。
DELETE /bjydemo
删除索引前后可以通过GET _cat/indices
查看索引。
插入数据的时候可以指定id,如果不指定的话,ES会自动帮我们生成。
以指定id 为例,如下代码创建了一个101的文档
// 指定id
POST /bjydemo/_doc/101
{
"id":1,
"name":"鸡哥",
"hobby":"唱 跳 rap 篮球",
"message":"练习两年半"
}
注意:
- 这里的指定id 不是指{ }里面的id。这里的id包括后面查询或者删除时候用到的id 是创建文档时候指定或者ES自动生成的那个id,那个是唯一id,即上述示例中的101。
- 插入数据的时候,如果我们的语句中指明了index和id,如果ES里面不存在,默认帮我们自动创建
运行结果如下:
不指定id 如下所示:
POST /bjydemo/_doc/
{
"id":1,
"name":"小鸡哥",
"hobby":"背带裤 篮球",
"message":"鸡你太美"
}
运行结果如下:
需要注意的是,ES里的文档是不可以修改的,但是可以覆盖,所以ES修改数据本质上是对文档的覆盖。ES对数据的修改分为 全局更新 和 局部更新。
PUT /bjydemo/_doc/101
{
"id":1,
"name":"鸡哥",
"hobby":"唱 跳 rap 篮球",
"message":"两年半的练习生"
}
多次执行全局更新,发现每次全局更新之后这个文档的_version
都会发生改变。
POST /bjydemo/_doc/101
{
"doc":{
"message":"食不食香精煎鱼"
}
}
多次执行局部更新,发现除了第一次执行,后续不管又执行了多少次,_version
都不再变化。
(注意:7.8.1版本的自己测试感觉POST也是全局更新,会把原来的数据覆盖)
问题:局部更新的时候ES的底层流程是怎样的?和全局更新相比性能怎么样?
- 内部先获取到对应的文档;
- 将传递过来的字段更新到文档的json中(这一步实质上也是一样的);
- 将老的文档标记为deleted(到了一定时候才会物理删除);
- 将修改后的新的文档创建出来。
性能对比:
- 全局更新本质上是替换操作,即使内容一样也会去替换;
- 局部更新本质上是更新操作,只有遇到新的东西才更新,没有新的修改就不更新;
- 局部更新比全局更新的性能好,因此推荐使用局部更新。
比如把 bjydemo索引下的id为101 的文档删除,可以使用如下命令:
DELETE /bjydemo/_doc/101
1、GET全局搜索数据:
GET /bjydemo/_search
2、match_all 全局搜索数据,可以加各种条件,比如排序:
POST /bjydemo/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"id": {
"order": "desc"
}
}
]
}
问题:查询出来的字段都是什么含义呢?
- took :Elasticsearch 运行查询需要多长时间(以毫秒为单位);
- timed_out :搜索请求是否超时;
- _shards :搜索了多少碎片,并对多少碎片成功、失败或跳过进行了细分;
- max_score :找到最相关的文档的最高得分;
- hits.total.value :找到了多少匹配的文档;
- hits.sort :文档排序后的位置
- hits_score :文档的相关性评分(在使用match_all时不适用)
GET /bjydemo/_doc/101
比如查找bjydemo索引下 ,hobby字段包含“篮球”的数据:
GET /bjydemo/_search?q=hobby:"篮球"
注意:使用高亮查询,会对要查询的数据进行分词搜索。
有时候我们想要把查询到的数据进行高亮显示,比如查到“鸡哥”后,想把name这个字段的数据高亮显示,如下:
POST /bjydemo/_search
{
"query": {
"match": {
"name": "鸡哥"
}
},
"highlight": {
"fields": {
"name": {}
}
}
}
简单来说,DSL就是ES的一种查询方式,DSL基于JSON实现了直观简单的结构化查询功能。由于DSL查询是JSON格式的,所以更加的灵活,而且可以同时包含查询和过滤器,可以很轻松的构造出复杂的查询功能。
term 查询需要完全匹配,不会对词汇进行分词器分析。主要用于查询精准匹配的值,比如数字,日期,布尔值或未经分析的文本数据类型的字符串(not_analyzed)。
比如查询id
字段为3 的数据(注意:这里的id不是文档id,是文档里的自己命名为id的字段):
POST /bjydemo/_search
{
"query": {
"term": {
"id": "3"
}
}
}
terms和term 有点类似,但 terms 允许指定多个匹配条件。如果某个字段指定了多个值,那么文档需要一起去做匹配。
比如查询id
字段为2和3的数据:
POST /ropledata/_search
{
"query": {
"terms": {
"id": [3,4]
}
}
}
range 主要用于过滤,通常用于按照指定范围查找一批数据,关键词如下:
比如查询id
字段大于等于5且小于10的数据:
POST /bjydemo/_search
{
"query": {
"range": {
"id": {
"gte": 5,
"lt": 10
}
}
}
}
exits 查询类似sql里的 is null 条件,通常用于查找文档中是否包含指定字段,包含这个字段就返回这条数据。
比如查询ropledata索引下,包含message
这个字段的数据:
POST /bjydemo/_search
{
"query": {
"exists": {
"field": "message"
}
}
}
match 查询是一个标准查询,不管是全文本查询还是精确查询基本上都要用到它。 在使用 match 查询一个全文本字段时,它会在真正查询之前用 分析器 先分析match一下查询字符;如果用match下指定了一个确切值,在遇到数字,日期,布尔值或者 not_analyzed 的字符串时,它将为你搜索你给定的值。
比如查询hobby
是”篮球“的数据:
POST /bjydemo/_search
{
"query": {
"match": {
"hobby": "篮球"
}
}
}
match_phrase 和 match 类似,在查询时都会对查询词进行分词,但是match会忽略查询词的空格,而 match_phrase 不会。因此 需要注意的是:查询包含空格的字符串,要用match_phrase!
比如查询hobby
是 “唱 跳 rap 篮球“的数据(注意:查询数据包含空格):
POST /bjydemo/_search
{
"query": {
"match_phrase": {
"hobby": "唱 跳 rap 篮球"
}
}
}
bool 查询可以用来合并多个条件查询结果的布尔逻辑,操作符关键字如下:
注意:这些参数可以分别继承一个查询条件或者一个查询条件的数组。
比如查询hobby
必须为篮球,id
必须不是1,message
可以是”食不食油饼“ 的数据
POST /bjydemo/_search
{
"query": {
"bool": {
"must": {
"match":{
"hobby":"篮球"
}
},
"must_not": {
"term":{
"id":1
}
},
"should": [
{
"term": {
"message": "食不食油饼"
}
}
]
}
}
}
filter 用于过滤查询,通常和 bool 连用,就像编程语言一样,用于过滤数据。
比如查询hobby
为”rap“的数据:
POST /bjydemo/_search
{
"query": {
"bool": {
"filter": {
"term": {
"hobby": "rap"
}
}
}
}
}
常用的就是如上所述,它们一般是融合在一起使用的。
比如查询"id"必须不是3,“hobby”包含“篮球”,同时“id”大于等于2小于等于5的数据:
POST /bjydemo/_search
{
"query": {
"bool": {
"filter": {
"range": {
"id": {
"gte": 2,
"lte": 5
}
}
},
"must_not": {
"term": {
"id":3
}
},
"must": {
"match" : {
"hobby":"篮球"
}
}
}
}
}
比如求id
的平均值,有两种写法,基础写法和脚本写法。
基础写法:
POST /bjydemo/_search
{
"aggs": {
"bjydemo": {
"avg": {
"field": "id"
}
}
},
"size": 0
}
脚本写法:
POST /bjydemo/_search
{
"aggs": {
"bjydemo": {
"avg": {
"script": {
"source": "doc.id.value"
}
}
}
},
"size": 0
}
问题:求平均值或者求和时,为什么要加 (”size”: 0)呢?
size用来控制返回多少条数组。我们时想要在所有文档里求平均值求和,所以要用size来控制返回一个数据即可,不然ES还会默认返回10条数据。
涉及到聚合查询的场景,如果需要去重的话,EStigon提供了cardinality 去重统计函数来解决这个问题。
比如想根据“id" 字段去重统计:
POST /bjydemo/_search
{
"aggs": {
"bjydemo": {
"cardinality": {
"field": "id"
}
}
},
"size": 0
}
涉及到统计个数的情景,可以使用 value_count。
比如需要统计有多少条数据:
POST /bjydemo/_search
{
"aggs": {
"bjydemo": {
"value_count": {
"field": "id"
}
}
},
"size": 0
}
terms 词聚合可以基于给定的字段,并按照这个字段对应的每一个数据为一个桶,然后计算每个桶里的文档个数。默认会按照文档的个数排序。
比如要根据’id’字段进行词聚合:
POST /bjydemo/_search
{
"aggs": {
"bjydemo": {
"terms": {
"field": "id"
}
}
}
}
当我们使用sql时可以很方便的处理top问题,ES也提供了对应的支持,top_hits 就是这样的函数,一般和terms 连用,可以获取到每组前n条数据。
比如想要根据"id"分组,然后拿到前 6 条数据:
POST /bjydemo/_search
{
"aggs": {
"bjydemo": {
"terms": {
"field": "id"
},
"aggs": {
"count": {
"top_hits": {
"size": 6
}
}
}
}
},
"size": 0
}
除了DSL查询时介绍的gt,lt函数,其实在聚合查询里还提供了range 用来进行范围查询。
比如查询id
字段的值在 6-9 之间 和 10-20 之间的文档有多少:
POST /bjydemo/_search
{
"aggs": {
"group_by_id": {
"range": {
"field": "id",
"ranges": [
{
"from": 6,
"to":9
},
{
"from": 10,
"to": 20
}
]
}
}
},
"size": 0
}
ES也提供了批量的操作,具体的用法如下。
POST _bulk
{ "create": { "_index" : "bjydemo", "_id" : "1001" } }
{"id": 12,"name": "白居不易","message": "攒钱买摩托"}
{ "create": { "_index" : "bjydemo", "_id" : "1002" } }
{"id": 13,"name": "白居_不易","message": "先买头盔后买车"}
{ "create": { "_index" : "bjydemo", "_id" : "1003" } }
{"id": 14,"name": "美团骑手","message": "一年又一年 一天又一天"}
{ "create": { "_index" : "bjydemo", "_id" : "1004" } }
{"id": 15,"name": "饿了么骑手","message": "摩托"}
比如想批量查询bjydemo索引下 文档id 为1002,1003,1004的文档数据,可以这样写:
POST /bjydemo/_mget
{
"ids": [
"1002",
"1003",
"1004"
]
}
比如想要批量修改1002,1003的文档里的name字段的值,可以这样写:
POST _bulk
{ "update" : {"_id" : "1002", "_index" : "bjydemo"} } { "doc" : {"name" : "批量更新1"} }
{ "update" : {"_id" : "1003", "_index" : "bjydemo"} } { "doc" : {"name" : "批量更新2"} }
(注意:7.8.1版本批量操作中,执行会报出” unknown field [update]“ )
比如想要批量删除文档id 为 1003 和1004 的文档,可以这样写:
POST _bulk
{ "delete" : { "_index" : "bjydemo", "_id" : "1003" } }
{ "delete" : { "_index" : "bjydemo", "_id" : "1004" } }
1、查看文档是否存在
有时候为了防止报错,在查询之前,需要查看这个文档是否存在,这时候只需要用到HEAD
关键字就可以了。
HEAD /ropledata/_doc/1001
2、查看集群的健康状况
GET _cat/health
green:每个索引的primary shard 和 replica shard 都处于 active状态。
yellow:每个索引的primary shard 是 active 的状态,但是部分replica shard 不是active的状态,处于不可用的状态。
3、查看ES中的index
GET _cat/indices
上述命令可能查询的不全,可以使用下面的命令
GET _all