这里是weihubeats,觉得文章不错可以关注公众号小奏技术,文章首发。拒绝营销号,拒绝标题党
伦敦的公寓内,Shay Banon 正在忙着寻找工作,而他的妻子正在蓝带 (Le Cordon Bleu) 烹饪学校学习厨艺。在空闲时间,他开始编写搜索引擎来帮助妻子管理越来越丰富的菜谱。
他的首个迭代版本叫做 Compass。第二个迭代版本就是 Elasticsearch(基于 Apache Lucene 开发)。他将 Elasticsearch 作为开源产品发布给公众,并创建了 #elasticsearch IRC 通道,剩下来就是静待用户出现了。
公众反响十分强烈。用户自然而然地就喜欢上了这一软件。由于使用量急速攀升,此软件开始有了自己的社区,并引起了人们的高度关注,尤其引发了 Steven Schuurman、Uri Boness 和 Simon Willnauer 的浓厚兴趣。他们四人最终共同组建了一家搜索公司
流行程度
这里我们看看github的star数就可以看到喜欢的人到底有多少
为了方便我们理解,我们与数据库中的一些概念做一些简单的比对
索引,相当于数据库中的一个数据库(Database)
我们可以简单看看elasticsearch中的所有索引
GET _cat/indices?v
PUT /test_index
DELETE /test_index
可以理解为数据库中的表。在elasticsearch 7.0.0之后就不建议使用了,创建文档后面的会有一个type为_doc
8.0.0版本后的elasticsearch将完全废弃
include_type_name 参数可控制type相关api
为什么废弃
其实原因很简单
索引(Index)中的每条记录就是Document
我们这里简单查看一下商品索引的里面的一些数据
GET test_index/_search
等价于
GET test_index/_doc/_search
_index
: 文档所属索引_type
: 后续将废弃_id
: Doc的主键。在写入的时候,可以指定该Doc的ID值,如果不指定,则系统自动生成一个唯一的UUID值_source
: 相关度评分PUT /test_index/doc/1
{
"post_date": "2022-09-03",
"name": "小奏技术",
"content": "这是测试小奏技术数据",
"age": 88
}
POST /test_index/_doc
{
"post_date": "2022-09-03",
"name": "小奏技术1",
"content": "这是测试小奏技术1数据",
"age": 882
}
PUT /test_index/doc/1
{
"post_date": "2022-09-03",
"name": "小奏技术",
"content": "这是测试小奏技术数据",
"age": 88,
"label": "开心"
}
DELETE test_index/_doc/oo0cAoABFI-iZ3BAcffx
这里的
oo0cAoABFI-iZ3BAcffx
是_id
Mapping
可以简单理解为数据库中表中字段的类型,与数据库不同的是在es中我们可以不用手动指定Mapping,在插入数据的时候es可以为我们自动生成Mapping
我们来简单看看我们之前自动生成的Mapping
查询语法
GET /test_index/_mapping
我们也可以在创建索引的时候同时指定_mapping
PUT /test_index
{
"mappings" : {
"properties" : {
"author_id" : {
"type" : "long"
},
"content" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"post_date" : {
"type" : "date"
},
"title" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
mapping只能创建index手动指定mapping或者新增field mapping,但是不能更新update field mapping
mapping的类型主要有如下几种
假设我们有如下内容,看看是如何建立倒排索引的
文档ID | 文档内容 |
---|---|
1 | Test ElasticSearch |
2 | ElasticSearch Server |
3 | ElasticSearch Server1 |
建立的倒排索引如下
Term | Count | DocumentId:Position |
---|---|---|
ElasticSearch | 3 | 1:1,2:0,3:0 |
Test | 1 | 1:0 |
Server | 1 | 2:1 |
Server1 | 1 | 3:1 |
es中的每个字段都有自己的倒排索引。
我们也可以对某个字段不做索引
简单来说就将我们的文本通过分词分成很多词根然后去建立倒排索引供我们搜索
我们在搜索的时候也会将我们的搜索词通过分词去搜索
POST _analyze
{
"analyzer": "standard",
"text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
[ the, 2, quick, brown, foxes, jumped, over, the, lazy, dog’s, bone ]
POST _analyze
{
"analyzer": "simple",
"text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
[ the, quick, brown, foxes, jumped, over, the, lazy, dog, s, bone ]
POST _analyze
{
"analyzer": "whitespace",
"text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
[ The, 2, QUICK, Brown-Foxes, jumped, over, the, lazy, dog’s, bone. ]
POST _analyze
{
"analyzer": "stop",
"text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
[ quick, brown, foxes, jumped, over, lazy, dog, s, bone ]
POST _analyze
{
"analyzer": "keyword",
"text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
[ The 2 QUICK Brown-Foxes jumped over the lazy dog’s bone. ]
POST _analyze
{
"analyzer": "pattern",
"text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
[ the, 2, quick, brown, foxes, jumped, over, the, lazy, dog, s, bone ]
POST _analyze
{
"analyzer": "fingerprint",
"text": "Yes yes, Gödel said this sentence is consistent and."
}
[ and consistent godel is said sentence this yes ]
GET /test_index/_search?q=小奏技术&df=name&sort=age:desc&from=0&size=1&timeout=1s
{
"profile": "true"
}
简单解释参数的意义
简单理解就是通过HTTP Request Body将查询请求发送到ElasticSearch
GET test_index/_search
{
"query": {
"match_all": {}
}
}
GET test_index/_search
{
"query": {
"match_all": {}
}
, "from": 0,
"size": 2
}
GET test_index/_search
{
"query": {
"match_all": {}
}
, "from": 0,
"size": 2,
"sort": [
{
"age": {
"order": "desc"
}
}
]
}
GET test_index/_search
{
"query": {
"match_all": {}
}
, "from": 0,
"size": 2,
"sort": [
{
"age": {
"order": "desc"
}
}
],
"_source": ["age", "name"]
}
GET test_index/_search
{
"query": {
"match": {
"name": "小奏 小奏技术"
}
}
}
实际等价于
GET test_index/_search
{
"query": {
"match": {
"name": {
"query": "小奏 小奏技术",
"operator": "or"
}
}
}
}
可以将 or 改成 and 意思就是必须保护 两者的分词
GET test_index/_search
{
"query": {
"term": {
"name": {
"value": "小奏"
}
}
}
}
GET test_index/_search
{
"query": {
"match_phrase": {
"name": "小奏 小奏技术"
}
}
}
term、match_phrase都是用于精准匹配,match用于模糊匹配
GET test_index/_search
{
"query": {
"query_string": {
"default_field": "name",
"query": "小奏 AND 小奏技术"
}
}
}
类似URI Query
GET test_index/_search
{
"query": {
"simple_query_string": {
"query": "小奏 AND 小奏技术",
"fields": ["name"]
}
}
}
query_string
查询小奏 AND 小奏技术
,会解析成查询必须要同时包含小奏
和小奏技术
而simple_query_string
则是将其分词成小奏
,AND
和小奏技术
,默认的operator为OR
GET test_index/_search
{
"query": {
"constant_score": {
"filter": {
"term": {
"name": "haofeng"
}
},
"boost": 1.2
}
}
}
有时候我们在一些查询不涉及到相关度评分,或者是完全的精准匹配。我们就可以使用filter代替query
使用filter的好处
GET test_index/_search
{
"query": {
"constant_score": {
"filter": {
"bool": {
"should":[
{
"term":{"age":221}
},
{
"term":{"age":222}
}
]
}
},
"boost": 1.2
}
}
}
bool查询是一个或多个查询子句的组合
总共四种子句。两种计分、两种不计分
GET test_index/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"name": {
"value": "小奏"
}
}
}
],
"must_not": [
{
"term": {
"age": {
"value": "222"
}
}
}
],
"should": [
{
"term": {
"content": {
"value": "测试"
}
}
},
{
"term": {
"post_date": {
"value": "2022-09-03"
}
}
}
],
"minimum_should_match": 1
}
}
}
boost主要用于控制相关得分的
简单举例
GET test_index/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"name": {
"value": "haofeng",
"boost": 2
}
}
}
],
"must_not": [
{
"term": {
"age": {
"value": "222"
}
}
}
],
"should": [
{
"term": {
"content": {
"value": "测试",
"boost": 2
}
}
},
{
"term": {
"post_date": {
"value": "2022-09-03"
}
}
}
],
"minimum_should_match": 1
}
}
}
在搜索返回单页结果的时候。如果数据量很大,一次性查询比如10w数据量数据性能可能会很差,elasticsearch提供了scoll滚动查询,一批一批的查,直到所有数据都查询完处理完
每次scroll搜索我们需要指定一个时间窗口,每次查询请求在这个时间窗口完成就可以
GET test_index/_search?scroll=1m
{
"query": {
"match": {
"name":{
"query": "小奏"
}
}
}
}
获得的结果会有一个scoll_id,下一次再发送scoll请求的时候,必须带上这个scoll_id
GET /_search/scroll
{
"scroll":"1m",
"scroll_id":"FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFms4a1IyNTNlUzlHTmppcTQtV3ZEVEEAAAAAAGmsnBZLNktRZVZBbFFTUzdyTk5EZWZ6TWxn"
}
scroll看着像分页,但实际应用场景可能有所不同,分页是一页一页搜索给用户看。scroll主要用于一批一批查询,让系统进行处理
聚合简单理解就是对数据进行分组聚合查询
类似
Mysql 的 select count(lable) from product group by type
其中
count(lable) == Metric
Group by type == Bucket
下面我们深入理解下Aggregation中的两个核心概念
city | name |
---|---|
北京 | 小张 |
北京 | 老李 |
广州 | 小奏 |
广州 | 关羽 |
广州 | 张飞 |
按city划分bucket,分为北京、广州两个bucket
当我们有了bucket之后我们可以对这一系列的bucket数据进行聚合分析。
简单理解就是Metric就是对Bucket进行聚合分析,比如求 平均值、最大值、最小值
简单实操
PUT /tvs
{
"mappings": {
"properties": {
"price": {
"type": "long"
},
"color": {
"type": "keyword"
},
"brand": {
"type": "keyword"
},
"sold_date": {
"type": "date"
}
}
}
}
POST /tvs/_bulk
{ "index": {}}
{ "price" : 1000, "color" : "红色", "brand" : "长虹", "sold_date" : "2016-10-28" }
{ "index": {}}
{ "price" : 2000, "color" : "红色", "brand" : "长虹", "sold_date" : "2016-11-05" }
{ "index": {}}
{ "price" : 3000, "color" : "绿色", "brand" : "小米", "sold_date" : "2016-05-18" }
{ "index": {}}
{ "price" : 1500, "color" : "蓝色", "brand" : "TCL", "sold_date" : "2016-07-02" }
{ "index": {}}
{ "price" : 1200, "color" : "绿色", "brand" : "TCL", "sold_date" : "2016-08-19" }
{ "index": {}}
{ "price" : 2000, "color" : "红色", "brand" : "长虹", "sold_date" : "2016-11-05" }
{ "index": {}}
{ "price" : 8000, "color" : "红色", "brand" : "三星", "sold_date" : "2017-01-01" }
{ "index": {}}
{ "price" : 2500, "color" : "蓝色", "brand" : "小米", "sold_date" : "2017-02-12" }
GET /tvs/_search
{
"size": 0,
"aggs": {
"group_colors": {
"terms": {
"field": "color"
}
}
}
}
GET /tvs/_search
{
"size": 0,
"aggs": {
"colors": {
"terms": {
"field": "color"
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
等价于如下sql
Select avg(price) from tvs._doc group by color
在sql 查询中使用的比较多的就是
GET /tvs/_search
{
"size": 0,
"aggs": {
"group_by_colors": {
"terms": {
"field": "color"
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
},
"min_price":{
"min": {
"field": "price"
}
},
"max_price":{
"min": {
"field": "price"
}
},
"sum_price":{
"sum": {
"field": "price"
}
}
}
}
}
}
指定一个interval去划分bucket
比如指定interval为2000,则划分的区域为
[0,2000) [2000,4000) [4000,6000) [6000,8000) [8000,10000)
GET tvs/_search
{
"size": 0,
"aggs": {
"price": {
"histogram": {
"field": "price",
"interval": 2000
},
"aggs": {
"revenue": {
"sum": {
"field": "price"
}
}
}
}
}
}
按颜色、颜色+品牌下钻分析
GET /tvs/_search
{
"size": 0,
"aggs": {
"group_by_color": {
"terms": {
"field": "color"
},
"aggs": {
"color_avg_price": {
"avg": {
"field": "price"
}
},
"group_by_brand":
{
"terms": {
"field": "brand"
},
"aggs": {
"brand_avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
}
}