各位老铁们好,相信很多人对刷手机业务网站源码分享都不是特别的了解,因此呢,今天就来为大家分享下关于刷手机业务网站源码分享以及的问题知识,还望可以帮助大家,解决大家的一些困惑,下面一起来看看吧!
引言
ElasticSearch(简称ES)是一个非常受欢迎的分布式全文检索系统,常用于数据分析,搜索,多维过滤等场景。蚂蚁金服从2017年开始向内部业务方提供ElasticSearch服务,我们在蚂蚁金服的金融级场景下,总结了不少经验,此次主要给大家分享我们在向量检索上的探索。
ElasticSearch的痛点
ElasticSearch广泛应用于蚂蚁金服内部的日志分析、多维分析、搜索等场景。当我们的ElasticSearch集群越来越多,用户场景越来越丰富,我们会面临越来越多的痛点:
如何管理集群;如何方便用户接入和管理用户;如何支持用户不同的个性化需求;…
为了解决这些痛点,我们开发了ZSearch通用搜索平台:
基于K8s底座,快速创建ZSearch组件,快捷运维,故障机自动替换;跨机房复制,重要业务方高保;插件平台,用户自定义插件热加载;SmartSearch简化用户搜索,开箱即用;Router配合ES内部多租户插件,提高资源利用率;
向量检索需求
基于ElasticSearch的通用搜索平台ZSearch日趋完善,用户越来越多,场景更加丰富。
随着业务的飞速发展,对于搜索的需求也会增加,比如:搜索图片、语音、相似向量。
为了解决这个需求,我们是加入一个向量检索引擎还是扩展ElasticSearch的能力使其支持向量检索呢?
我们选择了后者,因为这样我们可以更方便的利用ElasticSearch良好的插件规范、丰富的查询函数、分布式可扩展的能力。
接下来,我来给大家介绍向量检索的基本概念和我们在这上面的实践。
向量检索基本概念
向量从表现形式上就是一个一维数组。我们需要解决的问题是使用下面的公式度量距离寻找最相似的K个向量。
欧式距离:两点间的真实距离,值越小,说明距离越近;余弦距离:就是两个向量围成夹角的cosine值,cosine值越大,越相似;汉明距离:一般作用于二值化向量,二值化的意思是向量的每一列只有0或者1两种取值。汉明距离的值就两个向量每列数值的异或和,值越小说明越相似,一般用于图片识别;杰卡德相似系数:把向量作为一个集合,所以它可以不仅仅是数字代表,也可以是其他编码,比如词,该值越大说明越相似,一般用于相似语句识别;
因为向量检索场景的向量都是维度很高的,比如256,512位等,计算量很大,所以接下来介绍相应的算法去实现topN的相似度召回。
向量检索算法
KNN算法
KNN算法表示的是准确的召回topK的向量,这里主要有两种算法,一种是KDTtree,一种是BruteForce。我们首先分析了KDTree的算法,发现KDTree并不适合高维向量召回,于是我们实现的ES的BruteForce插件,并使用了一些Java技巧进行加速运算。
KDTree算法
简单来讲,就是把数据按照平面分割,并构造二叉树代表这种分割,在检索的时候,可以通过剪枝减少搜索次数。
构建树
以二维平面点(x,y)的集合(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)为例:
图片来源
按照x排序,确定中间值7,其他坐标分两边;第二层按照y排序,第三层按照x排序;并且在构建时维护每个节点中的x最大最小,y最大最小四个值;
查找最近点
图片来源
搜索(3,5)的最近邻:
到根节点距离为5;遍历到右节点(9,6),发现整棵右子树的x轴,最小值是8,所以所有右子树的节点到查询节点的距离一定都大于8-3=5,于是所有右子树的节点都不需要遍历;同理,在左子树,跟(5,4)节点比较,(7,2)被排除;遍历完(2,3),(4,7),最近点(5,4)返回;
结论
Lucene中实现了BKDTree,可以理解为分块的KDTree,并且从源码中可以看到MAX_DIMS=8,因为KDTree的查询复杂度为O(kn^((k-1)/k)),k表示维度,n表示数据量。说明k越大,复杂度越接近于线性,所以它并不适合高维向量召回。
BruteForce
顾名思义,就是暴力比对每一条向量的距离,我们使用BinaryDocValues实现了ES上的BF插件。更进一步,我们要加速计算,所以使用了JAVAVectorAPI。JAVAVectorAPI是在openJDKprojectPanama项目中的,它使用了SIMD指令优化。
结论
使用avx2指令优化,100w的256维向量,单分片比对,RT在260ms,是常规BF的1/6。ElasticSearch官方在7.3版本也发布了向量检索功能,底层也是基于Lucene的BinaryDocValues,并且它还集成入了painless语法中,使用起来更加灵活。
ANN算法
可以看到KNN的算法会随着数据的增长,时间复杂度也是线性增长。例如在推荐场景中,需要更快的响应时间,允许损失一些召回率。
ANN的意思就是近似K邻近,不一定会召回全部的最近点。ANN的算法较多,有开源的ESANN插件实现的包括以下这些:
基于Hash的LSH;基于编码的IVFPQ;基于图的HNSW;
ZSearch依据自己的业务场景也开发了ANN插件(适配达摩院Proxima向量检索引擎的HNSW算法)。
LSH算法
LocalSensitiveHashing局部敏感hash,我们可以把向量通过平面分割做hash。例如下面图例,0表示点在平面的左侧,1表示点在平面的右侧,然后对向量进行多次hash,可以看到hash值相同的点都比较靠近,所以在hash以后,我们只需要计算hash值类似的向量,就能较准确的召回topK。
IVF-PQ算法
PQ是一种编码,例如图中的128维向量,先把向量分成4份,对每一份数据做kmeans聚类,每份聚类出256个聚类中心,这样,原始向量就可以使用聚类中心的编号重新编码,可以看出,现在表示一个向量,只需要用4个字节就行。然后当然要记录下聚类中心的向量,它被称之为码本。
PQ编码压缩后,要取得好的效果,查询量还是很大,所以前面可以加一层粗过滤,如图,把向量先用kmeans聚类成1024个类中心,构成倒排索引,并且计算出每个原始向量与其中心的残差后,对这个残差数据集进行PQ量化。用PQ处理残差,而不是原始数据的原因是残差的方差能量比原始数据的方差能量要小。
这样在查询的时候,我们先找出查询出靠近查询向量的几个中心点,然后再在这些中心点中去计算PQ量化后的top向量,最后把过滤出来的向量再做一次精确计算,返回topN结果。
HNSW算法
讲HNSW算法之前,我们先来讲NSW算法,如下图,它是一个顺序构建图流程:
例如第5次构造D点的流程;构建的时候,我们约定每次加入节点只连3条边,防止图变大,在实际使用中,要通过自身的数据;随机一个节点,比如A,保存下与A的距离,然后沿着A的边遍历,E点最近,连边。然后再重新寻找,不能与之前重复,直到添加完3条边;
查找流程包含在了插入流程中,一样的方式,只是不需要构建边,直接返回结果。
HNSW算法是NSW算法的分层优化,借鉴了skiplist算法的思想,提升查询性能,开始先从稀疏的图上查找,逐渐深入到底层的图。
以上这3类算法都有ElasticSearch的插件实现:
LSH插件
IVSPQ插件
HNSW插件
概述
在创建index时传入抽样数据,计算出hash函数。写入时增加hash函数字段。召回用minimum_should_match控制计算量
在创建索引时传入聚类点和码本,写入数据就增加聚类点和编码字段,召回先召回编码后距离近的再精确计算
扩展docvalue,在每次生成segment前,获取docvalue里的原始值,并调用开源hnsw库生成索引
优点
1.实现简单,性能较高;2.无需借助其他lib库;3.无需考虑内存;
1.性能较高;2.召回率高>90%;3.无需考虑内存;
1.查询性能最高;2.召回率最高>95%;
缺点
1.召回率较其他两种算法较差,大概在85%左右;2.召回率受初始抽样数据影响;3.ES的metadata很大;
1.需要提前使用faiss等工具预训练;2.ES的metadata很大;
1.在构建的时候,segment合并操作会消耗巨大的CPU;2.多segment下查询性能会变差;3.全内存;
ZSearchHNSW插件
我们根据自己的场景(轻量化输出场景),选择了在ES上实现HNSW插件。因为我们用户都是新增数据,更关心top10的向量,所以我们使用了通过seqNo去join向量检索引擎方式,减少CPU的消耗和多余DocValues的开销。
对接Porxima向量检索框架:
Proxima是阿里内部达摩院开发的一个通用向量检索引擎框架,类似与facebook开源的faiss;支持多种向量检索算法;统一的方法和架构,方便使用方适配;支持异构计算,GPU;
实现ProximaEngine
写入流程,扩展ElasticSearch本身的InternalEngine,在写完Lucene以后,先写proxima框架,proxima框架的数据通过mmap方式会直接刷到磁盘,一次请求的最后,Translog刷入磁盘。就是一次完整的写入请求了。至于内存中的segment,ElasticSearch会异步到达某个条件是刷入磁盘。
Query流程
查询的时候,通过VectorQueryPlugin,先从proxima向量检索引擎中查找topN的向量,获得seqNo和相似度,再通过构造newSetQuery的FunctionScoreQuery,去join其他查询语句。
这里的数字型newSetQuery底层是通过BKDTree去一次遍历所得,性能还是很高效的。
Failover流程
当然我们还要处理各种的Failover场景:
数据从远端复制过来时:我们拦截了ElasticSearch的recoveryaction;然后生成Proxima索引的快照,这个时候需要通过写锁防止数据写入,快照生成由于都是内存的,其实非常快;把Proxima快照复制到目的端;再进行其他ElasticSearch自己的流程;数据从本地translog恢复时,我们会记录快照的LocalCheckPoint,如果当前CheckPoint小于等于LocalCheckPoint,可以直接跳过,否则我们会回查proxima检索引擎,防止数据重试;目前还有一个情况,数据会有重复,就是主副分片全部挂掉时,translog还未刷盘,数据可能已经写入proxima了。
对比
sift-128-euclidean100w数据集(https://github.com/erikbern/ann-benchmarks)
HNSW插件
ZSearch-HNSW插件
数据写入(单线程1000个bulk写入)
1.初始写入5min,25个segment,最大CPU300%;2.合并成1个segment5min,最大CPU700%(本地最大);
构建索引15min,因为单线程,所以最大CPU100%
查询
1.Top10,召回率98%;2.rt20ms,合并成1个segment后,5ms;
1.Top10,召回率98%;2.rt6ms;
优点
兼容failover
CPU消耗少,无额外存储
缺点
CPU消耗大,查询性能跟segment有关
主副分片全挂的情况下会有少量数据重复
总结
ES参数配置最佳实践
100w256维向量占用空间,大概是0.95GB,比较大:所以更大的堆外内存分配给pagecache;例如8C32G的机器,JVM设置8GB,其他24GB留给系统和pagecache;设置max_concurrent_shard_requests:6.x默认为节点数*5,如果单节点CPU多,可以设置更大的shards,并且调大该参数;BF算法使用支持AVX2的CPU,基本上阿里云的ECS都支持;
算法总结
KNN适合场景:数据量小(单分片100w以下);先过滤其他条件,只剩少量数据,再向量召回的场景;召回率100%;ANN场景:数据量大(千万级以上);先向量过滤再其他过滤;召回率不需要100%;LSH算法:召回率性能要求不高,少量增删;IVFPQ算法:召回率性能要求高,数据量大(千万级),少量增删,需要提前构建;HNSW算法:召回率性能要求搞,数据量适中(千万以下),索引全存内存,内存够用;
未来规划
深度学习里的算法模型都会转化成高维向量,在召回的时候就需要用相似度公式来召回topN,所以向量检索的场景会越来越丰富。
我们会继续探索在ElasticSearch上的向量召回功能,增加更多的向量检索算法适配不同的业务场景,将模型转化成向量的流程下沉到ZSearch插件平台,减少网络消耗。希望可以和大家共同交流,共同进步。
好了,文章到此结束,希望可以帮助到大家。