跨界協作:藉助gRPC實現Python數據分析能力的共享

gRPC是一個高性能、開源、通用的遠程過程調用(RPC)框架,由Google推出。
它基於HTTP/2協議標準設計開發,默認採用Protocol Buffers數據序列化協議,支持多種開發語言。

在gRPC中,客戶端可以像調用本地對象一樣直接調用另一臺不同的機器上服務端應用的方法,使得您能夠更容易地創建分佈式應用和服務。

gRPC支持多種語言,並提供了豐富的接口和庫,以及簡單易用的API,方便開發者進行快速開發和部署。
同時,gRPC的底層框架處理了所有強制嚴格的服務契約、數據序列化、網絡通訊、服務認證、訪問控制、服務觀測等等通常有關聯的複雜性,使得開發者可以更加專注於業務邏輯的實現。

1. 爲什麼用 gRPC

我平時用的最多的語言其實是golang,但是,做數據分析相關的項目,不太可能繞開python那些優秀的庫。
於是,就想把數據分析的核心部分用python來實現,並用gRPC接口的方式提供出來。
其他的業務部分,仍然用原先的語言來實現。

gRPC相比於http REST,性能和安全上更加有保障,而且對主流的開發語言都支持的很好,不用擔心與其他語言開發的業務系統對接的問題。

最後,gRPC雖然接口的定義和實現比http REST更復雜,但是,它提供了方便的命令行工具,
可以根據protocol buf的定義自動生成對應語言的類型定義,以及stub相關的代碼等等。

實際開發時,一般只要關注接口的定義和業務功能的實現即可,gRPC框架需要的代碼可以通過命令行工具生成。

2. 安裝

對於Python語言,安裝gRPC框架本身和對應的命令行工具即可:

$ pip install grpcio  # gRPC框架
$ pip install grpcio-tools # gRPC命令行工具

3. 開發步驟

