得物向量数据库落地实践(转载)

目录

一、背景

二、认识向量数据库

    1. 向量数据来源和存储

    2. 向量数据库是如何工作

三、向量数据库对比传统数据库

四、如何选择向量数据库

    1. 向量数据库比较

    2. 选择流行的索引

    3. 综合比较和选择

    4. 得物选择Milvus作为向量数据库

五、Milvus在得物的实践

    1. 部署架构演进

    2. 独立资源池迁移至共享资源池

    3. 引入Zilliz

六、向量数据库运维沉淀

    1. 索引结构和搜索原理

    2. 并不是你想的那样

    3. 错误处理

七、未来展望

  背景

信息通信技术(ICT)正经历着前所未有的变革浪潮,以大模型和生成式人工智能(GenAI)为代表的技术突破,正在引发全球产业体系的深刻变革,成为驱动企业技术架构革新和商业模式转型的关键引擎。

得物是广受年轻人喜爱的品质生活购物社区。在AI鉴别、图搜、算法、安全风控等场景下都广泛使用啦GenAI技术。

向量数据库作为GenAI的基础设施之一,通过量化的高维空间数据结构(如HNSW算法),实现对嵌入向量(Embeddings Vector)的高效存储、索引和最近邻搜索(ANN),支撑包括多模态数据表征在内的复杂智能应用。

  认识向量数据库

向量数据来源和存储

图片

一般向量数据库中向量的来源是将图片、音频、视频、文本等非结构化数据,将这些非结构化数据通过对应的量化算法计算出一个多维度的向量(生产使用一般向量维度会大于512),并且将向量数据持久化在特定的存储上。

向量数据库是如何工作

图片

向量数据库在查询的时候一般会将需要查询的非结构化数据通过量化,计算成一个多维度向量数据,然后在数据库中搜索出和查询向量相似的数据。(需要注意的是这边查询的是相似的数据而不是相同的数据)。

  向量数据库对比传统数据库

图片

图片

向量数据库在数据结构、检索方法、擅长领域与传统数据库有很大的不同。

传统数据库

结构是处理离散的标量数据类型(例如数字和字符串),并通过行和列来表达组织数据(就是一个表格)。传统数据库主要为了解决结构化数据的精确管理和高效查询问题。并且传统数据库通过B树索引、哈希索引等数据结构,能够快速定位到精确匹配的记录。更重要的是,传统数据库通过ACID事务特性(原子性、一致性、隔离性、持久性)确保了在数据中数据的绝对准确性。

向量数据库

为了解决非结构化数据的语义搜索问题,解决如何在海量的高维向量数据中,快速找到与查询向量最相似的结果。比如在推荐系统中找到与用户喜好相似的物品,或在图像库中检索出与查询图片最相近的图片。这类问题的特点是:

  1. 查询的不是精确匹配,而是相似度排名。

  2. 数据维度极高(通常128-2048维)。

  3. 数据规模庞大(可能达到十亿级别)。

传统数据库的精确查询方式在这种场景下完全失效,因为:

  1. 无法为高维向量建立有效的B树索引。

  2. 计算全量数据的精确相似度代价过高。

  3. 无法支持"相似但不完全相同"的搜索需求。

  如何选择向量数据库

向量数据库比较

下面我们通过10个不同维度来比较一下不同向量数据库的区别:

图片

从上面表格可以看到:

  1. 自 2016 年起 ,向量数据库逐渐崭露头角,成为 AI 和大数据领域的重要基础设施。而到了 2021 年之后 ,随着深度学习、大模型和推荐系统的迅猛发展,向量数据库正式迈入爆发式增长时代 ,成为现代数据架构中不可或缺的核心组件。

  2. 超过半数的向量数据库均采用分布式架构设计,并且这些支持分布式部署的系统普遍具备弹性扩缩容能力,能够根据业务需求实现资源的动态调整。

  3. 当业务需要处理亿级甚至更高规模的向量数据时,推荐以下高性能、可扩展的向量数据库:Vespa、Milvus/Zilliz、Vald、Qdrant。

  4. 当前主流的向量数据库普遍采用模块化、插件式的设计理念。其核心引擎大多基于 C/C++ 开发,以追求极致的性能表现。与此同时,Go 和 Rust 也正在这一领域崭露头角。

  5. 在向量数据库领域,NHSW(Hierarchical Navigable Small-World)和 DiskANN 正逐渐成为主流索引方案。其中NHSW主要以内存搜索为主,DiskANN主要以磁盘搜索为主。值得注意的是,Qdrant 在优化 NHSW 的基础上,进一步实现了 基于磁盘的 NHSW 检索能力。

