【技术派后端篇】ElasticSearch 实战指南:环境搭建、API 操作与集成实践

1 ES介绍及基本概念

ElasticSearch是一个基于Lucene 的分布式、高扩展、高实时的基于RESTful 风格API的搜索与数据分析引擎。

RESTful 风格API的特点:

  1. 接受HTTP协议的请求,返回HTTP响应;
  2. 请求的参数是JSON,返回响应的内容也是JSON格式;
  3. 请求的类型(GET、POST、PUT、DELETE)就代表对资源的操作类型。

1.1 基本概念

在Elasticsearch(ES)的体系中,各核心概念清晰且独特,支撑着其强大的搜索与数据分析能力:

  • 字段(Field):字段代表一种属性,类似于关系型数据库表中的列。在数据库里,每一行数据由多个属性值构成,比如员工表中,“姓名”“年龄”“职位”这些列就是字段,对应具体员工的数据,如“张三”“30”“工程师”就是字段值。在ES中,一个字段同样承载特定的属性信息,像一篇博客文章,“标题”“发布时间”“内容”等都可作为字段。
  • 文档(Document):ES的数据存储方式与传统关系型数据库截然不同,所有数据均以JSON格式的document形式存在。document作为ES索引与搜索的最小数据单元,类比于数据库中的一行数据。以电商平台为例,一条商品信息记录就是一个document,它由“商品名称”“价格”“库存”“描述”等多个字段值组成,可能像这样:
{
    "商品名称": "智能手表",
    "价格": 1299,
    "库存": 50,
    "描述": "具备多种健康监测功能的智能手表"
}
  • 映射(Mapping):Mapping用于定义document中每个字段的类型、字段所采用的分词器等细节,其作用等同于关系型数据库中的表结构,为数据的存储与处理提供规范。例如,在一个新闻资讯索引中,“标题”字段可定义为字符串类型,并使用标准分词器,以便对标题进行合理切分用于搜索;“发布时间”字段定义为日期类型,方便按时间范围检索新闻。通过这样的映射定义,ES能更高效地管理和利用数据。
  • 索引(Index):Index是Elasticsearch存储数据的载体,可理解为关系型数据库中的数据库概念,用于存放一类相同或相似的document。假设运营一个在线图书馆系统,可能会有“小说类书籍索引”“学术文献索引”等。“小说类书籍索引”中存放的就是所有小说书籍相关的document,每个document包含书籍的名称、作者、出版社、内容简介等信息。
  • 类型(Type):Type属于逻辑层面的数据分类方式,一种Type类似于一张表,像常见的用户表、订单表等。在Elasticsearch 6.X版本中,默认Type为_doc ;而到了ES 7.x版本,Type这一概念已被移除 。在早期的论坛系统ES应用中,可能会设置“用户类型”和“帖子类型”,“用户类型”的document存储用户的账号、密码、注册时间等信息;“帖子类型”的document存放帖子的标题、内容、发布者、发布时间等数据。但在7.x及之后版本,不再使用这种多类型区分方式,而是更强调数据的统一索引管理 。

1.2 ES原理与倒排索引

1.2.1 ES整体原理概述

ES 是一个分布式的搜索与数据分析引擎,基于 Lucene 构建。它将数据存储在分布式的节点上,以实现高可用性、高扩展性和高性能。其核心工作流程围绕索引和搜索两个关键环节。在索引阶段,数据被处理并构建索引结构;搜索阶段则依据构建好的索引快速检索出符合条件的数据。

1.2.2 倒排索引的概念

传统数据库使用的是正向索引,即从文档 ID 到文档内容及单词位置的映射。而倒排索引恰恰相反,是从单词到包含该单词的文档 ID 列表的映射。简单来说,倒排索引就是将文档中的每个单词提取出来,记录每个单词在哪些文档中出现过以及出现的位置等信息。

例如,有以下两篇技术博客文章:
文章 1(ID=1):“Elasticsearch 是强大的搜索工具,适用于大数据场景。”
文章 2(ID=2):“学习技术博客写作,掌握搜索技巧很重要,比如使用 Elasticsearch。”
倒排索引构建过程如下:

单词包含该单词的文档ID列表
Elasticsearch1, 2
1
强大的1
搜索1, 2
工具1
适用于1
大数据1
场景1
学习2
技术博客2
写作2
掌握2
技巧2
很重要2
比如2
使用2

这样在搜索时,根据输入的关键词能快速定位到包含该关键词的文档,大大提高搜索效率。

1.2.3 以技术博客网站文章检索为例

假设一个技术博客网站有大量文章,存储在ES中。当用户在搜索框输入关键词“Elasticsearch”进行检索时,ES的工作流程如下:

  1. 用户输入:用户在博客网站搜索框输入“Elasticsearch”。
  2. 请求发送:网站将搜索请求发送给ES服务器。
  3. 倒排索引查询:ES在倒排索引中查找“Elasticsearch”这个单词,找到对应的文档ID列表,即[1, 2]。
  4. 文档获取:根据文档ID,从存储文档的地方获取对应的两篇文章内容。
  5. 结果返回:将这两篇文章的相关信息(如标题、摘要等)返回给用户展示。

