ES的索引结构与算法解析

news2024/12/23 11:09:08

作者:京东物流 李洪吉

提到ES,大多数爱好者想到的都是搜索引擎,但是明确一点,ES不等同于搜索引擎。不管是谷歌、百度、必应、搜狗为代表的自然语言处理(NLP)、爬虫、网页处理、大数据处理的全文搜索引擎,还是有明确搜索目的的搜索行为,如各大电商网站、OA、站内搜索、视频网站的垂直搜索引擎,他们或多或少都使用到了ES。

​作为搜索引擎的一部分,ES自然具有速度快、结果准确、结果丰富等特点,那么ES是如何达到“搜索引擎”级别的查询效率呢?首先是索引,其次是压缩算法,接下来我们就一起了解下ES的索引结构和压缩算法

1 结构

1.1 Mysql

Mysql下的data目录存放的文件就是mysql相关数据,mysql文件夹对应的就是数据库mysql。

其中表columns_priv对应了3个文件:columns_priv.frm、columns_priv.MYD、columns_priv.MYI。

.frm:表结构;.MYD:myisam存储引擎原数据;.MYI:myisam存储引擎索引;.ibd:innodb存储引擎数据

1.2 Elasticsearch

cfe为索引文,cfs 为数据文件,cfe文件保存Lucene各文件在.cfs文件的位置信息

cfs、cfe 在segment还很小的时候,将segment的所有文件都存在在cfs中,在cfs逐渐变大时,大小超过shard的10%,则会拆分为其他文件,如tim、dvd、fdt等文件

1.3 存储结构

倒排索引结构分为倒排表、词项字典、词项索引

倒排表包含某个词项的所有id的数据存储了在.doc文件中

词项字典包含了index field的所有经过处理之后的词项数据,最终存储在.tim文件中

1.4 结构对比

我们以某商城的手机为例,左侧为es倒排索引结构,右侧为原始数据。左侧图示只是为了展示倒排索引结构,并不是说es中倒排表就是简单的数组

以上面结构对比示例图为例,假如共有10亿条数据需要存储在ES中(上图右),分词后存储的倒排表(上图左)大概包含分词term以及对应的id数组等,在10亿条数据中,分词“小米”相关的数据有100万条,也就是说分词“小米”对应的数组Posting List长度是100万

id是int类型的有序主键,分词“小米”在数组Posting List中100万int类型数字总长度=100万✖每个int占4字节=400万Byte≈4MB。1个分词占4MB空间,假如10亿条数据有500万个分词,总空间=4MB✖500万=2千万MB,磁盘空间直接爆炸

2 算法

分词对应的数组Posting List实际就是一个个有序数组,而有序数值数组是比较容易进行压缩处理的,而且一般来说压缩效益也不错,如果能对其进行压缩是能够大大节约空间资源的

ES中倒排索引的压缩算法主要有FOR算法(Frame Of Reference)和RBM算法(RoaringBitMap)

2.1 FOR

FOR算法的核心思想是用减法来削减数值大小,从而达到降低空间存储。 假设V(n)表示数组中第n个字段的值,那么经过FOR算法压缩的数值V(n)=V(n)-V(n-1)。也就是说存储的是后一位减去前一位的差值。存储是也不再按照int来计算了,而是看这个数组的最大值需要占用多少bit来计算

我们按照差值计算的方式来保存数据,初始值为1,2与1的差值为1,3与2的差值为1……最终我们就将原始Posting List数据转化为100万个1,每个1我们可以用1bit来记录,总空间=1bit✖100万=100万bit,相比原有400万Byte=3200bit,空间压缩了32倍

在实际生产中,不可能出现一个term的Posting List是这种差值均为1的情况,所以我们以通用示例举例。假如原数据为[73,300,302,332,343,372],数组中6个数字占据总空间为24字节。按照差值方式记录,数组转化为[73,227,2,30,11,29],最大数字为227,大于2的7次方128,小于2的8次方256,所以每个数字可以使用8bit即1Byte来保存,占据总空间为1Byte*6 + 1Byte=7Byte