选择流行的索引

在向量数据库技术领域,有NHSW 和 DiskANN 作为两大主流索引方案,各自展现了独特的技术优势。我们从以下关键维度进行专业对比分析。

图片

从上表格我们可以得到,NHSW和DiskANN适用于不同的场景:

  • NHSW :以 内存优先 的设计实现高性能搜索,适合对 低延迟、高吞吐 要求严格的场景,如实时推荐、广告检索等。

  • DiskANN :以 磁盘存储优化 为核心,在保证较高召回率的同时 显著降低硬件成本 ,适用于大规模数据下的经济型检索需求。

随着数据规模的持续增长,NHSW 和 DiskANN 的混合部署模式 或将成为行业标准,让用户能根据业务需求灵活选择 "极致性能" 或 "最优成本" 的检索策略。

综合比较和选择

图片

从表格中可以得到:

  1. 如果数据流比较小,并且自身对Redis、PG、ES比较熟悉,就可以选择Redis、PG、ES。如DBA团队就比较适合。

  2. 如果数据量比较大,并且前期人力不足可以使用云托管方案。选择Zilliz、Pinecone、Vespa或者Qdrant,如果后期计划从云上迁移到自建可以选择Zilliz、Vespa或者Qdrant。

得物选择Milvus作为向量数据库

我们的需求

社区图搜和AI鉴别需要大量的数据支持,得物业务场景要求能支持十亿级向量数据搜索,有如下要求:

  1. 大数据量高性能搜索,RT需要在90ms以内。

  2. 大数据量但是性能要求不高时,RT满足500ms以内。

需要支持快速扩缩容:

满足上面2点就已经锁定在Milvus、Qdrant这两个向量数据库。如果从架构复杂度和维护/学习成本的角度考虑,我们应该优先选择Qdrant,因为它的架构相比Milvus没有那么复杂,并且维护/学习成本没有Milvus高,重要的Qdrant可以单独集群部署,不需要k8s技术栈的支撑。

Milvus 和 Qdrant 架构比较

Milvus架构

Milvus部署依赖许多外部组件,如存储元信息的ETCD、存储使用的MinIO、消息存储Pulasr 等等。

图片

Qdrant

Qdrant完全独立开发,支持集群部署,不需要借助ETCD、Pulsar等组件。

图片

选择Milvus的原因

※ 业务发展需求

业务属于快速发展阶段,数量的变化导致扩缩容频繁,使用支持k8s的Milvus在扩缩容方面会比Qdrant快的多。

※ 技术储备和社区良好

对DBA而言,向量数据库领域需要持续的知识更新和技术支持。从问题解决效率来看,国内技术社区对Milvus的支持体系相较于Qdrant更为完善。

※ 契合得物DBA开发栈

Milvus使用的开发语言是Go,契合DBA团队技术栈,在部分运维场景下情,通过二次开发满足运维需求。例如:使用milvus-backup工具做迁移,部分的segment有问题需要跳过。自行修改一下代码编译运行即可满足需求。

图片

Milvus在得物的实践

部署架构演进

小试牛刀

初始阶段,我们把Milvus部署在K8S上,默认使用HNSW索引。架构图如下,Milvus整个架构较为复杂,外部依赖的组件多,每个集群需要部署自己的 ETCD、ZK、消息队列模块,多套集群共享着同一个存储。

图片

存储拆分,每个集群独立存储

共享存储瓶颈导致稳定性问题凸显。

随着业务规模扩展,集群数量呈指数级增长,我们观测到部分集群节点出现异常重启现象,经诊断确认该问题源于底层共享存储存在性能瓶颈。

图片

图片

独立资源池迁移至共享资源池

通过混布的方式提升资源利用率。

前期为了在性能和稳定性上更好的服务业务,Milvus部署的底层机器都是独立的,目的就是为了和其他应用隔离开,不相互影响。但是随着集群的越来越多,并不是所有的集群对稳定性和性能要求那么高,从监控上看Milvus集群池的资源使用不超过10%。为了提高公司资源利用率,我们将独立部署的Milvus迁移高共享资源池中,和大数据、业务应用等K8S部署相关服务进行混合部署。

图片

DiskANN索引的使用

