Python和Go:第一部分-gRPC

原文地址:https://www.ardanlabs.com/blog/2020/06/python-go-grpc.html

介紹

像工具一樣猛铅,編程語言也傾向于解決那些他們被設(shè)計(jì)之初想解決的問題。你可以使用小刀擰緊螺絲,但最好還是使用螺絲刀,同時(shí)這也可以避免在此過程中受傷。

Go編程語言在編寫高吞吐量服務(wù)時(shí)很有用袭厂,而Python在處理數(shù)據(jù)科學(xué)相關(guān)問題時(shí)表現(xiàn)的很棒。在這一系列博客文章中饭入,我們將探討如何使用各種語言來做的更好嵌器,并探討Go和Python之間的各種通信方法。

注意:在項(xiàng)目中使用多種語言會(huì)產(chǎn)生額外成本谐丢。如果你能只用Go或Python編寫所有功能爽航,那么一定要這樣做。但是乾忱,在某些情況下讥珍,使用正確的語言來完成工作可以減少可讀性,維護(hù)性和應(yīng)用性能各方面的總體開銷窄瘟。

在本文中衷佃,我們將學(xué)習(xí)Go和Python程序如何使用gRPC相互通信。這篇文章假定你具有一定Go和Python的基本知識(shí)儲(chǔ)備蹄葱。

gRPC概述

gRPC是Google的遠(yuǎn)程過程調(diào)用(RPC)框架氏义。它使用Protobuf作為序列化格式锄列,并使用HTTP2作為傳輸介質(zhì)。

通過使用這兩種完善的技術(shù)惯悠,你可以學(xué)習(xí)到許多現(xiàn)有的知識(shí)和工具邻邮。我咨詢過的許多公司,他們都在使用gRPC連接內(nèi)部服務(wù)克婶。

使用Protobuf的另一個(gè)優(yōu)點(diǎn)是筒严,你只需編寫一次消息定義,然后從同一來源生成對(duì)其他語言的綁定情萤。這意味著可以使用不同的編程語言編寫各種服務(wù)鸭蛙,并且所有應(yīng)用程序都統(tǒng)一消息的格式。

Protobuf也是一種高效的二進(jìn)制格式:你可以獲得更快的序列化速度和更少的在線字節(jié)數(shù)筋岛,僅此一項(xiàng)就可以節(jié)省很多成本娶视。在我的機(jī)器上進(jìn)行基準(zhǔn)測(cè)試,與JSON相比睁宰,序列化時(shí)間要快7.5倍左右歇万,生成的數(shù)據(jù)要小4倍左右。

示例:異常檢測(cè)

異常檢測(cè)是一種在數(shù)據(jù)中查找異常值的方法勋陪。系統(tǒng)從其服務(wù)中收集了大量指標(biāo)贪磺,很難通過簡(jiǎn)單的閾值找到有故障的服務(wù),這意味著即使凌晨2點(diǎn)也要呼叫開發(fā)人員诅愚。

我們將實(shí)現(xiàn)一個(gè)Go服務(wù)收集指標(biāo)寒锚。然后,使用gRPC违孝,我們會(huì)將這些指標(biāo)發(fā)送到Python服務(wù)刹前,該服務(wù)將對(duì)它們進(jìn)行異常檢測(cè)。

項(xiàng)目結(jié)構(gòu)

在這個(gè)項(xiàng)目中雌桑,我們將采用一種簡(jiǎn)單的方法喇喉,在源代碼樹中將Go作為主項(xiàng)目,將Python作為子項(xiàng)目校坑。

代碼1

.
├── client.go
├── gen.go
├── go.mod
├── go.sum
├── outliers.proto
├── pb
│   └── outliers.pb.go
└── py
    ├── Makefile
    ├── outliers_pb2_grpc.py
    ├── outliers_pb2.py
    ├── requirements.txt
    └── server.py

代碼1顯示了我們項(xiàng)目的目錄結(jié)構(gòu)拣技。該項(xiàng)目正在使用Go模塊并且在go.mod文件中定義了模塊的名稱(請(qǐng)參見代碼2)。我們將在多個(gè)地方引用模塊(github.com/ardanlabs/python-go/grpc)耍目。