在此基础上,我们将差值数组按照密集度划分为[73,227]和[2,30,11,29],其中[73,227]中最大值227介于2的7次方和2的8次方之间,所以用8bit=1Byte作为切割分段,[2,30,11,29]中最大数30介于2的4次方和2的5次方之间,所以用5bit作为切割分段。

数组[73,227]占据总空间为8bit✖2个=16bit=2Byte

数组[2,30,11,29]占据总空间为5bit✖4个=20bit=3Byte

为什么20bit=3Byte呢?因为8bit=1Byte,小于8bit也会占据1个字节空间,所以17bit到24bit均为3Byte

所以,最终占据总空间=1+2+1+3=7Byte

疑问一:既然原数组[73,300,302,332,343,372]要按照密集度拆分为[73,227]和[2,30,11,29]两个数组,那为什么不继续往下拆分,直接拆分到每个数字是一个数组,这样使用bit记录时占据总空间会更少?

答:如果继续拆分数组,空间确实会使用更少,但是,之前我们提到搜索引擎速度快的方式有两种:高效的压缩算法和快速的编码解码速度,单个数字存储确实压缩了空间,但是我们无法再通过解码的方式将源数据还原

疑问二:为什么源数据使用差值记录占据6Byte,拆分数组后占据7Byte,拆分后占据空间不变,有时候甚至会变大,为什么?

答:数据量小的情况下确实会出现该情况,因为我们需要拆分数组并记录拆分数组的长度(如上面示例中的8bit和5bit),在原数据存储空间基础上还要存储拆分长度,所以数据量小的情况下会出现比直接存储占据空间大的情况。但是不管是搜索引擎还是Elasticsearch更多处理的是海量数据,数据量越多,差值数组拆分的方式节省空间越明显

2.2 RBM

我们已经了解了FOR压缩算法,算法核心是将PostingList按照差值密集度转化成两个差值数组。在这里我们要考虑一种情况就是:在大数据中,10亿条数据分词500万个,如果分词“小米”所在PostList比较分散且差值很大,此时使用FOR算法效果就会大打折扣。所以稀疏的数组,不适合使用FOR算法

在这里我们以[1000,62101,131385,132052,191173,196658]为例,如果按照FOR算法,转化成的差值数组为[1000,61101,69284,667,59121,5485]密集度很低。我们采用RBM算法

源数据PostingList是由int类型组成的数组,int类型=4Byte=32bit,最大值=2的32次方-1=4294967295≈43亿。当数据较大且稀疏时,我们将32bit拆分为16bit和16bit,16bit最大值=65535,前16bit存放,后16bit存放余数,所以商和余数都不会超过65535.我们将源数组的值除以65536,得到的商和余数分别存放在前16bit和后16bit。

以数字196658为例,转化为2进制,前16位=3,后16位=50

得到的结果以K-V存放。Key最大值为16bit,所以以short[]数组存放,Value以Container存放。

由于源数组为有序数组,所以按照高低16位转化后,商和余数都是从小到大排列

通过看Container源码,我们可以看到Container有3种:ArrayContainer、BitmapContainer、RunContainer。

  1. ArrayContainer本质为集合,所以随着数组中数量越多,占用空间越多,呈正向增长。

当数组种数量为4096时,占据总空间=4096个✖16bit(即2Byte)➗1024=8KB

当数组种数量为65536时,占据总空间=65536个✖16bit(即2Byte)➗1024=128KB

  1. BitmapContainer位图,核心就是将原有存储数值转化成该数值在哪个位置上存在