数据量大且搜索QPS小时选择DiskANN 作为索引。通过监控发现有很多集群数据量比较大,但是QPS并不是那么高,这时候就考虑对这些性能要求不高的集群是否有降本的方案。通过了解我们默认使用的HNSW索引需要将所有数据都加载到内存中进行搜索,第一反应就是它的内存查询和Redis一样,那是否有类似pika的方案内存只存少部分数据大部分数据存在磁盘上。这时候发现DiskANN就能达到这样的效果。

性能压测

※ 集群规格

图片

图片

QPS

图片

延时(ms)

新增DiskANN索引后集群架构

增加DiskANN后我们需要对相关服务器上挂载 NVME SSD 磁盘,用于在磁盘上搜索最终数据。

图片

DiskANN 加载数据过程

图片

引入Zilliz

经过大规模生产验证,Milvus在实际业务场景中展现出卓越的性能表现和稳定性,获得业务方的高度认可。并且也吸引来了C端核心业务系统的使用。在使用前,我们使用了业务真实流量充分的对Milvus进行了压测,发现Milvus在亿级别数据量的情况下满足不了业务,因此对于部分核心场景我们使用了Zilliz。

Milvus和Zilliz 压测

业务的要求是集群返回的RT不能操过90ms。

使用真实的业务数据(亿级别)和业务请求对Milvus进行压测,发现Milvus并不能满足业务的需求。

类型

QPS

平均RT(ms)

客户端性能图

Milvus

110

200

图片

zilliz

350

65

图片

Milvus RT 200不满足业务需求,并且QPS一直上不去,无论我们对QueryNode扩容多大,其中还发生过,将Query扩容到60个后,反而RT上升的问题,排查后是因为有的QueryNode和Proxy交互的时候网络会抖动影响了整体的RT。

从上面可以看出就算业务能容忍RT=200ms的,Milvus也需要创建3个相同的集群提供业务访问,并且业务需要改造代码实现多写、多读的功能,最终还会发现3个集群的成本远高于Zilliz。

通过成本和性能上的考虑,对于大数据量并且性能和稳定性要求高场景,我们将选用Zilliz。

迁移方案

对于不同业务场景,我们分别制定了以下3种迁移方案:

方案1:业务自行导入数据使用

图片

方案2:备份恢复 + 业务增量

图片

方案3:全量 + 增量 + 业务双写/回滚

图片

高可用架构部署

随着业务关键性持续提升,Milvus对应的SLA变得越来越重要。在此背景下,构建完善的Milvus高可用架构与灾备体系已成为系统设计的核心考量要素。比如:主从、多zone部署,Proxy高可用,Minio高可用,一个zone完全挂了怎么办等问题?

方案1: 同城多机房混部

正常访问:

  • 该方案会有客户端会有跨机房访问的情况。

跨机房访问节点:

客户端 -> SLB

SLB -> Proxy

Proxy -> QueryNode

  • SLB有高可用

  • Proxy有高可用

图片

当部分节点不可用:

  1. 当zone 1中的proxy 1不可用,不影响整个访问链路,其他Proxy依然可以接受请求。

  2. 当 zone 1 中的 QueryNode 1 不可用,会出现访问报错的问题。需要重建QueryNode1,有可能在 Zone 2 新建QueryNode 5,原本请求QueryNode 1 的流量会重新指向 QueryNode 5。

图片

当 Zone1 不可用:

  1. 访问会切换到 Zone 2 的备用SLB中。

  2. 备用SLB会访问本机房的Proxy。

  3. 由于 QueryNode 1 和 QueryNode 2 已经不可用,需要重建QueryNode,新生成 QueryNode 5、QueryNode 6并且加载数据提供访问。

图片

方案2: 同城多zone多副本就近访问

正常访问:

  1. 不同zone的客户端访问本地的SLB。

  2. 使用了QueryNode多副本特性,各自zone的QueryNode都加载了所有数据。

Proxy -> QueryNode 的访问,目前Proxy只能随机访问所有zone的QueryNode(这是Milvus的限制)

图片

当部分节点不可用:

  1. 当每个zone 都有1个Proxy故障,并不影响业务正常访问。

  2. 由于QueryNode开启了副本,只要每个zone不相同的QueryNode故障,集群还是能正常运行。需要注意的是这时候需要考虑剩下的QueryNode性能是否满足需求。所以一般业务需要有限流功能,在剩余的QueryNode不满足需求时,业务需要限流,直到其他QueryNode恢复。

图片

整个Zone不可用:

  1. 当Zone1整个不可用,不影响Zone2的访问。

