一小时快速掌握elasticsearch

es作为一款搜索、数据分析中间件,在大多数项目中几乎都会用到。但是直接去官网学习,又会遇到各种问题,比如不知道环境如何搭建,如何构建测试数据等等。兜兜转转非常浪费时间,本文从环境搭建、测试数据准备,再到搜索、聚合的使用。让您一站式掌握es的关键技能点,希望能对您有所帮助。

一、环境搭建

我们通过docker开始启动elasticsearch、和kibana docker-compose.yml文件如下:

yaml 复制代码
services:
  elasticsearch:
    image: elasticsearch:8.10.1
    environment:
      - discovery.type=single-node # 单节点开发环境
      - xpack.security.enabled=true # 启用安全认证
      - ELASTIC_PASSWORD=your_elastic_password # 设置一个强密码
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m" # 根据需要调整内存
    ports:
      - "9200:9200"
      - "9300:9300"
    networks:
      - elastic
    healthcheck: # 改进的健康检查
      test: ["CMD-SHELL", "curl -s http://localhost:9200/_cluster/health >/dev/null || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 5
    volumes:
      - esdata:/usr/share/elasticsearch/data # 持久化数据

  kibana:
    image: kibana:8.10.1
    environment:
      ELASTICSEARCH_HOSTS: http://elasticsearch:9200
      ELASTICSEARCH_USERNAME: kibana_system # 这个用户是es默认创建的
      ELASTICSEARCH_PASSWORD: abcdefg # 设置成你自己的密码
      ELASTICSEARCH_SSL_VERIFICATIONMODE: none
      XPACK_SECURITY_ENABLED: "true"
      XPACK_ENCRYPTEDSAVEDOBJECTS_ENCRYPTIONKEY: "something_at_least_32_characters"
    ports:
      - "5601:5601"
    depends_on:
      - elasticsearch
    networks:
      - elastic
    healthcheck:
      test: ["CMD-SHELL", "curl -s http://localhost:5601 >/dev/null || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3

networks:
  elastic:

volumes:
  esdata:

由于8.10.1版本kibana不能通过默认的elastic(超级用户)账户登录,这里我们通过另外一个非超级用户的账号kibana_system做登录。 这个用户es默认已经创建好了,我们只需要初始化它的密码就行。

按如下步骤操作

1. 启动 Elasticsearch

首先只启动 Elasticsearch 服务:

bash 复制代码
docker-compose up elasticsearch -d

等待 Elasticsearch 完全启动(大约需要 1-2 分钟)。

2. 重置密码

执行命令docker-compose exec elasticsearch bin/elasticsearch-reset-password -u kibana_system -i进入交互界面

大概这样

bash 复制代码
dongmingyan@pro  ⮀ docker-compose exec elasticsearch bin/elasticsearch-reset-password -u kibana_system -i
This tool will reset the password of the [kibana_system] user.
You will be prompted to enter the password.
Please confirm that you would like to continue [y/N]y


Enter password for [kibana_system]: 
Re-enter password for [kibana_system]: 
Password for the [kibana_system] user successfully reset.

这里需要注意的一点是密码要设置成字符串,不要设置成纯数字------它不支持。 这里我把密码设置成abcdefg了,如果你是其它密码记得去docker-compose.ymlELASTICSEARCH_PASSWORD密码哦

3. 启动完整服务

启动所有服务:

bash 复制代码
docker-compose up -d

这里启动过程需要点时间,耐心等待下。

5. 访问服务

可以开始访问啦!

我们登录kibana,使用默认用户名 elastic,密码用docker-compose.yml中环境变量写的your_elastic_password直接登录.

补充知识: kibana是elasticsearch可视化数据分析工具,对于我们学习练习使用非常方便。

二、数据准备

在开始前,我们先准备一些测试数据,为方便理解,我们以电商系统常用数据做分析。

1. 初试

前面我们已经成功登录到kibana了,我们登录后,先找到左侧的三个横杠点击,management > Dev Tools 然后打开它。

上面我们可以看到在console中已经列出了一些请求数据,这是kibana为了我们方便学习,自动展示的。

让我为你解释下,elasticsearch对外提供的是http服务,因此这里看到的都是各种请求(POST/GET), 比如:

  • 新增
shell 复制代码
POST /products/_doc
{
  "name": "噜啦啦",
  "brand_name": "HuaWei",
  "city": "成都",
  "price": 5290,
  "create_at": "2022-04-09 13:45:12"
}

它相当于

shell 复制代码
curl -X POST http://localhost:9200/products/_doc -u "elastic:your_elastic_password" -H 'Content-Type: application/json' -d'
{
  "name": "噜啦啦",
  "brand_name": "HuaWei",
  "city": "成都",
  "price": 5290,
  "create_at": "2022-04-09 13:45:12"
}'

我们点击console中请求右侧的▶️即可发出请求。发出去后就会向es products索引中写入一条文档。

  • 概念说明
  1. products es中叫索引index
  2. 请求体{"name": "xx", "brand_name": "yy", ...} es中称这为文档doc,相当于数据库中一行数据记录
  3. 请求体中具体内容比如"name"就是字段/属性啦,需要注意的是es中字段是支持嵌套

让我们继续,继续点击第二个请求查询下products索引有哪些文档。

  • 搜索
bash 复制代码
# 匹配所有 默认最多返回10条
GET /products/_search
{
  "query": {
    "match_all": {}
  }
}

响应内容如下:

json 复制代码
{
  "took": 0,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "products",
        "_id": "SZcbJ5QBrDMs_aLvWrgT",
        "_score": 1,
        "_source": {
          "name": "噜啦啦",
          "brand_name": "HuaWei",
          "city": "成都",
          "price": 5290,
          "create_at": "2022-04-09 13:45:12"
        }
      }
    ]
  }
}