由于余数最大值为65535,所以我们需要65536位位图,数值是多少,在位图上对应的位置就是多少。数值等于4096,则位图上4096位值为1;数值等于65535,则位图上65535位值为1。每个位置上的数都占用8KB空间(8KB=65536bit)

  • RunContainer用法相对狭隘,这种类型是Lucene 5之后新增的类型,主要应用在连续数字的存储商,比如倒排表中存储的数组为 [1,2,3…100W] 这样的连续数组,如果使用RunContainer,只需存储开头和结尾两个数字:1和100W,即占用8个字节。这种存储方式的优缺点都很明显,它严重收到数字连续性的影响,连续的数字越多,它存储的效率就越高
  • 如果数组是如下形式 [1,2,3,4,5,100,101,102,999,1000,1001] 就会被拆分为三段:[1,5],[100,102],[999,1001]

至于每次存储采用什么容器,需要进行一下判定,比如ArrayContainer,当存储的元素少于4096个时,他会比BitmapContainer占用更少空间,而当大于4096个元素时,采用ArrayContainer所需要的空间就会大于8kb,那么采用BitmapContainer就会占用更少空间

3 总结

ES在处理海量数据时通过其独到的结构和压缩算法,将索引效率尽可能的提升。虽然在实际业务处理中我们极少遇到海量数据处理的情况,但是通过了解ES的原理,能够帮我们开阔下视野,了解数字之美,算法之美。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/455598.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DAY03_循环语句

1:for循环结构 来,继续啊,下面我们来学习循环结构语句,循环结构有三种语句,分别是: for循环 while循环 do…while循环 这三种循环可以做相同的事情,当然它们也有小的区别,至于它…

【LeetCode】322. 零钱兑换

322. 零钱兑换(中等) 思路 由于题目提到 「每种硬币的数量是无限的」,所以这道题本质上完全背包问题,我直接使用了空间压缩的写法。dp[j] 表示当前区间在 [0, i]的硬币可以表示成 j 的最小数量。注意,这里把 dp数组初始…

windows安装sqli-labs靶场,两种方式

1、安装phpstudy 官网打不开了,下载地址在这儿https://download.csdn.net/download/weixin_59679023/87711536 双击安装 点自定义安装,选择安装目录,注意目录不要有空格和中文 安装完成启动红框内的两个服务 2、安装sqli靶场 这个包支持ph…

4.23学习周报

文章目录 前言文献阅读摘要简介方法结果结论 时间序列预测学习总结 前言 本周阅读文献《A novel hybrid model for water quality prediction based on synchrosqueezed wavelet transform technique and improved long short-term memory》,文献主要提出一种新型混…

动力节点Springsecurity笔记06-13基于数据库的方法授权

6 密码处理 6.1 为什么要加密? csdn 密码泄露事件 泄露事件经过:https://www.williamlong.info/archives/2933.html 泄露数据分析:https://blog.csdn.net/crazyhacking/article/details/10443849 6.2加密方案 密码加密一般使用散列函数&a…

react-9 函数式写法rsc,配合HOOKS基础钩子函数

函数组件和类组件区别: 1.函数组件中没有this 2.函数组件无需继承 3.函数组件默认没有状态,想要使用组件状态必须通过 HOOK 函数引入 4.函数组件默认没有生命周期函数 5.函数组件的渲染,只需要一次函数调用即可 useState :用…

【react全家桶学习】react中JSX语法规则

目录 JSX的定义 XML的含义 JSX的语法规则 (1)定义类名,用className而不是class (2)如何使用内联样式设置样式 (3)虚拟dom必须只有一个根标签 (4)在react组件中使用变…

谷歌将发布全新搜索引擎,你期待吗?

Google一身自带AI属性的新搜索最首要的目标并非急于取代传统搜索引擎,或者说彻底打败ChatGPT,而是能够用全新的产品说服用户,变得与竞争对手同样“强大、能力出众以及顺应AI潮流”。 对于Google而言,搜索就是命脉。Google每年的收…

Java基础——多线程创建

(1)什么是线程? 线程(thread)是一个程序内部的一条执行路径。程序中只有一条执行路径,那么这个程序就是单线程的程序。 (2)多线程是什么? 多线程是指从软硬件上实现多执行流程的技术。 &…

Python入门教程+项目实战-10.5节: 程序实战-冒泡排序算法

