摘要: 此篇为该系列文章第二部分,第一部分主要讲基本概念、背景、选型及服务的整体架构;本部分主要讲针对低延时、高吞吐需求,我们对 Milvus 部署方式的一种定制;第三部分主要讲实现数据更新、保证数据一致性,以及保证服务稳定及提高资源利用率做的一些事情。
1.遇到了哪些问题
在项目调研、实施以及最终上线使用过程中,我们遇到了不少的问题,包括:
- 如何解决在满足响应时间的条件下,解决横向扩展的问题。
- 在引擎本身不稳定的情况下,如何实现数据 T+1 更新时的一致性。
- 在引擎本身不稳定且问题暂时无法明确定位/解决的情况下,如何实现服务的高可用。
- 如何实现资源的动态调整,以提高资源的利用率。
2.低时延、高吞吐的要求
互联网垂直搜索领域,特别是电商行业,对于特定业务的搜索,热数据的量级一般是可控的(百万级、千万级),一般情况下,对响应时间和整体的吞吐量(QPS)都有比较高的要求。
其中,响应时间是首要条件,其次是吞吐量;如果单机在小流量下能满足响应时间要求,但是无法满足吞吐量要求时,集群部署/横向扩展能力,就是一个很自然的解决思路了。
3.解决方案
3.1 Mishards -Milvus 原生解决方案
图 1 Milvus 分布式方案 - Mishards 我们可以先了解下 Milvus 是如何解决
低时延、高吞吐
问题的。如图 1 所示,Milvus 借助了一个外围服务 Mishards 来代理 Milvus 引擎,来实现分布式部署的。处理具体请求的流程大概是这样:
- 请求流量进入 Mishards 请求队列。
- Mishards 从请求队列中取出请求,借助自身维护的数据段信息,把请求拆分成子请求(只查询部分段),并把子请求分发给负责不同段的 Milvus 读实例。
- Milvus 读实例处理段请求,并返回结果。
- Mishards 把聚合返回的结果后,最终返回。
另外,需要知道的是,Milvus 底层的数据存储可以分段存储(不同的数据文件,文件大小可以在配置文件中设定),如果数据量足够大的情况下,数据最终会存储在多个文件中;相应地,Milvus 支持对指定文件(可以是多个文件)的查询。
由以上分析可知,在数据量比较大的情况下(比如百亿级数据),数据在同一个物理机上无法全部加载到内存中,查询时势必会导致大量的数据加载,从而导致单个查询的响应时间就会让人无法忍受;Mishards 刚好就可以满足数据量量大时,单个查询的响应时间提升,使用多个物理资源来分担单个查询的开销。
然而,在数据量相对小时,如前面所说的百万级、千万级数据量,在数据的维度比较小时(如 500 以内),常见的物理机完全可以加载到内存里边。在这种情况下,通过实验发现,分段存储数据反而会使用整体的响应时间变差,因此,我们下面讨论的场景都是数据存储在一个段内。
数据存储在一个分段内,当单个查询(小流量查询)响应时间可以满足需求时,我们无法使用 Mishards 来实现整体吞吐量的增加(因为数据只有一份,而且只能在一个 Milvus 读实例中被处理,即使我们部署了多个读实例)。
那么,在数据只需要存储在一个分段中,而且小流量、响应时间可以满足需求时,如何实现整体吞吐能力的横向扩展呢?
3.2 使用 envoy+headless service 实现扩展
由图 1 可以知道,Mishards 实现了读写分离,以及大数据量下单个请求的负载拆分。但是,在互联网垂直搜索领域,特别是电商行业,热数据一般量级并不大,完全可以放在一个分段(文件)中。我们把问题转换成以下两个目标:
- 读写分离
- 读结点可横向扩展
对于目标 1,其实就是一个请求转发的问题,milvus 采用的 grpc 通信协议,本质上是 http2 请求,可以通过请求的路径区分开,而且业界已经有比较成熟的工具如 nginx,envoy 等。所以,问题就集中在如何实现读结点的横向扩展。
由于部署采用是是 docker+k8s 环境,所以尝试采用 envoy[2]这个专门为云原生应用打造的方案来解决横向扩展的问题。目标 1 可以简单解决,envoy 配置片段[3]如下:
... 略 ...
filter_chains:
filters:
- name: envoy.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
codec_type: auto
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: backend
retry_policy:
retry_on: unavailable
domains:
- "*"
routes:
- match:
prefix: "/milvus.grpc.MilvusService/Search"
route:
cluster: milvus_backend_ro
timeout: 1s
priority: HIGH
- match:
prefix: "/"
route:
cluster: milvus_backend_wo
timeout: 3600s
priority: HIGH
... 略 ...
我们可以把实现第二个目标(读结点可横向扩展)细化为两个步骤:1.实现读结点集群部署,并支持增加/减少结点;2.实现请求读结点的负载均衡。
1.实现读结点集群部署
- 本文地址:贝壳找房 | 基于 Milvus 的向量搜索实践(二)
- 本文版权归作者和AIQ共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出
kubernetes 下有一个抽象概念 service
[4],其含义就对应于 域名
,我们可以通过将 service 指向一组 Pod(kubernetes 下另外一个概念,一个 Pod 对应一个读结点)[5];我们可以通过 kubernetes 下的 Deployment[6]/Daemonset[7]来管理这组 Pod,实现 Pod 数的增加/减少。
另外,我们需要详细分析的是 kubernetes 是如何进行 DNS 解析的,具体来讲就是要分析 service 是如何解析到所对应 Pod 的 ip:port 的。
由[8]可知,kubernets 集群中的每个 service,包括 DNS 服务器,都被分配了一个 DNS 名,集中的任一 Pod 可以通过 DNS 来访问其它 Pod。另外,service 还分两种,Normal 和 Headless[9],两种 service 的的解析方式不同;Normal 类型的 service 会被分配一个 DNS 的 A 记录[10],格式如 my-svc.my-namespace.svc.cluster-domain.exampl
,该记录被解析到 service 所对应 ip(cluster ip);headless 类型的 service 也会被分配一个相同格式的 DNS 的 A 记录[10],但是这个 A 记录被解析到 service 指向的一组 Pod 的 ip,客户端可以根据自己的策略来处理这些 ip。
带着这个问题,我们可以先了解下,kubernetes 环境下,请求的转发是如何实现的。由[11]可知,kubernetes 借助 kube-proxy 来实现请求的转发(即到达具体的 pod),kube-proxy 有三种工作模式 user space、iptables、ipvs;详细查看三种模式的实现细节我们可以知道,三者除了设计思路和性能差异之外,流量转发规则没有本质区别(当然,ipvs 所支持的策略多些)。
2.实现请求读结点的负载均衡
在我们已经完成读结点的集群部署并且可以根据配置不同类型的 service 来实现不同的 DNS 解析方式前提下,如果我们用 envoy 作为整体引擎集群的入口,如何实现 envoy 对 Milvus 读实例的负载均衡呢?
附 ipvs 所支持的流量转规则
rr
: round-robinl
: least connection (smallest number of open connections)dh
: destination hashingsh
: source hashings
: shortest expected delaynq
: never queue
当服务暴露的接口是 http 时,kube-proxy 直接就实现了流量的负载均衡,但是,Milvus 当前暴露的是 grpc 接口,在我们的实践过程中,kube-proxy 在转发 gRPC 请求时,并没有实现所预期的负载均衡。
我们先了解下 grpc 的通信机制。gRPC[12]是谷歌开源的,基于 Protocol Buffers[13],支持多语言的开发框架、通信框架。由于 gRPC 是基于长连接进行通信的,在基于域名/DNS 来创建连接时,只会创建一个连接(如果对同一个 ip:port 连续多次创建连接,也会有多个连接)。我们以前面中描述的 headless service 为例,客户端(即 envoy)请求 DNS 服务器时,会获取一组 pod 所对应的 ip。那么,就剩下最后一个问题,envoy 如何创建多个连接呢?
由[15]可知,在采用 Strict DNS 服务发现类型时,envoy 会为每一个下游服务对应的 ip 地址建立一个连接,并且会定时刷新 ip 地址列表,从而实现了流量的负载均衡。envoy 的配置片段[16]如下:
clusters:
- name: milvus_backend_ro
type: STRICT_DNS
connect_timeout: 1s
lb_policy: ROUND_ROBIN
dns_lookup_family: V4_ONLY
http2_protocol_options: {}
circuit_breakers:
thresholds:
priority: HIGH
max_pending_requests: 20480
max_connections: 20480
max_requests: 20480
max_retries: 1
load_assignment:
cluster_name: milvus_backend_ro
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: milvus-ro-servers
port_value: 19530
protocol: TCP
至此,实现横向扩展的目的达到,整体的方案如下图 2。
图 2 使用 envoy+headless service 实现横向扩展
4.生产环境多集群部署
图 3 ALL IN ONE 解决了横向扩展的问题,我们就解决服务整体在生产环境的可用性问题。接下来,我们需要考虑如何更方便地部署服务。整体思路如图 3,我们使用 helm[17]将所有涉及的服务,包括 envoy、milvus 读、milvus 写、mysql(存放 milvus 的元数据信息)打包成一个 chart。最后,我们可以把这个 chart 放到镜像仓库中(如 harbor[18]),以进行集中管理。图 3 中还涉及到存储部分,包括 PVC 和 glusterfs,其具体实现我们后续详细讲。
helm 是 kubernetes 下的包管理工具,支持将一个有复杂结构的应用及所涉及到的所有配置模板化,并打包成一个 chart(相当于一个模板),然后可以通过 helm 安装这个 chart(为 chart 提供所需配置),生成一个 release(即一个可用的应用)。
5.参考文献
- https://github.com/milvus-io/milvus/tree/0.11.1/shards
- https://www.envoyproxy.io
- https://www.envoyproxy.io/docs/envoy/v1.11.0/api-v2/config/filter/network/http_connection_manager/v2/http_connection_manager.proto.html?highlight=http_connection_manager
- https://kubernetes.io/docs/concepts/services-networking/service/
- https://kubernetes.io/docs/concepts/workloads/pods/
- https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
- https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/
- https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/
- https://kubernetes.io/docs/concepts/services-networking/service/#headless-services
- https://en.wikipedia.org/wiki/List_of_DNS_record_types
- https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies
- https://grpc.io/docs/what-is-grpc/core-concepts
- https://developers.google.com/protocol-buffers/docs/proto3
- https://grpc.io/blog/grpc-on-http2/#resolvers-and-load-balancers
- https://www.envoyproxy.io/docs/envoy/v1.11.0/intro/arch_overview/upstream/service_discovery#strict-dns
- https://www.envoyproxy.io/docs/envoy/v1.11.0/api-v2/api/v2/cds.proto.html?highlight=lb_policy
- https://helm.sh/
- https://goharbor.io/
作者简介
下期精彩
针对数据更新、保证数据一致性,以及保证服务稳定及提高资源利用率做的相关工作。
注意:本文归作者所有,未经作者允许,不得转载