代碼2

01 module github.com/ardanlabs/python-go/grpc
02
03 go 1.14
04
05 require (
06     github.com/golang/protobuf v1.4.2
07     google.golang.org/grpc v1.29.1
08     google.golang.org/protobuf v1.24.0
09 )

代碼2展示了go.mod項(xiàng)目的文件膏斤。你可以在第01行看到定義模塊名稱的位置。

定義消息和服務(wù)
在gRPC中邪驮,您首先要編寫一個(gè).proto文件莫辨,該文件定義了要發(fā)送的消息和RPC方法。

代碼3

01 syntax = "proto3";
02 import "google/protobuf/timestamp.proto";
03 package pb;
04
05 option go_package = "github.com/ardanlabs/python-go/grpc/pb";
06
07 message Metric {
08    google.protobuf.Timestamp time = 1;
09    string name = 2;
10    double value = 3;
11 }
12
13 message OutliersRequest {
14    repeated Metric metrics = 1;
15 }
16
17 message OutliersResponse {
18    repeated int32 indices = 1;
19 }
20
21 service Outliers {
22    rpc Detect(OutliersRequest) returns (OutliersResponse) {}
23 }

代碼3顯示了outliers.proto的內(nèi)容。這里要重點(diǎn)提及第02行沮榜,這里導(dǎo)入了Protobuf定義的timestamp盘榨,然后在05行,定義了完整的Go包名稱-github.com/ardanlabs/python-go/grpc/pb

指標(biāo)是對(duì)資源使用情況的計(jì)量標(biāo)準(zhǔn)蟆融,用于監(jiān)視和診斷系統(tǒng)较曼。我們?cè)诘?7行定義一個(gè)Metric,帶有時(shí)間戳振愿,名稱(例如“ CPU”)和浮點(diǎn)值。例如弛饭,我們可以說在2020-03-14T12:30:14測(cè)量到CPU利用率為41.2%冕末。

每個(gè)RPC方法都有一個(gè)或多個(gè)輸入類型和一個(gè)輸出類型。我們的方法Detect(第22行)使用OutliersRequest消息類型(第13行)作為輸入侣颂,并使用OutliersResponse消息類型(第17行)作為輸出档桃。OutliersRequest消息類型是Metric的列表/切片,OutliersResponse消息類型是列表索引,表示發(fā)現(xiàn)的異常值的列表和/切片憔晒。例如藻肄,如果我們具有的值[1, 2, 100, 1, 3, 200, 1],則結(jié)果將2, 5]表示100和200的索引拒担。

Python服務(wù)

在本節(jié)中嘹屯,我們將介紹Python服務(wù)代碼。

代碼4

.
├── client.go
├── gen.go
├── go.mod
├── go.sum
├── outliers.proto
├── pb
│   └── outliers.pb.go
└── py
    ├── Makefile
    ├── outliers_pb2_grpc.py
    ├── outliers_pb2.py
    ├── requirements.txt
    └── server.py

代碼4中我們可以看見Python服務(wù)的代碼位于項(xiàng)目根目錄之外的py目錄中从撼。

要生成Python綁定州弟,需要安裝protoc編譯器,你可以在此處下載低零。也可以使用操作系統(tǒng)軟件包管理器(例如apt-get婆翔,brew…)安裝編譯器。

安裝編譯器后掏婶,還需要安裝Python grpcio-tools軟件包啃奴。

注意:我強(qiáng)烈建議你為所有Python項(xiàng)目都使用虛擬環(huán)境。閱讀這部分內(nèi)容以了解更多信息雄妥。

代碼5

$ cat requirements.txt

OUTPUT:
grpcio-tools==1.29.0
numpy==1.18.4

$ python -m pip install -r requirements.txt

代碼5顯示了如何檢查和安裝Python項(xiàng)目的外部依賴最蕾。在requirements.txt為項(xiàng)目指定外部依賴,很像go項(xiàng)目中的go.mod老厌。

