SpringCloud 微服务全栈体系(十二)

第十一章 分布式搜索引擎 elasticsearch

一、初识 elasticsearch

1. 了解 ES

1.1 elasticsearch 的作用
  • elasticsearch 是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容

  • 例如:

    • 在 GitHub 搜索代码
    • 在电商网站搜索商品
    • 在谷歌搜索答案
    • 在打车软件搜索附近的车
1.2 ELK 技术栈
  • elasticsearch 结合 kibana、Logstash、Beats,也就是 elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域:
  • 而 elasticsearch 是 elastic stack 的核心,负责存储、搜索、分析数据。
1.3 elasticsearch 和 lucene
  • elasticsearch 底层是基于lucene来实现的。

  • Lucene是一个 Java 语言的搜索引擎类库,是 Apache 公司的顶级项目,由 DougCutting 于 1999 年研发。官网地址:https://lucene.apache.org/

  • elasticsearch的发展历史:

    • 2004 年 Shay Banon 基于 Lucene 开发了 Compass
    • 2010 年 Shay Banon 重写了 Compass,取名为 Elasticsearch。
1.4 为什么不是其他搜索技术?
  • 目前比较知名的搜索引擎技术排名:
  • 虽然在早期,Apache Solr 是最主要的搜索引擎技术,但随着发展 elasticsearch 已经渐渐超越了 Solr,独占鳌头。
1.5 总结
  • 什么是 elasticsearch?

    • 一个开源的分布式搜索引擎,可以用来实现搜索、日志统计、分析、系统监控等功能
  • 什么是 elastic stack(ELK)?

    • 是以 elasticsearch 为核心的技术栈,包括 beats、Logstash、kibana、elasticsearch
  • 什么是 Lucene?

    • 是 Apache 的开源搜索引擎类库,提供了搜索引擎的核心 API

2. 倒排索引

  • 倒排索引的概念是基于 MySQL 这样的正向索引而言的。
2.1 正向索引
  • 那么什么是正向索引呢?例如给下表(tb_goods)中的 id 创建索引:
  • 如果是根据 id 查询,那么直接走索引,查询速度非常快。

  • 但如果是基于 title 做模糊查询,只能是逐行扫描数据,流程如下:

1)用户搜索数据,条件是 title 符合"%手机%"

2)逐行获取数据,比如 id 为 1 的数据

3)判断数据中的 title 是否符合用户搜索条件

4)如果符合则放入结果集,不符合则丢弃。回到步骤 1

  • 逐行扫描,也就是全表扫描,随着数据量增加,其查询效率也会越来越低。当数据量达到数百万时,就是一场灾难。
2.2 倒排索引
  • 倒排索引中有两个非常重要的概念:

    • 文档(Document):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息
    • 词条(Term):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条
  • 创建倒排索引是对正向索引的一种特殊处理,流程如下:

    • 将每一个文档的数据利用算法分词,得到一个个词条
    • 创建表,每行数据包括词条、词条所在文档 id、位置等信息
    • 因为词条唯一性,可以给词条创建索引,例如 hash 表结构索引
  • 如图:

  • 倒排索引的搜索流程如下(以搜索"华为手机"为例):

1)用户输入条件"华为手机"进行搜索。

2)对用户输入内容分词 ,得到词条:华为手机

3)拿着词条在倒排索引中查找,可以得到包含词条的文档 id:1、2、3。

4)拿着文档 id 到正向索引中查找具体文档。

  • 如图:
  • 虽然要先查询倒排索引,再查询正向索引,但是无论是词条、还是文档 id 都建立了索引,查询速度非常快!无需全表扫描。
2.3 正向和倒排
  • 那么为什么一个叫做正向索引,一个叫做倒排索引呢?

    • 正向索引 是最传统的,根据 id 索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程

    • 倒排索引 则相反,是先找到用户要搜索的词条,根据词条得到包含词条的文档的 id,然后根据 id 获取文档。是根据词条找文档的过程

  • 是不是恰好反过来了?

  • 那么两者方式的优缺点是什么呢?

2.3.1 正向索引
  • 优点:
    • 可以给多个字段创建索引
    • 根据索引字段搜索、排序速度非常快
  • 缺点:
    • 根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。
2.3.2 倒排索引
  • 优点:
    • 根据词条搜索、模糊搜索时,速度非常快
  • 缺点:
    • 只能给词条创建索引,而不是字段
    • 无法根据字段做排序

