后端服务集成ElasticSearch搜索功能技术方案

相关链接:

ElasticSearch官网地址:https://www.elastic.co/cn/

Lucene官网地址:https://lucene.apache.org/

IK分词器官网地址:https://github.com/infinilabs/analysis-ik

Canal官网地址:https://github.com/alibaba/canal

一、为什么选用ElasticSearch

ElasticSearch是一款非常强大的开源的分布式搜索引擎,具备从海量数据中快速找到需要内容的功能,可以用来实现搜索、日志统计、分析、系统监控等功能。

在这里插入图片描述

在实际项目开发中,我们经常将Mysql作为业务数据库,ES作为查询数据库,一是可以用来实现读写分离,使项目的架构有更好的扩展性。二是可以缓解Mysql数据库的查询压力,应对海量数据的复杂查询。

ES 几个显著的特点,能够有效补足 MySQL 在企业级数据操作场景的缺陷,而这也是我们将其选择作为下游数据源重要原因。

核心特点:支持分词检索,多维筛选性能好,支持海量数据查询。

  • 文本搜索能力:ES 是基于倒排索引实现的搜索系统,配合多样的分词器,在文本模糊匹配搜索上表现得比较好,业务场景广泛。

  • 多维筛选性能好:亿级规模数据使用宽表预构建(消除 join),配合全字段索引,使 ES 在多维筛选能力上具备压倒性优势,而这个能力是诸如 CRM, BOSS, MIS 等企业运营系统核心诉求,加上文本搜索能力,独此一家。

  • 开源和商业并行:ES 开源生态非常活跃,具备大量的用户群体,同时其背后也有独立的商业公司支撑,而这让用户根据自身特点有了更加多样、渐进的选择。

ElasticSearch的发展历史:

  • 2004年Shay Banon基于Lucene开发了Compass。

  • 2010年Shay Banon重写了Compass,并取名为Elasticsearch。

后端服务系统集成ElasticSearch的好处:

  • 强大的搜索功能,支持大数据量的模糊搜索功能。比如:设备、测点等。
  • 一定程度上做到了读写分离,减轻MySQL数据库的读写压力。
  • 也方便后续的ELK搭建,为服务日志提供搜索功能。
    在这里插入图片描述

二、ElasticSearch基本概念

ElasticSearch官网地址:https://www.elastic.co/cn/

1、文档和字段

ES是面向文档(Document)存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中。

ES中的文档和字段对应MySQL中行记录和列属性。

在这里插入图片描述

2、索引和映射

索引(Index),就是相同类型的文档的集合。

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

ES中的索引和映射对应MySQL中的表和约束。

为避免大家对这两个概念比较陌生,这里给出ES的索引和映射的更具象化的列子:在ES中获取索引和映射。

  • 方式1:基于HTTP请求获取
# 获取索引的全部信息
curl -X GET 10.0.0.101:9200/mybook?pretty=true
# 获取索引的映射信息
curl -X GET 10.0.0.101:9200/mybook/_mapping?pretty=true
  • 方式2:在kibana中基于API获取
# 获取索引的全部信息
GET /mybook
# 获取索引的映射信息
GET /mybook/_mapping

无法是哪种获取方式,都是在组装DSL并通过Restful接口进行调用【这也是ES可被任何语言调用的原因】。
这里获取结果如下:

# mybook索引的全部信息
{
   
   
  "mybook" : {
   
   
    "aliases" : {
   
    },
    "mappings" : {
   
   
      "dynamic" : "false",
      "properties" : {
   
   
        "author" : {
   
   
          "type" : "text"
        },
        "id" : {
   
   
          "type" : "long"
        },
        "isbn" : {
   
   
          "type" : "text"
        },
        "publisherName" : {
   
   
          "type" : "text"
        },
        "title" : {
   
   
          "type" : "text"
        }
      }
    },
    "settings" : {
   
   
      "index" : {
   
   
        "routing" : {
   
   
          "allocation" : {
   
   
            "include" : {
   
   
              "_tier_preference" : "data_content"
            }
          }
        },
        "number_of_shards" : "1",
        "provided_name" : "mybook",
        "creation_date" : "1735287860250",
        "number_of_replicas" : "1",
        "uuid" : "eAT8Z7V9ThiPyRu5rnVQLQ",
        "version" : {
   
   
          "created" : "7130499"
        }
      }
    }
  }
}

