Serverless 是一種新興的無服務器架構,使用它的時候,開發者只需專一於代碼,無需關心運維、資源交付或者部署。本文將從代碼的角度,經過改造一個 Python 應用來幫助讀者從側面理解 Serverless,讓應用繼承 Serverless 架構的優勢。python
現有資源:
1.一個成熟的基因對比算法(Python實現,運行一次的時間花費爲 2 秒)
2.2020 個基因樣本文件(每一個文件的大小爲 2M,能夠直接做爲算法的輸入)
3.一臺 8 核心雲主機算法
<strong>基因檢測服務</strong>api
咱們使用上面的資源來對比兩我的的基因樣本並 print 對比結果(如:有直系血緣關係的機率)
咱們構造目錄結構以下:
.服務器
├── relation.py └── samples ├── one.sample └── two.sample
relations.py 代碼以下:微信
import sys def relationship_algorithm(human_sample_one, human_sample_two): # it's a secret return result if __name__ == "__main__": length = len(sys.argv) # sys.argv is a list, the first element always be the script's name if length != 3: sys.stderr.write("Need two samples") else: # read the first sample with open(sys.argv[1], "r") as sample_one: sample_one_list = sample_one.readlines() # read the second sample with open(sys.argv[2], "r") as sample_two: sample_two_list = sample_two.readlines() # run the algorithm print relationship_algirithm(sample_one_list, sample_two_list)
使用方法以下:架構
➜ python relation.py ./samples/one.sample ./samples/two.sample ➜ 0.054
流程比較簡單,從本地磁盤讀取兩個表明基因序列的文件,通過算法計算,最後返回結果。併發
<strong>咱們接到了以下業務需求</strong>
假設有 2000 人尋找本身的孩子,20 人尋找本身的父親。less
首先收集唾液樣本通過專業儀器分析後,而後生成樣本文件並上傳到咱們的主機上,一共 2020 個樣本文件,最後咱們須要運行上面的算法運維
2000 * 20 = 40000(次)異步
纔可完成需求,咱們計算一下總花費的時間:
40000(次) * 2(秒)= 80000 (秒)
80000(秒)/ 60.0 / 60.0 ≈ 22.2(小時)
串行須要花費22小時才能算完,太慢了,不過咱們的機器是 8 核心的,開 8 個進程一塊兒算:
22.2 / 8 ≈ 2.76(小時)
也要快3個小時,仍是太慢,假設 8 覈算力已經到極限了,接下來如何優化呢?
<strong>1.介紹一種 Serverless 產品:UGC
與 AWS 的 lambda 不一樣,UGC 容許你將計算密集型算法封裝爲 Docker Image (後文統稱爲「算法鏡像」),只需將算法鏡像 push 到指定的算法倉庫中,UGC 會將算法鏡像預先 pull 到一部分計算節點上,當你使用如下兩種形式:
•算法鏡像的名字和一些驗證信息經過 querystring 的形式 (例如:http://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 核心機器,有數萬核心可供使用,那麼如何使用如此海量的計算資源呢,程序須要小小的改造一下
<strong>2.針對此 Serverless 架構的改造</strong>
兩部分:
1.改造算法中元數據從「文件輸入」改成「標準輸入」,輸出改成「標準輸出」
2.開發客戶端構造 HTTP 請求,並提升併發
<strong>1. 改造算法輸入輸出</strong>
① 改造輸入爲 stdin
cat ./samples/one.sample ./samples/two.sample | python relation.py
這樣把內容經過管道交給 relation.py 的 stdin,而後在 relation.py 中經過如下方式拿到:
import sys mystdin = sys.stdin.read()
# 這裏的 mystdin 包含 ./samples/one.sample ./samples/two.sample 的所有內容,無分隔,實際使用能夠本身設定分隔符來拆分
② 將算法的輸出數據寫入 stdout
# 把標準輸入拆分爲兩個 sample
sample_one, sample_two = separate(mystdin)
# 改造算法的輸出爲 STDOUT
def relationship_algorithm(sample_one, sample_two)
# 改造前
return result
# 改造後
sys.stdout.write(result)
到此就改造完了,很快吧
<strong>2. 客戶端與併發</strong>
剛纔咱們改造了算法鏡像的邏輯(任務的執行),如今咱們來看一下任務的提交:
構造 HTTP 請求並讀取返回結果。
imageName = "cn-bj2.ugchub.service.ucloud.cn/testbucket/relationship:0.1" token = tokenManager.getToken() # SDK 有現成的 # summitTask 構造 HTTP 請求並將鏡像的 STDOUT 打成 tar 包返回 response = submitTask(imageName, token, data)
它也支持異步請求,以前提到,此 Serverless 產品會將算法的標準輸出打成 tar 包放到 HTTP body 中返回給客戶端,因此咱們準備此解包函數:
import tarfile import io def untar(data): tar = tarfile.open(fileobj=io.BytesIO(data)) for member in tar.getmembers(): f = tar.extractfile(member) with open('result.txt','a') as resultf: strs = f.read() resultf.write(strs)
解開 tar 包,並將結果寫入 result.txt 文件。
假設咱們 2200 個樣本文件的絕對路徑列表能夠經過 get_sample_list 方法拿到:
sample_2000_list, sample_20_list = get_sample_list()
計算 2000 個樣本與 20 個樣本的笛卡爾積,咱們能夠直接使用 itertools.product
import itertools all = list(itertools.product(sample_2000_list, sample_20_list)) assert len(all) == 40000
結合上面的代碼段,咱們封裝一個方法:
def worker(two_file_tuple): sample_one_dir, sample_two_dir = two_file_tuple with open(sample_one_dir) as onef: one_data = onef.read() with open(samle_two_dir) as twof: two_data = twof.read() data = one_data + two_data response = summitTask(imageName, token, data) untar(response)
由於構造 HTTP 請求提交是 I/O 密集型而非計算密集型,因此咱們使用協程池處理是很是高效的:
import gevent.pool import gevent.monkey gevent.monkey.patch_all() # 猴子補丁 pool = gevent.pool.Pool(200) pool.map(worker, all)
只是提交任務 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 架構的優勢:免運維、高可用、按需付費、發佈簡單。
<strong>免運維</strong> -- 由於你沒有服務器了...
<strong>高可用</strong> -- Serverless 服務通常依託雲計算的強大基礎設施,任何模塊都不會只有單點,都儘量作到跨可用區,或者跨交換機容災,並且本次使用的服務有一個有趣的機制:同一個任務,你提交一次,會被多個節點執行,若是一個計算節點掛了,其餘節點還能夠正常返回,哪一個先執行完,先返回哪一個。
<strong>按需付費</strong> -- 文中說每一個算法執行一次花費單核心 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 分錢)
<strong>發佈簡單</strong> -- 由於使用 Docker 做爲載體,因此它是語言無關的,並且發佈也很快,代碼寫好直接上傳鏡像就行了,至於灰度,客戶端 imageName 指定不一樣版本便可區分不一樣代碼了
不一樣的 Serverless 產品可能有不一樣的改造方法,做爲工程師,我比較喜歡這種方式,改形成本低,靈活性高,你以爲呢?若是對 Serverless 架構或者 UGC 感興趣的話能夠添加微信 u_nknow 加入咱們的技術交流羣!