图片

方案3: 同城多zone单独部署业务交叉访问互相backup

正常访问:

  1. 每个zone都有单独部署的milvus集群。

  2. 每个集群的有同时满足 业务1、业务2 访问的数据。

  3. 业务访问Proxy的时候是有交叉访问的情况

  4. 业务改造会比较多,需要实现双写。

图片

当部分节点不可用:

  1. 当Zone1中的Proxy1部可用,不会影响Zone1的整个集群访问。

  2. 当Zone1的QueryNode1不可用,会影响到线路1、2的正常访问,这时候业务需要切换不访问Zone1的SLB。

图片

当整个zone不可用

  1. 整个zone1不可用,由于线路1会访问到zone1的SLB,因此线路1访问会报错,业务需要将线路1切换成线路2。

图片

  向量数据库运维沉淀

索引结构和搜索原理

NHSW 索引

※ 相关信息

图片

※ 内存结构

由于空间问题,图中并没有完全按 M=16、ef=200 参数进行画图。

图片

※ NHSW搜索过程

现在需要搜索向量N = [....]

第一步:

在第一层随机选择一个节点,如:3。

图片

第三步:

  1. 。通过节点6,从第二层跳到底3层

  2. 在第3层,通过节点6获取到相邻的节点:节点2、节点3、节点6、节点9,其中 节点2、节点3、节点6 已经存在,因此只需要将 节点9 放入候选结果集。

  3. 如果候选结果集合没有满,则继续便利 节点2、节点3、节点9 的邻居,直到 节点数=ef=200。

图片

第二步:

  1. 。通过节点3,从第1层转跳到第2层

  2. 在第2层,通过节点3获取到相邻的节点:节点1、节点2、节点5、节点6。

  3. 将搜索节点N逐个和 节点1、节点2、节点5、节点6 进行计算出各自的距离。并且选择距离最短的节点6。

    1. 节点N -> 节点1:10

    2. 节点N -> 节点2:6

    3. 节点N -> 节点5:9

    4. 节点N -> 节点6:3

    5. 节点N -> 节点3:4

  4. 将 节点1、节点2、节点5、节点6、节点3 放入结果候选集中。

图片

DiskANN 索引

※ 相关信息

图片

※ 存储结构和裁剪过程

由于画图空间问题,没办法将 聚类数=10、100/内存聚类、1w/磁盘聚类 信息画全。

图片

  1. 初始化随机连接:DiskANN算法会将向量数据生成一个密集的网络图,其中点和点是随机链接的,并且每个点大概有500个链接。

  2. 裁剪冗余链接:通过计算点和点点距离裁剪掉一些冗余的链接,留下质量高的链接。

  3. 添加快速导航链接:计算出图中若干个中心点,并且将这些中心点进行链接,并且这些链接会跳过其他点,如果图中黄色链接。

  4. 重复进行裁剪优化过程,达到最优的情况。

  5. PQ量化操作生成索引:

    1. 将向量分成多个子空间。

    2. 独立对每个子空间进行聚类操作,并且计算出多个质心。

    3. 将每个子向量映射到最近的质心ID。

※ DiskANN搜索过程

现在需要搜索向量N = [....]

第一步(内存索引中搜索):

将搜索的向量进行量化。

将量化后的数据在内存中索引寻找到离自己较近的质心为入口进行下一步搜索。

图片

第二步(内存中搜索):

通过第一个节点,寻找到它的所有相邻节点。

通过内存PQ代码计算搜索节点和相邻节点的近似距离。

这些邻居节点都是潜在的下次搜索候选口节点。

图片

第三步(磁盘中搜索):

通过计算搜索节点和相邻节点的真实距离,并且得到距离最近的一个节点(需要从磁盘上读取证实的节点数据并计算)。

并且通过得到的最新节点获取到新的相邻节点。

图片

第四步(磁盘中搜索):

反复先进二、三步骤操作,直到找到足够数量的邻居。

图片

并不是你想的那样

querynode 越多越快?

querynode 越多,查询越快,并发越高?

※误区原因

将querynode看成redis cluster,增加节点数能提高查询并发,然而并不是。redis cluster 增加节点,数据量会尽可能的打散到每个节点中,所以增加节点和性能提升是相对成正比。但是milvus不一样,milvus打散的基本单位是segment,一般segment大小(1G/个),他的粒度比redis cluster要大。理论上的理想情况是1个segment对应1个querynode,但是实际情况会收到多因素的干扰,会导致querynode越多出现不稳定的概率越大,如某个querynode网络抖动会影响整体的查询RT。

