機器學習模型python線上服務部署的兩種例項

屈天航 發表於 2020-09-22

背景

眾所周知python在機器學習實踐中的應用廣泛深入,而在我們業務中的應用集中在提供線上實時風控輸出服務,比如國內業務的模型線上服務架構和海外業務的後臺決策引擎架構。這兩種應用的結合就要求我們考慮如何高效安全便捷地來實現模型的線上部署,為上游提供服務。
   在我們的考慮中,無論是程式碼複雜程度和業務場景,還是語言本身的特點,模型部署都有趨於向微服務架構轉型的趨勢和需要。一方面,需要進行程式碼分離來明確責任分工提高開發效率和容錯性。另外一個方面,python在CPU密集型的應用中表現是無法令人滿意的。為了使用協程來提高非同步性從而處理更多的併發請求,最直接地就是將CPU密集轉化為IO密集,因為Python天生就適合IO密集型的網路應用。
   因此,我們生產中將模型計算抽取為model_lib程式碼庫,並且通過微服務online_model進行互動。這裡我們調研過兩種模型部署的方式,最終選擇了第一種。

一、基於flask框架進行模型部署

Flask是一個輕量級的可定製框架,具有靈活、輕便且高效的特點,並且是標準的wsgi介面框架,易於擴充套件和維護。

1. 為什麼選用nginx+uwsgi+flask這種技術架構

1)    Uwsgi搭配nginx效能快,記憶體佔用低,高度可定製,自帶詳盡日誌功能,支援平滑重啟。

2)    Flask完全相容了wsgi標準; 微框架,擴充套件性強; 完全基於unicode,不需處理編碼問題;自帶服務可獨立做單元測試及開發。
image.png

3)    我們客戶端採用了tornado協程,已經實現了將cpu計算轉為io操作,服務端完全是CPU密集的模型計算,不會釋放程式,非同步框架保持大量檔案描述符狀態耗費記憶體,因此不適用非同步IO框架。

2. 業務流程框架

image.png

3. 部署方式:

部署方式採用nginx+uwsgi+flask的方式,uwsgi可直接接受socket而不是http請求提高效能,再將服務轉發給flask框架,這裡注意flask此類wsgi標準介面的服務框架比如djangoweb.py在生產中一般不使用自帶服務,而是在上層部署uwsgi或者gunicorn作為伺服器來進行服務轉發,上層再用nginx來做負載均衡,這樣可以提高服務穩定性和效能。
image.png

4. 程式碼示例:

uwsgi服務配置:

[uwsgi]
# 監聽埠
socket=127.0.0.1:8200
# 程式數
processes=20

;async=4
;threads=2
;enable-threads = true
# 執行的目錄
chdir = /home/rong/www/online_model
# wsgi檔案
wsgi-file = model_main.py
callable=app
# 是否要有主程式
master = true
# 後臺執行及其列印的日誌
daemonize = /home/rong/www/log/uwsgi.log
# 主程式pid檔案
pidfile = /home/rong/www/log/online_model/pid/uwsgi.pid
# 日誌切割大小
log-maxsize = 5000000
# 不記錄請求資訊的日誌。只記錄錯誤以及uWSGI內部訊息到日誌中。
disable-logging = false
# 超時時間
http-timeout= 3
# 傳輸資料大小限制
buffer-size  = 1048576
# 每個程式單獨載入
lazy-apps = true

flask服務關鍵程式碼:

import importlib
import json
import cProfile
import pstats
import StringIO
import time
import traceback
from flask import Flask, request
from common import rong_logger
from common.global_variable import StaticCacheClass
import autopath  # 不能去掉

app = Flask(__name__)
# 這裡是模型程式碼庫的統一入口,模型程式碼庫中是通過抽象類實現的規範化的模型程式碼例項,通過此服務提供呼叫,也通過離線排程進行跑批任務。保證線上線下模型呼叫一致性
online_model_main = importlib.import_module('online_model_main')

MUST_PARAMS = ['resource_id', 'feature_dict']
SUCCESS_STATUS = 0
ERROR_STATUS = 1


