【Grpc】使用grpc配置通過端口訪問的python服務

0x00 前言

近期興趣使然的技術調研越發的少了(TTS算一個),主要的都是爲了項目和任務去研究的東西。目前的情況是爲了節約顯存,對一個較大的模型而言,比起使用4個worker來重複的佔用顯存,不如只佔用一份顯存,但是開啓服務流式或觸發式地處理不同項目的需求。
於是 @caoyixuan93 學長向我推薦了GRPC,經過 @hongfeng 和 @phchang 的幫助,終於得以成功實現了一個小的自定義demo,後續再花些時間把模型裝載上去。

0x01 GRPC介紹

GRPC: A high performance, open source, general-purpose RPC framework

簡單的來說,就是一個開源的“服務端-客戶端”框架,你可以把你的服務(例如模型的預測函數)掛載起來,隨時接受到通過端口發送來的輸入數據,計算後將輸出返回回去。當有多個訪問時,以隊列或者流的形式逐個處理。

0x02 環境配置

我們來參照 Python Quick Start ,對於一個簡單的grpc服務,逐步搭建起來依次需要哪些東西,從而來對應的看看需要配置的環境吧:

  • Python >= 3.4 grpc有很多版本,這裏我選擇使用便捷的python
  • grpcio>=1.28.1 既然要用grpc那自然要裝上grpc了,這裏的grpcio是指grpc的python版本,此外還有其他各種語言的版本就沒有多做研究啦,直接 pip install grpcio 即可
  • grpcio-tools 這個工具的作用是通過讀取.proto格式的配置文件,產生兩段python代碼供直接調用,直接 pip install grpcio-tools 即可
  • protobuf==3.6.0 上一步的 pip install 會附贈安裝一個3.11.x的,結果版本太高了反而容易出錯,這裏我們選用穩定的3.6.0版本了,用途是讀取上面提到的.proto格式的文件,進行服務的變量名配置

0x03 源碼及分析

個人可能腦袋瓜比較笨,看 Python Quick Start 裏的例子是硬生生沒明白,所以比起他的helloworld,不如自己自定義一個demo來嘗試跑通,這用來學習一種新框架是一個不錯的方法。

配置文件

https://github.com/okcd00/CDAlter/tree/master/CDMemory/protos

首先是令人勸退的 protobuf 語言,啊我沒學過怎麼辦,啊看起來頭好疼。
然後看着看着,哦,就這呀?還好還好,又不用寫邏輯,就是配置嘛,那我會,yml我也不會呀但我照葫蘆畫瓢寫配置還是能做到的嘛。

// 意思是我們用了proto3的版本,不用管這行,大概python2會變成proto2吧,我瞎猜的
syntax = "proto3";  

// 我們自定義的這個配置文件叫啥,會用在產生代碼的時候當名字用
package keyvaluestore;  

// service裏就是預先定義一下你這個服務有哪些函數可以調用
// 這裏只需要定義不需要實現任何功能,功能都是在python裏實現的
service KeyValueStore {
  // 函數格式: rpc 函數名 (輸入變量) returns (輸出變量) {}
  // 變量都要在下面用 message 來定義了才能用哦
  rpc ask (Key) returns (Response) {}
  rpc remember (Item) returns (Response) {}
}

// 變量名:Key
// 有一個成員變量,爲詞典裏的鍵
message Key {
  string key = 1;
}

// 變量名:Item
// 有兩個成員變量,爲詞典裏的鍵和值
message Item {
  string key = 1;
  string value = 2;  
  // 這個1和2就當作是標識符就好,有幾個變量就要寫到幾,這樣服務器才能數的清楚不會搞錯
}

// 變量名:Response 
// 有一個成員變量,爲詞典裏的值
message Response {
  string value = 1;
}

這個proto文件設置好了之後,通過命令行(cmd,命令提示符,git bash都可以)來調用tool自動生成兩個python文件,調用方法爲,先進入proto所在的文件夾(例如 cd /i/Github/CDAlter/CDMemory/protos),然後執行如下命令(這裏我的proto文件叫作keyvaluestore.proto):

python -m grpc_tools.protoc \
    -I ./ \ 
    --python_out=. \
    --grpc_python_out=. \
    keyvaluestore.proto

裏面的 \ 是爲了格式好看點,偷懶的話在提供個可以直接輸入一整行的:

python -m grpc_tools.protoc -I ./ --python_out=. --grpc_python_out=. keyvaluestore.proto

因爲我的配置文件叫keyvaluestore.proto,所以在當前目錄下生成了keyvaluestore_pb2.pykeyvaluestore_pb2_grpc.py兩個文件。

服務端與客戶端

https://github.com/okcd00/CDAlter/tree/master/CDNerve

服務端

# -*- coding: gbk -*-
# ==========================================================================
#   Copyright (C) since 2020 All rights reserved.
#
#   filename : grpc_server.py
#   author   : chendian / [email protected]
#   date     : 2020-04-16
#   desc     : server in grpc service
# ==========================================================================
import sys
import time
import grpc
from concurrent import futures
from multiprocessing import Pool
from collections import OrderedDict
from grpc._cython.cygrpc import CompressionAlgorithm, CompressionLevel

