elasticsearch
Posted 你觉得为时已晚的时候就是最早的时候
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了elasticsearch相关的知识,希望对你有一定的参考价值。
ElasticSearch是一个分布式,高性能、高可用、可伸缩、RESTful 风格的搜索和数据分析引擎
ES是一个近实时(NRT)的搜索引擎,一般从添加数据到能被搜索到只有很少的延迟(大约是1s),而查询数据是实时的。一般我们可以把ES配合logstash(数据分析和转化后传给es),kibana(可视化)来做日志分析系统,或搜索方面的系统功能,比如在网上商城系统里实现搜索商品的功能也会用到ES。
搜索商品的时候为啥要用ES呢?用sql的like进行模糊查询不香吗?
我们要买苹果吃,咱们想买天水特产的花牛苹果,然后在搜索框输入天水花牛苹果,这时候咱们希望搜索到所有的售卖天水花牛苹果的商家,但是如果咱们技术上根据这个天水花牛苹果使用sql的like模糊查询,是不能匹配到诸如天水特产花牛苹果,天水正宗,果园直送精品花牛苹果这类店铺的。所以sql的like进行模糊查询来搜索商品还真不香
整理了一下ES和mysql相关的基本概念的对比表格:
ES MySql
字段 列
文档 一行数据
类型(已废弃) 表
索引 数据库
所谓ES里的数据其实就是指索引下的类型里的JSON格式的数据
文档
Elasticsearch是面向文档的,也就是说文档是所有可搜索数据的最小单元。ES的文档就像MySql中的一条记录,只是ES的文档会被序列化成json格式
这个json对象是由字段组成,字段就相当于Mysql的列,每个字段都有自己的类型(字符串、数值、布尔、二进制、日期范围类型)创建文档时,如果不指定字段的类型,会帮我们自动匹配类型
每个文档都有一个ID,可以自己指定,也可以让Elasticsearch自动生成
类型
类型就相当于MySql里的表,我们知道MySql里一个库下可以有很多表,最原始的时候ES是这样,一个索引下可以有很多类型,但是从6.0版本开始,type已经被逐渐废弃,但是这时候一个索引仍然可以设置多个类型,一直到7.0版本开始,一个索引就只能创建一个类型(_doc)
索引
索引就相当于MySql里的数据库
在单个集群中,可以定义任意多个索引
节点
一个节点就是一个ES实例,其实本质上就是一个java进程
节点的名称可以通过配置文件配置,或者在启动的时候使用-E node.name=ropledata指定,默认是随机分配的
ES的节点类型主要分为如下几种:
Master Eligible节点:每个节点启动后,默认就是Master Eligible节点,可以通过设置node.master: false 来禁止。Master Eligible可以参加选主流程,并成为Master节点(当第一个节点启动后,它会将自己选为Master节点);注意:每个节点都保存了集群的状态,只有Master节点才能修改集群的状态信息
Data节点:可以保存数据的节点。主要负责保存分片数据,利于数据扩展。
Coordinating 节点:负责接收客户端请求,将请求发送到合适的节点,最终把结果汇集到一起
每个节点默认都起到了Coordinating node的职责。一般在开发环境中一个节点可以承担多个角色,但是在生产环境中,还是设置单一角色比较好,有助于提高性能。
分片
了解分布式或者学过mysql分库分表的应该对分片的概念比较熟悉,ES里面的索引可能存储大量数据,这些数据可能会超出单个节点的硬件限制。
ES提供了将索引细分为多个碎片的功能,这就是分片。这里咱们可以简单去理解,在创建索引时,只需要咱们定义所需的碎片数量就可以了,每个分片都可以看作是一个完全功能性和独立的索引,可以托管在集群中的任何节点上。
分片有什么好处和注意事项呢?
通过分片技术,咱们可以水平拆分数据量,同时它还支持跨碎片(可能在多个节点上)分布和并行操作,从而提高性能/吞吐量;
主分片数在索引创建时指定,后续只能通过Reindex修改,但是较麻烦,一般不进行修改。
副本分片
为了实现高可用、遇到问题时实现分片的故障转移机制,ElasticSearch允许将索引分片的一个或多个复制成所谓的副本分片。
当分片或者节点发生故障时提供高可用性 副本分片永远不会分配到复制它的原始或主分片所在的节点上;可以提高扩展搜索量和吞吐量,因为ES允许在所有副本上并行执行搜索;
默认情况下,ES中的每个索引都分配5个主分片,并为每个主分片分配1个副本分片。主分片在创建索引时指定,不能修改,副本分片可以修改。
咱们如果想很爽的使用ES,需要安装3个东西:ES、Kibana、ElasticSearch Head。通过Kibana可以对ES进行便捷的可视化操作,通过ElasticSearch Head可以查看ES集群的状态及数据
docker search elasticsearch
docker pull nshou/elasticsearch-kibana
docker run -d -p 9200:9200 -p 9300:9300 -p 5601:5601 --name eskibana nshou/elasticsearch-kibana
ElasticSearch Head,它是一个浏览器的扩展程序,直接在chrome浏览器扩展程序里下载安装即可.
验证ES:打开浏览器,输入IP:端口,比如我的:http://127.0.0.1:9200/,然后就看到那句经典:You Know, for Search:
验证Kibana:打开浏览器,输入Kibana的IP:端口,比如我的:http://127.0.0.1:5601/,咱们学习期间只要使用右下角那个扳手形状的Dev Tools就可以了,点击后会出现一界面,可在上面可视化操作
ES 是RESTful 风格的系统,所以我们需要先掌握RESTful 的四个关键词:PUT(修改),POST(添加),DELETE(删除),GET(查询)。其中在ES里面PUT和POST的界限并不是很分明,有时候PUT也作为添加。
咱们创建了一个0副本2分片的ropledata索引
PUT /ropledata
“settings”:
“number_of_shards”: “2”,
“number_of_replicas”: “0”
修改副本数
咱们如果对刚才创建的索引副本数量不满意,可以进行修改,注意:分片不允许修改。
PUT ropledata/_settings
“number_of_replicas” : “2”
删除索引
DELETE /ropledata
插入数据
插入数据的时候可以指定id,如果不指定的话,ES会自动帮我们生成。如下代码是我们创建了一个101的文档,创建成功后,可以在Elasticsearch Head看到这些数据
POST /ropledata/_doc/101
“id”:1,
“name”:“且听_风吟”,
“page”:“https://ropledata.blog.csdn.net”,
“say”:“欢迎点赞,收藏,关注,一起学习”
修改数据
这里大家要特别注意,ES里的文档是不可以修改的,所以ES修改数据本质上是对文档的覆盖。ES对数据的修改分为全局更新和局部更新,对比:
全局更新
PUT /ropledata/_doc/101
“id”:1,
“name”:“且听_风吟”,
“page”:“https://ropledata.blog.csdn.net”,
“say”:“再次欢迎点赞,收藏,关注,一起学习”
多全局更新几次,会发现每次全局更新之后这个文档的_version都会发生改变!
局部更新
POST /ropledata/_update/101
“doc”:
“say”:“奥力给”
多次去执行上面的局部更新代码,会发现除了第一次执行,后续不管又执行了多少次,_version都不再变化
局部更新的底层流程:
内部先获取到对应的文档;
将传递过来的字段更新到文档的json中(这一步实质上也是一样的);
将老的文档标记为deleted(到一定时候才会物理删除);
将修改后的新的文档创建出来。
全局更新本质上是替换操作,即使内容一样也会去替换;
局部更新本质上是更新操作,只有遇到新的东西才更新,没有新的修改就不更新;
查询数据
GET /ropledata/_doc/101
删除数据
我们想把ropledata索引下的id为101的文档删除
DELETE /ropledata/_doc/101
查询或者删除的时候指定的ID是文档里面的字段id吗?
不是的,这点容易混淆,查询或者删除时候用到的ID是创建文档时候指定或者ES自动生成的那个id,而不是文档里面的那个叫id 字段!文档里面的文档字段是可以没有id 的。
咱们知道Elasticsearch之所以模糊查询这么快,是因为采用了倒排索引,而倒排索引的核心就是分词,把text格式的字段按照分词器进行分词并编排索引。
可以发现,这些内置分词器擅长处理单词和字母, 那处理中文效果怎么样呢?
内置分词器对中文的局限性
首先咱们创建一个索引,并批量插入一些包含中文和英文的数据:
// 批量插入数据
POST _bulk
“create” : “_index” : “ropledata”, “_id” : “1001”
“id”:1,“name”: “且听风吟”,“hobby”: “music and movie”
“create” : “_index” : “ropledata”, “_id” : “1002”
“id”:2,“name”: “静待花开”,“hobby”: “music”
“create” : “_index” : “ropledata”, “_id” : “1003”
“id”:3,“name”: “大数据”,“hobby”: “movie”
“create” : “_index” : “ropledata”, “_id” : “1004”
“id”:4,“name”: “且听_风吟”,“hobby”: “run”
使用iterm查询匹配的数据,分别对比中文英文:
首先咱们查询爱好包含 ”music“ 的用户数据,根据咱们之前录入的数据,应该返回第一条和第二条才对,代码如下:
POST /ropledata/_search
“query” :
“term” :
“hobby” : “music”
很顺利的就查出来咱们期望的数据,所以在英文词汇下,即使是默认的分词器Standard也够用了。
然后咱们试一下查找名字包含 “风吟” 的用户,理想情况下,应该能返回第一条和第四条数据才对
POST /ropledata/_search
“query” :
“term” :
“name” : “风吟”
查中文词汇居然什么都没有匹配到,好奇怪呀!
为什么在默认分词器下,不能查找到词汇呢?
内置分词器是没有考虑到这类情况的,它们切分汉字时就会全部切分成单个汉字了,因此咱们找不到“风吟”这条数据,但是可以找到“风”这条数据
如果想匹配到某条数据而不想让它分词,需要使用keyword,这样对应的text就会作为一个整体来查询:
POST /ropledata/_search
“query” :
“term” :
“name.keyword” : “且听风吟”
有一个更简便的方法,就是利用Elasticsearch的_analyze,比如咱们想看“且听风吟”被默认分词器standard分词后的效果,只需要执行如下代码:
POST /_analyze
“analyzer”:“standard”,
“text”:“且听风吟”
这样看就更加简单直观了,可以看到不同分词器对text格式数据的分词结果
为了解决中文分词的问题,接下来在docker环境下把IK分词器装一下。
ik分词器的名字可以使用:ik_smart , ik_max_word:
POST /_analyze
“analyzer”: “ik_max_word”,
“text”: “且听风吟”
可以发现 分为了 且听 和 风吟
创建一个索引
PUT /ropledata
“settings”:
“index”:
“number_of_shards”: “2”,
“number_of_replicas”: “0”
,
“mappings”:
“properties”:
“id”:
“type”: “integer”
,
“name”:
“type”: “text”,
“analyzer”: “ik_max_word”
,
“hobby”:
“type”: “text”
POST _bulk
“create” : “_index” : “ropledata”, “_id” : “1001”
“id”:1,“name”: “且听风吟,静待花开”,“hobby”: “music and movie”
“create” : “_index” : “ropledata”, “_id” : “1002”
“id”:2,“name”: “且听_风吟”,“hobby”: “music”
“create” : “_index” : “ropledata”, “_id” : “1003”
“id”:3,“name”: “大数据领域”,“hobby”: “movie”
“create” : “_index” : “ropledata”, “_id” : “1004”
“id”:4,“name”: “一起学习”,“hobby”: “run”
首先验证一下英文默认分词效果,查找hobby包含music的数据:
POST /ropledata/_search
“query”:
“match”:
“hobby”: “music”
然后验证一下中文ik分词器效果,查找name包含风吟的数据:
POST /ropledata/_search
“query”:
“match”:
“name”: “风吟”
ik分词器是根据什么来分词的呢?如果有些特殊的词汇比如人名,店名,网名,想根据自己的要求特殊处理来分词,能不能解决呢?
ik分词器本身维护了一个超大的词汇文本,里面有非常多的中文词汇。这个文件在ik/config/下,名为main.dic如果要根据自己的特殊词汇来切分,咱们可以把想要切分成的词汇加入到这个文件里面。
搜索全部数据(默认展示10条数据)
GET全局搜索数据:
GET /ropledata/_search
match_all全局搜索数据,可以加各种条件,比如排序:
POST /ropledata/_search
“query”:
“match_all”:
,
“sort”: [
“id”:
“order”: “asc”
]
查询后的结果:4条结果(因为我只添加了四个文档)
查询出来的字段都是什么含义呢?
took:Elasticsearch运行查询需要多长时间(以毫秒为单位);
timed_out :搜索请求是否超时 ;
_shards 搜索了多少碎片,并对多少碎片成功、失败或跳过进行了细分;
_max_score 找到最相关的文档的得分;
hits.total.value :找到了多少匹配的文档;
hits.sort :文档排序后的位置(比如上面查询的1,2,3…) ;
hits._score:文档的相关性评分(在使用match_all时不适用)
指定文档id搜索数据:
GET /ropledata/_doc/101
根据关键字搜索数据
想查找ropledata这个索引下,name字段为**“且听风吟,静待花开”**的数据:
GET /ropledata/_search?q=name:“且听风吟,静待花开”
使用高亮查询,会对要查询的数据进行分词搜索
有时候我们想把查询到的数据进行高亮显示,比如查到“且听风吟”后,想把name这个字段的数据高亮显示,
POST /ropledata/_search
“query”:
“match”:
“name”: “且听风吟”
,
“highlight”:
“fields”:
“name”:
"em 且听 /em_em 风吟 /em " 就是且听_风吟这个被查出来了 然后高亮显示且听和风吟
注:这里的"em /em "标签在html里有强调文本的作用
DSL是啥意思呢?domain specific language
基于JSON实现了直观简单的结构化查询功能。由于DSL查询是JSON格式的,所以更加的灵活,而且可以同时包含查询和过滤器
term查询
term需要完全匹配,不会对词汇进行分词器分析。主要用于查询精确匹配的值,比如数字,日期,布尔值或未经分析的文本数据类型的字符串 (not_analyzed)
比如咱们查询id字段为9的数据
POST /ropledata/_search
“query”:
“term”:
“id”: 9
terms查询
terms和term 有点类似,但 terms 允许指定多个匹配条件。 如果某个字段指定了多个值,那么文档需要一起去做匹配。
比如咱们查询id字段为9和5的数据:
POST /ropledata/_search
“query”:
“terms”:
“id”: [5,9]
range查询
range 主要用于过滤,通常用于按照指定范围查找一批数据,咱们需要记忆如下四个关键字的含义:
gt :大于
gte : 大于等于
lt :小于
lte : 小于等于
比如咱们想要查询id字段大于等于5且小于10的数据:
POST /ropledata/_search
“query”:
“range”:
“id”:
“gte”: 5,
“lt”: 10
exists查询
exists查询类似sql里的is null条件,通常用于查找文档中是否包含指定字段,包含这个字段就返回这条数据。
比如咱们想查询ropledata这个索引下,包含hobby这个字段的数据:
POST /ropledata/_search
“query”:
“exists”:
“field”: “hobby”
match查询
在使用 match 查询一个全文本字段时,它会在真正查询之前用分析器先分析match一下查询字符;如果用 match 下指定了一个确切值, 在遇到数字,日期,布尔值或者 not_analyzed 的字符串时,它将为你搜索你给定的值。
比如咱们查询hobby是run的数据:
POST /ropledata/_search
“query”:
“match”:
“hobby”: “run”
match_phrase查询
match_phrase和match有啥区别呢?
match_phrase和match类似,在查询时都会对查询词进行分词,但是match会忽略查询词的空格,而match_phrase不会。因此需要注意的是:查询包含空格的字符串,要用match_phrase!
比如咱们查询hobby是music and movie的数据(注意:这里要查询的数据包含空格):
POST /ropledata/_search
“query”:
“match_phrase”:
“hobby”: “music and movie”
bool查询
bool 查询可以用来合并多个条件查询结果的布尔逻辑,咱们需要记忆如下操作符关键字:
must:多个查询条件的完全匹配,相当 于 and;
must_not:多个查询条件的相反匹配,相当于 not;
should:至少有一个查询条件匹配, 相当于 or。
注意:这些参数可以分别继承一个查询条件或者一个查询条件的数组。
比如咱们想查询hobby必须是run,id必须不是4,name可以是一起学习或者且听风吟的数据:
POST /ropledata/_search
“query”:
“bool”:
“must”:
“term”:
“hobby”: “run”
,
“must_not”:
“term”:
“id”: 4
,
“should”: [
“term”:
“name”: “且听风吟”
,
“term”:
“name”: “一起学习”
]
filter查询
filter用于过滤查询,通常和bool连用,就像编程语言一样,用于过滤数据。
比如咱们想查询hobby为music的用户:
POST /ropledata/_search
“query”:
“bool”:
“filter”:
“term”:
“hobby”: “music”
其实常用的就是上面这些,它们一般是融合在一起使用的。比如咱们要查询id必须不是4,name包含且听风吟,同时id大于等于2且小于10的数据:
POST /ropledata/_search
“query”:
“bool”:
“filter”:
“range”:
“id”:
“gte”: 2,
“lt”: 10
,
“must_not”:
“term”:
“id”: 4
,
“must”:
“match”:
“name”: “且听风吟”
聚合查询
avg:平均值
max:最大值
min:最小值
sum:求和
POST /ropledata/_search
“aggs”:
“ropledata”:
“avg”:
“field”: “id”
,
“size”: 0
求平均值或求和时,为什么要加(“size”: 0)呢?
size用来控制返回多少数据,由于咱们是想要在所有文档里求平均值和求和,所以要用size来控制返回一个数据即可,不然ES还会默认返回10条数据。
cardinality去重
涉及到聚合查询的场景,当然少不了去重了,ES提供了cardinality去重统计函数来解决这个问题。
比如咱们想根据id字段去重统计:
POST /ropledata/_search
“aggs”:
“ropledata”:
“cardinality”:
“field”: “id”
,
“size”: 0
value_count计数统计
有时候咱们会遇到让统计个数的场景,这时候就可以使用value_count来解决了。
比如咱们要统计有多少条数据:
POST /ropledata/_search
“aggs”:
“ropledata”:
“value_count”:
“field”: “id”
,
“size”: 0
terms词聚合
terms词聚合可以基于给定的字段,并按照这个字段对应的每一个数据为一个桶,然后计算每个桶里的文档个数。默认会按照文档的个数排序。
比如咱们要根据id字段进行词聚合:
POST /ropledata/_search
“aggs”:
“ropledata”:
“terms”:
“field”: “id”
top_hits聚合
咱们使用sql时可以很方便的处理top问题,ES也提供了对应的支持,top_hits就是这样的函数,一般和terms连用,可以获取到每组前n条数据。
比如咱们想根据id分组,然后拿到前6条数据:
POST /ropledata/_search
“aggs”:
“ropledata”:
“terms”:
“field”: “id”
,
“aggs”:
“count”:
“top_hits”:
“size”: 6
,
“size”: 0
range范围查询
在咱们日常进行数据统计时,控制数据的范围是必不可少的,除了前面DSL查询时介绍的gt,lt函数,其实在聚合查询里还提供了range用来进行范围查询。
比如咱们想查询id字段的值在6-9之间和10-20之间的文档有多少:
POST /ropledata/_search
“aggs”:
“group_by_id”:
“range”:
“field”: “id”,
“ranges”: [
“from”: 6,
“to”: 9
,
“from”: 10,
“to”: 20
]
,
“size”: 0
批量操作
批量插入
POST _bulk
“create” : “_index” : “ropledata”, “_id” : “1009”
“id”:9,“name”: “且听风吟,静待花开”,“hobby”: “music and movie”
“create” : “_index” : “ropledata”, “_id” : “1010”
“id”:10,“name”: “且听_风吟”,“hobby”: “music”
“create” : “_index” : “ropledata”, “_id” : “1011”
“id”:11,“name”: “大数据领域”,“hobby”: “movie”
“create” : “_index” : “ropledata”, “_id” : “1012”
“id”:12,“name”: “一起学习”,“hobby”: “run”
批量查询
比如咱们想批量查询ropledata这个索引下文档id为1010,1011,1012的文档数据,可以这样写:
POST /ropledata/_mget
“ids”: [
“1010”,
“1011”,
“1012”
]
批量更新
如果咱们想批量修改1011和1012的文档里的name字段的值,可以这样写:
POST _bulk
“update” : "_id" : “1011”, “_index” : “ropledata” “doc” : “name” : “批量修改”
“update” : "_id" : “1012”, “_index” : “ropledata” “doc” : “name” : “大家好”
批量删除
如果咱们想批量删除文档id为1011和1012的文档:
POST _bulk
“delete” : “_index” : “ropledata”, “_id” : “1011”
“delete” : “_index” : “ropledata”, “_id” : “1012”
实用骚操作
浏览器查询结果美化
咱们之前介绍的所有的操作,都是在kibana里输入的,里面可以对输入的命令和输出的结果进行格式化。但是如果咱们直接使用浏览器进行查询时,输出的查询结果会乱成一团,那么怎么去美化呢?
在查询的最后加上pretty参数
http://127.0.0.1:9200/ropledata/_doc/1001?pretty
指定返回的字段
咱们有时候不需要返回整个文档所有的字段,只想要查看其中的一个或者多个字段,这时候ES也提供的有方法,只需要在最后使用**_source**参数,并传递想要返回的字段就可以了。
http://127.0.0.1:9200/ropledata/_doc/1001?pretty&_source=id,name
不显示元数据
咱们查询的时候,会返回一大堆数据,上面那些称为元数据,那不需要的时候,怎么去掉呢?别急,只需要把_doc换成_source就可以了。
http://127.0.0.1:9200/ropledata/_source/1001?pretty&_source=id,name
查看文档是否存在
有时候咱们为了防止报错,在查询之前,需要查看这个文档是否存在,这时候只需要用到HEAD关键字就可以了。
在kibana下:
HEAD /ropledata/_doc/1001
分页查询
对于海量存储的数据,有时候咱们需要分页查看。ES提供了size和from两个参数,size代表每页的个数,默认是10个,from代表从第几个获取。
http://127.0.0.1:9200/ropledata/_search?size=1&from=2&pretty
以上是关于elasticsearch的主要内容,如果未能解决你的问题,请参考以下文章