# mybook索引的映射信息
{
   
   
  "mybook" : {
   
   
    "mappings" : {
   
   
      "dynamic" : "false",
      "properties" : {
   
   
        "author" : {
   
   
          "type" : "text"
        },
        "id" : {
   
   
          "type" : "long"
        },
        "isbn" : {
   
   
          "type" : "text"
        },
        "publisherName" : {
   
   
          "type" : "text"
        },
        "title" : {
   
   
          "type" : "text"
        }
      }
    }
  }
}
  • MySQL基本概念和ES基本概念对比
Elasticsearch 说明 MySQL
Index 索引(index),就是文档的集合,类似数据库的表(table) Table
Document 文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式 Row
Field 字段(Field),就是JSON文档中的字段,类似数据库中的列(Column) Column
Mapping Mapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema) Schema
DSL DSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD SQL

3、倒排索引、文档和词条

倒排索引的概念比较奇特,它是根据MySQL的索引(也即正向索引)概念而命名的。

理解倒排索引需要先理解两个基本概念:文档和词条。

文档就是用来搜索的数据,每一条数据就是一个文档。
词条,即term,也称词项。就是对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。

还是来举一个具体的例子来说明:“华为小米充电器” 就是文档,它可以分为:华为、小米、充电器等这样的几个词条。

倒排索引就是以词条为核心,记录哪些文档包含了该词条。

实际上,倒排索引除了记录哪些文档包含该词条,还记录了词条的词频,词条在文本里的偏移量等信息。

在这里插入图片描述

正向索引 V.S 倒排索引

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

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

正向索引—优点:

​ 可以给多个字段创建索引;根据索引字段搜索、排序速度非常快。

正向索引—缺点:

​ 根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。

倒排索引—优点:

​ 根据词条搜索、模糊搜索时,速度非常快。

倒排索引—缺点:

​ 只能给词条创建索引,而不是字段;无法根据字段做排序。

4、分词器

分词器有两个作用:一是创建倒排索引时对文档分词,二是用户搜索时,对输入的内容进行分词。

IK 分词器(IK Analyzer)是专门为 ElasticSearch 设计开发的一款中文分词插件。主要用于对中文文本进行准确且合理的分词处理,以便更好地实现搜索、索引等功能。

IK 分词原理:IK 分词器基于词典和一些特定的规则来对文本进行切分。它内部有一个较为丰富的中文词典,里面包含了大量常见的中文词汇、短语等内容。在对文本进行分词时,会根据词典中的词条以及一些预设的规则去识别和划分词语。

IK分词有两种分词模式

  • ik_max_word:这是一种细粒度的分词模式。例如对于文本 “中华人民共和国”,它会被切分成 “中华”“中华人民”“中华人民共和国”“华人”“人民”“人民共和国”“共和”“共和国” 等多个词语,尽可能多地拆分出不同组合的词语,这种模式适用于需要更全面捕捉文本语义的场景,比如搜索引擎的索引构建,能让更多的词语组合参与到搜索匹配中。
  • ik_smart:属于粗粒度的分词模式。同样对于 “中华人民共和国”,可能就直接切分成 “中华人民共和国” 这样一个完整的词语,相对而言拆分得较为 “简洁”,比较适合在一些对文本理解不需要特别细致拆分的场景中使用,例如对文档做简单分类等情况。

下载:可以从官方 GitHub 仓库或者相关的软件下载站点获取对应的 IK 分词器版本,要确保其版本和所使用的 ElasticSearch 版本兼容。

安装到 ElasticSearch:将下载好的 IK 分词器压缩包解压后,把整个目录复制到 ElasticSearch 安装目录下的 plugins 文件夹中(如果没有 plugins 文件夹就新建一个)。然后重启 ElasticSearch,它就会自动加载 IK 分词器插件。