在这里插入图片描述

通过这种基于倒排索引的机制,ES能够快速准确地从海量的技术博客文章中找到用户需要的内容,为用户提供高效的搜索体验 。

2 ES环境搭建

2.1 Windows版安装步骤

2.1.1 ElasticSearch安装

  1. 安装Java环境:下载并安装JDK(Java Development Kit),并配置好Java环境变量。具体可参考相关Java安装教程。
  2. 下载安装包:从ElasticSearch官网下载适合Windows系统的zip安装包。

注意:不要下载最新版本,最好根据IK分词器的版本来下载,不然可能会没有对应的分词器版本

  1. 解压文件:将下载后的zip文件解压到指定目录,例如D:\elasticsearch

  2. 启动服务:进入解压目录下的bin目录,双击elasticsearch.bat文件启动ElasticSearch服务。启动后,在浏览器地址栏输入http://localhost:9200/ ,若能看到ElasticSearch相关信息,说明启动成功。
    在这里插入图片描述

  3. 安装为Windows服务(可选)

    • 设置环境变量:右键点击“此电脑”,选择“属性”,点击“高级系统设置”,在弹出窗口中点击“环境变量”。在系统变量中新建变量,变量名可设为ELASTICSEARCH_HOME ,变量值为ElasticSearch的安装目录(如D:\elasticsearch );然后在Path变量中添加%ELASTICSEARCH_HOME%\bin
    • 安装服务:以管理员身份运行命令提示符,进入ElasticSearch的bin目录,执行elasticsearch-service.bat install命令安装服务。可使用elasticsearch-service.bat start启动服务、elasticsearch-service.bat stop停止服务、elasticsearch-service.bat remove移除服务。
  4. 修改 Elasticsearch 配置为 HTTP(不推荐用于生产环境):如果发现启动ES后无法访问,打开 Elasticsearch 安装目录下的 config 文件夹,找到 elasticsearch.yml 文件,修改配置之后再重启ElasticSearch,即可正常访问。
    在这里插入图片描述

2.1.2 Kibana安装

  1. 下载安装包:从Kibana官网下载与ElasticSearch版本匹配的Windows版.zip安装包。
  2. 解压文件:在D盘新建kibana目录,将下载的.zip文件解压到该目录下,解压后得到kibana-版本号-windows-x86_64文件夹(即$KIBANA_HOME )。
  3. 配置文件:打开$KIBANA_HOME\config\kibana.yml配置文件,设置elasticsearch.url为指向ElasticSearch实例地址,默认http://localhost:9200一般无需修改。如果ElasticSearch服务地址有变化,需相应调整。
  4. 启动服务:进入$KIBANA_HOME\bin目录,双击kibana.bat启动Kibana。启动后可在浏览器输入http://localhost:5601访问Kibana界面。
    在这里插入图片描述

2.1.3 IK分词器安装

  1. 下载:根据ElasticSearch版本,从https://github.com/medcl/elasticsearch-analysis-ik/releases下载对应的IK分词器压缩包。例如,若ElasticSearch是7.10.1版本,则下载ik-710.1.zip
  2. 解压与放置:解压下载的文件,将解压后的文件夹复制到ElasticSearch安装目录下的plugins文件夹内,并将文件夹重命名为analysis-ik 。比如ElasticSearch安装在D:\elasticsearch ,则将文件夹复制到D:\elasticsearch\plugins\analysis-ik
  3. 重启ElasticSearch:以管理员身份运行bin目录下的elasticsearch.bat重启ElasticSearch,即可加载IK分词器。可通过发送请求测试分词效果,如使用Postman等工具。
    在这里插入图片描述

2.2 Ubuntu版(Linux版)安装步骤

