操作ElasticSearch
参考官网:https://www.elastic.co/guide/index.html
基本概念
Elasticsearch也是基于Lucene的全文检索库,本质也是存储数据,很多概念与MySQL类似的。
对比关系:
索引(indices)--------------------------------Databases 数据库
类型(type)-----------------------------Table 数据表
文档(Document)----------------Row 行
字段(Field)-------------------Columns 列
详细说明:
概念 | 说明 |
---|---|
索引库(indices) | indices是index的复数,代表许多的索引, |
类型(type) | 类型是模拟mysql中的table概念,一个索引库下可以有不同类型的索引,比如商品索引,订单索引,其数据格式不同。不过这会导致索引库混乱,因此未来版本中会移除这个概念 |
文档(document) | 存入索引库原始的数据。比如每一条商品信息,就是一个文档 |
字段(field) | 文档中的属性 |
映射配置(mappings) | 字段的数据类型、属性、是否索引、是否存储等特性 |
是不是与Lucene和solr中的概念类似。
另外,在SolrCloud中,有一些集群相关的概念,在Elasticsearch也有类似的:
- 索引集(Indices,index的复数):逻辑上的完整索引 collection1
- 分片(shard):数据拆分后的各个部分
- 副本(replica):每个分片的复制
要注意的是:Elasticsearch本身就是分布式的,因此即便你只有一个节点,Elasticsearch默认也会对你的数据进行分片和副本操作,当你向集群添加新数据时,数据也会在新加入的节点中进行平衡。
索引
语法:
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2
}
}
settings:索引库的设置
- number_of_shards:分片数量
- number_of_replicas:副本数量
测试
创建索引
使用postMan测试
可以看到索引创建成功了。
使用kibana创建
kibana的控制台,可以对http请求进行简化,示例:
相当于是省去了elasticsearch的服务器地址
而且还有语法提示,非常舒服。
查看索引设置
Get请求可以帮我们查看索引信息,格式:
GET /索引库名
删除索引
删除索引使用DELETE请求
DELETE /索引库名
查看test2是否存在
也可以用HEAD请求,查看索引是否存在:
映射配置
索引有了,接下来肯定是添加数据。但是,在添加数据之前必须定义映射。
什么是映射?
映射是定义文档的过程,文档包含哪些字段,这些字段是否保存,是否索引,是否分词等
只有配置清楚,Elasticsearch才会帮我们进行索引库的创建(不一定)
创建映射字段
语法
PUT /索引库名/_mapping/类型名称
{
"properties": {
"字段名": {
"type": "类型",
"index": true,
"store": true,
"analyzer": "分词器"
}
}
}
- type:类型,可以是text、long、short、date、integer、object等
- index:是否索引,默认为true
- store:是否存储,默认为false
- analyzer:分词器,这里的
ik_max_word
即使用ik分词器
示例:
PUT test/_mapping/goods
{
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word"
},
"images": {
"type": "keyword",
"index": "false"
},
"price": {
"type": "float"
}
}
}
查看映射关系
语法:
GET /索引库名/_mapping
新增数据
随机生成id
通过POST请求,可以向一个已经存在的索引库中添加数据。
语法
POST /索引库名/类型名
{
"key":"value"
}
示例
POST /test/goods/
{
"title":"小小江",
"sex":"nan",
"age":18
}
通过kibana查看数据:
get _search
{
"query":{
"match_all":{}
}
}
_source
:源文档信息,所有的数据都在里面。_id
:这条文档的唯一标示,与文档自己的id字段没有关联
自定义id
如果我们想要自己新增的时候指定id,可以这么做:
语法:
POST /索引库名/类型/id值
{
...
}
示例:
POST /test/goods/2
{
"title":"小小江2",
"sex":"男",
"age":19
}
智能判断
在学习Solr时我们发现,我们在新增数据时,只能使用提前配置好映射属性的字段,否则就会报错。
不过在Elasticsearch中并没有这样的规定。
事实上Elasticsearch非常智能,你不需要给索引库设置任何mapping映射,它也可以根据你输入的数据来判断类型,动态添加数据映射。
测试一下
POST /test/goods/3
{
"title":"小小江2",
"sex":"男",
"age":19 ,
"hasGirlFriend": true,
"weight": 70
}
我们额外添加了hasGirlFriend库存,和weight是否上架两个字段。
结果
查看索引库映射关系
修改数据
把刚才新增的请求方式改为PUT,就是修改了。不过修改必须指定id,
- id对应文档存在,则修改
- id对应文档不存在,则新增
比如,我们把id为3的数据进行修改
PUT /test/goods/3
{
"title":"小小江2",
"sex":"男",
"age":19 ,
"hasGirlFriend": true,
"weight": 70
}
删除数据
删除使用DELETE请求,同样,需要根据id进行删除:
语法
DELETE /索引库名/类型名/id值
查询
基本查询
语法
GET /索引库名/_search
{
"query":{
"查询类型":{
"查询条件":"查询条件值"
}
}
}
这里的query代表一个查询对象,里面可以有不同的查询属性
查询类型:
match_all
, match
,term
, range
查询所有(match_all)
GET /test/_search
{
"query":{
"match_all": {}
}
}
- took:查询花费时间,单位是毫秒
- time_out:是否超时
- _shards:分片信息
- hits:搜索结果总览对象
- total:搜索到的总条数
- max_score:所有结果中文档得分的最高分
- hits:搜索结果的文档对象数组,每个元素是一条搜索到的文档信息
- _index:索引库
- _type:文档类型
- _id:文档id
- _score:文档得分
- _source:文档的源数据
匹配查询(match)
我们先加入一条数据,便于测试:
PUT /test/goods/3
{
"title":"大大江",
"sex": "女",
"age": 20
}
现在,索引库中有2个小小江,1大大江:
or关系
match
类型查询,会把查询条件进行分词,然后进行查询,多个词条之间是or的关系
GET /test/_search
{
"query":{
"match":{
"title":"小小江"
}
}
}
在上面的案例中,不仅会查询到电视,而且与小小江相关的都会查询到,多个词之间是or
的关系。
在上面的案例中,不仅会查询到小小江,而且与小小江相关的都会查询到,多个词之间是or
的关系。
and关系
某些情况下,我们需要更精确查找,我们希望这个关系变成and
,可以这样做:
GET /test/_search
{
"query":{
"match": {
"title": {
"query": "小小江2",
"operator": "and"
}
}
}
}
只有同时包含小小江
和2
的词条才会被搜索到。
match
查询支持 minimum_should_match
最小匹配参数, 这让我们可以指定必须匹配的词项数用来表示一个文档是否相关。我们可以将其设置为某个具体数字,更常用的做法是将其设置为一个百分数
,因为我们无法控制用户搜索时输入的单词数量:
GET /test/_search
{
"query":{
"match":{
"title":{
"query":"小小江是2",
"minimum_should_match": "75%"
}
}
}
}
三个词,匹配百分数是75%,只要75%*3约等于2,只要匹配两个词就出来结果。
多字段查询(multi_match)
multi_match
与match
类似,不同的是它可以在多个字段中查询
GET /test/_search
{
"query":{
"multi_match": {
"query": "小小江",
"fields": [ "title", "subTitle" ]
}
}
}
词条匹配(term)
term
查询被用于精确值 匹配,这些精确值可能是数字、时间、布尔或者那些未分词的字符串
GET /test/_search
{
"query":{
"term":{
"age": 18
}
}
}
多词条精确匹配(terms)
terms
查询和 term 查询一样,但它允许你指定多值进行匹配。如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件:
GET /test/_search
{
"query":{
"terms":{
"age": [18, 20]
}
}
}
结果过滤
默认情况下,elasticsearch在搜索的结果中,会把文档中保存在_source
的所有字段都返回。
如果我们只想获取其中的部分字段,我们可以添加_source
的过滤
直接指定字段
GET /test/_search
{
"_source": ["title","age"],
"query": {
"term": {
"age": 18
}
}
}
指定includes和excludes
- includes:来指定想要显示的字段
- excludes:来指定不想要显示的字段
GET /test/_search
{
"_source": {
"includes":["title","age"]
},
"query": {
"term": {
"age": 20
}
}
}
高级查询
布尔组合(bool)
bool
把各种其它查询通过must
(与)、must_not
(非)、should
(或)的方式进行组合
GET /test/_search
{
"query":{
"bool":{
"must": { "match": { "title": "小小江" }},
"must_not": { "match": { "title": "大大江" }},
"should": { "match": { "title": "2" }}
}
}
}
范围查询(range)
range
查询找出那些落在指定区间内的数字或者时间
GET /test/_search
{
"query":{
"range": {
"age": {
"gte": 10,
"lt": 19
}
}
}
}
操作符 | 说明 |
---|---|
gt | 大于 |
gte | 大于等于 |
lt | 小于 |
lte | 小于等于 |
模糊查询(fuzzy)
fuzzy
查询是 term
查询的模糊等价。它允许用户搜索词条与实际词条的拼写出现偏差,但是偏差的编辑距离不得超过2:
GET /test/_search
{
"query": {
"fuzzy": {
"title": "小小"
}
}
}
过滤(filter)
所有的查询都会影响到文档的评分及排名。如果我们需要在查询结果中进行过滤,并且不希望过滤条件影响评分,那么就不要把过滤条件作为查询条件来用。而是使用filter
方式:
条件查询中进行过滤
GET /test/_search
{
"query":{
"bool":{
"must":{ "match": { "title": "小小江" }},
"filter":{
"range":{"age":{"gt":10,"lt":20}}
}
}
}
}
注意:filter
中还可以再次进行bool
组合条件过滤。
无查询条件,直接过滤
如果一次查询只有过滤,没有查询条件,不希望进行评分,我们可以使用constant_score
取代只有 filter 语句的 bool 查询。在性能上是完全相同的,但对于提高查询简洁性和清晰度有很大帮助。
GET /test/_search
{
"query":{
"constant_score": {
"filter": {
"range":{"age":{"gt":10,"lt":21}}
}
}
}
}
排序
单字段排序
sort
可以让我们按照不同的字段进行排序,并且通过order
指定排序的方式
GET /test/_search
{
"query": {
"match": {
"title": "小小江"
}
},
"sort": [
{
"age": {
"order": "desc"
}
}
]
}
多字段排序
假定我们想要结合使用 price和 _score(得分) 进行查询,并且匹配的结果首先按照价格排序,然后按照相关性得分排序:
GET /test/goods/_search
{
"query":{
"bool":{
"must":{ "match": { "title": "小小江" }},
"filter":{
"range":{"price":{"gt":10,"lt":30}}
}
}
},
"sort": [
{ "age": { "order": "desc" }},
{ "_score": { "order": "desc" }}
]
}
聚合aggregations
桶(bucket)
桶的作用,是按照某种方式对数据进行分组,每一组数据在ES中称为一个桶
,例如我们根据国籍对人划分,可以得到中国桶
、英国桶
,日本桶
……或者我们按照年龄段对人进行划分:010,1020,2030,3040等。
度量(metrics)
分组完成以后,我们一般会对组中的数据进行聚合运算,例如求平均值、最大、最小、求和等,这些在ES中称为度量
为了测试聚合,我们先批量导入一些数据
创建索引:
PUT /cars
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0
},
"mappings": {
"transactions": {
"properties": {
"color": {
"type": "keyword"
},
"make": {
"type": "keyword"
}
}
}
}
}
keyword类型,这个类型不会被分词,将来就可以参与聚合
导入数据
POST /cars/transactions/_bulk
{ "index": {}}
{ "price" : 10000, "color" : "red", "make" : "honda", "sold" : "2014-10-28" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 30000, "color" : "green", "make" : "ford", "sold" : "2014-05-18" }
{ "index": {}}
{ "price" : 15000, "color" : "blue", "make" : "toyota", "sold" : "2014-07-02" }
{ "index": {}}
{ "price" : 12000, "color" : "green", "make" : "toyota", "sold" : "2014-08-19" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 80000, "color" : "red", "make" : "bmw", "sold" : "2014-01-01" }
{ "index": {}}
{ "price" : 25000, "color" : "blue", "make" : "ford", "sold" : "2014-02-12" }
按照 汽车的颜色color
来划分桶
(聚合为桶)
GET /cars/_search
{
"size" : 0,
"aggs" : {
"popular_colors" : {
"terms" : {
"field" : "color"
}
}
}
}
- size: 查询条数,这里设置为0,因为我们不关心搜索到的数据,只关心聚合结果,提高效率
- aggs:声明这是一个聚合查询,是aggregations的缩写
- popular_colors:给这次聚合起一个名字,任意。
- terms:划分桶的方式,这里是根据词条划分
- field:划分桶的字段
- terms:划分桶的方式,这里是根据词条划分
- popular_colors:给这次聚合起一个名字,任意。
结果
- hits:查询结果为空,因为我们设置了size为0
- aggregations:聚合的结果
- popular_colors:我们定义的聚合名称
- buckets:查找到的桶,每个不同的color字段值都会形成一个桶
- key:这个桶对应的color字段的值
- doc_count:这个桶中的文档数量
通过聚合的结果我们发现,目前红色的小车比较畅销!
为聚合结果添加 求价格平均值的度量(桶内度量)
GET /cars/_search
{
"size" : 0,
"aggs" : {
"popular_colors" : {
"terms" : {
"field" : "color"
},
"aggs":{
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
- aggs:我们在上一个aggs(popular_colors)中添加新的aggs。可见
度量
也是一个聚合 - avg_price:聚合的名称
- avg:度量的类型,这里是求平均值
- field:度量运算的字段
统计每种颜色的汽车中,分别属于哪个制造商,按照make
字段再进行分桶(桶内嵌套桶)
GET /cars/_search
{
"size" : 0,
"aggs" : {
"popular_colors" : {
"terms" : {
"field" : "color"
},
"aggs":{
"avg_price": {
"avg": {
"field": "price"
}
},
"maker":{
"terms":{
"field":"make"
}
}
}
}
}
}
- 原来的color桶和avg计算我们不变
- maker:在嵌套的aggs下新添一个桶,叫做maker
- terms:桶的划分类型依然是词条
- filed:这里根据make字段进行划分
- 红色车共有4辆
- 红色车的平均售价是 $32,500 美元。
- 其中3辆是 Honda 本田制造,1辆是 BMW 宝马制造。
划分桶的其他方式
对汽车的价格进行分组,指定间隔interval为5000(阶梯分桶Histogram)
GET /cars/_search
{
"size":0,
"aggs":{
"price":{
"histogram": {
"field": "price",
"interval": 5000
}
}
}
}
中间有大量的文档数量为0 的桶,看起来很丑。
我们可以增加一个参数min_doc_count为1,来约束最少文档数量为1,这样文档数量为0的桶会被过滤
GET /cars/_search
{
"size":0,
"aggs":{
"price":{
"histogram": {
"field": "price",
"interval": 5000,
"min_doc_count": 1
}
}
}
}
完美,!
如果你用kibana将结果变为柱形图,会更好看