3. es 的一些概念

  • elasticsearch 中有很多独有的概念,与 mysql 中略有差别,但也有相似之处。
3.1 文档和字段
  • elasticsearch 是面向文档(Document)存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为 json 格式后存储在 elasticsearch 中:
  • 而 Json 文档中往往包含很多的字段(Field),类似于数据库中的列。
3.2 索引和映射
  • 索引(Index),就是相同类型的文档的集合。

  • 例如:

    • 所有用户文档,就可以组织在一起,称为用户的索引;
    • 所有商品的文档,可以组织在一起,称为商品的索引;
    • 所有订单的文档,可以组织在一起,称为订单的索引;
  • 因此,我们可以把索引当做是数据库中的表。

  • 数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库中就有映射(mapping),是索引中文档的字段约束信息,类似表的结构约束。

3.3 mysql 与 elasticsearch
  • 我们统一的把 mysql 与 elasticsearch 的概念做一下对比:
MySQL Elasticsearch 说明
Table Index 索引(index),就是文档的集合,类似数据库的表(table)
Row Document 文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是 JSON 格式
Column Field 字段(Field),就是 JSON 文档中的字段,类似数据库中的列(Column)
Schema Mapping Mapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema)
SQL DSL DSL 是 elasticsearch 提供的 JSON 风格的请求语句,用来操作 elasticsearch,实现 CRUD
  • 是不是说,学习了 elasticsearch 就不再需要 mysql 了呢?

  • 并不是如此,两者各自有自己的擅长支出:

    • Mysql:擅长事务类型操作,可以确保数据的安全和一致性

    • Elasticsearch:擅长海量数据的搜索、分析、计算

  • 因此在企业中,往往是两者结合使用:

    • 对安全性要求较高的写操作,使用 mysql 实现
    • 对查询性能要求较高的搜索需求,使用 elasticsearch 实现
    • 两者再基于某种方式,实现数据的同步,保证一致性

4. 安装 es、kibana

4.1 安装 es
4.1.1 部署单点 es
4.1.1.1 创建网络
  • 因为还需要部署 kibana 容器,因此需要让 es 和 kibana 容器互联。这里先创建一个网络:
bash 复制代码
docker network create es-net
4.1.1.2 加载镜像
  • 采用 elasticsearch 的 7.12.1 版本的镜像,这个镜像体积非常大,接近 1G。
  • 资料提供了镜像的 tar 包。
    见专栏 -> 全栈资料包 -> 资源包/02_cloud
  • 将其上传到虚拟机中,然后运行命令加载即可:
bash 复制代码
# 导入数据
docker load -i es.tar
  • 同理还有kibana的 tar 包也需要这样做。
4.1.1.3 运行
  • 运行 docker 命令,部署单点 es:
bash 复制代码
docker run -d \
	--name es \
    -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
    -e "discovery.type=single-node" \
    -v es-data:/usr/share/elasticsearch/data \
    -v es-plugins:/usr/share/elasticsearch/plugins \
    --privileged \
    --network es-net \
    -p 9200:9200 \
    -p 9300:9300 \
elasticsearch:7.12.1
  • 命令解释:

    • -e "cluster.name=es-docker-cluster":设置集群名称
    • -e "http.host=0.0.0.0":监听的地址,可以外网访问
    • -e "ES_JAVA_OPTS=-Xms512m -Xmx512m":内存大小
    • -e "discovery.type=single-node":非集群模式
    • -v es-data:/usr/share/elasticsearch/data:挂载逻辑卷,绑定 es 的数据目录
    • -v es-logs:/usr/share/elasticsearch/logs:挂载逻辑卷,绑定 es 的日志目录
    • -v es-plugins:/usr/share/elasticsearch/plugins:挂载逻辑卷,绑定 es 的插件目录
    • --privileged:授予逻辑卷访问权
    • --network es-net :加入一个名为 es-net 的网络中
    • -p 9200:9200:端口映射配置
  • 在浏览器中输入:http://192.168.150.101:9200 即可看到 elasticsearch 的响应结果:

4.1.2 部署 kibana
  • kibana 可以给我们提供一个 elasticsearch 的可视化界面。
