一文详解粗排服务 向量计算引擎单机高可用模式设计

本文主要是介绍一文详解粗排服务 向量计算引擎单机高可用模式设计,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 1. 前言
    • 2.初步设计
      • 1.需求
      • 3.单机的实现
      • 4. 高可用的实现
    • 3.详细设计
      • 1. 数据库设计
      • 2.manager设计
        • 1.manager接口
          • 1. 注册接口
          • 2.上传向量接口
          • 3.向量生效接口
          • 4.server通知开始加载向量接口
          • 5.server通知完成加载接口
          • 6.server通知完成切换接口
          • 7.向量详情接口
      • 3. server端设计
        • 1. 项目启动的加载
        • 2. 定时任务

1. 前言

  这篇向量计算引擎就是粗排服务,选用了双塔的粗排服务之后,粗排的实现就是快速的向量计算了。

  这次项目的选型过程历经时间比较长,前期调研了ES的实现方案,vearch的实现方案,以及milvus的实现方案。vearch的bug比较多,可能有些还是比较常见的问题,milvus目前尚且不支持标量的过滤,而且使用的k8s来管理的,可能还要有一些成本。ES的实现方案是计算的时间比较长,500个数据,可能都要消耗50ms,在性能上无法满足使用的要求。之所以花了这么多时间调研解决方案,主要还是想要更好的解决高可用的方案,因为这个涉及到了数据的存储,所以对服务的稳定性要求也比较高,本来想着是把这一块儿托管到第三方来做的,毕竟自己想要实现难度还是比较大的,因为开始是想做成ES那种数据可以分片存储,又可以通过副本来备份的方式来支持分布式存储+分布式计算。

  坑爹的是,我最开始的技术选型定的是ES,使用ES效果不佳的情况下强行花了很多时间优化,最开始实际上我做过一些测试,500个基本上是在30ms左右,所以才决定选用ES,但是我忽略了es计算的稳定性,于是在开发很多代码后只能硬上了(换别的方案已经来不及了)。最开始使用2000个物品id去访问ES,发现响应不行,于是改成了500,这样的话自己的任务中就需要做并行处理,代码的复杂度也会上升,同时,我还破解了ES的默认的routing规则,每次请求中的500个id都在同一个shard上面,但是即使这样,还是不行,上线即使只开了10%的流量,服务的稳定性波动都比较大,根本就是无法支持线上服务的状态。就在调用方同学即将放弃的时候,我又想到了一个优化点,就是把es的索引设置为内存模式,这个设置最终算是救了我一命吧,至少支持住了20%的实验流量。让我阶段性的完成了任务。

 "store" : {"type" : "mmapfs"}

本次使用es集群做粗排方案时使用的是一个线上的集群,部署的有其他业务,尚且不确定假如使用的是是单独的ES集群效果会有多大的提升。我总体的感觉是ES是一个通用解决方案,可能他的向量计算解决方案尚且不够优秀,没有做太多加速,我计算的时候,有些只需要20ms,有些需要100ms,感觉表现很不稳定。cpu也很容易就打上来了。

  最终迫不得已自己来做一个向量计算引擎,简单的使用map放到内存中进行计算的话,5000个向量的点积计算也就是毫秒级别,所以非常的快,相对ES提升了太多。因为性能吊打ES,所以就决定自己来开发向量的计算引擎了。因为前面使用ES做解决方案花费了很多时间(两周),所以后面留给我的时间已经非常少了,所以勉强在一周多一点的时间开发了一个高可用的向量计算引擎,真是自己坑自己啊。

2.初步设计

1.需求

  要从600w向量中,找出5000个,计算和目标向量的点积,然后按照点积大小排序,选择前topN个进行排序。向量的维度32-128,均为float32。前期的工作可以暂时不考虑增量的数据情况,只需要做好全量的向量更新情况即可。向量文件存储在hdfs上。

在粗排系统更新完向量之后,对应的后续的预测系统也需要更新预测是模型才行,因为他们是放在一起进行计算的。所以业务方的模型和粗排系统的向量需要协同更新才行。