2.2.1 ElasticSearch安装

  1. 下载和解压
    • 从ElasticSearch官网(https://www.elastic.co/cn/downloads/elasticsearch )选择合适版本下载,然后上传到Ubuntu服务器。也可在命令行执行wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-版本号-linux-x86_64.tar.gz下载(下载速度可能较慢)。
    • 执行解压缩命令tar -zxvf elasticsearch-版本号-linux-x86_64.tar.gz -C /usr/local
  2. 配置JDK(若有冲突):新版本ElasticSearch压缩包自带JDK,若Ubuntu已安装JDK且版本不符,启动时会报错。可切换到解压目录的bin目录,执行export JAVA_HOME=/usr/local/elasticsearch-版本号/jdk指定使用自带JDK 。
  3. 创建专用用户root用户不能直接启动ElasticSearch,需创建专用用户,如user-es 。使用命令useradd user-es创建用户,passwd user-es设置密码 。
  4. 修改配置文件:进入config文件夹,编辑elasticsearch.yml文件。可修改http.port等参数,如http.port: 19200 。还可根据需求配置集群相关参数等。
  5. 解决内存权限问题:若启动报错max virtual memory areas vm.max_map_count (65530) is too low, increase to at least (262144) ,切换到root用户(sudo su ),编辑/etc/sysctl.conf文件,在文件末尾添加vm.max_map_count=262144 ,保存退出后执行sysctl -p刷新配置 。
  6. 启动服务:切换到专用用户(su user-es ),进入ElasticSearch的bin目录,执行./elasticsearch启动服务。启动成功后可通过http://服务器IP:端口号(如http://127.0.0.1:19200 )访问,出现相关信息则安装成功。
    在这里插入图片描述
    在这里插入图片描述

2.2.2 Kibana安装

  1. 下载和解压:从Kibana官网(https://www.elastic.co/cn/downloads/kibana )下载与ElasticSearch版本匹配的安装包,例如kibana-版本号-linux-x86_64.tar.gz 。使用命令wget https://artifacts.elastic.co/downloads/kibana/kibana-版本号-linux-x86_64.tar.gz下载后,执行tar -zxvf kibana-版本号-linux-x86_64.tar.gz解压 。
  2. 修改配置文件:进入解压后的目录,编辑config/kibana.yml文件,配置内容如下:
server.port: 5601
server.host: "0.0.0.0"
elasticsearch.url: "http://服务器IP:9200"
kibana.index: ".kibana"

服务器IP替换为实际ElasticSearch服务器的IP地址。
3. 启动服务:切换到解压目录的bin目录,执行./kibana &&表示在后台运行)启动Kibana。启动过程中若ElasticSearch未启动会有警告,连接成功后可通过浏览器访问http://服务器IP:5601

2.2.3 IK分词器安装

  1. 下载:根据ElasticSearch版本,从https://github.com/medcl/elasticsearch-analysis-ik/releases下载对应的IK分词器压缩包。
  2. 解压与放置:解压下载的文件,将解压后的文件夹(需命名为analysis-ik )复制到ElasticSearch安装目录下的plugins文件夹内。比如ElasticSearch安装在/usr/local/elasticsearch-版本号 ,则将文件夹复制到/usr/local/elasticsearch-版本号/plugins/analysis-ik
  3. 重启ElasticSearch:切换到启动ElasticSearch的专用用户,进入ElasticSearch的bin目录,执行./elasticsearch重启服务,即可加载IK分词器。

3 RESTful API操作ES

3.1 操作索引

  • 添加索引
PUT user

向Elasticsearch集群添加名为user的索引。若索引已存在,会返回相应错误提示。

  • 查询单个索引
GET /teacher

获取名为teacher的索引的相关信息,如索引的基本设置、映射等。若索引不存在,会返回404错误。

  • 查询多个索引
GET /user,student

同时获取userstudent两个索引的相关信息。可一次性查看多个索引的状态,方便对比和管理。

  • 查询所有索引
GET /_all

返回Elasticsearch集群中的所有索引的相关信息。在管理多个索引,需要快速了解整体索引情况时使用。

  • 删除索引
DELETE /student

从Elasticsearch集群中删除名为student的索引及其包含的所有文档数据。删除操作不可逆,谨慎使用。

  • 打开索引
POST /student/_open

将处于关闭状态的student索引打开,使其可进行数据的读写和查询操作。索引关闭时,对其进行的任何数据操作都会失败。

  • 关闭索引
POST /student/_close

关闭student索引,关闭后索引不可读写,但索引数据仍存储在磁盘上。可用于在维护索引或减少资源占用时临时关闭索引。

3.2 数据类型

3.2.1 简单数据类型

我们先来看一个简单的映射定义:

PUT teacher/_mapping
{
    "properties": {
      "id": {
        "type": "integer"  
      },
      "name": {
        "type": "text"
      },
      "isMale": {
        "type": "boolean"
      }
    }
}

在定义映射的时候,类比于定义数据库中的表结构,我们需要指明每一个字段的名称,数据类型等等信息,所以我们先得了解映射中包含的数据类型。

  • 字符串

    • text:会分词,不支持聚合
    • keyword:不会分词,将全部内容作为一个词条,支持聚合
  • 数值:long, integer, short, byte, double, float, half_float, scaled_float

  • 布尔:boolean

  • 二进制:binary

  • 范围类型:integer_range,float_range, long_range, double_range, date_range

  • 日期:date

3.2.2 复杂数据类型

  • 数组:[ ] 没有专门的数组类型,ES会自动处理数组类型数据

比如说 给某一个字段 定义为 integer类型,那么这个字段是可以存储 integer数组的,ES会自动处理

  • 对象:{ } Object: object(for single JSON objects 单个JSON对象)

3.3 操作映射

  • 给已存在索引添加映射
PUT /user/_mapping
{
    "properties":{
        "id": {"type":"integer"},
        "name":{"type":"keyword"},
        "age":{"type":"long"}
    }
}

为已存在的user索引添加字段映射,定义id为整数类型、name为关键词类型、age为长整型。添加映射时,字段类型一旦确定,后续修改会受限制。

  • 创建索引并指定映射
PUT teacher
{
    "mappings": {
        "properties": {
            "tid":{"type": "integer"},
            "tname":{"type": "text"}
        }
    }
}

创建名为teacher的索引,并同时指定其映射,定义tid为整数类型、tname为文本类型。在创建索引时规划好映射,能确保数据存储和查询的准确性。

  • 查询映射
GET /teacher/_mapping

获取teacher索引的映射信息,包括字段类型、分词器设置等。方便查看索引的结构定义,排查查询问题。

  • 修改映射(只能添加字段)
PUT /teacher/_mapping
{
    "properties":{
        "height":{"type":"float"},
        "weight":{"type":"double"}
    }
}

teacher索引的映射中添加heightweight字段,分别为浮点型和双精度型。Elasticsearch不支持直接修改已存在字段的类型,只能添加新字段。

3.4 操作文档

  • 添加文档 - 指定文档id
POST /user/_doc/1
{
    "id": 1001,
    "name":"贾宝玉",
    "age":15
}

user索引中添加一个文档,并指定文档的id为1。若指定id的文档已存在,会覆盖原有文档。

  • 添加文档 - 不指定文档id
POST /user/_doc
{
    "id":1002,
    "name":"贾迎春",
    "age":13
}

user索引添加文档,Elasticsearch会自动生成一个唯一的文档id。适用于对文档id无特定要求的场景。

  • 查询文档 - 根据文档id查询
GET /user/_doc/1
GET /user/_doc/NsORPo0BJehhapNsslSw

根据文档id获取user索引中的文档。若文档不存在,会返回404错误。

  • 查询文档 - 查询所有文档
GET /user/_search

获取user索引中的所有文档。在数据量较大时,可能需要结合分页参数使用,避免一次性返回过多数据。

  • 修改文档 - 直接覆盖
POST /user/_doc/1
{
    "id": 2001,
    "name":"刘姥姥",
    "age":80
}

用新的文档数据覆盖user索引中id为1的文档。会完全替换原有文档内容,包括所有字段。

  • 修改指定字段
POST /user/_update/1
{
    "doc": {
        "name":"贾琏",
        "age": 30
    }
}

仅修改user索引中id为1的文档的nameage字段,其他字段保持不变。比直接覆盖更灵活,可减少数据传输量。

  • 删除文档(指定文档id)
DELETE /user/_doc/1

删除user索引中id为1的文档。删除操作不可逆,操作前需确认数据是否不再需要。

3.5 IK分词器测试

  • ik_max_word分词模式
GET /_analyze
{
    "text": "湖南是全国唯一一个叫湖南的省份",
    "analyzer": "ik_max_word"
}

使用ik_max_word分词器对文本进行分词,它会尽可能细粒度地切分文本,如上述文本会被切分成“湖南”“是”“全国”“唯一”“一个”“叫”“湖南”“的”“省份”等多个词条。

  • ik_smart分词模式
GET /_analyze
{
    "text": "湖南是全国唯一一个叫湖南的省份",
    "analyzer": "ik_smart"
}

ik_smart分词器的粒度更粗,会对文本进行更合理的合并切分,上述文本可能被切分成“湖南”“是”“全国”“唯一一个”“叫”“湖南的省份”等词条。

  • 使用ES自带的分词器
GET /_analyze
{
    "text": "stone is best man, ciggar is handsome man",
    "analyzer": "standard"
}

使用ES自带的standard分词器对英文文本进行分词,它会按空格和标点符号进行切分,并将单词转换为小写形式,如上述文本会被切分成“stone”“is”“best”“man”“ciggar”“is”“handsome”“man”等词条。

3.6 ES批量操作

  • 创建索引并设置映射
DELETE /teacher
PUT /teacher
{
    "mappings": {
        "properties": {
            "id":{"type": "integer"},
            "name":{
                "type": "text",
                "analyzer": "ik_max_word"
            },
            "age":{
                "type": "integer"
            }
        }
    }
}

先删除可能存在的teacher索引,再创建新的teacher索引,并设置其映射。其中name字段使用ik_max_word分词器进行分词。

  • 查询索引中的所有文档
GET /teacher/_search

获取teacher索引中的所有文档,用于查看当前索引中的数据内容。

  • 批量操作
POST /_bulk
{"create":{"_index":"teacher","_id":"1"}}
{"id":1,"name":"金角大王","age":30}
{"create":{"_index":"teacher","_id":"2"}}
{"id":2,"name":"银角大王","age":20}
{"create":{"_index":"teacher","_id":"3"}}
{"id":3,"name":"小钻风","age":10}
{"delete":{"_index":"teacher","_id":"1"}}
{"update":{"_index":"teacher","_id":"2"}}
{"doc":{"name":"狮驼岭","age":1000}}

在一次请求中对teacher索引执行多个操作,包括创建文档(create)、删除文档(delete)和更新文档(update)。批量操作可减少网络请求次数,提高数据处理效率。

  • 创建另一个索引并设置映射
PUT /member
{
    "mappings": {
        "properties": {
            "id":{"type": "integer"},
            "name":{"type": "keyword"},
            "addr":{
                "type": "text",
                "analyzer": "ik_max_word"
            },
            "gender":{"type": "keyword"},
            "money":{"type": "integer"}
        }
    }
}

创建名为member的索引,并设置其映射。addr字段使用ik_max_word分词器,方便对地址进行分词查询。

  • 查询索引信息
GET /member/_search
GET /member/_mapping

分别查询member索引中的所有文档和获取member索引的映射信息,用于了解索引的数据和结构。

3.7 ES高级查询

  • match_all查询
GET /member/_search
{
    "query": {
        "match_all": {}
    },
    "from": 0,
    "size": 3
}

查询member索引中的所有文档,并指定从第0条开始,返回3条数据。常用于获取索引的部分数据样本,或在不关心具体查询条件时获取全部数据。

  • term查询
GET /member/_search
{
    "query": {
        "term": {
            "addr": {
                "value": "山区"
            }
        }
    }
}

member索引中查询addr字段值为“山区”的文档。term查询不会对搜索关键字进行分词,直接与目标字段进行精确匹配。

  • 全文查询 - match
GET /member/_search
{
    "query": {
        "match": {
            "addr": "湖北武汉"
        }
    }
}

member索引的addr字段进行全文查询,会对搜索关键字“湖北武汉”进行分词,然后与addr字段的分词结果进行匹配。默认取并集,即只要匹配到其中一个分词结果的文档就会被返回。

  • 模糊查询 - 通配符查询
GET /member/_search
{
    "query": {
        "wildcard": {
            "addr": {
                "value": "武?"
            }
        }
    }
}

member索引中查询addr字段值以“武”开头且后面跟任意一个字符的文档。?表示占位,*表示通配任意多个字符。

  • 正则查询
GET /member/_search
{
    "query": {
        "regexp": {
            "addr": "[A-Z a-z 0-9_]+(.)*"
        }
    }
}

使用正则表达式对member索引的addr字段进行查询,匹配由字母、数字、下划线组成且后面可跟任意字符的地址。

  • 前缀查询
GET /member/_search
{
    "query": {
        "prefix": {
            "addr": {
                "value": "青"
            }
        }
    }
}

member索引中查询addr字段值以“青”开头的文档。前缀查询不太适用于text类型字段,因为text类型字段会被分词,可能导致查询结果不准确。

  • queryString多条件查询
GET /member/_search
{
    "query": {
        "query_string": {
            "fields": ["addr","gender"], 
            "query": "武汉 AND 男"
        }
    }
}

member索引中查询addr字段包含“武汉”且gender字段为“男”的文档。注意ANDOR要大写,且不要使用simple_query_string,它不会识别ANDOR关键字。

  • 排序查询
GET /member/_search
{
    "query": {
        "match_all": {}
    },
    "sort": [
        {
            "money": {
                "order": "asc"
            }
        }
    ]
}

查询member索引中的所有文档,并按money字段的值进行升序排序。可根据需求指定其他字段进行排序,以及选择升序(asc)或降序(desc)。

  • 范围查询
GET /member/_search
{
    "query": {
        "range": {
            "money": {
                "gte": 300,
                "lte": 600
            }
        }
    },
    "sort": [
        {
            "money": {
                "order": "desc"
            }
        }
    ],
    "from": 0,
    "size": 3
}

member索引中查询money字段值在300到600之间的文档,并按money字段的降序排序,返回前3条数据。

3.8 bool查询

  • must条件
GET /member/_search
{
    "query": {
        "bool": {
            "must": [
                {
                    "term": {
                        "addr": {
                            "value": "武汉"
                        }
                    }
                },
                {
                    "term": {
                        "gender": {
                            "value": "女"
                        }
                    }
                }
            ]
        }
    }
}

查询member索引中addr字段为“武汉”且gender字段为“女”的文档,会计算文档的近似度得分。must条件下的所有子条件都必须满足才能匹配到文档。

  • filter条件
GET /member/_search
{
    "query": {
        "bool": {
            "filter": [
                {
                    "match": {
                        "addr": "湖北武汉"
                    }
                },
                {
                    "range": {
                        "money": {
                            "gte": 200,
                            "lte": 500
                        }
                    }
                }
            ]
        }
    }
}

查询member索引中addr字段包含“湖北武汉”且money字段值在200到500之间的文档,filter不会计算近似度得分,查询效率更高,适用于不需要计算得分的精确过滤场景。

  • must_not条件
GET /member/_search
{
    "query": {
        "bool": {
            "must_not": [
                {
                    "match": {
                        "addr": "武汉"
                    }
                },
                {
                    "term": {
                        "gender": {
                            "value": "女"
                        }
                    }
                }
            ]
        }
    }
}

查询member索引中addr字段不包含“武汉”且gender字段不为“女”的文档,同样不会计算近似度得分。

  • should条件
GET /member/_search
{
    "query": {
        "bool": {
            "should": [
                {
                    "term": {
                        "addr": {
                            "value": "武汉"
                        }
                    }
                },
                {
                    "term": {
                        "gender": {
                            "value": "女"
                        }
                    }
                }
            ]
        }
    }
}

查询member索引中addr字段为“武汉”或者gender字段为“女”的文档,会计算近似度得分。只要满足should中的一个子条件,文档就可能被返回。

  • 综合练习
GET /member/_search
{
    "query": {
        "bool": {
            "filter": [
                {
                    "range": {
                        "money": {
                            "gte": 200,
                            "lte": 600
                        }
                    }
                },
                {
                    "term": {
                        "gender": "男"
                    }
                }
            ],
            "must": [
                {
                    "match": {
                        "addr": "湖北武汉"
                    }
                }
            ]
        }
    }
}

查询member索引中money字段值在200到600之间、gender为“男”且addr包含“湖北武汉”的文档。

4 技术派整合ES

4.1 构造 RestHighLevelClient 客户端

  • 引入依赖:在 paicoding-service 模块的 pom 文件中,我们需要引入 es 和 es-client 相关的依赖。具体的依赖配置如下:
<!--引入es-high-level-client相关依赖  start-->
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>6.8.2</version>
</dependency>

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>6.8.2</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>6.8.2</version>
</dependency>
  • 配置 yml 文件:在 paicoding-web 模块的 src->main->resource-env->dev->application-dal.yml 中,配置 ES 的相关信息。具体配置如下:
# elasticsearch配置
elasticsearch:
  # 是否开启ES?本地启动如果没有安装ES,可以设置为false关闭ES
  open: false
  # es集群名称
  clusterName: elasticsearch
  hosts: 127.0.0.1:9200
  userName: elastic
  password: elastic
  # es 请求方式
  scheme: http
  # es 连接超时时间
  connectTimeOut: 1000
  # es socket 连接超时时间
  socketTimeOut: 30000
  # es 请求超时时间
  connectionRequestTimeOut: 500
  # es 最大连接数
  maxConnectNum: 100
  # es 每个路由的最大连接数
  maxConnectNumPerRoute: 100

在配置 yml 文件时,一定要注意格式的正确性,避免因格式错误导致配置失效。

  • 编写 config 配置类 RestHighLevelClient:配置类的作用是将 RestHighLevelClient 客户端的创建交由 Spring 管理。具体的配置类代码如下:
/**
 * es配置类
 *
 * @author ygl
 * @since 2023-05-25
 **/
@Slf4j
@Data
@Configuration
// 下面这个表示只有 elasticsearch.open = true 时,采进行es的配置初始化;当不使用es时,则不会实例 RestHighLevelClient
@ConditionalOnProperty(prefix = "elasticsearch", name = "open")
@ConfigurationProperties(prefix = "elasticsearch")
public class ElasticsearchConfig {

    // 是否开启ES
    private Boolean open;

    // es host ip 地址(集群)
    private String hosts;

    // es用户名
    private String userName;

    // es密码
    private String password;

    // es 请求方式
    private String scheme;

    // es集群名称
    private String clusterName;

    // es 连接超时时间
    private int connectTimeOut;

    // es socket 连接超时时间
    private int socketTimeOut;

    // es 请求超时时间
    private int connectionRequestTimeOut;

    // es 最大连接数
    private int maxConnectNum;

    // es 每个路由的最大连接数
    private int maxConnectNumPerRoute;


    /**
     * 如果@Bean没有指定bean的名称,那么这个bean的名称就是方法名
     */
    @Bean(name = "restHighLevelClient")
    public RestHighLevelClient restHighLevelClient() {

        // 此处为单节点es
        String host = hosts.split(":")[0];
        String port = hosts.split(":")[1];
        HttpHost httpHost = new HttpHost(host, Integer.parseInt(port));

        // 构建连接对象
        RestClientBuilder builder = RestClient.builder(httpHost);

        // 设置用户名、密码
        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(userName, password));

        // 连接延时配置
        builder.setRequestConfigCallback(requestConfigBuilder -> {
            requestConfigBuilder.setConnectTimeout(connectTimeOut);
            requestConfigBuilder.setSocketTimeout(socketTimeOut);
            requestConfigBuilder.setConnectionRequestTimeout(connectionRequestTimeOut);
            return requestConfigBuilder;
        });
        // 连接数配置
        builder.setHttpClientConfigCallback(httpClientBuilder -> {
            httpClientBuilder.setMaxConnTotal(maxConnectNum);
            httpClientBuilder.setMaxConnPerRoute(maxConnectNumPerRoute);
            httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
            return httpClientBuilder;
        });

        return new RestHighLevelClient(builder);
    }
}