cat命令的輸出中可以看到揖膜,我們需要兩個(gè)外部依賴項(xiàng):grpcio-toolsnumpy。好的做法是將此文件置于源代碼管理中梅桩,并始終對(duì)依賴項(xiàng)(例如numpy==1.18.4)進(jìn)行版本控制壹粟,類似于對(duì)Go項(xiàng)目中go.mod的操作。

一旦安裝完成,就可以生成Python綁定了趁仙。

代碼6

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

代碼6顯示了如何為gRPC支持生成Python綁定洪添。讓我們分解一下這個(gè)長(zhǎng)命令:

  • python -m grpc_tools.protocgrpc_tools.protoc模塊作為腳本運(yùn)行。
  • -I..告訴工具.proto可以在哪里找到雀费。
  • --python_out=. 告訴該工具在當(dāng)前目錄中生成Protobuf序列化代碼干奢。
  • --grpc_python_out=. 告訴工具在當(dāng)前目錄中生成gRPC代碼。
  • ../outliers.proto 是Protobuf+ gRPC定義文件的名稱盏袄。

這條命令運(yùn)行時(shí)沒有任何輸出忿峻,最后,你將看到兩個(gè)新文件:outliers_pb2.py這是Protobuf代碼辕羽,outliers_pb2_grpc.py這是gRPC客戶端和服務(wù)器代碼逛尚。

注意:我通常使用 Makefile來自動(dòng)化Python項(xiàng)目中的任務(wù),并創(chuàng)建一條make規(guī)則來運(yùn)行此命令刁愿。將生成的文件添加到源代碼管理中绰寞,以便部署計(jì)算機(jī)不必安裝protoc編譯器。

要編寫Python服務(wù)铣口,你需要繼承outliers_pb2_grpc.py中的OutliersServicer并覆寫Detect方法滤钱。我們將使用numpy包,并使用一種簡(jiǎn)單的方法來選擇所有與均值超過兩個(gè)標(biāo)準(zhǔn)差的值脑题。

代碼7

01 import logging
02 from concurrent.futures import ThreadPoolExecutor
03
04 import grpc
05 import numpy as np
06
07 from outliers_pb2 import OutliersResponse
08 from outliers_pb2_grpc import OutliersServicer, add_OutliersServicer_to_server
09
10
11 def find_outliers(data: np.ndarray):
12     """Return indices where values more than 2 standard deviations from mean"""
13     out = np.where(np.abs(data - data.mean()) > 2 * data.std())
14     # np.where returns a tuple for each dimension, we want the 1st element
15     return out[0]
16
17
18 class OutliersServer(OutliersServicer):
19     def Detect(self, request, context):
20          logging.info('detect request size: %d', len(request.metrics))
21          # Convert metrics to numpy array of values only
22          data = np.fromiter((m.value for m in request.metrics), dtype='float64')
23          indices = find_outliers(data)
24          logging.info('found %d outliers', len(indices))
25          resp = OutliersResponse(indices=indices)
26          return resp
27
28
29 if __name__ == '__main__':
30     logging.basicConfig(
31          level=logging.INFO,
32          format='%(asctime)s - %(levelname)s - %(message)s',
33  )
34     server = grpc.server(ThreadPoolExecutor())
35     add_OutliersServicer_to_server(OutliersServer(), server)
36     port = 9999
37     server.add_insecure_port(f'[::]:{port}')
38     server.start()
39     logging.info('server ready on port %r', port)
40     server.wait_for_termination()

代碼7顯示了server.py文件中的代碼件缸。這就是我們編寫Python服務(wù)所需的全部代碼。在第19行中叔遂,我們復(fù)寫Detect為編寫實(shí)際的異常值檢測(cè)代碼停团。在第34行中,我們創(chuàng)建了一個(gè)使用ThreadPoolExecutor的gRPC服務(wù)器掏熬,在第35行中佑稠,我們注冊(cè)了OutliersServer來處理服務(wù)器中的請(qǐng)求。

代碼8

$ python server.py

OUTPUT:
2020-05-23 13:45:12,578 - INFO - server ready on port 9999

代碼8顯示了如何運(yùn)行服務(wù)旗芬。

Go客戶端

現(xiàn)在我們已經(jīng)運(yùn)行了Python服務(wù)舌胶,我們可以編寫與其通信的Go客戶端。