不能提升性能例1:

部分segment数 < querynode数,或部分querynode没有任何segment。

图片

不能提升性能例2:

querynode 过多,其中1个querynode RT 高,导致整体客户端RT高。

图片

标量索引提高性能

在标量上创建索引,搜索带上标量过来能提高性能?

※误区原因

使用传统关系型数据库的索引查询来理解Milvus的索引查询,字段上创建了索引能使用到索引扫描进行数据查询,比全表扫描快。然而并不是,关系型数据库的属于精确查询,Milvus属于近似最近邻搜索(ANNS),milvus的查询是不保证绝对精确,使用了标量索引查询反而会导致数据变稀疏查询会变慢。

使用标量索引筛选不一定快原因,如下示例:

通过标量搜索后再使用ANNS搜索过程:

1.Collection A 中的数据如下,其中is_delete 是标量,其中的值有0和1。

图片

3.再使用 ANNS 搜索获取最终数据。

图片

2.使用标量搜索is_delete=1后剩余的数据。

图片

通过标量is_delete=1搜索成功之后,有可能结果集会很大,结果集数据如果内存存放不下需要使用到磁盘存储,之后再在结果集中通过ANNS获取到最终需要的数据,如果需要频繁进行磁盘交互则搜索性能会很差。

通过ANNS搜索后再使用标量搜索,过程如下:

1.Collection A 中的数据如下,其中is_delete 是标量,其中的值有0和1。

图片

2.使用ANNS搜索

使用ANNS搜索能直接很快地获取到满足条件的数据。

图片

3.再使用标量过滤,获取到最终的数据。

图片

思考:

在第二步如果使用ANNS搜索完成之后到底是否需要使用标量索引进行搜索。

如果需要使用标量索引进行搜索那边在ANNS搜索后的结果集需要额外的进行索引构建,然后再进行过滤。构建构建过程其实也是需要便利结果集,那么是不是可以直接在便利的时候直接进行结果集的筛选。

那么其实在某种程度上是不是标量索引没那么好用。

大量单行dml不批量写入能提高数据库性能

大量单行dml,不使用批量写入操作,能提高数据库性能。

※误区原因

使用传统关系型数据库为了让系统尽量少的大事务,减少锁问题并且提高数据库性能。然而实际上Milvus如果有很多的小事务反而会影响到数据库的性能。因为Milvus进行dml操作会生成deltalog、insertlog,当dml都是小事务就会生成大量的相对较小的deltalog和insertlog文件,deltalog和insertlog在和segment做合并的时候会增加打开和关闭文件次数,并且增加做合并次数,导致io一直处于繁忙状态。

deltalog 和 insertlog 生成的契机有2种:

  1. 当数据量达到了一定的阈值会进行生成deltalog 或 insertlog。

  2. Milvus会定时进行生成deltalog 或 insertlog。

eltalog、insertlog  和 segment 合并过程

图片

人为让 deltalog、segment 执行时机可预测

如果业务对数据是实现要求不是那么高,建议使用定时批量的方式对数据进行写入,比如可以通过监控获取到每天的波谷时间段,在波谷时间段内进行集中式数据写入。原因是如果不停的在做写入,无法判断进行合并segment的时间点,要是在高峰期进行了合并操作,很有可能会影响到集群性能。

错误处理

※ 2.2.6 批量删除数据bug,导致业务无法查询

报错:

pymilvus.exceptions.MilvusException: <MilvusException: (code=1, message=syncTimestamp Failed:err: find no available rootcoord, check rootcoord state, /go/src/github.com/milvus-io/milvus/internal/util/trace/stack_trace.go:51 github.com/milvus-io/milvus/internal/util/trace.StackTrace/go/src/github.com/milvus-io/milvus/internal/util/grpcclient/client.go:329 github.com/milvus-io/milvus/internal/util/grpcclient.(*ClientBase[...]).ReCall/go/src/github.com/milvus-io/milvus/internal/distributed/rootcoord/client/client.go:421 github.com/milvus-io/milvus/internal/distributed/rootcoord/client.(*Client).AllocTimestamp/go/src/github.com/milvus-io/milvus/internal/proxy/timestamp.go:61 github.com/milvus-io/milvus/internal/proxy.(*timestampAllocator).alloc/go/src/github.com/milvus-io/milvus/internal/proxy/timestamp.go:83 github.com/milvus-io/milvus/internal/proxy.(*timestampAllocator).AllocOne/go/src/github.com/milvus-io/milvus/internal/proxy/task_scheduler.go:172 github.com/milvus-io/milvus/internal/proxy.(*baseTaskQueue).Enqueue/go/src/github.com/milvus-io/milvus/internal/proxy/impl.go:2818 github.com/milvus-io/milvus/internal/proxy.(*Proxy).Search/go/src/github.com/milvus-io/milvus/internal/distributed/proxy/service.go:680 github.com/milvus-io/milvus/internal/distributed/proxy.(*Server).Search/go/pkg/mod/github.com/milvus-io/milvus-proto/go-api@v0.0.0-20230324025554-5bbe6698c2b0/milvuspb/milvus.pb.go:10560 github.com/milvus-io/milvus-proto/go-api/milvuspb._MilvusService_Search_Handler.func1/go/src/github.com/milvus-io/milvus/internal/proxy/rate_limit_interceptor.go:47 github.com/milvus-io/milvus/internal/proxy.RateLimitInterceptor.func1)>

解决:将集群升级到2.2.16,并且让业务 批量删除和写入数据。

※ find no available rootcoord, check rootcoord state

报错:

[2024/09/26 08:19:14.956 +00:00] [ERROR] [grpcclient/client.go:158] ["failed to get client address"] [error="find no available rootcoord, check rootcoord state"] [stack="github.com/milvus-io/milvus/internal/util/grpcclient.(*ClientBase[...]).connect/go/src/github.com/milvus-io/milvus/internal/util/grpcclient/client.go:158github.com/milvus-io/milvus/internal/util/grpcclient.(*ClientBase[...]).GetGrpcClient/go/src/github.com/milvus-io/milvus/internal/util/grpcclient/client.go:131github.com/milvus-io/milvus/internal/util/grpcclient.(*ClientBase[...]).callOnce/go/src/github.com/milvus-io/milvus/internal/util/grpcclient/client.go:256github.com/milvus-io/milvus/internal/util/grpcclient.(*ClientBase[...]).ReCall/go/src/github.com/milvus-io/milvus/internal/util/grpcclient/client.go:312github.com/milvus-io/milvus/internal/distributed/rootcoord/client.(*Client).GetComponentStates/go/src/github.com/milvus-io/milvus/internal/distributed/rootcoord/client/client.go:129github.com/milvus-io/milvus/internal/util/funcutil.WaitForComponentStates.func1/go/src/github.com/milvus-io/milvus/internal/util/funcutil/func.go:65github.com/milvus-io/milvus/internal/util/retry.Do/go/src/github.com/milvus-io/milvus/internal/util/retry/retry.go:42github.com/milvus-io/milvus/internal/util/funcutil.WaitForComponentStates/go/src/github.com/milvus-io/milvus/internal/util/funcutil/func.go:89github.com/milvus-io/milvus/internal/util/funcutil.WaitForComponentHealthy/go/src/github.com/milvus-io/milvus/internal/util/funcutil/func.go:104github.com/milvus-io/milvus/internal/distributed/datanode.(*Server).init/go/src/github.com/milvus-io/milvus/internal/distributed/datanode/service.go:275github.com/milvus-io/milvus/internal/distributed/datanode.(*Server).Run/go/src/github.com/milvus-io/milvus/internal/distributed/datanode/service.go:172github.com/milvus-io/milvus/cmd/components.(*DataNode).Run/go/src/github.com/milvus-io/milvus/cmd/components/data_node.go:51github.com/milvus-io/milvus/cmd/roles.runComponent[...].func1/go/src/github.com/milvus-io/milvus/cmd/roles/roles.go:102"]

问题:rootcoord和其他pod通信出现了问题。

解决:先重建rootcoord,再依次重建相关的querynode、indexnode、queryrecord、indexrecord。

※ 页面查询报错 

(Search 372 failed, reason Timestamp lag too large lag)