大概您也能看明白,一般我们就关注hitstotal_source数据就行啦。

  • 更新 我们来更新文档试试呢?在上面我们看到其中一个文档的idSZcbJ5QBrDMs_aLvWrgT,我们就更新它啦。

在console中写下如下的请求

shell 复制代码
# products 索引名
# SZcbJ5QBrDMs_aLvWrgT 文档id
POST /products/_update/SZcbJ5QBrDMs_aLvWrgT
{
  "doc": {
    "name": "新产品",
    "price": 5800
  }
}

发送后我们重新,查询products有哪些文档,发现对应id的name已经变了。

  • 删除 那删除文档怎么写呢?
shell 复制代码
DELETE /products/_doc/SZcbJ5QBrDMs_aLvWrgT

好啦!其它您可以自行探索啦。

2. 准备数据

在开始准备测试数据之前,先补充下前面我们漏掉的知识点,前面我们执行

shell 复制代码
POST /products/_doc
{
  "name": "噜啦啦",
  "brand_name": "HuaWei",
  "city": "成都",
  "price": 5290,
  "create_at": "2022-04-09 13:45:12"
}

直接就将数据写入到了products 索引中,我们也说了它类似于数据库中的表;但是在关系型数据库中,必须先建表才能写数据的,这里为啥就直接能写呢?

es中它会自动生成映射,也就是索引不存在时,自动创建索引 您可以把products换成其它任意索引名试试,也是可以成功的。

  • 查看索引mapping 那如何查看一个索引有哪些字段/属性呢?es中称为mapping关系
shell 复制代码
# console中执行查看
GET /products/_mapping

# {
#   "products": {
#     "mappings": {
#       "properties": {
#         "brand_name": {
#           "type": "text",
#           "fields": {
#             "keyword": {
#               "type": "keyword",
#               "ignore_above": 256
#             }
#           }
#         },
#         .....
#       }
#     }
#   }
# }
  • 创建索引mapping 前面我们的索引mapping,是通过自动生成的,这里我们手动生成

我们以电商系统经常用到的订单举例,假设索引名为orders

json 复制代码
// console执行 创建名为orders的索引,mapping结构体如下
PUT /orders 
{
  "mappings": {
    "properties": {
      "order_id": {
        "type": "keyword"
      },
      "customer_id": {
        "type": "keyword"
      },
      "customer_name": {
        "type": "text"
      },
      "order_date": {
        "type": "date"
      },
      "total_amount": {
        "type": "float"
      },
      "payment_method": {
        "type": "keyword"
      },
      "shipping_address": {
        "type": "text"
      },
      "order_status": {
        "type": "keyword"
      },
      "items": {
        "type": "nested",
        "properties": {
          "product_id": {
            "type": "keyword"
          },
          "product_name": {
            "type": "text"
          },
          "category": {
            "type": "keyword"
          },
          "quantity": {
            "type": "integer"
          },
          "price": {
            "type": "float"
          }
        }
      }
    }
  }
}

执行完后,可以通过GET /orders/_mapping查看会发现索引、mapping都已成功建立。

  • 批量构建测试数据 我们构建测试数据,最好的方式是通过脚本快速生成测试数据,但从方便性上考虑,我们直接生成了测试数据bulk_data.json,到这里下载json数据到您本地的bulk_data.json使用即可。

直接发请求去创建数据 PS:注意curl目录应该和bulk_data.json目录在同一目录哦