我們從為gRPC生成Go綁定開始疮丛。為了使這個(gè)過程自動(dòng)化幔嫂,我通常有一個(gè)帶有go:generate命令的gen.go文件來生成綁定。你可以在github.com/golang/protobuf/protoc-gen-go下載Go的gRPC插件模塊誊薄。

代碼9

01 package main
02
03 //go:generate mkdir -p pb
04 //go:generate protoc --go_out=plugins=grpc:pb --go_opt=paths=source_relative outliers.proto

代碼9顯示了gen.go文件以及go:generate如何執(zhí)行g(shù)RPC插件來生成綁定履恩。

讓我們分解第04行的命令:

  • protoc 是Protobuf編譯器。
  • --go-out=plugins=grpc:pb告訴protoc使用gRPC插件并將文件放置在pb目錄中呢蔫。
  • --go_opt=source_relative告訴protoc在pb相對(duì)于當(dāng)前目錄中生成代碼切心。
  • outliers.proto 是Protobuf+ gRPC定義文件的名稱飒筑。
    當(dāng)你運(yùn)行go generate后,你應(yīng)該看不到輸出绽昏,但會(huì)在pb目錄出現(xiàn)一個(gè)名為outliers.pb.go的新文件协屡。

代碼10

.```
├── client.go
├── gen.go
├── go.mod
├── go.sum
├── outliers.proto
├── pb
│ └── outliers.pb.go
└── py
├── Makefile
├── outliers_pb2_grpc.py
├── outliers_pb2.py
├── requirements.txt
└── server.py

代碼10顯示了`pb`目錄和調(diào)用·go generate·生成的新文件`outliers.pb.go`。我將`pb`目錄添加到源代碼管理中全谤,因此肤晓,如果將項(xiàng)目克隆到新計(jì)算機(jī)上,無需重新安裝`protoc`該項(xiàng)目也可以運(yùn)行认然。

現(xiàn)在我們可以構(gòu)建并運(yùn)行Go客戶端补憾。

**代碼11**

01 package main
02
03 import (
04 "context"
05 "log"
06 "math/rand"
07 "time"
08
09 "github.com/ardanlabs/python-go/grpc/pb"
10 "google.golang.org/grpc"
11 pbtime "google.golang.org/protobuf/types/known/timestamppb"
12 )
13
14 func main() {
15 addr := "localhost:9999"
16 conn, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithBlock())
17 if err != nil {
18 log.Fatal(err)
19 }
20 defer conn.Close()
21
22 client := pb.NewOutliersClient(conn)
23 req := pb.OutliersRequest{
24 Metrics: dummyData(),
25 }
26
27 resp, err := client.Detect(context.Background(), &req)
28 if err != nil {
29 log.Fatal(err)
30 }
31 log.Printf("outliers at: %v", resp.Indices)
32 }
33
34 func dummyData() []pb.Metric {
35 const size = 1000
36 out := make([]
pb.Metric, size)
37 t := time.Date(2020, 5, 22, 14, 13, 11, 0, time.UTC)
38 for i := 0; i < size; i++ {
39 m := pb.Metric{
40 Time: Timestamp(t),
41 Name: "CPU",
42 // Normally we're below 40% CPU utilization
43 Value: rand.Float64() * 40,
44 }
45 out[i] = &m
46 t.Add(time.Second)
47 }
48 // Create some outliers
49 out[7].Value = 97.3
50 out[113].Value = 92.1
51 out[835].Value = 93.2
52 return out
53 }
54
55 // Timestamp converts time.Time to protobuf *Timestamp
56 func Timestamp(t time.Time) *pbtime.Timestamp {
57 return &pbtime.Timestamp {
58 Seconds: t.Unix(),
59 Nanos: int32(t.Nanosecond()),
60 }
61 }

代碼11顯示了`client.go`中的代碼。在第23行代碼為`OutliersRequest`的值填充了一些虛擬數(shù)據(jù)(由第34行的`dummyData`函數(shù)生成)卷员,然后在第27行調(diào)用Python服務(wù)盈匾。對(duì)Python服務(wù)的調(diào)用返回一個(gè)`OutlirsResponse`。

讓我們進(jìn)一步分解代碼:

* 在第16行子刮,我們使用`WithInsecure`選項(xiàng)連接到Python服務(wù)器验辞,因?yàn)槲覀兙帉懙腜ython服務(wù)器不支持HTTPS篷帅。
* 在第22行,我們使用第16行創(chuàng)建的鏈接創(chuàng)建了一個(gè)新`OutliersClient`對(duì)象鼻由。
* 在第23行担钮,我們創(chuàng)建了gPRC請(qǐng)求橱赠。
* 在第27行,我們執(zhí)行了實(shí)際的gRPC調(diào)用箫津。每個(gè)gRPC調(diào)用都有一個(gè)`context.Context`作為第一個(gè)參數(shù)狭姨,這讓我們可以控制超時(shí)和取消請(qǐng)求。
* gRPC擁有自己的`Timestamp`結(jié)構(gòu)實(shí)現(xiàn)苏遥。在第56行饼拍,我們使用一個(gè)通用的程序函數(shù)將Go的`time.Time`值轉(zhuǎn)換為gRPC的`Timestamp`值。

**代碼12**

$ go run client.go

OUTPUT:
2020/05/23 14:07:18 outliers at: [7 113 835]


代碼12顯示了如何運(yùn)行Go客戶端田炭。假設(shè)Python服務(wù)器在同一臺(tái)計(jì)算機(jī)上運(yùn)行师抄。

### 結(jié)論

gRPC使得將消息從一種服務(wù)傳遞到另一種服務(wù)變得容易且安全。你可以維護(hù)一個(gè)定義了所有數(shù)據(jù)類型和方法的地方教硫,同時(shí)gRPC框架提供了出色的工具并進(jìn)行過實(shí)踐叨吮。

整個(gè)代碼:`outliers.proto`,`py/server.py`和`client.go`少于100行瞬矩。你可以在[grpc](https://github.com/ardanlabs/python-go/tree/master/grpc)查看項(xiàng)目代碼茶鉴。

gRPC還有更多功能,例如超時(shí)景用,負(fù)載均衡涵叮,TLS和流式傳輸。我強(qiáng)烈建議瀏覽[官方網(wǎng)站](https://grpc.io/)閱讀文檔并嘗試一下提供的示例。

在本系列的下一篇文章中围肥,我們將調(diào)換角色并讓Python調(diào)用Go服務(wù)剿干。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市穆刻,隨后出現(xiàn)的幾起案子置尔,更是在濱河造成了極大的恐慌,老刑警劉巖氢伟,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件榜轿,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡朵锣,警方通過查閱死者的電腦和手機(jī)谬盐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诚些,“玉大人飞傀,你說我怎么就攤上這事∥芘耄” “怎么了砸烦?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)绞吁。 經(jīng)常有香客問我幢痘,道長(zhǎng),這世上最難降的妖魔是什么家破? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任颜说,我火速辦了婚禮,結(jié)果婚禮上汰聋,老公的妹妹穿的比我還像新娘门粪。我一直安慰自己,他們只是感情好烹困,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布庄拇。 她就那樣靜靜地躺著,像睡著了一般韭邓。 火紅的嫁衣襯著肌膚如雪措近。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天女淑,我揣著相機(jī)與錄音瞭郑,去河邊找鬼。 笑死鸭你,一個(gè)胖子當(dāng)著我的面吹牛屈张,可吹牛的內(nèi)容都是我干的擒权。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼阁谆,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼碳抄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起场绿,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤剖效,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后焰盗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體璧尸,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年熬拒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了爷光。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡澎粟,死狀恐怖蛀序,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情活烙,我是刑警寧澤徐裸,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站瓣颅,受9級(jí)特大地震影響倦逐,放射性物質(zhì)發(fā)生泄漏譬正。R本人自食惡果不足惜宫补,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望曾我。 院中可真熱鬧粉怕,春花似錦、人聲如沸抒巢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蛉谜。三九已至稚晚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間型诚,已是汗流浹背客燕。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留狰贯,地道東北人也搓。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓赏廓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親傍妒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子幔摸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348