4.1.2.1 部署
  • 运行 docker 命令,部署 kibana

    bash 复制代码
    docker run -d \
    --name kibana \
    -e ELASTICSEARCH_HOSTS=http://es:9200 \
    --network=es-net \
    -p 5601:5601  \
    kibana:7.12.1
    • --network es-net :加入一个名为 es-net 的网络中,与 elasticsearch 在同一个网络中
    • -e ELASTICSEARCH_HOSTS=http://es:9200":设置 elasticsearch 的地址,因为 kibana 已经与 elasticsearch 在一个网络,因此可以用容器名直接访问 elasticsearch
    • -p 5601:5601:端口映射配置
  • kibana 启动一般比较慢,需要多等待一会,可以通过命令查看运行日志:

bash 复制代码
docker logs -f kibana
4.1.2.2 DevTools
  • kibana 中提供了一个 DevTools 界面
  • 这个界面中可以编写 DSL 来操作 elasticsearch。并且对 DSL 语句有自动补全功能。
4.2 安装分词器
4.2.1 在线安装 ik 插件(较慢)
bash 复制代码
# 进入容器内部
docker exec -it elasticsearch /bin/bash

# 在线下载并安装
./bin/elasticsearch-plugin  install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip

#退出
exit
#重启容器
docker restart elasticsearch
4.2.2 离线安装 ik 插件(推荐)
4.2.2.1 查看数据卷目录
  • 安装插件需要知道 elasticsearch 的 plugins 目录位置,而我们用了数据卷挂载,因此需要查看 elasticsearch 的数据卷目录,通过下面命令查看:
bash 复制代码
docker volume inspect es-plugins
  • 显示结果:
json 复制代码
[
    {
        "CreatedAt": "2022-05-06T10:06:34+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/es-plugins/_data",
        "Name": "es-plugins",
        "Options": null,
        "Scope": "local"
    }
]
  • 说明 plugins 目录被挂载到了:/var/lib/docker/volumes/es-plugins/_data 这个目录中。
4.2.2.2 解压缩分词器安装包
  • 把资料中的 ik 分词器解压缩,重命名为 ik
    见专栏 -> 全栈资料包 -> 资源包/02_cloud
4.2.2.3 上传到 es 容器的插件数据卷中
  • 也就是/var/lib/docker/volumes/es-plugins/_data
4.2.2.4 重启容器
bash 复制代码
# 重启容器
docker restart es
bash 复制代码
# 查看es日志
docker logs -f es
4.2.2.5 测试
  • IK 分词器包含两种模式:

    • ik_smart:最少切分

    • ik_max_word:最细切分

json 复制代码
GET /_analyze
{
  "analyzer": "ik_max_word",
  "text": "小帽课堂学习java太棒了"
}
  • 结果
json 复制代码
{
  "tokens" : [
    {
      "token" : "小帽",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "课堂",
      "start_offset" : 2,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "学习",
      "start_offset" : 5,
      "end_offset" : 7,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "java",
      "start_offset" : 7,
      "end_offset" : 11,
      "type" : "ENGLISH",
      "position" : 3
    },
    {
      "token" : "太棒了",
      "start_offset" : 11,
      "end_offset" : 14,
      "type" : "CN_WORD",
      "position" : 4
    },
    {
      "token" : "太棒",
      "start_offset" : 11,
      "end_offset" : 13,
      "type" : "CN_WORD",
      "position" : 5
    },
    {
      "token" : "了",
      "start_offset" : 13,
      "end_offset" : 14,
      "type" : "CN_CHAR",
      "position" : 6
    }
  ]
}
4.2.3 扩展词词典
  • 随着互联网的发展,"造词运动"也越发的频繁。出现了很多新的词语,在原有的词汇列表中并不存在。比如:"奥力给" 等。

  • 所以我们的词汇也需要不断的更新,IK 分词器提供了扩展词汇的功能。

4.2.3.1 打开 IK 分词器 config 目录
4.2.3.2 在 IKAnalyzer.cfg.xml 配置文件内容添加
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 扩展配置</comment>
        <!--用户可以在这里配置自己的扩展字典 *** 添加扩展词典-->
        <entry key="ext_dict">ext.dic</entry>
</properties>
4.2.3.3 新建一个 ext.dic,可以参考 config 目录下复制一个配置文件进行修改
bash 复制代码
奥力给
4.2.3.4 重启 elasticsearch
bash 复制代码
docker restart es

# 查看 日志
docker logs -f elasticsearch
  • 日志中已经成功加载 ext.dic 配置文件