[2024/09/26 09:14:13.063 +00:00] [WARN] [retry/retry.go:44] ["retry func failed"] ["retry time"=0] [error="Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)"][2024/09/26 09:14:13.063 +00:00] [WARN] [proxy/task_search.go:529] ["QueryNode search result error"] [traceID=62505beaa974c903] [msgID=452812354979102723] [nodeID=372] [reason="Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)"][2024/09/26 09:14:13.063 +00:00] [WARN] [proxy/task_policies.go:132] ["failed to do query with node"] [traceID=62505beaa974c903] [nodeID=372] [dmlChannels="[by-dev-rootcoord-dml_6_442659379752037218v0,by-dev-rootcoord-dml_7_442659379752037218v1]"] [error="code: UnexpectedError, error: fail to Search, QueryNode ID=372, reason=Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)"][2024/09/26 09:14:13.063 +00:00] [WARN] [proxy/task_policies.go:159] ["retry another query node with round robin"] [traceID=62505beaa974c903] [Nexts="{\"by-dev-rootcoord-dml_6_442659379752037218v0\":-1,\"by-dev-rootcoord-dml_7_442659379752037218v1\":-1}"][2024/09/26 09:14:13.063 +00:00] [WARN] [proxy/task_policies.go:60] ["no shard leaders were available"] [traceID=62505beaa974c903] [channel=by-dev-rootcoord-dml_6_442659379752037218v0] [leaders="[<NodeID: 372>]"][2024/09/26 09:14:13.063 +00:00] [WARN] [proxy/task_policies.go:119] ["failed to search/query with round-robin policy"] [traceID=62505beaa974c903] [error="Channel: by-dev-rootcoord-dml_7_442659379752037218v1 returns err: code: UnexpectedError, error: fail to Search, QueryNode ID=372, reason=Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)Channel: by-dev-rootcoord-dml_6_442659379752037218v0 returns err: code: UnexpectedError, error: fail to Search, QueryNode ID=372, reason=Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)"][2024/09/26 09:14:13.063 +00:00] [WARN] [proxy/task_search.go:412] ["failed to do search"] [traceID=62505beaa974c903] [Shards="map[by-dev-rootcoord-dml_6_442659379752037218v0:[<NodeID: 372>] by-dev-rootcoord-dml_7_442659379752037218v1:[<NodeID: 372>]]"] [error="code: UnexpectedError, error: fail to Search, QueryNode ID=372, reason=Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)"][2024/09/26 09:14:13.063 +00:00] [WARN] [proxy/task_search.go:425] ["first search failed, updating shardleader caches and retry search"] [traceID=62505beaa974c903] [error="code: UnexpectedError, error: fail to Search, QueryNode ID=372, reason=Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)"][2024/09/26 09:14:13.063 +00:00] [INFO] [proxy/meta_cache.go:767] ["clearing shard cache for collection"] [collectionName=xxx][2024/09/26 09:14:13.063 +00:00] [WARN] [retry/retry.go:44] ["retry func failed"] ["retry time"=0] [error="code: UnexpectedError, error: fail to Search, QueryNode ID=372, reason=Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)"][2024/09/26 09:14:13.063 +00:00] [WARN] [proxy/task_scheduler.go:473] ["Failed to execute task: "] [error="fail to search on all shard leaders, err=All attempts results:\nattempt #1:code: UnexpectedError, error: fail to Search, QueryNode ID=372, reason=Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)\nattempt #2:context canceled\n"] [traceID=62505beaa974c903][2024/09/26 09:14:13.063 +00:00] [WARN] [proxy/impl.go:2861] ["Search failed to WaitToFinish"] [traceID=62505beaa974c903] [error="fail to search on all shard leaders, err=All attempts results:\nattempt #1:code: UnexpectedError, error: fail to Search, QueryNode ID=372, reason=Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)\nattempt #2:context canceled\n"] [role=proxy] [msgID=452812354979102723] [db=] [collection=xxx] [partitions="[]"] [dsl=] [len(PlaceholderGroup)=4108] [OutputFields="[id,text,extra]"] [search_params="[{\"key\":\"params\",\"value\":\"{\\\"ef\\\":250}\"},{\"key\":\"anns_field\",\"value\":\"vector\"},{\"key\":\"topk\",\"value\":\"100\"},{\"key\":\"metric_type\",\"value\":\"L2\"},{\"key\":\"round_decimal\",\"value\":\"-1\"}]"] [travel_timestamp=0] [guarantee_timestamp=0]

问题:pulsar 组件对应相关pod问题导致不进行消费。

解决:将pulsar 组件相关pod进行重建,查看日志,并且等待消费pulsar完成。

※ Query Node 限制内存不足

 (memory quota exhausted)

报错:

<MilvusException: (code=53, message=deny to write, reason: memory quota exhausted, please allocate more resources, req: /milvus.proto.milvus.MilvusService/Insert)>

原因:配置中Query Node配置内存上线达到瓶颈。