需要注意的是,在 yml 中的 elasticsearch.hosts 配置中,不要添加 https 前缀,只需填写 127.0.0.1:9200 即可,否则在配置类中创建 HttpHost 时会出错。

4.2 技术派首页全局查询走 ES 数据库

在进行 ES 查询整合之前,我们先来了解一下大致的逻辑:

  1. 根据查询条件在 ES 中查询数据。
  2. 从查询结果中提取出 id,并将这些 id 添加到 ids 集合中。
  3. 使用 ids 集合去 MySQL 数据库中查询相应的数据。

通过这种方式,可以大大提高查询的效率,相比直接在 MySQL 中进行 like% 值 % 的查询,性能有显著提升。

在 paicoding-service 模块的 ArticleReadServiceImpl 类中,我们需要注入 RestHighLevelClient 客户端,然后编写实现代码。具体代码如下:

public List<SimpleArticleDTO> querySimpleArticleBySearchKey(String key) {
    // TODO 当KEY为空时,返回热门推荐
    if (StringUtils.isBlank(key)) {
        return Collections.emptyList();
    }
    key = key.trim();
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery(key,
            ESFieldConstant.ES_FIELD_TITLE,
            ESFieldConstant.ES_FIELD_SHORT_TITLE);
    searchSourceBuilder.query(multiMatchQueryBuilder);
    SearchRequest searchRequest = new SearchRequest(new String[]{ESIndexConstant.ES_INDEX_ARTICLE}, searchSourceBuilder);

    SearchResponse searchResponse = null;
    try {
        searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
    } catch (IOException e) {
        e.printStackTrace();
    }

    SearchHits hits = searchResponse.getHits();
    SearchHit[] hitList = hits.getHits();
    List<Integer> ids = new ArrayList<>();
    for (SearchHit documentFields : hitList) {
        ids.add(Integer.parseInt(documentFields.getId()));
    }

    if (ObjectUtils.isEmpty(ids)) {
        return null;
    }
    List<ArticleDO> records = articleDAO.selectByIds(ids);
    return records.stream().map(s -> new SimpleArticleDTO()
           .setId(s.getId())
           .setTitle(s.getTitle())).collect(Collectors.toList());
}