shell 复制代码
curl -X POST "http://localhost:9200/orders/_bulk" -u "elastic:your_elastic_password" -H "Content-Type: application/json" --data-binary "@bulk_data.json"

我bulk_data.json内是200条数据,让我们验证下

shell 复制代码
# kibana console 中执行
GET /orders/_count

# {
#   "count": 200,
#   "_shards": {
#     "total": 1,
#     "successful": 1,
#     "skipped": 0,
#     "failed": 0
#   }
# }

ok, 我们的测试数据已经构建完成啦。

三、搜索

1. 单一条件搜索

假设我们想搜索顾客姓名为张三的订单数据

shell 复制代码
curl -X GET "http://localhost:9200/orders/_search" -u "elastic:your_elastic_password" -H "Content-Type: application/json" -d'
{
  "query": {
    "match": { "customer_name": "张三" }
  }
}'

2. 多条件搜索

添加支付方式为微信的订单 PS: 多条件时,将匹配条件放于must中

shell 复制代码
curl -X GET "http://localhost:9200/orders/_search" -u "elastic:your_elastic_password" -H "Content-Type: application/json" -d'
{
  "query": {
    "bool": {
      "must": [
        { "match": { "customer_name": "张三" } },
        { "term": { "payment_method": "微信" } }
      ]
    }
  }
}'

这里会正常匹配出张三、微信支付的数据,需要注意的是:

  • must用于多个条件同时满足。
  • match 用于text类型数据的全文搜索,会有分词的情况
  • term 适合keyword类型数据,它是不分词的------作为整体去处理

假设我们像在前面进一步搜索,订单金额大于200的订单呢?

shell 复制代码
curl -X GET "http://localhost:9200/orders/_search" -u "elastic:your_elastic_password" -H "Content-Type: application/json" -d'
{
  "query": {
    "bool": {
      "must": [
        { "match": { "customer_name": "张三" } },
        { "term": { "payment_method": "微信" } },
        {"range": { "total_amount": {"gt": 200 }}}
      ]
    }
  }
}'

3. 嵌套搜索

复制代码
来继续.我们搜索商品类型是服饰的商品呢?
我们的商品信息是放在items内部这里嵌套了
  1. 使用nested 包裹一层,path: 指明嵌套路径
  2. 使用外层字段.内层字段方式嵌套搜索
shell 复制代码
curl -X GET "http://localhost:9200/orders/_search" -u "elastic:your_elastic_password" -H "Content-Type: application/json" -d'
{
  "query": {
    "bool": {
      "must": [
        { "match": { "customer_name": "张三" } },
        { "term": { "payment_method": "微信" } },
        {"range": { "total_amount": {"gt": 200 }}},
        {
          "nested": {
            "path": "items",
            "query": {
              "term": {
                "items.category": "服饰"
              }
            }
          }
        }
      ]
    }
  }
}'

好啦,相信进过前面的练习,您也对基本查询有了足够的了解。

四、聚合

搜索只是es最基础的功能,es最强大的地方是数据分析,要数据分析,一定要会使用聚合统计。我们由简单到复杂一步步开始。

1.term分组聚合

go 复制代码
比如我们按照支付方式,统计每种支付方式的订单数据。这是最简单的统计,如果您在关系型数据库中,也就是 `Group` 配合`Count`完成,那在es中要怎么写呢?
shell 复制代码
curl -X GET "http://localhost:9200/orders/_search" -u "elastic:your_elastic_password" -H "Content-Type: application/json" -d' {
  "aggs": {
    "payment_methods": {
      "terms": {
        "field": "payment_method"
      }
    }
  },
  "size": 0
}'

# {
#     "took": 13,
#     "timed_out": false,
#     "_shards": {
#         "total": 1,
#         "successful": 1,
#         "skipped": 0,
#         "failed": 0
#     },
#     "hits": {
#         "total": {
#             "value": 200,
#             "relation": "eq"
#         },
#         "max_score": null,
#         "hits": [ ]
#     },
#     "aggregations": {
#         "payment_methods": {
#             "doc_count_error_upper_bound": 0,
#             "sum_other_doc_count": 0,
#             "buckets": [
#                 {
#                     "key": "银行卡",
#                     "doc_count": 70
#                 },
#                 {
#                     "key": "支付宝",
#                     "doc_count": 66
#                 },
#                 {
#                     "key": "微信",
#                     "doc_count": 64
#                 }
#             ]
#         }
#     }
# }

解释下聚会的基本格式是:

json 复制代码
{
  "size": 0, 
  "aggs": {
    "your_aggregation_name": {
      "terms": {
        "field": "your_field_name",
        "size": 10 
      }
    }
  }
}