三、ElasticSearch工作原理

相关链接:https://mp.weixin.qq.com/s/RUQXIyN95hvi2wM3CyPI9w

1、Term Dictionary、Term index

文档可以通过ik分词器分为多个词条(又称词项,即Term)。词条term会按字典排好序形成Term Dictionary(用于二分查找)。将 Term Dictionary 的部分词条的前缀信息提取出来构建出一个精简的目录树。目录树的节点中存放这些词条在磁盘中的偏移量,也就是指向磁盘中的位置。这个目录树结构,体积小,适合放内存中,它就是所谓的 Term Index。用它可以加速搜索。

这样当需要查找某个词项的时候,只需要搜索 Term Index,就能快速获得词项在 Term Dictionary 中的大概位置。再跳转到 Term Dictionary,通过少量的检索,定位到词条内容。

在这里插入图片描述

2、Stored Fields

倒排索引,搜索到的是文档 id,我们还需要拿着这个 id 找到文档内容本身,才能返回给用户。因此还需要有个地方,存放完整的文档内容,它就是 Stored Fields(行式存储)。

在这里插入图片描述

3、Docs Values

后端的业务经常需要根据某个字段排序文档,比如按时间排序或商品价格排序。但问题就来了,这些字段散落在文档里。也就是说,我们需要先获取 Stored Fields 里的文档,再提取出内部字段进行排序。也不是说不行,但其实有更高效的做法。我们可以用空间换时间的思路,再构造一个列式存储结构,将散落在各个文档的某个字段,集中存放,当我们想对某个字段排序的时候,就只需要将这些集中存放的字段一次性读取出来,就能做到针对性地进行排序。这个列式存储结构,就是所谓的 Doc Values

在这里插入图片描述

4、Segment

倒排索引用于搜索,Term Index 用于加速搜索,Stored Fields 用于存放文档的原始信息,以及 Doc Values 用于排序和聚合。这些结构共同组成了一个复合文件,也就是所谓的"segment", 它是一个具备完整搜索功能的最小单元

在这里插入图片描述

5、Lucene

多个文档记录可以用来生成一份 segment,如果新增文档时,还是写入到这份 segment,那就得同时更新 segment 内部的多个数据结构,这样并发读写时性能肯定会受影响。所以规定:segment 一旦生成,则不能再被修改。如果还有新的文档要写入,老的segment已经写满,那就生成新的 segment。这样老的 segment 只需要负责读,写则生成新的 segment。同时保证了读和写的性能。

随着数据量增大,segment文件数会变多,这是就可以并发同时读多个 segment。当然segment文件数也不能无限制的变多,程序会不定期的合并多个小的 segment, 也就是段合并(segment merging) 。这就是有名的Lucene,一个单机文本检索库

在这里插入图片描述

6、高性能、高扩展性、高可用

ElasticSearch就是基于单机检索库Lucene构建了一个高性能、高扩展性、高可用的强大的搜索引擎。

①高性能

当多个调用方同时读写同一个 lucene 必然导致争抢计算资源。 所以ES首先将不同类型的数据写入到了不同的Lucene中,这样在读取数据时,根据需要搜索不同的 Index Name,这就大大降低了单个 lucene 的压力。其次,ES还将某些Index Name内数据可能过多的单个lucene 拆成好几份,每份都是一个 shard 分片每个 shard 分片本质上就是一个独立的 lucene 库。这样就可以将读写操作分摊到多个 分片 中去,大大降低了争抢,提升了系统性能。

在这里插入图片描述

②高扩展性

随着 分片 变多,如果 分片 都在同一台机器上的话,就会导致单机 cpu 和内存过高,影响整体系统性能。

在ES中,可以使用更多的机器,将 分片 分散部署在多台机器上,这每一台机器,就是一个 Node。通过增加 Node 缓解机器 cpu 过高带来的性能问题。

在这里插入图片描述

③高可用