4.3 兼容未安装 ES 继续走 MySQL 查询

考虑到有些小伙伴可能没有安装 ES 和 Canal,为了保证代码在这种情况下也能正常运行,我们进行了兼容性处理,使得在未安装 ES 时,首页查询依然可以走 MySQL 数据库。
首先,在 yml 中增加了是否开启 ES 的配置项:

# elasticsearch配置
elasticsearch:
  # 是否开启ES?本地启动如果没有安装ES,可以设置为false关闭ES
  open: true
  # es集群名称
  clusterName: elasticsearch
  hosts: 127.0.0.1:9200
  userName: elastic
  password: elastic
  # es 请求方式
  scheme: http
  # es 连接超时时间
  connectTimeOut: 1000
  # es socket 连接超时时间
  socketTimeOut: 30000
  # es 请求超时时间
  connectionRequestTimeOut: 500
  # es 最大连接数
  maxConnectNum: 100
  # es 每个路由的最大连接数
  maxConnectNumPerRoute: 100

然后,在 Service 层将 open 值注入:

@Value("${elasticsearch.open}")
private Boolean openEs;

最后,在业务层的代码中实现兼容逻辑:

@Override
public List<SimpleArticleDTO> querySimpleArticleBySearchKey(String key) {
    // TODO 当KEY为空时,返回热门推荐
    if (StringUtils.isBlank(key)) {
        return Collections.emptyList();
    }
    // 如果没有开启ES,那么继续从MYSQL中获取数据
    if (!openEs) {
        List<ArticleDO> records = articleDAO.listSimpleArticlesBySearchKey(key);
        return records.stream().map(s -> new SimpleArticleDTO()
               .setId(s.getId())
               .setTitle(s.getTitle())).collect(Collectors.toList());
    }

    // ES整合逻辑
    key = key.trim();
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery(key,
            ESFieldConstant.ES_FIELD_TITLE,
            ESFieldConstant.ES_FIELD_SHORT_TITLE);
    searchSourceBuilder.query(multiMatchQueryBuilder);
    SearchRequest searchRequest = new SearchRequest(new String[]{ESIndexConstant.ES_INDEX_ARTICLE}, searchSourceBuilder);

    SearchResponse searchResponse = null;
    try {
        searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
    } catch (IOException e) {
        e.printStackTrace();
    }

    SearchHits hits = searchResponse.getHits();
    SearchHit[] hitList = hits.getHits();
    List<Integer> ids = new ArrayList<>();
    for (SearchHit documentFields : hitList) {
        ids.add(Integer.parseInt(documentFields.getId()));
    }

    if (ObjectUtils.isEmpty(ids)) {
        return null;
    }
    List<ArticleDO> records = articleDAO.selectByIds(ids);
    return records.stream().map(s -> new SimpleArticleDTO()
           .setId(s.getId())
           .setTitle(s.getTitle())).collect(Collectors.toList());
}