// 两个size都是可省略的
// 1. 外层size: 0 代表不返回具体文档
// 2. 内层size: 10 代表只返回聚合后桶的个数(比如前面的例子如果2的话,只返回2个桶------银行卡、支付宝)

2.Range范围聚合

go 复制代码
如果我们想知道总价在各个价格段的订单数量,我们可以使用range聚合实现。
shell 复制代码
curl -X GET "http://localhost:9200/orders/_search" -u "elastic:your_elastic_password" -H "Content-Type: application/json" -d' {
  "aggs": {
    "total_amount_range": {
      "range": {
        "field": "total_amount",
        "ranges": [
          {"key": "<50", "to": 50},
          {"key": "50-100(不包含)", "from": 50, "to": 100},
          {"key": "100-150(不包含)", "from": 100, "to": 150},
          {"key": "100-200(不包含)", "from": 150, "to": 200},
          {"key": ">200", "from": 200}
        ]
      }
    }
  },
  "size": 0
}'

# "aggregations": {
#  "total_amount_range":{
#   "buckets":[
#     {"key":"<50","to":50.0,"doc_count":0},
#     {"key":"50-100(不包含)","from":50.0,"to":100.0,"doc_count":27},
#     {"key":"100-150(不包含)","from":100.0,"to":150.0,"doc_count":18},
#     {"key":"100-200(不包含)","from":150.0,"to":200.0,"doc_count":20},
#     {"key":">200","from":200.0,"doc_count":135}
#   ]
# }}

需要注意的点:

  1. range下面还有一个ranges数组区分不同区间
  2. ranges中key可以省略,推荐的写法是写上
  3. from(包含)to(不包含该值)

3. Date Histogram 按日期统计

javascript 复制代码
Date Histogram 可以让我们根据日期去做些统计,比如:
我们按日期统计2024年10月每天的订单数,可以这样写:
shell 复制代码
curl -X GET "http://localhost:9200/orders/_search" -u "elastic:your_elastic_password" -H "Content-Type: application/json" -d '{
  "query": {
    "range": {
      "order_date": {
        "gte": "2024-10-01",  
        "lte": "2024-10-31" 
      }
    }
  },
  "aggs": {
    "by_day_orders": {
      "date_histogram": {
        "field": "order_date",
        "calendar_interval": "1M"
      }
    }
  },
  "size": 0
}'

# "aggregations":{
#   "by_day_orders":
#     { "buckets":[
#     {"key_as_string":"2024-10-01T00:00:00.000Z","key":1727740800000,"doc_count":2},
#     {"key_as_string":"2024-10-02T00:00:00.000Z","key":1727827200000,"doc_count":2}
# ...

注意的点:

  1. 我们额外添加查询在aggs外重新添加query就行
  2. calendar_interval非常灵活,按分-m、小时-h、天-d、周-w、月-M、季度-q都支持推荐使用这个
  3. 另外还有一个fixed_interval按主要用于固定间隔,比如按分-m、小时-h、天-d,它不支持w/M/q

4. 指标聚合

复制代码
所谓指标聚合就是我们常说的,平均值、最大值、最小等等。
shell 复制代码
curl -X GET "http://localhost:9200/orders/_search" -u "elastic:your_elastic_password" -H "Content-Type: application/json" -d '{
  "aggs": {
    "avg_amount": {
      "avg": {
        "field": "total_amount"
      }
    },
    "sum_amounts": {
      "sum": {
        "field": "total_amount"
      }
    }
  },
  "size": 0
}'

# "aggregations": {
# "avg_amount":{"value":269.77175117492675},"sum_amounts":{"value":53954.35023498535}}

补充:

  • avg平均

  • sum求和

  • max最大

  • min最小 另外我们注意到,是可以一次做多个层面聚合的哦。

5. 嵌套聚合

复制代码
假设我们想先按支付方式聚合,然后再在每种支付方式下按照,订单状态聚合。

这里就有层级了,本质上是对每种支付方式的进一步划分,现实业务中很多这样的场景。

shell 复制代码
curl -X GET "http://localhost:9200/orders/_search" -u "elastic:your_elastic_password" -H "Content-Type: application/json" -d '{
  "aggs": {
    "payment_methods": {
      "terms": {
        "field": "payment_method"
      },
      "aggs": {
        "order_statues": {
          "terms": {
            "field": "order_status"
          }
        }
      }
    }
  },
  "size": 0
}'

