ElasticSearch实践分享

  •  

背景介绍

最近在做一个采购商城搜索的功能,核心功能是

根据用户录入的关键字在商品库中根据全称匹配进行检索得到结果

方案比较

目标

在 不超过 100万 的数据集合中,根据用户录入的 关键字 和 条件 检索到结果

方案

全部数据量不大,最直接的想法 SQL like 查询,示例如下

select * from mati where mati_full_name like '%电脑%'

然而,在实际实践中发现一些问题

用户输入的内容可能比较复杂,关键字需要根据符号可以进行分割组合,例如 ”华为 电脑 白色”,即使这样简单的一个组合,就需要全文检索数据库至少6次,性能下降的比较厉害

原理

倒排索引

索引或者正排索引,想必都不陌生,例如书本的目录,查找的顺序是

目录 ==> 页码 ==> 内容

试想一下,如果数据量比较大,例如在 《Elasticsearch实战与原理解析》这本书中找出包含 Elasticsearch 关键字的页面,这将是个非常巨大的工作量,需要一次次的翻找目录及页码才可以定位到内容,是否可以直接根据某个单词找到页码再找到内容

单词 ==> 页码 ==> 内容

索引结构

假如现有三份数据文档,

生成的倒排索引的结果大致如下

这种结构由文档中所有不重复词的列表构成,对于其中每个词都有一个文档ID与之关联。这种由词来确定记录的位置的结构就是倒排索引,带有倒排索引的文件我们称为倒排文件,词典和倒排列表是实现快速检索的重要基石。

查找词条

上面简单介绍了倒排索引的基本结构,在进行实际搜索前,还面临一个问题:如何从词典中高效的查询到词条,特别是词条比较多的时候?

稍微回忆下,当记录数很多的时候,Mysql 是如何快速找到需要的记录呢?答案是索引,如果查询条件使用索引,查询效率将会非常高,Mysql 使用 B+ 树来实现索引。ES的做法也是类似的,即创建 Term Index。

ElasticSearch 内部的 Term Index 是用的变种的trie树(前缀树/字典树/单词查找树),即FST(Finite State Transducer),trie树只共享了前缀,而 FST 既共享前缀也共享后缀,更加的节省空间。FST 在兼顾查询性能的情况下,占用空间更小: 1)查询速度快。O(len(str))的查询时间复杂度。2)空间占用小。通过对词典中单词前缀和后缀的重复利用,压缩了存储空间;

例如:

核心流程

词-索引 不包含所有的 词,包含的是 词 的一些前缀,通过索引可以快速地定位到词典 的某个偏移,然后从这个偏移位置再往后顺序查找到指定词,在根据指定词定位到内容。

 

落地实践

资源申请

类型

计算公式

存储资源

存储容量 ≈ 源数据 × (1 + 副本数量) × 1.45 ×(1 + 预留空间)

≈ 源数据 × (1 + 副本数量) × 1.67

计算资源

峰值线程数 ≈ 每秒峰值检索请求数 * 每个请求的平均响应时间(毫秒)/1000

线程队列大小 ≈ (每个节点的物理 cpu 核数 * 每核的线程数 * 3 / 2)+ 1

总数据节点个数 ≈ 峰值线程数 / 线程队列大小 ×(1 + 预留空间)

结构/类型/语法

以 ES 7.0 以上版本为例,介绍下常用的结构定义和类型,更多的内容可以参考官方文档

结构

MySQL

ElasticSearch

Table

Index

Row

Document

Column

Field

Schema

Mapping

类型

分类

类型

说明

简单类型

字符串类型

text: 分词检索

keyword:精确匹配

 

数值类型

byte, short, integer, long, float, double ...

 

布尔值类型

可以接受表示真、假的字符串或数字:

真值: true, "true", "on", "yes", "1"

假值: false, "false", "off", "no", "0", ""(空字符串), 0.0, 0

 

时间类型

没有单独的时间类型,可以是

1.  包含格式化日期的字符串, "2018-10-01", 或"2018/10/01 12:10:30".

2.  代表时间毫秒数的长整型数字.

3.  代表时间秒数的整数.

 

范围类型

用来进行范围搜索,主要为数字,时间,ip等值

     

复杂类型

数组类型

没有专门的数组类型, 直接使用[]定义即可

必须是同一种数据类型

 

对象类型

文档可以包含内部对象, 内部对象也可以包含内部对象

 

嵌套类型

主要用来解决对象类型的不足,如果需要对子对象进行索引, 且保留数组中对象的独立性,则需要使用嵌套类型

语法

类型

说明

插入

PUT /{index}/_doc/{id}

{

"field": "value",

...

}

获取

GET /{index}/_doc/{id}

更新

POST /{index}/_update/{id}

{

"doc":{ // 在doc字段中指定需要更新的字段

// 需要更新的字段列表

}

}

删除

DELETE /{index}/_doc/{id}

查询

GET /{index}/_search

{

"from" : 0, // 返回搜索结果的开始位置

"size" : 10, // 分页大小,一次返回多少数据

"_source" :[ ...需要返回的字段数组... ],

"query" : { ...query子句... }, // 查询条件

"aggs" : { ..aggs子句.. }, // 聚合条件

"sort" : { ..sort子句.. } //排序条件

}

数据接入

日常业务中,我们会使用 MYSQL 用做 OLTP,需要将 MYSQL 的数据同步到 ElasticSearch,一般来说,同步数据的方式有以下几种:

方案

优点

缺点

代码双写

1.  实现简单

2.  数据实时性较好

1.  容易造成代码冗杂和不必要的藕合,数据结构修改的开发和测试成本高

2.  事务一致性不容易保障,例如运营数据库,则需要同时同步更新 ElasticSearch 数据

定时任务同步

1.  与数据库业务解藕

2.  实现也比较简单

1.  数据实时性不足

2.  批量查询的SQL性能不佳

基于binlog事件

1.  与数据库业务解藕

2.  数据实时性较好

1.  实现复杂度比较高

 

posted @ 2023-02-01 16:45  S&L·chuck  阅读(30)  评论(0编辑  收藏  举报