
需求背景
上一篇文章已經(jīng)完成了對(duì)該開源庫的調(diào)研,對(duì)于一些性能和使用姿勢(shì)來說也有了一些基本的了解。在調(diào)研期內(nèi)也通讀了faiss的官方wiki,現(xiàn)在,準(zhǔn)備開始動(dòng)手了。有興趣了解的同學(xué)可以戳鏈接faiss的一些相關(guān)調(diào)研。
動(dòng)手之前還是需要再次靜靜,好好思考這一次任務(wù)要輸出的是什么內(nèi)容。emm,本打野選手在讀條時(shí)已經(jīng)養(yǎng)成了為整局游戲?qū)憚”镜牧?xí)慣了。這次繼續(xù)以流程圖的方式來輸出,流程圖的好處除了能讓你理清思路以外,更重要的是能讓你確定你的思路是不是和leader在同一條線上,確定這次任務(wù)的分界線(這個(gè)很重要),也是工作結(jié)果的痕跡。

上圖中主要表達(dá)了一個(gè)使用faiss來進(jìn)行搜索的API接口的流程,不同于以往的跟數(shù)據(jù)互動(dòng)的方式(結(jié)構(gòu)數(shù)據(jù)庫,非結(jié)構(gòu)數(shù)據(jù)庫,圖數(shù)據(jù)庫等),faiss只是一個(gè)比較簡(jiǎn)陋的開源庫,他并沒有完整的提供一套解決方案。類比的話就好像elasticsearch和solr中的lucene包一樣,而我要做的就是在此基礎(chǔ)上二次開發(fā),提供一套可用的解決方案??紤]到faiss是一個(gè)C++的開源工具庫,它只提供了python的接口,所以只能使用python來做這次接口開發(fā)??疾爝^后決定技術(shù)選型為flask+uwsgi+faiss來完成這個(gè)接口。
環(huán)境依賴
- faiss的庫
- python2.7
- uwsgi
- pycharm
吃井不忘挖水人
專門開一個(gè)標(biāo)題來感謝一下這哥們plippe faiss-web-service。他開源的這個(gè)demo滿足了我百分60以上的需求,本次的開發(fā)基本就是在讀懂他的代碼以后才能這么胸有成竹。
過程復(fù)盤
也不想嘮嘮叨叨的復(fù)盤整個(gè)開發(fā)過程,就只復(fù)盤幾個(gè)比較棘手的點(diǎn)
python的API如何開發(fā)
本身比較習(xí)慣于使用Java,對(duì)于python更多的是會(huì)用來做一些小運(yùn)維的腳本,所以對(duì)于python中的web開發(fā)會(huì)比較模糊。短時(shí)間內(nèi)我不太愿意再投入學(xué)習(xí)成本在flask的所有細(xì)節(jié)上,因?yàn)橐院罂赡茉俅沃胤甑木壏植⒉欢唷D敲丛趺纯焖俚挠胒lask開發(fā)一個(gè)web接口呢,如果你是Java工程師你可以參考下我的思路,大概兩部就可以完成一個(gè)簡(jiǎn)單可拓展的接口,首先是主入口
# -*- coding: UTF-8 -*-
from flask import Flask
from faiss_index import blueprint as FaissIndexBlueprint
app = Flask(__name__)
app.config.from_pyfile('config.py')
app.register_blueprint(FaissIndexBlueprint.blueprint)
if __name__ == '__main__':
app.run()
其次是在主入口中注冊(cè)的模塊(Blueprint)
# -*- coding: UTF-8 -*-
from jsonschema import validate, ValidationError
from flask import Blueprint, jsonify, request
from werkzeug.exceptions import BadRequest
from faiss_index import FaissIndex
import json
try:
import uwsgi
except ImportError:
print('Failed to load python module uwsgi')
print('Periodic faiss index updates isn\'t enabled')
uwsgi = None
blueprint = Blueprint('faiss_index', __name__)
@blueprint.route('/ping')
def ping():
return "pong"
OK直接運(yùn)行app.py就可以用flask自帶的wsgi服務(wù)器啟動(dòng)app,看到這里是否會(huì)覺得和springboot非常相似?以及包括@blueprint.route('/ping')這樣的路由方式,讓我覺得像極了springmvc的路由注解方式,所以幾乎不需要耗費(fèi)特別多的學(xué)習(xí)成本投入到這上面,節(jié)省了比較多的時(shí)間。余下的工作就是開始慢慢拓展開程序。
搜索的參數(shù)
API調(diào)用方要提供的參數(shù)是一維向量,目的是搜索距離最近的K個(gè)向量,向量具體在程序中怎么表示呢?就是一維數(shù)組。比如[1,2,3,4,5],但是在拿到向量后不能馬上進(jìn)行搜索,要進(jìn)行處理,如下
vectors = [np.array(vectors, dtype=np.float32)]
vectors = np.atleast_2d(vectors)
部署的方式-Docker
相比其他python web API來說,faiss搜索有一點(diǎn)特殊的地方,就是它最重要的依賴faiss本身。而faiss本身安裝有兩種方式
- 通過下載源代碼進(jìn)行編譯 非常麻煩
- 通過anconda進(jìn)行安裝 一行代碼完成
但是服務(wù)器上本身是可能有其他python環(huán)境在運(yùn)行的。不可能專門的讓我為了部署我的工程而去改動(dòng),可能還能通過env的方式解決?但是我不夠熟悉python,所以選擇了我擅長的隔離方式Docker。原理就是從ubunt的image開始構(gòu)建,首先構(gòu)建出一個(gè)faiss運(yùn)行環(huán)境的image,然后在第二個(gè)image上打包自己的flask程序。附上構(gòu)建運(yùn)行環(huán)境的Dockerfile
ARG IMAGE
FROM ${IMAGE}
ARG FAISS_CPU_OR_GPU
ARG FAISS_VERSION
RUN apt-get update && \
apt-get install -y curl bzip2 && \
curl https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh > /tmp/conda.sh && \
bash /tmp/conda.sh -b -p /opt/conda && \
/opt/conda/bin/conda update -n base conda && \
/opt/conda/bin/conda install -y -c pytorch faiss-${FAISS_CPU_OR_GPU}=${FAISS_VERSION} && \
apt-get remove -y --auto-remove curl bzip2 && \
apt-get clean && \
rm -fr /tmp/conda.sh
ENV PATH="/opt/conda/bin:${PATH}"
以及我打包到dockerhub上的鏡像faiss-docker
可以在docker中搜索我的鏡像pull下來使用
docker search huangqq
docker pull huangqq/faiss-docker:1.2.1-cpu
總結(jié)
最麻煩的使用姿勢(shì)其實(shí)已經(jīng)在上一篇調(diào)研中理清的差不多了,本篇主要是在探討faiss在工程化應(yīng)用的一些實(shí)踐,如果有需要交流的歡迎右手邊微信~