3.单机的实现

  分为server端和manager端,server端负责向量的计算服务,manager端负责管理向量的业务的元数据。
  考虑加入只有一个单机如何实现,可以用一个数据表来保存各个业务的向量,每个业务要有一个业务编号src。

  1. 程序在启动的时候扫描这个表,并行的去加载数据向量。向量加载完成后放到内存中的map,然后对外提供服务。
  2. 程序在运行中,可以监控这个表,不断的看看是否有新的向量想要加入,或者是否有更新任务,然后加载向量
    1. 这里的向量加载完毕后理论上不能立刻投入使用,还是要看业务方的信号,给业务方一个接口,让业务方选择合适的时间切换向量
  3. 因为需要操作数据库,这样看来程序需要一个操作数据库的manager端,同时为了简化向量计算服务的逻辑,server短所有的指令都通过检查数据库获取,而不是从外部调用获取,简化了server端的处理逻辑。
  4. 同样的,因为是存储服务,所以对稳定性要求比较重要,服务中的报警通知,异常管理要比较清晰,严重的异常能够通知到位,同时要能够查看各个服务节点的业务向量信息,像ES的cluster state一样。

4. 高可用的实现

  同样的,因为是线上服务,肯定不能只有一个节点,因为单个服务器出现一些故障还是有可能的,如果节点提升到两个,服务的稳定性就大大提升了。如果是两个服务的话,就会引入更多的问题,比如向量更新的时候需要保证两个节点都更新成功了才算成功,可以对外提供服务了,所以需要一些同步机制。
  manager如何感知server端对向量更新的状态呢,我这里使用的是manager端提供http api,server端在完成对应的进程后调用manager端,由manager端来更新数据库的状态。同时为了避免数据库竞争,manager端使用了单点模式,同时内部使用互斥锁来同步对数据库的访问。实际上这里的方案不是最好的,比如server端和manager端是可以通过kafka来做到真正的解耦的,同时,manager端的互斥也可以使用分布式的锁,这样的话manager也可以是高可用的了,我这里为了赶进度,就用了最简单的方式来处理了。

3.详细设计

1. 数据库设计

数据库是manager和server端的交互协议层,比较重要的业务逻辑都可以通过数据库来表达。