開發一個gPRC接口一般分爲4個步驟

  1. 使用[protocal buf](https://protobuf.dev/overview)定義服務接口
  2. 通過命令行生成clientserver的模板代碼
  3. 實現server端代碼(具體業務功能)
  4. 實現client端代碼(具體業務功能)

下面通過一個示例演示gRPC接口的開發步驟。
這個示例來自最近做量化分析時的一個指標(MACD)的實現,
爲了簡化示例,下面實現MACD指標的業務功能部分是虛擬的,不是實際的計算方法。

3.1. 定義服務接口

接口主要定義方法,參數,返回值。

syntax = "proto3";

package idc;

// 定義服務,也就是對外提供的功能
service Indicator {
    rpc GetMACD(MACDRequest) returns (MACDReply) {}
}

// 請求的參數
message MACDRequest {
    string start_date = 1; // 交易開始時間
    string end_date = 2;   // 交易結束時間
}

// 返回值中每個對象的詳細內容
message MACDData {
    string date = 1;  // 交易時間
    float open = 2;   // 開盤價
    float close = 3;  // 收盤價
    float high = 4;   // 最高價
    float low = 5;    // 最低價
    float macd = 6;   // macd指標值
}

// 返回的內容,是一個數組
message MACDReply {
    repeated MACDData macd = 1;
}

3.2. 生成模板代碼

grpc_sample目錄下,執行命令:

python -m grpc_tools.protoc -I./protos --python_out=. --pyi_out=. --grpc_python_out=. ./protos/indicator.proto

生成後文件結構如下:
image.png
生成了3個文件:

  1. indicator_pb2.pyproto文件定義的消息類
  2. indicator_pb2_grpc.py:服務端和客戶端的模板代碼
  3. indicator_pb2.pyi:不是必須的,爲了能讓mypy等工具校驗代碼類型是否正確

3.3. server端代碼

通過繼承indicator_pb2_grpc.py文件中的服務類,實現服務端功能。

# -*- coding: utf-8 -*-

from concurrent import futures

import grpc
import indicator_pb2
import indicator_pb2_grpc


class Indicator(indicator_pb2_grpc.IndicatorServicer):
    def GetMACD(self, request, context):
        macd = []
        for i in range(1, 5):
            data = indicator_pb2.MACDData(
                date=request.start_date,
                open=i * 1.1,
                close=i * 2.1,
                high=i * 3.1,
                low=i * 0.1,
                macd=i * 2.5,
            )
            macd.append(data)

        return indicator_pb2.MACDReply(macd=macd)


def serve():
    port = "50051"
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    indicator_pb2_grpc.add_IndicatorServicer_to_server(Indicator(), server)
    server.add_insecure_port("[::]:" + port)
    server.start()
    print("Server started, listening on " + port)
    server.wait_for_termination()


if __name__ == "__main__":
    serve()

服務端需要實現proto文件中定義接口的具體業務功能。

3.4. client端代碼

使用indicator_pb2_grpc.py文件中的Stub來調用服務端的代碼。

# -*- coding: utf-8 -*-


import grpc
import indicator_pb2
import indicator_pb2_grpc


def run():
    with grpc.insecure_channel("localhost:50051") as channel:
        stub = indicator_pb2_grpc.IndicatorStub(channel)
        response = stub.GetMACD(
            indicator_pb2.MACDRequest(
                start_date="2023-01-01",
                end_date="2023-12-31",
            )
        )

        print("indicator client received: ")
        print(response)


if __name__ == "__main__":
    run()

3.5. 運行效果

加入客戶端和服務端代碼後,最後的文件結構如下:
image.png

測試時,先啓動服務:

$  python.exe .\idc_server.py
Server started, listening on 50051

然後啓動客戶端看效果:

$  python.exe .\idc_client.py
indicator client received:
macd {
  date: "2023-01-01"
  open: 1.1
  close: 2.1
  high: 3.1
  low: 0.1
  macd: 2.5
}
macd {
  date: "2023-01-01"
  open: 2.2
  close: 4.2
  high: 6.2
  low: 0.2
  macd: 5
}
macd {
  date: "2023-01-01"
  open: 3.3
  close: 6.3
  high: 9.3
  low: 0.3
  macd: 7.5
}
macd {
  date: "2023-01-01"
  open: 4.4
  close: 8.4
  high: 12.4
  low: 0.4
  macd: 10
}

4. 傳輸文件/圖片

除了上面的返回列表數據的接口比較常用以外,我用的比較多的還有一種接口就是返回圖片。
將使用pythonmatplotlib等庫生成的分析結果圖片提供給其他系統使用。

開發的步驟和上面是一樣的。

4.1. 定義服務接口

定義文件相關的服務接口,文件的部分需要加上stream關鍵字,也就是流式數據。

syntax = "proto3";

package idc;

// 定義服務,也就是對外提供的功能
service IndicatorGraph {
    rpc GetMACDGraph(MACDGraphRequest) returns (stream MACDGraphReply) {}
}

// 請求的參數
message MACDGraphRequest {
    string start_date = 1; // 交易開始時間
    string end_date = 2;   // 交易結束時間
}

// 返回的內容,是一個圖片
message MACDGraphReply {
    bytes macd_chunk = 1;
}

注意,定義服務接口GetMACDGraph時,返回值MACDGraphReply前面加上stream關鍵字。
返回的文件內容是 bytes 二進制類型。

4.2. 生成模板代碼

執行命令:

python -m grpc_tools.protoc -I./protos --python_out=. --pyi_out=. --grpc_python_out=. ./protos/indicator_graph.proto

生成3個文件:

  1. indicator_graph_pb2.py
  2. indicator_graph_pb2.pyi
  3. indicator_graph_pb2_grpc.py

4.3. server端代碼

首先,生成一個MACD指標的圖片(macd.png)。
image.png

然後,服務端的代碼主要就是按塊讀取這個文件並返回。


import grpc
import indicator_graph_pb2
import indicator_graph_pb2_grpc


class IndicatorGraph(indicator_graph_pb2_grpc.IndicatorGraphServicer):
    def GetMACDGraph(self, request, context):
        chunk_size = 1024

        with open("./macd.png", mode="rb") as f:
            while True:
                chunk = f.read(chunk_size)
                if not chunk:
                    return

                response = indicator_graph_pb2.MACDGraphReply(macd_chunk=chunk)
                yield response

4.4. client端代碼

客戶端的代碼也要相應修改,不再是一次性接受請求的結果,而是循環接受,直至結束。

import grpc
import indicator_graph_pb2
import indicator_graph_pb2_grpc


def run():
    with grpc.insecure_channel("localhost:50051") as channel:
        stub = indicator_graph_pb2_grpc.IndicatorGraphStub(channel)

        print("indicator client received: ")
        with open("./received_macd.png", mode="wb") as f:
            for response in stub.GetMACDGraph(
                indicator_graph_pb2.MACDGraphRequest(
                    start_date="2023-01-01",
                    end_date="2023-12-31",
                )
            ):
                f.write(response.macd_chunk)

客戶端接收完成後,圖片保存在 received_macd.png 中。

實際執行後,圖片可以正常保存並顯示。

5. 回顧

本篇是最近用gPRC封裝python數據分析相關業務過程中一些簡單的總結。

這裏沒有對gPRC做系統的介紹,它的官方文檔已經非常完善,而且文檔中針對主流編程語言的示例也都有。
本篇筆記中的兩個示例雖然簡單,卻是我用的最多的兩種情況:
一種是返回對象數組:是爲了將pandasnumpy等庫處理後的數據返回出來供其他系統使用;
一種是返回文件/圖片:是爲了將matplotlibseaborn等庫生成的分析圖片返回出來供其他系統使用。

目前gPRC對我最大的好處是,它提供了一種穩定可靠的,將python強大的數據分析能力結合到其他系統中的能力。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章