milvus 简介
milvus是干什么的?通俗的讲,milvus可以让你在海量向量库中快速检索到和目标向量最相似的若干个向量,这里相似度量标准可以是内积或者欧式距离等。借用官方的话说就是:
Milvus 是一款开源的、针对海量特征向量的相似性搜索引擎。基于异构众核计算框架设计,成本更低,性能更好。 在有限的计算资源下,十亿向量搜索仅毫秒响应。
说白了就是速度快,先不说十亿向量,自己写代码去完成对100万300维向量的余弦相似度计算并排序就需要不小的响应时间吧,就本人测试而言,即便使用scipy库计算速度依然要比milvus慢很多。
milvus和Faiss的对比
其实在milvus开源之前,也存在高性能向量相似性搜索引擎(库),这个引擎就是Facebook的Faiss,它的功能和milvus是一样的,所以就不再做过多介绍,具体可以参考官网
就我个人而言,我是推荐使用milvus的,主要是在我个人看来,milvus有如下几个好处:
- 多平台通用,mac,windows和linux都是支持的,因为milvus可以通过docker部署,因此平台通用性好了不少。
- 支持编程语言多,Java,c,c++和python都支持,要知道Faiss是不支持java的,这一点简直让人抓狂,github上好几个项目就是关于把Faiss转成java的,因为我Java和python都是要使用的,我把github上关于faiss转java的项目都试了个遍,结论就是非常难安装,只要Faiss版本更新了,必须要重来一遍,即便最后java可以用了,也不敢保证其稳定性。所以想在Java上用Faiss还是放弃吧。
- 在速度方面,就我自己测试而言,milvus不输Faiss,但是我没有使用GPU测试,有兴趣的小伙伴可以试一下。
当然milvus也有难用的地方,我自己发现了两点,如果是我自己使用不当造成的,还请各位朋友在评论指出:
- milvus的向量格式不支持numpy,要用列表的形式存储向量,而列表占用内存要远远大于ndarray的,这对于小内存主机简直是个天灾,我16GB内存情况下,把500000*768的矩阵转成list,电脑直接卡死。
- milvus在第一次search时速度会慢,之后机会快起来了,这应该是个小bug,有人在github提出了,应该很快就可以修复,所以你们在测试milvus的速度时千万别算第一次的时间。
milvus 安装及常见问题
milvus 一共有两种安装方式:自己编译安装和使用docker安装。这里推荐大家使用docker安装,docker安装方便快捷,可在Windows上使用。自己编译安装,由于每个人环境不同,很容易出问题。本文只介绍基于docker的安装,另外因为我比较穷,所以只介绍cpu版本的安装,不过gpu安装也是大同小异。
基于docker安装milvus CPU版本
简言之安装比较简单,毕竟大佬们已经把milvus部署在了docker里,我们只要启动起来就行了
。
安装docker
首先就是要安装docker,还不了解docker可以了解一波,非常好用的虚拟机工具,直接去官网下载对应平台的安装文件即可。
下载相应版本镜像
安装好docker后,要去pull对应的镜像(image),首先进到dockerhub官网中,然后搜索milvus,第一个结果就是。因为我们安装的是CPU版本,所以在tags里找cpu-latest,然后pull下来就可以了,即在你的命令行窗口输入
docker pull milvusdb/milvus:cpu-latest
。注意:随着版本迭代更新,这一条命令在未来可能会失效,建议先去dockerhub搜索一下,去看一下应该用什么tag。
设置工作目录和配置文件
在创建启动容器之前,我们要先设置好工作目录和配置文件。
一共要设置三个目录,分别是数据库存储目录,日志存储目录和配置文件目录。其中配置文件目录就存放着我说的配置文件。配置文件一共有两个,分别是服务器设置文件和日志设置文件。
所以我们要想好这三个文件夹放在哪里,比如我们可以在当前用户目录下建立一个milvus文件夹,然后在这里面存储上述三个目录。下面我们需要设置两个配置文件,记得要把服务器配置文件名改为server_config.yaml,把日志配置文件改为log_config.conf。
两个配置文件的内容:服务器配置文件 日志配置文件。配置文件也可以到官网下载。
下面是我的文件目录结构,共大家参考:
milvus
│
├─conf //配置文件目录
│ log_config.conf //服务器配置文件
│ server_config.yaml //日志配置文件
│
├─db //数据库存储目录
│
└─logs //日志存储目录
│
启动docker服务
设置好工作目录后,就可以使用镜像创建容器了,我的工作目录是C:\Users\Zhdun\milvus
,所以我的创建命令是:
docker run -td --name mymilvus -e "TZ=Asia/Shanghai" -p 19530:19530 -p 8080:8080 -v C:\Users\Zhdun\milvus\db:/var/lib/milvus/db -v C:\Users\Zhdun\milvus\conf:/var/lib/milvus/conf -v C:\Users\Zhdun\milvus\logs:/var/lib/milvus/logs milvusdb/milvus:cpu-latest
命令看起来有点长, 我稍微解释下,-td是后台运行,--name是给自己的容器起个名字,-p是端口映射,不想用默认的话,可以去服务器配置文件里改,-v就是为了映射三个工作目录。具体可以参考docker的run命令。
执行完命令后,运行docker ps -a,如果发现自己创建的容器excited的了,那就docker logs一下,看出了什么问题。如果发现容器在运行了,就代表基本没问题了。
接下来我会说一下常见的安装问题,以及如何去使用milvus。
安装时的常见问题及解决
Config check fail: Invalid config version: . Expected config version: 0.1 遇到这种问题就在服务器的配置文件第一行加上version: 0.1
。
Config check fail: Invalid cpu cache capacity: 1. Possible reason: sum of cache_config.cpu_cache_capacity and db_config.insert_buffer_size exceeds system memory.
这种问题就说明内存超出了限制,首先检查服务器配置里的 cpu_cache_capacity 和 insert_buffer_size 是不是过大了。
然后再检查给定docker设定的内存是多少,可以通过docker info来检查。
milvus 基本使用
安装完成后,终于可以开始使用milvus了,milvus支持python,java和c++。在这里我只介绍python的使用。
首先安装 pymilvus库:pip install pymilvus
,然后就可以使用这个库来写代码了,接下来我会直接把自己写的范例代码贴上去,其中每一步的具体含义以及可能的扩展我会直接在注释里告诉大家,如有错误还请各位指出。
# -*- coding: utf-8 -*-
#导入相应的包
import numpy as np
from milvus import Milvus, IndexType, MetricType
# 初始化一个Milvus类,以后所有的操作都是通过milvus来的
milvus = Milvus()
# 连接到服务器,注意端口映射,要和启动docker时设置的端口一致
milvus.connect(host='localhost', port='19530')
# 向量个数
num_vec = 5000
# 向量维度
vec_dim = 768
# 创建表
# 参数含义
# table_name: 表名
# dimension: 向量维度
# metric_type: 向量相似度度量标准, MetricType.IP是向量内积; MetricType.L2是欧式距离
table_param = {'table_name': 'mytable', 'dimension':vec_dim, 'index_file_size':1024, 'metric_type':MetricType.IP}
milvus.create_table(table_param)
# 随机生成一批向量数据
vectors_array = np.random.rand(num_vec,vec_dim)
vectors_list = vectors_array.tolist()
# 官方建议在插入向量之前,建议先使用 milvus.create_index 以便系统自动增量创建索引
# 索引类型有:FLAT / IVFLAT / IVF_SQ8 / IVF_SQ8H,其中FLAT是精确索引,速度慢,但是有100%的召回率
index_param = {'index_type': IndexType.FLAT, 'nlist': 128}
milvus.create_index('mytable', index_param)
# 把向量添加到刚才建立的表格中
# ids可以为None,使用自动生成的id
status, ids = milvus.add_vectors(table_name="mytable",records=vectors_list,ids=None) # 返回这一组向量的ID
# 官方建议 向量插入结束后,相同的索引需要手动再创建一次
milvus.create_index('mytable', index_param)
# 输出一些统计信息
status, tables = milvus.show_tables()
print("所有的表格:",tables)
print("表格的数据量(行):{}".format((milvus.count_table('mytable')[1])))
print("mytable表格是否存在:",milvus.has_table("mytable")[1])
# 加载表格到内存
milvus.preload_table('mytable')
# 创建查询向量
query_vec_array = np.random.rand(1,vec_dim)
query_vec_list = query_vec_array.tolist()
# 进行查询, 注意这里的参数nprobe和建立索引时的参数nlist 会因为索引类型不同而影响到查询性能和查询准确率
# 对于 FLAT类型索引,两个参数对结果和速度没有影响
status, results = milvus.search(table_name='mytable', query_records=query_vec_list, top_k=4, nprobe=16)
print(status)
print(results)
# 删除表格和索引, 不删除的话,下一次还可以继续使用
milvus.drop_index(table_name="mytable")
milvus.delete_table(table_name="mytable")
# 断开连接
milvus.disconnect()
milvus 多进程使用
写这一章的主要目的是为了进行并发测试,以及多进程能否节省时间,官方说明在使用多进程时需要满足下面两个条件:
- 程序执行时主进程中没有创建 client
- 每个子进程分别创建 client 进行操作
下面是我的测试代码:
# -*- coding: utf-8 -*-
import time
from multiprocessing import Pool
import numpy as np
import random
from milvus import Milvus, IndexType, MetricType
def create_data(host,port,num_vec,vec_dim):
""" 创建一些表格和索引用来做多进程测试 """
milvus = Milvus()
milvus.connect(host=host, port=port)
# 创建2个表
table_param = {'table_name': 'table1', 'dimension':vec_dim, 'index_file_size':1024, 'metric_type':MetricType.IP}
milvus.create_table(table_param)
table_param = {'table_name': 'table2', 'dimension':vec_dim, 'index_file_size':1024, 'metric_type':MetricType.L2}
milvus.create_table(table_param)
# 随机生成一批向量数据
vectors_array = np.random.rand(num_vec,vec_dim)
vectors_list = vectors_array.tolist()
# 创建索引
index_param = {'index_type': IndexType.FLAT, 'nlist': 128}
milvus.create_index('table1', index_param)
milvus.create_index('table2', index_param)
# 添加数据
milvus.add_vectors(table_name="table1",records=vectors_list,ids=None)
milvus.add_vectors(table_name="table2",records=vectors_list,ids=None)
# 创建索引
milvus.create_index('table1', index_param)
milvus.create_index('table2', index_param)
print(milvus.show_tables())
# 断开连接
milvus.disconnect()
def clear_table(host,port):
""" 清空表格和索引 """
milvus = Milvus()
milvus.connect(host=host, port=port)
for table_name in milvus.show_tables()[1]:
milvus.drop_index(table_name=table_name)
milvus.delete_table(table_name=table_name)
milvus.disconnect()
def milvus_search(host,port,table_name,query_vec,search_time=10):
""" 测试查询, 返回查询的秒数"""
milvus = Milvus()
milvus.connect(host=host, port=port)
# 因为bug的原因,要先搜索一次
milvus.search(table_name,4,8,query_vec)
# 开始测试
for _ in range(search_time):
query_vec[0][0] = random.random() # 稍微随机化一下
milvus.search(table_name, 4, 8, query_vec)
if __name__ == "__main__":
host = "localhost"
port = "19530"
num_vec = 100000
vec_dim = 768
num_proc = 3 # 进程数
search_time = 2000 # 搜索次数
####### Step1 先创建用于测试的数据 运行一次就行了
# create_data(host=host,port=port,num_vec=num_vec,vec_dim=vec_dim)
# clear_table(host,port)
# exit(0)
####### Step2 测试依次执行的时间
start_time = time.time()
for _ in range(num_proc):
query_vec = np.random.rand(1,vec_dim).tolist()
milvus_search(host,port,"table1",query_vec,search_time)
end_time = time.time()
print("顺序执行milvus_search的时间总和是:",end_time-start_time)
####### Step3 测试多进程时间
pool = Pool(num_proc)
start_time = time.time()
for _ in range(num_proc):
query_vec = np.random.rand(1,vec_dim).tolist()
pool.apply_async(milvus_search,args=(host,port,"table1",query_vec,search_time))
pool.close()
pool.join()
end_time = time.time()
print("并行执行milvus_search的时间总和是:",end_time-start_time)
结论就是对于search操作,依次search100次,和10个进程同时开,每个进程search10次,开多进程速度是会变快的。我的理解和结论比较肤浅,希望大家批评指正。
后续我会补充java的使用,感谢大家的阅读。
最后,可以随意转载,但请注明出处