什么是elasticsearch?
一个开源的分布式搜索引擎,底层是开源库Lucene,可以用来实现海量数据的搜索、日志统计、分析、系统监控等功能。对搜索引擎的操作封装成了RESTful的API,通过http请求就能对其进行操作。
ELK技术栈
准备工作之安装docker
mac商城直接安装desktop,
安装好了直接pull elasticsearch和kibana镜像(注意版本适配,参考官网)
docker相关学习可参考我之前的一篇文章juejin.cn/post/684490…
运行容器
访问kibana localhost:5601
-
获取token
找到elasticsearch容器运行IDdocker ps或者通过桌面工具直接复制
进容器docker exec -it 容器运行ID bash
生成tokenbin/elasticsearch-create-enrollment-token --scope kibana -
获取elasticsearch的账号密码
进容器./bin/elasticsearch-setup-passwords auto自动生成账号密码
登陆进去就可以愉快的玩耍devtool
ES数据库基本概念
索引和映射
索引就像数据库里的表,映射就像数据库中定义的表结构 例如:
- 所有用户文档,就可以组织在一起,称为用户的索引;(ps:文档可以看走表中的一条数据,下文有相关学习)
- 所有商品的文档,可以组织在一起,称为商品的索引;
- 所有订单的文档,可以组织在一起,称为订单的索引;
因此,我们可以把索引当做是数据库中的表。
数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库中就有映射(mapping) ,是索引中文档的字段约束信息,类似表的结构约束。
Mapping映射
mapping是对索引库中文档的约束,常见的mapping属性包括:
-
type:字段数据类型,常见的简单类型有:
-
字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
keyword类型只能整体精确搜索,不支持搜索部分内容
-
数值:long、integer、short、byte、double、float、
-
布尔:boolean
-
日期:date
-
对象:object
-
-
index:是否创建索引参与搜索,默认为true
-
analyzer:使用哪种分词器
-
properties:该字段的子字段
举个🌰
{
"age": 21,
"weight": 52.1,
"isMarried": false,
"info": "真相只有一个!",
"email": "zy@itcast.cn",
"score": [99.1, 99.5, 98.9],
}
对应的每个字段映射(mapping):
- age:类型为 integer;参与搜索,因此需要index为true;无需分词器
- weight:类型为float;参与搜索,因此需要index为true;无需分词器
- isMarried:类型为boolean;参与搜索,因此需要index为true;无需分词器
- info:类型为字符串,需要分词,因此是text;参与搜索,因此需要index为true;分词器可以用ik_smart
- email:类型为字符串,但是不需要分词,因此是keyword;不参与搜索,因此需要index为false;无需分词器
- score:虽然是数组,但是我们只看元素的类型,类型为float;参与搜索,因此需要index为true;无需分词器
索引库操作
CRUD简单描述:
- 创建索引库:PUT /索引库名
- 查询索引库:GET /索引库名
- 删除索引库:DELETE /索引库名
- 修改索引库(添加字段):PUT /索引库名/_mapping
创建
查询
修改(添加新字段)
文档和字段
一个文档就像数据库里的一条数据,字段就像数据库里的列,所有文档组织在一起可以称之为索引。 elasticsearch是面向文档(Document) 存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中:
而Json文档中往往包含很多的字段(Field) ,类似于mysql数据库中的列。
文档操作
文档操作有哪些?
- 创建文档:POST /{索引库名}/_doc/文档id
- 查询文档:GET /{索引库名}/_doc/文档id
- 删除文档:DELETE /{索引库名}/_doc/文档id
- 修改文档:
- 全量修改:PUT /{索引库名}/_doc/文档id
- 增量修改:POST /{索引库名}/_update/文档id { "doc": {字段}}
创建
查询
修改
- 全量修改是覆盖原来的文档,其本质是:
- 根据指定的id删除文档
- 新增一个相同id的文档
- 增量修改
- 增量修改是只修改指定id匹配的文档中的部分字段。
POST /{索引库名}/_update/文档id
{
"doc":
{
"字段名": "新的值",
}
}
查看所有索引
IK分词
github:github.com/infinilabs/…
进es容器 安装对应版本分词器后需重启es服务:
`bin/elasticsearch-plugin install https://get.infini.cloud/elasticsearch/analysis-ik/8.13.0`
IK分词器包含两种模式:
ik_smart:最少切分ik_max_word:最细切分
测试分词功能:
DSL
elasticsearch的查询依然是基于JSON风格的DSL来实现的。
//查询的语法基本一致
GET /indexName/_search
{
"query": {
"查询类型": {
"查询条件": "条件值"
}
}
}
常见的查询类型
-
查询所有:查询出所有数据,一般测试用。例如:match_all
-
全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:
- match: 根据一个字段查询
- multi_match: 根据多个字段查询
-
精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如:
- ids
- range:根据数值范围查询,可以是数值、日期的范围
- term: 根据词条精确匹配,一般搜索keyword类型、数值类型、布尔类型、日期类型字段
-
地理(geo)查询:根据经纬度查询。例如:
- geo_distance
- geo_bounding_box
-
复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:
- bool
- function_score:算分函数查询,可以控制文档相关性算分,控制文档排名
全文检索
全文检索查询的基本流程如下:
- 对用户搜索的内容做分词,得到词条
- 根据词条去倒排索引库中匹配,得到文档id
- 根据文档id找到文档,返回给用户
常用场景:如很多网站的输入框搜索
- match
//语法
GET /indexName/_search
{
"query": {
"match": {
"FIELD": "TEXT"
}
}
}
- mulit_match
// 语法
GET /indexName/_search
{
"query": {
"multi_match": {
"query": "TEXT",
"fields": ["FIELD1", " FIELD12"]
}
}
}
精确检索
因为精确查询的字段搜是不分词的字段,因此查询的条件也必须是不分词的词条,换句话来说,不会搜索具有分词功能的字段。查询时,用户输入的内容跟自动值完全匹配时才认为符合条件。
term
// 语法
GET /indexName/_search
{
"query": {
"term": {
"FIELD": {
"value": "VALUE"
}
}
}
}
如果搜具有分词功能也就是类型是text的字段,则查不出数据
range
范围查询,一般应用在对数值类型做范围过滤的时候。比如做价格范围过滤。
// 语法
GET /indexName/_search
{
"query": {
"range": {
"FIELD": {
"gte": 10, // 这里的gte代表大于等于,gt则代表大于
"lte": 20 // lte代表小于等于,lt则代表小于
}
}
}
}
复合查询
fuction score
1.1 相关性算分
elasticsearch会根据词条和文档的相关度做打分,目前采用BM25算法。
当我们利用match查询时,文档结果会根据与搜索词条的关联度打分query_score(_score),返回结果时按照分值降序排列。
1.2 算分函数查询
在搜索出来的结果的分数基础上,再手动与指定的数字进行一定运算来改变算分,从而改变结果的排序。(以百度搜索为例,花钱的广告会排在前位)
function score query定义的三要素是什么?
- 过滤条件:哪些文档要加分
- 算分函数:如何计算function score
- 加权方式:function score 与 query score如何运算
1.1中的例子可以看出彭猛排在陈楠后面,如果彭猛花钱了,想排在前面,怎么解决?
GET /biaoxun/_search
{
"query": {
"function_score": {
"query": { // 原始查询,可以是任意条件,基于BM25算法,得出原始算分query_score
"match": {
"jineng": "吃饭"
}
},
"functions":[//算分函数,复合条件的才会加分
{
"filter":{ //过滤条件,【这里是加分条件】,得出函数算分function_score
"match":{
"name":"彭猛"
}
},
"weight":2//算分权重为2
}
],
"boost_mode": "sum" // 加权模式,求和 = function_score + query_score
}
}
}
运行后会发现彭猛排在前面了
function score 查询中包含四部分内容:
-
原始查询条件:query部分,基于这个条件搜索文档,并且基于BM25算法给文档打分,原始算分(query score)
-
过滤条件:filter部分,符合该条件的文档才会重新算分
-
算分函数:符合filter条件的文档要根据这个函数做运算,得到的函数算分(function score),有四种函数
- weight:函数结果是常量
- field_value_factor:以文档中的某个字段值作为函数结果
- random_score:以随机数作为函数结果
- script_score:自定义算分函数算法
-
运算模式:算分函数的结果、原始查询的相关性算分,两者之间的运算方式,包括:
- multiply:相乘
- replace:用function score替换query score
- 其它,例如:sum、avg、max、min
bool查询
布尔查询是一个或多个查询子句的组合,每一个子句就是一个子查询。子查询的组合方式有:
- must:必须匹配每个子查询,类似“与”
- should:选择性匹配子查询,类似“或”
- must_not:必须不匹配,不参与算分,类似“非”
- filter:必须匹配,不参与算分
注意:尽量在筛选的时候多使用不参与算分的must_not和filter,以保证性能良好,搜索时,参与打分的字段越多,查询的性能也越差。因此这种多条件查询时,建议这样做:
- 搜索框的关键字搜索,是全文检索查询,使用must查询,参与算分
- 其它过滤条件,采用filter查询。不参与算分
GET /biaoxun/_search
//查询技能含吃饭,年龄大于20小于35的人
{
"query": {
"bool": {
"must": [{
"match": {
"jineng": "吃饭"
}
}],
"filter": [
{
"range": {
"age": {
"gte": 20,
"lte": 35
}
}
}
]
}
}
}
设置搜索结果
可以按照用户的想法设置返回结果,查询的DSL是一个大的JSON对象,包含下列属性:
- query:查询条件
- from和size:分页条件
- sort:排序条件
- highlight:高亮条件
- aggs:定义聚合
高亮&&排序
GET /biaoxun/_search
{
"query":{
"match":{
"jineng": "牛皮旅游" // 分词为牛皮和旅游
}
},
"from":0,//从第几个文档开始,默认为0
"size":10,//期望获取的文档总数
"sort":[
{"age":"desc"} //排序字段、排序方式升序ASC、降序DESC
],
"highlight": {
"fields": {
"jineng":{ //【要和上面的查询字段FIELD一致】
"pre_tags": "<em>", // 用来标记高亮字段的前置标签
"post_tags": "</em>" // 用来标记高亮字段的后置标签
}
}
}
}
聚合
注意: 参加聚合的字段必须是keyword、日期、数值、布尔类型
可以让我们极其方便的实现对数据的统计、分析、运算;比如:
- 什么品牌的手机最受欢迎?
- 这些手机的平均价格、最高价格、最低价格?
聚合常见的有三类:
-
桶(Bucket) 聚合:用来对文档做分组
- TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组
- Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
-
度量(Metric) 聚合:用以计算一些值,比如:最大值、最小值、平均值等
- Avg:求平均值
- Max:求最大值
- Min:求最小值
- Stats:同时求max、min、avg、sum等
-
管道(pipeline) 聚合:其它聚合的结果为基础做聚合
-
如:用桶聚合实现种类排序,然后使用度量聚合实现各个桶的最大值、最小值、平均值等
-
//查询年龄大于30并且小于55的,按照city降序聚合
GET /biaoxun/_search
{
"query": {
"range": {
"age": { // 年龄小于55,大于30
"gte": 30,
"lte": 55
}
}
},
"size": 0, // 设置size为0,结果中不包含查询结果文档,只包含聚合结果,
"aggs": {
"ageGroupTtems": {//给聚合起个名字
"terms": {
"field": "city",
"order": {
"_count": "desc" // 对聚合结果降序排列
},
"size": 20 // 希望获取的聚合结果数量【设置多少就最多只显示多少】
}
}
}
}
未完待续...