一次 Serverless 架構改造實踐:基因樣本比對

Serverless 是一種新興的無伺服器架構,使用它的時候,開發者只需專註於代碼,無需關心運維、資源交付或者部署。本文將從代碼的角度,通過改造一個 Python 應用來幫助讀者從側面理解 Serverless,讓應用繼承 Serverless 架構的優點。

現有資源:

1.一個成熟的基因對比演算法(Python實現,運行一次的時間花費為 2 秒)2.2020 個基因樣本文件(每個文件的大小為 2M,可以直接作為演算法的輸入)3.一台 8 核心雲主機

基因檢測服務

我們使用上面的資源來對比兩個人的基因樣本並 print 對比結果(如:有直系血緣關係的概率)。

我們構造目錄結構如下:

.n├── relation.pyn└── samplesn ├── one.samplen └── two.samplen

relations.py 代碼如下:

import sysndef relationship_algorithm(human_sample_one, human_sample_two):n # its a secretn return resultnif __name__ == __main__:n length = len(sys.argv)n # sys.argv is a list, the first element always be the scripts namen if length != 3:n sys.stderr.write(Need two samples)n else:n # read the first samplen with open(sys.argv[1], r) as sample_one:n sample_one_list = sample_one.readlines()n # read the second samplen with open(sys.argv[2], r) as sample_two:n sample_two_list = sample_two.readlines()n # run the algorithmn print relationship_algirithm(sample_one_list, sample_two_list)n

使用方法如下:

? python relation.py ./samples/one.sample ./samples/two.samplen? 0.054n

流程比較簡單,從本地磁碟讀取兩個代表基因序列的文件,經過演算法計算,最後返回結果。

我們接到了如下業務需求

假設有 2000 人尋找自己的孩子,20 人尋找自己的父親。

首先收集唾液樣本經過專業儀器分析後,然後生成樣本文件並上傳到我們的主機上,一共 2020 個樣本文件,最後我們需要運行上面的演算法:

2000 * 20 = 40000(次)

才可完成需求,我們計算一下總花費的時間:

40000(次) * 2(秒)= 80000 (秒)

80000(秒)/ 60.0 / 60.0 ≈ 22.2(小時)

串列需要花費22小時才能算完,太慢了,不過我們的機器是 8 核心的,開 8 個進程一起算:

22.2 / 8 ≈ 2.76(小時)

也要快3個小時,還是太慢,假設 8 核算力已經到極限了,接下來如何優化呢?

介紹一種 Serverless 產品:UGC

UCloud General Compute

與 AWS 的 lambda 不同,UGC 允許你將計算密集型演算法封裝為 Docker Image (後文統稱為「演算法鏡像」),只需將演算法鏡像 push 到指定的演算法倉庫中,UGC 會將演算法鏡像預先 pull 到一部分計算節點上,當你使用以下兩種形式:

?演算法鏡像的名字和一些驗證信息通過 querystring 的形式 (例如:api.ugc.service.ucloud.cn?ImageName=relation& AccessToken=!Q@W#E)

?演算法鏡像所需的數據通過 HTTP body 的形式

特別構造的 HTTP請求發送到 UGC 的 API 服務時,「任務調度器」會幫你挑選已經 pull 成功演算法鏡像的節點,並將請求調度過去,然後啟動此演算法鏡像「容器」將此請求的 HTTP body 以標準輸入 stdin 的形式傳到容器中。

經過演算法計算,再把演算法的標準輸出 stdout 和標準錯誤 stderr 打成一個 tar 包,以 HTTP body 的形式返回給你,你只需要把返回的 body 當做 tar 包來解壓即可得到本次演算法運行的結果。

講了這麼多,這個產品使你可以把密集的計算放到了數萬的計算節點上,而不是我們小小的 8 核心機器,有數萬核心可供使用,那麼如何使用如此海量的計算資源呢,程序需要小小的改造一下。

針對此 Serverless 架構的改造

兩部分:

1.改造演算法中元數據從「文件輸入」改為「標準輸入」,輸出改為「標準輸出」

2.開發客戶端構造 HTTP 請求,並提高並發

1.改造演算法輸入輸出

① 改造輸入為 stdin

cat ./samples/one.sample ./samples/two.sample | python relation.pyn

這樣把內容通過管道交給 relation.py 的 stdin,然後在 relation.py 中通過以下方式拿到:

import sysmystdin = sys.stdin.read()n

# 這裡的 mystdin 包含 ./samples/one.sample ./samples/two.sample 的全部內容,無分隔,實際使用可以自己設定分隔符來拆分。

② 將演算法的輸出數據寫入 stdout

# 把標準輸入拆分為兩個 sample

sample_one, sample_two = separate(mystdin)n

# 改造演算法的輸出為 STDOUT

def relationship_algorithm(sample_one, sample_two)n

# 改造前

return resultn

# 改造後

sys.stdout.write(result)n

到此就改造完了,很快吧!

2.客戶端與並發

剛才我們改造了演算法鏡像的邏輯(任務的執行),現在我們來看一下任務的提交:構造 HTTP 請求並讀取返回結果。

imageName = cn-bj2.ugchub.service.ucloud.cn/testbucket/relationship:0.1ntoken = tokenManager.getToken() # SDK 有現成的nn# summitTask 構造 HTTP 請求並將鏡像的 STDOUT 打成 tar 包返回nresponse = submitTask(imageName, token, data)n

它也支持非同步請求,之前提到,此 Serverless 產品會將演算法的標準輸出打成 tar 包放到 HTTP body 中返回給客戶端,所以我們準備此解包函數:

import tarfilenimport ionndef untar(data):n tar = tarfile.open(fileobj=io.BytesIO(data))n for member in tar.getmembers():n f = tar.extractfile(member)n with open(result.txt, a) as resultf:n strs = f.read()n resultf.write(strs)n

解開 tar 包,並將結果寫入 result.txt 文件。

假設我們 2200 個樣本文件的絕對路徑列表可以通過 get_sample_list 方法拿到:

sample_2000_list,nsample_20_list = get_sample_list()n

計算 2000 個樣本與 20 個樣本的笛卡爾積,我們可以直接使用 itertools.product

import itertoolsnall = list(itertools.product(sample_2000_list,nsample_20_list))nassert len(all) == 40000n

結合上面的代碼段,我們封裝一個方法:

def worker(two_file_tuple):n sample_one_dir, sample_two_dir = two_file_tuplenn with open(sample_one_dir) as onef:n one_data = onef.read()n with open(samle_two_dir) as twof:n two_data = twof.read()n n data = one_data + two_datan response = summitTask(imageName, token, data)n untar(response)n

因為構造 HTTP 請求提交是 I/O 密集型而非計算密集型,所以我們使用協程池處理是非常高效的:

import gevent.poolnimport gevent.monkeyngevent.monkey.patch_all() # 猴子補丁nnpool = gevent.pool.Pool(200)npool.map(worker, all)n

只是提交任務 200 並發很輕鬆。全部改造完成,我們來簡單分析一下:

之前是 8 個進程跑計算密集型演算法,現在我們把計算密集型演算法放到了 Serverless 產品中,因為客戶端是 I/O 密集型的,單機使用協程可以開很高的並發,我們不貪心,按 200 並發來算:

進階閱讀:上面這種情況下帶寬反而有可能成為瓶頸,我們可以使用 gzip 來壓縮 HTTP body,這是一個計算比較密集的操作,為了 8 核心算力的高效利用,可以將樣本數據分為 8 份,啟動 8 個進程,進程中再使用協程去提交任務就好了。

40000 * 2 = 80000(秒)80000 / 200 = 400(秒)

也就是說進行一組檢測只需要 400 秒,從之前的 7 天提高到 400 秒,成果斐然,圖表更直觀:

而且算力瓶頸還遠未達到,任務提交的並發數還可以提升,再給我們一台機器提交任務,便可以縮短到 200 秒,4 台 100 秒,8 台 50 秒…

最重要的是改造後的架構還繼承了 Serverless 架構的優點:免運維、高可用、按需付費、發布簡單。

免運維 — 因為你沒有伺服器了…

高可用 — Serverless 服務一般依託雲計算的強大基礎設施,任何模塊都不會只有單點,都儘可能做到跨可用區,或者跨交換機容災,而且本次使用的服務有一個有趣的機制:同一個任務,你提交一次,會被多個節點執行,如果一個計算節點掛了,其他節點還可以正常返回,哪個先執行完,先返回哪個。

按需付費 — 文中說每個演算法執行一次花費單核心 CPU 時間 2 秒,我們直接算一下花費:

2000 * 20 * 2 = 80000(秒)

80000 / 60 / 60 / 24.0 = 0.93(小時)

0.93(小時) * 0.09(元) * 1(核心) ~= 0.08(元)

每核時 0.09 元(即單核心 CPU 時間 1 小時,計費 9 分錢)

發布簡單 — 因為使用 Docker 作為載體,所以它是語言無關的,而且發布也很快,代碼寫好直接上傳鏡像就好了,至於灰度,客戶端 imageName 指定不同版本即可區分不同代碼了

不同的 Serverless 產品可能有不同的改造方法,作為工程師,我比較喜歡這種方式,改造成本低,靈活性高,你覺得呢?

推薦閱讀:

群雄逐鹿 誰會成為雲計算行業第一股?
七牛的商業模式可以成功嗎?
微軟的雲計算服務 Azure 與亞馬遜的 AWS 有什麼區別?
手把手帶你搭建第一個個人網站(下)

TAG:Serverless | UGC | 云计算 |