# 主体格式如下
# "aggregations": {
#   "payment_methods": {
#     "doc_count_error_upper_bound": 0,
#     "sum_other_doc_count": 0,
#     "buckets": [
#       {
#         "key": "银行卡",
#         "doc_count": 70,
#         "order_statues": {
#           "doc_count_error_upper_bound": 0,
#           "sum_other_doc_count": 0,
#           "buckets": [
#             {
#                 "key": "cancel",
#                 "doc_count": 26
#             },
#             {
#                 "key": "paid",
#                 "doc_count": 25
#             },
#             {
#                 "key": "unpaid",
#                 "doc_count": 19
#             }
#           ]
#         }
# ....

6. Pipeline聚合

go 复制代码
它是一种高级聚合形式,是对聚合出来的结果进行二次聚合计算,为啥叫管道`pipeline`呢?其实是取类似于linux的管道。

举例: 比如我们想先按月统计每月的总销售额,然后计算本月相对上月增长百分比,如果上月销售额为0,则本月增长为1000,该怎么写呢?

shell 复制代码
curl -X GET "http://localhost:9200/orders/_search" -u "elastic:your_elastic_password" -H "Content-Type: application/json" -d '
{
  "aggs": {
    "sales_by_month": {
      "date_histogram": {
        "field": "order_date",
        "calendar_interval": "month"
      },
      "aggs": {
        "total_sales": {
          "sum": {
            "field": "total_amount"
          }
        },
        "sales_diff": {
          "derivative": {
            "buckets_path": "total_sales"
          }
        },
        "growth_rate": {
          "bucket_script": {
            "buckets_path": {
              "sales_diff": "sales_diff",
              "current_sales": "total_sales"
            },
            "script": "params.sales_diff == null ? null : params.sales_diff == params.current_sales && params.current_sales > 0 ? 1000 : params.sales_diff / (params.current_sales - params.sales_diff)"
          }
        }
      }
    }
  },
  "size": 0
}'

# "aggregations": {
#   "sales_by_month": {
#     "buckets": [
#       {
#         "key_as_string": "2024-09-01T00:00:00.000Z",
#         "key": 1725148800000,
#         "doc_count": 1,
#         "total_sales": {
#             "value": 301.5899963378906
#         }
#       },
#         {
#           "key_as_string": "2024-10-01T00:00:00.000Z",
#           "key": 1727740800000,
#           "doc_count": 84,
#           "total_sales": {
#               "value": 21864.190086364746
#           },
#           "sales_diff": {
#               "value": 21562.600090026855
#           },
#           "growth_rate": {
#               "value": 71.49640356727512
#           }
#         }
# ...

说明下

shell 复制代码
"sales_diff": {
  "derivative": {
    "buckets_path": "total_sales"
  }
}

这里sales_diff存储的是当前销售额和上月销售额之间的差值derivative是es中的特殊管道,自动帮我们做了计算。

然后,我们只是去利用这个差额,就能计算出上月销售额 = 本月 - 差额,以此计算百分比进行实现的。

对于script部分,本质是先将要用到的params值,在buckets_path中先定义好,然后去使用的。

上面的例子已经算es中比较复杂的例子了。在使用时,对于script计算的部分,我们实际可以放在程序中自己去处理的,看需要吧!

至此聚合的部分也就差不多了。

五、最后

本篇我们从es环境的搭建,测试数据的准备,再到各种查询、聚合统计,相信您看完本文也有了一个全盘的认识,以后遇到具体的问题只需稍微拓展就能使用了。

最后,真心希望本篇能给您带来真正的帮助。

相关推荐
Traving Yu几秒前
Spring源码与框架原理
java·后端·spring
王家视频教程图书馆4 分钟前
rust 写gui 程序 最流行的是哪个
开发语言·后端·rust
要记得喝水6 分钟前
适用于 Git Bash 的脚本,批量提交和推送多个仓库的修改
git·elasticsearch·bash
好大哥呀13 分钟前
如何在Spring Boot中配置数据库连接?
数据库·spring boot·后端
二十七剑18 分钟前
Elasticsearch的索引问题
大数据·elasticsearch·搜索引擎
老神在在00120 分钟前
企业级 SpringBoot 后端通用开发规范|统一响应 + 敏感字段加密
spring boot·后端·状态模式
csdn_aspnet29 分钟前
在 ASP.NET Core (WebAPI) 中启用 CORS
后端·asp.net·.netcore
好家伙VCC29 分钟前
**InfluxDB实战进阶:基于Golang的高性能时序数据采集与可视化方
java·开发语言·后端·python·golang
心静财富之门2 小时前
Flask 详细讲解 + 实战实例(零基础可学)
后端·python·flask
大鸡腿同学8 小时前
【成长类】《只有偏执狂才能生存》读书笔记:程序员的偏执型成长地图
后端