4.2.3.5 测试效果
json 复制代码
GET /_analyze
{
  "analyzer": "ik_max_word",
  "text": "小帽学习Java,奥力给!"
}

注意:当前文件的编码必须是 UTF-8 格式,严禁使用 Windows 记事本编辑

4.2.4 停用词词典
  • 在互联网项目中,在网络间传输的速度很快,所以很多语言是不允许在网络上传递的,那么我们在搜索时也应该忽略当前词汇。

  • IK 分词器也提供了强大的停用词功能,让我们在索引时就直接忽略当前的停用词汇表中的内容。

4.2.4.1 IKAnalyzer.cfg.xml 配置文件内容添加
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 扩展配置</comment>
        <!--用户可以在这里配置自己的扩展字典-->
        <entry key="ext_dict">ext.dic</entry>
         <!--用户可以在这里配置自己的扩展停止词字典  *** 添加停用词词典-->
        <entry key="ext_stopwords">stopword.dic</entry>
</properties>
4.2.4.2 在 stopword.dic 添加停用词
bash 复制代码
神经病
4.2.4.3 重启 elasticsearch
bash 复制代码
# 重启服务
docker restart elasticsearch
docker restart kibana

# 查看 日志
docker logs -f elasticsearch
  • 日志中已经成功加载 stopword.dic 配置文件
4.2.4.4 测试效果
bash 复制代码
GET /_analyze
{
  "analyzer": "ik_max_word",
  "text": "小帽课堂学习Java,神经病都点赞,奥力给!"
}

注意:当前文件的编码必须是 UTF-8 格式,严禁使用 Windows 记事本编辑

4.3 部署 es 集群
  • 部署 es 集群可以直接使用 docker-compose 来完成,不过要求 Linux 虚拟机至少有4G的内存空间。
  • 首先编写一个 docker-compose 文件,内容如下:
bash 复制代码
version: '2.2'
services:
  es01:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1
    container_name: es01
    environment:
      - node.name=es01
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es02,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - data01:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
    networks:
      - elastic
  es02:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1
    container_name: es02
    environment:
      - node.name=es02
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es01,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - data02:/usr/share/elasticsearch/data
    networks:
      - elastic
  es03:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1
    container_name: es03
    environment:
      - node.name=es03
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es01,es02
      - cluster.initial_master_nodes=es01,es02,es03
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - data03:/usr/share/elasticsearch/data
    networks:
      - elastic

volumes:
  data01:
    driver: local
  data02:
    driver: local
  data03:
    driver: local

networks:
  elastic:
    driver: bridge
  • Run docker-compose to bring up the cluster:
bash 复制代码
docker-compose up
4.4 总结
  • 分词器的作用是什么?

    • 创建倒排索引时对文档分词
    • 用户搜索时,对输入的内容分词
  • IK 分词器有几种模式?

    • ik_smart:智能切分,粗粒度
    • ik_max_word:最细切分,细粒度
  • IK 分词器如何拓展词条?如何停用词条?

    • 利用 config 目录的 IkAnalyzer.cfg.xml 文件添加拓展词典和停用词典
    • 在词典中添加拓展词条或者停用词条
相关推荐
先睡2 小时前
Redis的缓存击穿和缓存雪崩
redis·spring·缓存
Bug退退退1236 小时前
RabbitMQ 高级特性之死信队列
java·分布式·spring·rabbitmq
Code季风10 小时前
深入理解微服务中的服务注册与发现(Consul)
java·运维·微服务·zookeeper·架构·go·consul
光军oi11 小时前
java微服务(Springboot篇)——————IDEA搭建第一个Springboot入门项目
java·spring boot·微服务
guojl12 小时前
RestTemplate使用手册
spring cloud·微服务
guojl12 小时前
RestTemplate原理分析
spring cloud·微服务
booooooty12 小时前
基于Spring AI Alibaba的多智能体RAG应用
java·人工智能·spring·多智能体·rag·spring ai·ai alibaba
极光雨雨12 小时前
Spring Bean 控制销毁顺序的方法总结
java·spring
Ken_111512 小时前
SpringCloud系列(51)--SpringCloud Stream之使用分组解决消息重复消费问题
spring cloud
Spirit_NKlaus12 小时前
解决HttpServletRequest无法获取@RequestBody修饰的参数
java·spring boot·spring