高可用问题基本都是通过副本解决。ES中也是一样, 通过给 分片 多加几个副本。将 分片 分为 Primary shardReplica shard,也就是主分片和副本分片 。主分片会将数据同步给副本分片,副本分片既可以同时提供读操作,还能在主分片挂了的时候,升级成新的主分片让系统保持正常运行,提高性能的同时,还保证了系统的高可用

在这里插入图片描述

7、ES架构

从架构角度来看,ES给了一套方案,让一个单机系统 lucene 变成一个高性能、高扩展、高可用的分布式系统。

在ES集群中,分为三类node角色。

  • 主节点(Master Node), 负责管理集群。
  • 协调节点(Coordinate Node),负责存储管理数据。
  • 数据节点(Data Node),负责接受客户端请求。

集群规模小的时候,一个 Node 可以同时充当多个角色,随着集群规模变大,可以让一个 Node 一个角色。

ES集群中的节点之间基于类似一致性算法 Raft 的方式,在节点间互相同步数据,让所有 Node 看到的集群数据状态都是一致的。这样,集群内的 Node 就能参与选主过程,还能了解到集群内某个 Node 是不是挂了等信息。

在这里插入图片描述

  • ES架构 V.S Kafka架构 V.S RocketMQ架构

确实:很多优秀的开源项目架构都是相似的。

架构 ES Kafka RocketMQ
消息分类 index name topic topic
数据分片 Shard Partition msgqueue
节点 node broker broker
高可用 多副本 多副本 master-slave
数据一致性 类似Raft协议 ISR和ack机制 Raft协议
元数据 coordinate node zookeeper nameserver

8、ES写入流程

ES 对外提供 http 接口,任何语言的客户端都可以通过 HTTP 接口接入 es,实现对数据的增删改查。

  • 客户端应用发起数据写入请求,请求会先发到集群中协调节点
  • 协调节点根据 hash 路由,判断数据该写入到哪个数据节点里的哪个分片(Shard),找到主分片并写入。分片底层是 lucene,所以最终是将数据写入到 lucene 库里的 segment 内,将数据固化为倒排索引Stored Fields 以及 Doc Values 等多种结构。
  • 主分片 写入成功后会将数据同步给 副本分片
  • 副本分片 写入完成后,主分片会响应协调节点一个 ACK,意思是写入完成。
  • 最后,协调节点响应客户端应用写入完成。

在这里插入图片描述

9、ES搜索流程

ES 的搜索流程分为两个阶段:分别是查询阶段(Query Phase)获取阶段(Fetch Phase)

Query Phase

  • 客户端应用发起搜索请求,请求会先发到集群中的协调节点
  • 协调节点根据 index name 的信息,可以了解到 index name 被分为了几个 分片,以及这些分片 分散哪个数据节点上,将请求转发到这些数据节点的 分片 上面。
  • 搜索请求到达分片后,分片 底层的 lucene 库会并发搜索多个 segment,利用每个 segment 内部的倒排索引获取到对应文档 id,并结合 doc values 获得排序信息。分片将结果聚合返回给协调节点
  • 协调节点对多个分片中拿到的数据进行一次排序聚合舍弃大部分不需要的数据。

在这里插入图片描述

Fetch Phase

  • 协调节点再次拿着文档 id 请求数据节点里的 分片,分片 底层的 lucene 库会从 segment 内的 Stored Fields 中取出完整文档内容,并返回给协调节点。
  • 协调节点最终将数据结果返回给客户端。完成整个搜索过程。

在这里插入图片描述

四、如何使用ElasticSearch

1、安装部署

通过docker-compose一键部署一套es+kibana容器服务。【第七章节有详细部署步骤】

vim docker-compose-es.yml

version: '3'

services:
  #elasticsearch服务
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.13.4
    container_name: elasticsearch
    user: root
    environment:
      - discovery.type=single-node
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
      - TZ=Asia/Shanghai
    volumes:
      - ./data/elasticsearch/data:/usr/share/elasticsearch/data
    restart: always
    ports:
    - 9200:9200
    - 9300:9300
    networks:
      - looklook_net

  #查看elasticsearch数据
  kibana:
    image: docker.elastic.co/kibana/kibana:7.13.4
    container_name: kibana
    environment:
      - elasticsearch.hosts=http://elasticsearch:9200
      - TZ=Asia/Shanghai
    restart: always
    networks:
      - looklook_net
    ports:
      - "5601:5601"
    depends_on:
      - elasticsearch