5 ES集群相关知识

5.1 ES集群基础概念

  1. 定义与组成:Elasticsearch(ES)集群是由多个ES节点组成的集合 ,节点间协同工作,实现数据存储、搜索及分析功能。
  2. 优势
    • 效率提升:多节点并行处理搜索请求,相比单节点搜索效率更高。
    • 存储扩容:突破单节点磁盘空间限制,利用多个节点存储资源,扩大数据存储容量。
    • 高可用性:部分节点故障时,集群仍能对外提供服务,保障整体可用性。
      在这里插入图片描述

5.2 ES集群节点

  1. 节点类型
    • 主节点(Master Node):负责管理集群元数据,如索引创建、删除,分片分配,监控节点状态等。为防止脑裂,一般设置奇数个主节点。脑裂是指在集群中,多个节点都认为自己是主节点,导致集群出现多个“大脑”,从而使集群状态混乱、数据不一致等问题。设置奇数个主节点,是因为在选举主节点时,奇数个节点能避免出现平局情况,降低脑裂风险 。
    • 数据节点(Data Node):用于存储数据,执行数据的增、删、改、查及聚合操作,需根据数据量和查询需求合理配置资源。
    • 协调节点(Coordinate Node):不承担主节点和数据节点功能,专门负责接收请求、转发请求至其他节点、汇总返回数据。大规模集群并发查询量大时,可添加独立协调节点。
    • Ingest节点:对文档数据进行预处理,如通过管道实现数据转换与丰富,可按需水平扩展。一个节点可兼具多种角色,小规模集群可不严格区分。
  2. 节点协作:数据节点向主节点发送心跳数据包汇报状态,主节点依此监控集群状态。