"""
# generate it at first
python -m grpc_tools.protoc \
    -I ./ \ 
    --python_out=. \
    --grpc_python_out=. \
    keyvaluestore.proto
# then you can get keyvaluestore_pb2_grpc and keyvaluestore_pb2
"""
# 這裏我的protos不在當前目錄下,所以加了個pythonpath
sys.path.append('../CDMemory/protos/')
from CDMemory.protos import keyvaluestore_pb2_grpc, keyvaluestore_pb2


class KVServicer(keyvaluestore_pb2_grpc.KeyValueStoreServicer):
    def __init__(self):
    	# 這裏可以把模型什麼的都放進來,比如 self.model = AlbertModel()
        self.records = OrderedDict()  # 這裏用一個字典來作爲Demo服務的載體

    # 之前在 service 的部分預先定義的函數都要在這裏重載,而且變量名的個數要一致,
    # 不然會報錯:"Exception iterating requests!"
    # 我這裏統一用了重載的函數名,也可以寫成 `def ask(self, Key, context):` 
    def ask(self, request, context):  
    	# 對應 rpc ask (Key) returns (Response) {}
        # 這個 request 就是 `ask` 後面那個 `Key`
        _value = self.records.get(request.key, "Empty")  
        # 這個`keyvaluestore_pb2.Response`就是 `returns` 後面的那個 `Response`
        return keyvaluestore_pb2.Response(value=_value)
	
	# 我這裏統一用了重載的函數名,也可以寫成 `def remember(self, Item, context):` 
    def remember(self, request, context):
	    # 對應 rpc remember (Item) returns (Response) {}
        # 這個 request 就是 `remember ` 後面那個 `Item`
        key, value = request.key, request.value  # 就是我們在Item裏定義的key/value
        self.records.update({key: value})  # 這個grpc的demo效果就是字典的寫和查
        return keyvaluestore_pb2.Response(
            value="Remembered: the value for {} is {}".format(key, value))


def serve(n_worker=4, port=20416):
    max_receive_message_length = 512
    # 初始化 Servicer 實例
    service = KVServicer()
    # 多線程池,設定線程數量
    service.process_pool = Pool(processes=n_worker)
    # 建立 server 實例
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=n_worker), 
    	options=[  # 這裏的options可以省去不要,我這裏加的設置是限制最大長度和壓縮
	        ('grpc.max_receive_message_length', max_receive_message_length),
	        ('grpc.default_compression_algorithm', CompressionAlgorithm.gzip),
	        ('grpc.grpc.default_compression_level', CompressionLevel.high)
    ])
    # 用自動生成的 keyvaluestore_pb2_grpc 來添加 Servicer 到服務中
    keyvaluestore_pb2_grpc.add_KeyValueStoreServicer_to_server(service, server)
	# 設置端口號(內網外網均可訪問)
    server.add_insecure_port('[::]:{}'.format(port))
	# 服務啓動
    server.start()
    # server.wait_for_termination()
    return server


if __name__ == "__main__":
    print("starting server...")
    server_agent = serve()
    print("server started.")
    while True:
        time.sleep(10000)
        print("[ALIVE] {}".format(time.ctime()))

客戶端

# -*- coding: gbk -*-
# ==========================================================================
#   Copyright (C) since 2020 All rights reserved.
#
#   filename : grpc_client.py
#   author   : chendian / [email protected]
#   date     : 2020-04-16
#   desc     : client in grpc service
# ==========================================================================
import os
import sys
if os.environ.get('https_proxy'):
    del os.environ['https_proxy']
if os.environ.get('http_proxy'):
    del os.environ['http_proxy']
import grpc

"""
# generate it at first
python -m grpc_tools.protoc \
    -I ./ \ 
    --python_out=. \
    --grpc_python_out=. \
    keyvaluestore.proto
# then you can get keyvaluestore_pb2_grpc and keyvaluestore_pb2
"""
# 這裏我的protos不在當前目錄下,所以加了個pythonpath
sys.path.append('../CDMemory/protos/')  
from CDMemory.protos import keyvaluestore_pb2_grpc, keyvaluestore_pb2


if __name__ == '__main__':
	# 連接上對應端口的grpc服務
    with grpc.insecure_channel('localhost:20416') as channel:
        # 初始化一個實例作爲客戶端
        stub = keyvaluestore_pb2_grpc.KeyValueStoreStub(channel)
		# 調用我們定義的remember函數,效果是寫入 key=name, value=cd
        response = stub.remember(keyvaluestore_pb2.Item(key='name', value='cd'))
        print(response)  # value: "Remembered: the value for name is cd"
        # 調用我們定義的ask函數,效果是查詢 key=name 對應的 value
        response = stub.ask(keyvaluestore_pb2.Key(key='name'))
        print(response)  # value: "cd"
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章