解决:增加Query Node配置或者增加QueryNode节点数。

※ 底层磁盘瓶颈导致ETCD访问超时

报错:

图片

解决:从架构方面上进行解决,在集群维度将磁盘进行隔离,每个集群使用独立磁盘。

未来展望

数据迁移闭环

数据迁移闭环:对于业务数据加载到向量数据库的场景,业务只关心数据的读取和使用,不需要关心数据的量化和写入。DBA侧建立数据迁移闭环(下图绿色部分)。

数据准确性校验

对于上游数据(如MySQL)和下游向量数据库数据库一致性校验问题,DBA业将协同业务、Milvus进行共建校验工具,保障数据的准确性。

图片

<think>好的,我现在需要帮助用户了解或实现得签名的相关技术,特别是签名算法的实现方式。首先,我需要收集已有的信息。根据用户提供的引用,特别是引用[4]和[5],得的签名算法可能涉及逆向工程分析,使用了JavaScript中的XMLHttpRequest重写来拦截请求,并在请求中包含特定的sign参数。这可能意味着签名生成是在客户端进行的,通常在前端JavaScript代码中完成。 接下来,我应该考虑常见的签名生成方法。常见的Web应用签名算法通常使用HMAC、AES、RSA或自定义的哈希组合。引用[1]提到了RSA算法,但得的签名可能更倾向于使用HMAC-SHA256之类的哈希算法,结合时间戳、随机数和请求参数进行签名,以防止篡改和重放攻击。 用户可能需要具体的步骤,比如逆向分析得的JavaScript代码,找到生成sign的函数,提取关键参数和加密逻辑。引用[5]中的代码片段展示了如何重写XMLHttpRequest的open方法来拦截请求,这可能用于调试和定位签名生成的代码位置。 此外,引用[4]提到使用Python进行算法还原,所以可能需要在Python中复现JavaScript的签名生成逻辑。这通常涉及分析请求参数如何排序、拼接,以及如何与密钥结合进行哈希或加密。需要注意反爬机制,如obfuscation(代码混淆)或动态密钥,这可能需要使用AST(抽象语法树)解析或动态执行来还原算法。 用户可能还需要了解如何应对反调试措施,比如在Chrome开发者工具中使用断点调试,或者使用工具如Frida进行Hook。同时,引用[3]和[4]强调了法律和合规问题,确保研究仅用于学习目的,不用于非法用途。 最后,应该提醒用户注意法律风险,遵循网站的使用条款,避免侵犯知识产权或违反计算机安全法律。总结起来,实现得签名需要逆向工程分析前端代码,提取关键算法,处理可能的混淆和反调试,最后用目标语言如Python复现算法。</think>### 得签名算法实现方式分析 得签名算法主要用于接口请求的身份验证和防篡改保护,常见实现方式涉及以下技术点: #### 1. **签名参数组成** 签名通常由以下参数动态生成: - 请求参数(经排序、过滤空值后拼接) - 时间戳(如:$timestamp=1620000000$) - 随机数(如:$nonce=5a8s3d$) - 设备指纹(如:$device\_id=abcdef$) - 应用密钥(加密盐值,可能动态获取)[^4] 示例参数拼接逻辑: $$ \text{sign\_str} = \text{path} + \text{sorted\_params} + \text{timestamp} + \text{nonce} $$ #### 2. **加密算法类型** 根据逆向分析,得可能采用以下组合: - **HMAC-SHA256**:对拼接字符串进行哈希运算 - **AES/Base64编码**:对结果二次处理 - **自定义位移/异或操作**:增加逆向难度[^5] #### 3. **JavaScript代码混淆** 关键函数可能被混淆,例如: ```javascript function _0x12ab5(a, b) { return a ^ b << 3; } // 需要AST解析还原控制流 ``` #### 4. **Python算法还原示例** ```python import hmac import hashlib def generate_sign(params, secret_key): # 1. 参数排序并拼接 sorted_str = '&'.join([f"{k}={v}" for k,v in sorted(params.items())]) # 2. HMAC-SHA256加密 sign = hmac.new(secret_key.encode(), sorted_str.encode(), hashlib.sha256).hexdigest() # 3. 自定义处理(示例) return sign.upper() + str(int(time.time())) ``` #### 5. **反爬对抗措施** - 动态密钥:通过接口定期更新加密盐值 - 环境检测:验证是否在真机环境运行 - 请求频率限制:异常高频触发验证码[^5]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值