5.3 ES集群分片

  1. 主分片(Primary Shard)
    • 定义:将索引文档数据分散存储的单元,所有主分片数据合起来构成完整索引数据,主分片数量至少为1 。
    • 作用:承载索引的原始数据存储,是数据写入和读取的基础单元。
  2. 副本分片(Replica Shard)
    • 定义:主分片的备份,数量大于等于0 。
    • 作用:提高数据可用性和查询性能,主分片故障时可顶替提供服务,还可分担查询负载。
  3. 分片规则
    • 主分片和副本分片不能存储在同一节点上。
    • 单节点ES可有多主分片,但无副本分片。
    • 创建索引时可指定主、副本分片数量,索引创建后,主分片数量不可更改,仅副本分片数量可调整。
      在这里插入图片描述

5.4 文档在ES集群中的操作

  1. 存储流程

    • 用户将存储请求发送到集群中任意一个节点。
    • 接收请求的节点通过文档路由,把数据发送给对应的主分片。
    • 主分片将数据同步给它的所有副本分片。
      在这里插入图片描述
  2. 搜索流程

    • 用户向集群中任意一个节点发起搜索请求。
    • 接收请求的节点(协调节点)把每个主分片和它的副本分片看作“同一组”分片,从中选择一组有完整数据的分片(默认轮训选择),然后分发请求。
    • 被选中的分片进行查询,之后返回结果。
    • 协调节点把收到的结果聚合,返回给用户。
      在这里插入图片描述