目录 10.5.1 排序算法简介 10.5.2 冒泡排序算法 10.5.3 系统学习python 10.5.1 排序算法简介 所谓排序,是指将数据集合中的元素按从小到大的顺序进行排列,或按从大到小的顺序进行排列。前者称为升序排序,后者称为降序排序。在数据结构与算…

jenkins自动化部署配置

文章目录 1. jenkins 插件安装2. 配置2.1 全局工具配置2.2 全局配置2.2.1 gitee 配置 3. 创建任务添加gitee ssh jenkins 开机自启动 1. jenkins 插件安装 ant Build Failure AnalyzerBuild Monitor ViewBuild Timeout dockerEmail Extension Plugin giteegithubgradle javama…

【11 EL表达式JSTL 学习笔记 】

EL表达式 学习笔记 1. EL表达式介绍2. EL表达式的基本使用3. EL 表达式获取数据4. EL注意事项5. EL表达式运算符关系运算符逻辑运算符empty&三元运算符 6. EL表达式细节7. EL表达式11个隐式对象8. JSTL8.1 JSTL的基本使用 1. EL表达式介绍 简化了 java代码块和jsp表达式的…

ChatGPT的开源平替,终于来了!

最近这段时间,一个号称全球最大ChatGPT开源平替项目Open Assistant引起了大家的注意。 这不最近还登上了GitHub的Trending热榜。 https://github.com/LAION-AI/Open-Assistant 根据官方的介绍,Open Assistant也是一个对话式的大型语言模型项目&#xff…

RUST 每日一省:生命周期作用域

生命周期 一个变量的生命周期就是它从创建到销毁的整个过程。 作用域 我们声明的每个变量都有作用域。作用域其实是变量和值存在的环境。作用域是由一对花括号表示的。例如,使用块表达式会创建一个作用域,即任何以花括号开头和结尾的表达式。此…

RabbitMQ-整合mqtt

用 springboot rabbitmq可以搭建物联网(IOT)平台,rabbitmq 不是消息队列吗,原来rabbitmq有两种协议,消息队列是用的AMQP协议,而用在智能硬件中的是MQTT协议。 一、rabbitmq是什么? RabbitMQ就…

一张图了解GPU、CUDA、CUDA toolkit和pytorch的关系

文章目录 GPU、Cuda Driver和 Cuda Toolkit的图解关系省流,简略版本要实现多版本的cuda怎么办 复杂版(你要是觉得简略版说的太简单,这里给你找文档证明)一、Nvidia Driver和CUDA Toolkit的关系安装GPU显卡驱动Nvidia Driver 二、C…

uniapp请求图片时候发现提示GET http://localhost:xxxx/undefined 401,undefined:1解决办法【伸手党福利】

同理解决问题:所有请求发起完成之后执行业务逻辑 目录 现象原因解决办法方法1:提前给变量一个非空默认值方法2:使用前端图片代替后端方法3:使用异步加载判断:注意:这种直接在页面判断内容是否为空或者undef…

Java JVM基础入门(一):jvm的组成、串池、常量池、常用程序调优参数

JVM JVM是java的虚拟机,java的运行环境(java二进制字节码的运行环境) 好处: 一次编写,到处运行自动内存管理,垃圾回收功能 JDK、JRE、JVM的关系图 常见的JVM: oracle的Hotspot是我们通常使…

矿山电子封条智能监管算法 yolov8

矿山电子封条智能监管系统通过YOLOv8python网络模型技术,矿山电子封条智能监管算法模型在对矿井人数变化、生产作业状态、出入井人员等情况实时监测分析,发现煤矿人员作业及状态异常动态及时告警,自动将报警信息推送给后台。YOLOv8 算法的核心…

k8s部署ingress-nginx步骤

目录 一、ingress简介 二、部署ingress controller、ingress-service 三、创建对外服务deployment和service 四、创建HTTP代理yaml 五、测试 六、公网域名测试 七、参考博客 一、ingress简介 service的作用体现在两个方面,对集群内部,它不断跟踪…