# 路由函式只允許post請求
@app.route("/", methods=['POST'])
def model_main():
    uniq_id = '[%s][%s]' % (request.form.get('resource_id', ''), request.url)
    try:
        status, msg, params = _check_params(request)
        if status != SUCCESS_STATUS:
            rong_logger.error(uniq_id + 'params error, detail: %s' % msg)
            status, msg, result = status, msg, None
        else:
            resource_id, feature_dict = params['resource_id'], json.loads(params['feature_dict'])
            status, msg, result = online_model_main.main(resource_id, feature_dict, request, rong_logger)
            rong_logger.info(uniq_id + '[%s][%s][%s]' % (status, msg, result))

    except Exception as e:
        rong_logger.error(uniq_id + 'error: %s, detail: %s' % (str(e), traceback.format_exc()))
        status, msg, result = 5, 'online_model_error:' + str(e), None
    return _get_response(status, msg, result)

模型程式碼庫模型例項:

其中 XgboostExecutor類是基於xgb模型抽象類實現的類,通過它來例項化一個模型物件,給flask應用提供呼叫。具體我們不再深究,有興趣可以再寫一個專題來介紹模型程式碼庫的部署。

# -*- coding:utf-8 -*-
# !/usr/bin/python

import logging
import os

from executor.src.load_helper import read_cur_path
from executor.xgboost_model_executor import XgboostExecutor

logging.basicConfig(level=logging.INFO, format='%(asctime)s:%(message)s')




[model_path, features_path,feature_importance_path] = map(
    read_cur_path, ["xgb_model", "feature_list","feature_importance"]
)
model = XgboostExecutor(model_path, features_path,
                        feature_check_white_list=["n21_score"],
                        white_or_weight=False,
                        feature_check_weight_limit= 1,
                        feature_importance_path=feature_importance_path,
                        manager="[email protected]",
                        developer="[email protected]",
                        correlation="negative")

5. 效能比對

微服務改造後20併發請求模型:
image.png
微服務改造前20併發請求模型:
image.png

本機測試併發效能就提高了20%,但注意這是在高併發的情況下,就單條請求來看,微服務並不能顯著提高效能。

二、 基於grpc進行線上模型部署

在 gRPC 裡客戶端應用可以像呼叫本地物件一樣直接呼叫另一臺不同的機器上服務端應用的方法,能夠更容易地建立分散式應用和微服務。與許多 RPC 系統類似,gRPC 也是基於以下理念:定義一個服務,指定其能夠被遠端呼叫的方法(包含引數和返回型別)。在服務端實現這個介面,並執行一個 gRPC 伺服器來處理客戶端呼叫。在客戶端擁有一個存根能夠執行在服務端實現的一樣的方法(這個方法就類似於介面)
image.png

1. 為什麼選用grpc進行模型部署

  • 1)grpc使用ProtoBuf來定義服務、請求返回的資料格式,壓縮和傳輸效率高,語法簡單,表達力強。(如下為ProtoBuf的序列化和反序列話效能表現

image.pngimage.png

  • 2)grpc可支援tensorflow serving的介面呼叫,tensorflow完成模型訓練和部署後,可提供服務介面,給grpc進行呼叫。實現方便,高效,自帶版本管理、模型熱更新等,很適合大規模線上業務,是我們下一步模型部署的技術方向。
  • 3)gRPC支援多種語言,並能夠基於語言自動生成客戶端和服務端功能庫。

2. 部署方式(業務流程與之前相同)

部署方式採用nginx+grpc,要求nginx支援http2.0。在客戶端將json特徵字典轉為protobuf。(https://github.com/NextTuesday/py-pb-converters/blob/master/pbjson.py 這裡附上json和protobuf互相轉化的指令碼。)

image.png

3. 服務發現與負載均衡

image.png

4. 開發流程

image.png

客戶端:
image.png

服務端:
image.png

三、 兩種方式線上模型部署對比

1)    grpc使用protbuf更加複雜,需要在客戶端服務端均保留protbuf檔案並做校驗,而flask只需要做好統一的介面標準規範即可。

2)    grpc使用http2.0更適用移動端的線上模型預測,或者基於tensorflowd的大規模線上模型部署和預測,flask更適用後端面向服務的手動模型部署和預測。

3) grpc節省資料空間,但與python互動需要做json和protbuf資料轉換,flask相容wsgi標準,適用於RESTful類服務,但資料傳輸佔用空間較大。