CREATE TABLE `quick_rank_meta` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',`src` varchar(64) NOT NULL DEFAULT '""' COMMENT '业务名称,唯一',`description` varchar(255) NOT NULL DEFAULT '""' COMMENT '描述',`person` varchar(100) NOT NULL DEFAULT '-' COMMENT '该向量负责人',`dimensions` int(11) NOT NULL DEFAULT '32' COMMENT 'vector 维度',`doc_count` int(255) NOT NULL DEFAULT '100000' COMMENT 'vector doc 数量',`local_path` varchar(255) NOT NULL DEFAULT '""' COMMENT 'local path',`hdfs_path` varchar(255) NOT NULL DEFAULT '""' COMMENT 'hdfs path',`build_start` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'index build time',`build_end` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'index recv time',`wait_for_load` int(11) NOT NULL DEFAULT '0' COMMENT '是否等待vector加载完毕,当前http接口是否会立即返回 0,不等待   1,等待',`load_success_num` int(11) NOT NULL DEFAULT '0' COMMENT '已经完成加载的服务器节点数量',`switch_success_num` int(11) NOT NULL DEFAULT '0' COMMENT '已经完成向量切换的服务器节点数量',`status` int(11) DEFAULT NULL COMMENT '-1 下线状态,0 初始化,1 待更新,2 更新中,3 更新完成,4切换向量中,  5正在提供服务',`crtime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',`uptime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'uptime',`work_after_load` int(11) NOT NULL DEFAULT '0' COMMENT '是否在向量加载完毕之后立刻生效,不再等待调用生效接口',`over_host` varchar(255) NOT NULL DEFAULT '-' COMMENT '处在当前status状态下的节点的hostname',PRIMARY KEY (`id`),UNIQUE KEY `uniq_src` (`src`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COMMENT='store quick rank meta info';

over_host 字段的内容详情

{"status_on_load_hosts":["quick-rank-online1","quick-rank-online2"],"status_over_load_hosts":["quick-rank-online2","quick-rank-online1"],"status_over_switch_hosts":["quick-rank-online1","quick-rank-online2"]

2.manager设计

1.manager接口
1. 注册接口
参数类型含义样例
srcstring业务标识,需要保证唯一性xxxx
descriptionstringsrc简介推荐实验
personstring负责人chenc@aaa.com
dimensionsint向量维度64
2.上传向量接口
参数类型含义样例备注
srcstring注册接口使用的srcxxxx
hdfsstring对应的hdfs 路径/user/u_vector/show/20211007每次更新想地址不能重复,否则可能不加载
wait_for_loadboolean是否等待向量加载完毕true|false
work_after_loadboolean是否在向量加载完毕后立刻投入使用true|false

wait_for_load

  • true: 为true的时候,该接口会持续阻塞,直到粗排系统将向量全部加载(但是不会立刻投入使用),这个过程耗时相对较长,一般500w左右的向量耗时大概在20分钟左右
  • false:为false的时候,该接口会立刻返回,这个时候 work_after_load 自动设置为true,也就是在向量加载完成后会立刻投入使用

work_after_load

  • true:为true的时候,向量在加载完成后会自动切换,使用新的向量
  • false: 为false的时候,向量在加载完成后不会自动切换,需要使用方再调用一次下面的向量生效接口

这两个参数主要是在更新向量的时候使用,如果是第一次上线,则直接设置wait_for_load=false即可,向量更新完毕后在相关群里会有通知,业务方再择机上线对应的粗排接口调用即可

这个接口需要生成local_path, server端会对应的下载到这个地址,防止重复下载

3.向量生效接口
参数类型含义样例
srcstring注册接口使用的srcprofile_feed

该接口修改status=4,等待server端切换向量

备注

  向量生效接口并不一定必须要调用,取决于向量上传接口中的 wait_for_loadwork_after_load的设置

wait_for_loadwork_after_load是否需要再调用向量生效接口备注
truetrueNO
truefalseYES
falsetrue|falseNO因为调用方此时无法获取何时向量加载完毕的信息,所以work_after_load强制设置为true

使用建议

  1. 在上传新的向量的时候,如果感觉影响比较小或者不会产生向量的一致性问题(模型未更新),可以直接设置wait_for_load=false
  2. 如果使用方模型更新比较快的话,可以设置wait_for_load=true&work_after_load=true 这样的话,不一致的时间窗口只是使用方的模型更新时间
  3. 如果使用方更新模型影响比较大,耗时比较长,可以设置wait_for_load=true&work_after_load=false,
    1. 使用方在调用上传接口返回后再更新自己的模型
    2. 模型更新完成后再调用粗排服务的向量生效接口
    3. 这样不一致的时间控制的会相对比较小,一般应该在分钟级别
4.server通知开始加载向量接口

这个接口是预留给server端开始加载向量的时候调用的,驱动manager通过数据库发出新的一轮指令,在接口收到请求的时候对应的记录的status=2, over_host 中添加server信息

参数类型含义样例备注
srcstring注册接口使用的srcxxxx
hdfsstring对应的hdfs 路径/user/u_vector/show/20211007每次更新想地址不能重复,否则可能不加载
hoststring节点的hostname
5.server通知完成加载接口

这个接口是预留给server端加载向量完成的时候调用的,驱动manager通过数据库发出新的一轮指令,manager需要知道server的数量(可以做到配置文件中server_num),

  1. 收到请求后load_success_num++,
    1. 如果load_success_num==server_num, 那么说明加载都完成了,
      1. 数据库中的work_after_load=true的话则修改status=4, 驱动server端切换向量来使用
      2. 如果work_after_load=false的话,则修改status=3
  2. 同时更新over_host中记录的信息
参数类型含义样例备注
srcstring注册接口使用的srcxxxx
hdfsstring对应的hdfs 路径/user/u_vector/show/20211007
hoststring节点的hostname
doc_numint向量的数量
6.server通知完成切换接口

这个接口是预留给server端向量生效使用的时候调用的,驱动manager通过数据库发出新的一轮指令

  1. 收到请求后switch_success_num++
    1. 如果switch_success_num==server_num则说明都切换完成了,status=5
参数类型含义样例备注
srcstring注册接口使用的srcxxxx
hdfsstring对应的hdfs 路径/user/u_vector/show/20211007每次更新想地址不能重复,否则可能不加载
hoststring节点的hostname
7.向量详情接口
参数类型含义样例
srcstring注册接口使用的srcxxx

3. server端设计

1. 项目启动的加载

slelect所有status>0的记录,直接并行加载即可,中间不需要调用manager系统。

2. 定时任务
  1. 定时任务,每分钟执行一次,slelect所有status>0的记录
  2. 判断状态,执行行为
    1. 如果status=1||status=2,说明该向量文件需要被加载,
      1. 先判断内存中是否有正在加载该向量的任务,有的话忽略
        1. 需要增加一个互斥锁判断
        2. 互斥锁条件满足后(不存在互斥锁)再判断一下数据库中的数据over_host.status_on_load_hosts是否有自己,有的话也忽略
      2. 没有话执行加载(加载中,或者加载已经完成了)
        1. 加载之前通知manager
        2. 加载完成后调用manager告知加载完毕
    2. 如果status=3|5,不做任何处理
    3. 如果status=4,则说明需要切换向量投入使用
      1. 判断是否正在切换该向量,正在切换,则忽略(切换中或者切换已经完成了)
        1. 需要增加一个互斥锁判断
        2. 互斥锁条件满足后再判断一下数据库中的数据over_host.status_over_switch_hosts是否有自己,有的话也忽略
      2. 没有切换的话
        1. 切换向量
        2. 通知manager

这篇关于一文详解粗排服务 向量计算引擎单机高可用模式设计的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1124294

相关文章

CSS place-items: center解析与用法详解

《CSSplace-items:center解析与用法详解》place-items:center;是一个强大的CSS简写属性,用于同时控制网格(Grid)和弹性盒(Flexbox)... place-items: center; 是一个强大的 css 简写属性,用于同时控制 网格(Grid) 和 弹性盒(F

spring中的ImportSelector接口示例详解

《spring中的ImportSelector接口示例详解》Spring的ImportSelector接口用于动态选择配置类,实现条件化和模块化配置,关键方法selectImports根据注解信息返回... 目录一、核心作用二、关键方法三、扩展功能四、使用示例五、工作原理六、应用场景七、自定义实现Impor

一文深入详解Python的secrets模块

《一文深入详解Python的secrets模块》在构建涉及用户身份认证、权限管理、加密通信等系统时,开发者最不能忽视的一个问题就是“安全性”,Python在3.6版本中引入了专门面向安全用途的secr... 目录引言一、背景与动机:为什么需要 secrets 模块?二、secrets 模块的核心功能1. 基

一文详解MySQL如何设置自动备份任务

《一文详解MySQL如何设置自动备份任务》设置自动备份任务可以确保你的数据库定期备份,防止数据丢失,下面我们就来详细介绍一下如何使用Bash脚本和Cron任务在Linux系统上设置MySQL数据库的自... 目录1. 编写备份脚本1.1 创建并编辑备份脚本1.2 给予脚本执行权限2. 设置 Cron 任务2

一文详解如何在idea中快速搭建一个Spring Boot项目

《一文详解如何在idea中快速搭建一个SpringBoot项目》IntelliJIDEA作为Java开发者的‌首选IDE‌,深度集成SpringBoot支持,可一键生成项目骨架、智能配置依赖,这篇文... 目录前言1、创建项目名称2、勾选需要的依赖3、在setting中检查maven4、编写数据源5、开启热

Python常用命令提示符使用方法详解

《Python常用命令提示符使用方法详解》在学习python的过程中,我们需要用到命令提示符(CMD)进行环境的配置,:本文主要介绍Python常用命令提示符使用方法的相关资料,文中通过代码介绍的... 目录一、python环境基础命令【Windows】1、检查Python是否安装2、 查看Python的安

HTML5 搜索框Search Box详解

《HTML5搜索框SearchBox详解》HTML5的搜索框是一个强大的工具,能够有效提升用户体验,通过结合自动补全功能和适当的样式,可以创建出既美观又实用的搜索界面,这篇文章给大家介绍HTML5... html5 搜索框(Search Box)详解搜索框是一个用于输入查询内容的控件,通常用于网站或应用程

Python并行处理实战之如何使用ProcessPoolExecutor加速计算

《Python并行处理实战之如何使用ProcessPoolExecutor加速计算》Python提供了多种并行处理的方式,其中concurrent.futures模块的ProcessPoolExecu... 目录简介完整代码示例代码解释1. 导入必要的模块2. 定义处理函数3. 主函数4. 生成数字列表5.

Redis Cluster模式配置

《RedisCluster模式配置》:本文主要介绍RedisCluster模式配置,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录分片 一、分片的本质与核心价值二、分片实现方案对比 ‌三、分片算法详解1. ‌范围分片(顺序分片)‌2. ‌哈希分片3. ‌虚

Python中使用uv创建环境及原理举例详解

《Python中使用uv创建环境及原理举例详解》uv是Astral团队开发的高性能Python工具,整合包管理、虚拟环境、Python版本控制等功能,:本文主要介绍Python中使用uv创建环境及... 目录一、uv工具简介核心特点:二、安装uv1. 通过pip安装2. 通过脚本安装验证安装:配置镜像源(可