5.5 ES 集群相关问题及解决

  • 脑裂问题:因网络问题,集群被分成两个,出现两个 “大脑”(主节点) ,导致数据不一致 。解决方法包括设置
    • 最小主节点数量,计算公式为discovery.zen.minimum_master_nodes: (有master资格节点数/2) + 1
    • 设置专门奇数个主节点
    • 采用单播发现机制(关闭多播发现机制discovery.zen.ping.multicast.enabled: false ,配置单播发现主节点 ip 地址discovery.zen.ping.unicast.hosts : ("master1", "master2", "master3") )
    • 配置选举发现数,延长 ping master 等待时长(如discovery.zen.ping_timeout: 30 ,discovery.zen.minimum_master_nodes: 2 ) 。
  • 分片数量配置:索引分片数指定后一般不可更改,除非重做索引 。ElasticSearch 推荐最大 JVM 堆空间 30 - 32G ,可据此估算分片数量,如预计数据量 200GB ,最多分配 7 - 8 个分片 。初始阶段,可按节点数量的 1.5 - 3 倍创建分片,如 3 个节点,分片数最多不超 9 个 。对于基于日期、搜索少、数据量小(如每个索引 1GB 甚至更小 )的索引需求,如日志管理,建议分配 1 个分片 。
    在这里插入图片描述

6 总结

本文全面讲解 ElasticSearch(ES)相关知识。ES 是基于 Lucene 的分布式搜索与分析引擎,有分布式、高扩展、高实时特性,采用 RESTful 风格 API 。介绍了字段、文档等基本概念,基于倒排索引的原理。详述 Windows 和 Ubuntu 版搭建步骤,RESTful API 操作及 ES 集成。还提及 ES 集群中的脑裂问题,主节点在管理集群元数据中起关键作用,为防止脑裂,通常设置奇数个主节点,避免选举时出现平局,降低集群状态混乱、数据不一致等风险。

7 参考链接

  1. 技术派实现ES查询
  2. 项目仓库(GitHub)
  3. 项目仓库(码云)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值