networks:
  looklook_net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.16.0.0/16
  • 一键启动
docker-compose -f docker-compose-es.yml up -d

2、CRUD操作

有多种方式可以对ES进行CRUD操作,比如:shell命令行操作、kibana界面操作、基于各种开发语言的客户端调用操作等等。这些操作的本质都一样:即通过编写组装DSL语句,基于Restful请求通过http接口调用发送给ES服务端。

① index索引操作
  • 查看当前节点的所有 Index

    # 如何是shell命令操作
    curl -X GET localhost:9200/_cat/indices?v
    curl -X GET http://localhost:9200/_cat/indices?v
    curl -X GET "http://localhost:9200/_cat/indices?v"
    # 等同于在kibana界面操作
    GET /_cat/indices?v
    

可以看出shell命令不大方便,所有基本都推荐使用 Kibana。后面统一使用Kibana编写DSL的方式来演示。

  • 创建Index

    PUT /mybook
    {
         
         
      "mappings": {
         
         
        "dynamic": false,  
      "properties": {
         
         
        "id": {
         
         
          "type": "keyword"      //使用 keyword 类型,适合精确匹配
        },
        "title": {
         
         
          "type": "text",        // 使用 text 类型,支持全文搜索
          "analyzer": "standard" // 使用标准分析器
        },
        "isbn": {
         
         
          "type": "text"
        },
        "author": {
         
         
          "type": "text"
        },
        "created_at": {
         
         
          "type": "date",    // 使用 date 类型,处理日期和时间
           "format": "yyyy-MM-dd'T'HH:mm:ss" // 日期格式
        },
        "publisherName": {
         
         
          "type": "text"
        }
      }
      }
    }
    

    常见的mapping属性包括:

    • type:字段数据类型,常见的简单类型有:

      • 字符串:text(可分词的文本)、keyword(精确值,例如:唯一id、品牌、国家、ip地址)
      • 数值:long、integer、short、byte、double、float
      • 布尔:boolean
      • 日期:date
      • 对象:object
    • index:是否创建索引,默认为true

    • analyzer:使用哪种分词器

    • properties:该字段的子字段

  • 删除Index

    DELETE  /mybook
    
  • 修改Index

    索引库一旦创建,无法修改mapping。这是因为虽然倒排索引结构并不复杂,但是一旦索引数据结构有改变(比如改变了分词器),就需要重新创建倒排索引。 因此索引修改只允许添加新的字段到mapping中,因为不会对倒排索引产生影响。

    PUT /mybook/_mapping
    {
         
         
      "properties" : {
         
         
        "description" : {
         
         
          "type" : "text"
        }
      }
    }
    
  • 查询Index

    GET /mybook
    # 等同于
    curl -X GET localhost:9200/mybook
    curl -X GET localhost:9200/mybook?pretty=true
    
② 文档操作

索引创建完毕之后,可以开始文档的操作。

## 创建索引
PUT /books
## 查看索引
GET /books
## 查看索引映射【这时索引映射是空的】
GET /books/_mapping

## 创建文档
POST /books/_doc
{
   
   
  "name": "Snow Crash liuwen",
  "author": "Neal Stephenson",
  "release_date": "1992-06-01",
  "page_count": 470
}

## 查看索引映射【索引映射是可以根据文档自动生成的】
GET /books/_mapping

POST /books/_doc
{
   
   
  "name": "The Great Gatsby",
  "author": "F. Scott Fitzgerald",
  "release_date": "1925-04-10",
  "page_count": 180,
  "language": "EN" 
}

GET /books/_mapping

POST /_bulk
{
   
    "index" : {
   
    "_index" : "books" } }
{
   
   "name": "Revelation Space", "author": "Alastair Reynolds", "release_date": "2000-03-15"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的程序猿~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值